微信小程序支付实现

一、 官方流程图

商户系统和微信支付系统主要交互:
  1. 小程序内调用登录接口,获取到用户的openid,api参见公共api 小程序登录API

  2. 商户server调用支付统一下单,api参见公共api 统一下单API

  3. 商户server调用再次签名,api参见公共api 再次签名

  4. 商户server接收支付通知,api参见公共api 支付结果通知API

  5. 商户server查询支付结果,api参见公共api 查询订单API

当然在开发之前,我们需要有下面这些东西:
  • appId(小程序分配)
  • 小程序密钥(小程序配置界面获取)
  • 商户号
  • api密钥(商家后台自己设置)

二、 简单说明

不同的公司需求各有不同,流程也有不同,由于公司是做支付的,因此流程中统一下单之前会经过加签,下单之后还要经过验签服务,保证参数传递的正确性及安全性,验签成功之后,才会去返回微信小程序支付所需参数(微信的统一下单是由商户组去调用)

三、 支付流程的代码实现

  • 配置文件类 WxMinPayConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class WxMinPayConfig {

//小程序appid
public static final String appid = "小程序分配";
//小程序appSecret
public static final String appSecret = "微信公众平台小程序密钥";
//微信支付的商户id
public static final String mch_id = "商户号";
//微信支付的商户密钥
public static final String key = "商户密钥";
//获取openID接口
public static final String get_openid_url = "https://api.weixin.qq.com/sns/jscode2session";
//支付成功后的服务器回调url
// public static final String notify_url = "https://??/weixin/wxNotify";
//签名方式(视公司而定)默认为MD5,支持HMAC-SHA256和MD5
// public static final String SIGNTYPE = "MD5";
//交易类型
// public static final String TRADETYPE = "JSAPI";
//微信统一下单接口地址(此文章不会直接调用此接口)
// public static final String pay_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";

//加签接口地址
public static final String sign_url = "xxxxxxxx";

// 验签接口地址
public static final String verify_url = "xxxxxxx";

//商户下单接口地址
public static final String create_order_url = "xxxxxx";
}
  • 小程序支付所需参数返回的实现类 WeiXinServiceImpl,日志打印直接用 LogFactory
1
private static Log log = LogFactory.getLog(WeiXinServiceImpl.class);

微信小程序支付流程
用code调取微信获取openID接口
拼接xml调加签验签服务接口
验签成功后调用下单接口返回微信小程序支付参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Override
public WXMinPayOut Pay(WXMinPayIn wxMinPayIn, String deviceIp) {
WXMinPayOut out = new WXMinPayOut();
try{
JSONObject jsStr = WeiXinServiceImpl.getOpenId(wxMinPayIn);
String openid = String.valueOf(jsStr.get("openid"));
String session_key = String.valueOf(jsStr.get("session_key"));
log.info("openid :" + openid + "====" +"session_key :" + session_key);
String xml = WeiXinServiceImpl.paramXml(wxMinPayIn, deviceIp, openid);
//加签
JSONObject signParam = new JSONObject();
signParam.put("sourceMsg", xml);
Map signMap = digitalCertificateService.sign(signParam.toJSONString());
// JSONObject signObject = WeiXinServiceImpl.getJsonObject(WxMinPayConfig.base_url+WxMinPayConfig.sign_url,signParam); //由于签名服务此项目自己维护了,因此这种调用方法暂时不用了
log.info("=====返回数据=signMap=================:" + signMap);
String signMsg = String.valueOf(signMap.get("signMsg"));
if (null != signMap.get("retCode") && "0000".equals(signMap.get("retCode")) && null != signMsg){

String xmlSignStr = xml.replace("<SIGNED_MSG></SIGNED_MSG>", "<SIGNED_MSG>" + signMsg + "</SIGNED_MSG>");
log.info("====调用下单接口参数xml=================:" + xmlSignStr);
log.info("====调用下单接口传参appid=================:" + wxMinPayIn.getAppId());
String xmlStr = WeiXinServiceImpl.httpXmlData(WxMinPayConfig.base_url+WxMinPayConfig.create_order_url,xmlSignStr);
Map map = XmlCommonUtil.xml2map(xmlStr);
log.info("=====返回数据=map=================:" + map);
if (null != map.get("SIGNED_MSG") && "0000".equals(map.get("RET_CODE"))){
xmlStr = xmlStr.replace("<SIGNED_MSG>" + map.get("SIGNED_MSG") + "</SIGNED_MSG>","<SIGNED_MSG></SIGNED_MSG>");
//验签
JSONObject verifyParam = new JSONObject();
verifyParam.put("sourceMsg", xmlStr);
verifyParam.put("signMsg",signMsg);
Map vierfyMap = digitalCertificateService.sign(verifyParam.toJSONString());
// JSONObject verifyObject = WeiXinServiceImpl.getJsonObject(WxMinPayConfig.base_url+WxMinPayConfig.verify_url,verifyParam);//由于签名服务此项目自己维护了,因此这种调用方法暂时不用了

if (null != vierfyMap.get("retCode") && "0000".equals(vierfyMap.get("retCode"))){
log.info("=====返回数据=PAY_STR=================:" + map.get("PAY_STR"));
JSONObject resultMap = JSONObject.parseObject((String)map.get("PAY_STR")); //将字符串{“id”:1}
log.info("=====调用下单接口返回appId=================:" + resultMap.get("appId"));
out.setAppId(String.valueOf(resultMap.get("appId")));
out.setNonceStr(String.valueOf(resultMap.get("nonceStr")));
out.setPackageStr(String.valueOf(resultMap.get("package")));
out.setPaySign(String.valueOf(resultMap.get("paySign")));
out.setSignType(String.valueOf(resultMap.get("signType")));
Long timeStamp = System.currentTimeMillis() / 1000;
out.setTimeStamp(String.valueOf(timeStamp));
}
}
}
}catch (Exception e){
log.error(e.getMessage(), e);
}
return out;
}

获取openID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static JSONObject getOpenId(WXMinPayIn wxMinPayIn){
String wxspAppid = wxMinPayIn.getAppId();
//小程序的 app secret (在微信小程序管理后台获取)
String wxspSecret = WxMinPayConfig.appSecret;
String grantType = "authorization_code";

//请求参数
String params = "appid=" + wxspAppid + "&secret=" + wxspSecret + "&js_code=" + wxMinPayIn.getWxcode() + "&grant_type=" + grantType;
//发送请求
String turl = String.format("%s?%s",WxMinPayConfig.get_openid_url,params);
CloseableHttpClient client = HttpClients.createDefault();
HttpGet get = new HttpGet(turl);
JSONObject jsStr = null; // 响应内容
try {
HttpResponse res = client.execute(get);
HttpEntity entity = res.getEntity();
log.info("WeiXin open id : " + entity);
String responseContent = EntityUtils.toString(entity, "UTF-8");
log.info("WeiXin getToken response content : " + responseContent);
jsStr = JSONObject.parseObject(responseContent); //将字符串{“id”:1}
log.info("jsStr : " + jsStr.get("openid"));
}catch (Exception e){
log.info(e.getMessage(),e);
}
return jsStr;
}

xml参数拼接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public static String paramXml(WXMinPayIn wxMinPayIn, String deviceIp, String openid){
//生成的随机字符串
//String nonce_str = StringUtils.getRandomStringByLength(32);
long rand = RandomUtils.nextInt();
String nonce_str = DateFormatUtils.format(new Date(), "yyyyMMdd")+rand;
//订单日期
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");//设置日期格式
System.out.println(df.format(new Date()));
String orderDate = df.format(new Date());
//商品名称
String body = wxMinPayIn.getOrderSubject();
//获取本机的ip地址
String spbill_create_ip = deviceIp;
//支付金额,单位:分,这边需要转成字符串类型,否则后面的签名会失败,(xml拼接字段依据自己公司需求而定)
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<AIPG>"
+ "<INFO>"
+ "<TRX_CODE>" + "xxx" +"</TRX_CODE>"
+ "<VERSION>"+ "xxx"+"</VERSION>"
+ "<DATA_TYPE>"+ "xxx"+ "</DATA_TYPE>"
+ "<REQ_SN>" + nonce_str + "</REQ_SN>"
+ "<SIGNED_MSG>" + "" + "</SIGNED_MSG>"
+ "</INFO>"
+ "<BODY>"
+ "<MERCHANT_ID>" + wxMinPayIn.getMerchantId() + "</MERCHANT_ID>"
+ "<MERC_ORDER_NO>" + nonce_str + "</MERC_ORDER_NO>"
+ "<MERC_ORDER_DATE>" + orderDate + "</MERC_ORDER_DATE>"
+ "<TRANS_AMT>" + wxMinPayIn.getTransAmt() + "</TRANS_AMT>"
+ "<PLACE_ORDER_IP>" + spbill_create_ip + "</PLACE_ORDER_IP>"
+ "<OPEN_ID>" + openid + "</OPEN_ID>"
+ "<APPID>" + wxMinPayIn.getAppId() + "</APPID>"
+ "<ORDER_SUBJECT>" + body + "</ORDER_SUBJECT>"
+ "<PAY_CHANNEL>" + wxMinPayIn.getPayChannel() + "</PAY_CHANNEL>"
+ "<SPLIT_FLAG>" + "xxxx" + "</SPLIT_FLAG>"
+ "<ASSURE_FLAG>" + "xxxx" + "</ASSURE_FLAG>"
+ "</BODY>"
+ "</AIPG>";
log.info("调试模式_统一下单接口 请求XML数据:" + xml);
return xml;
}

加签验签(暂时不用此写法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static JSONObject getJsonObject(String url, JSONObject jsonParam){
CloseableHttpClient closeableHttpClient = HttpClients.createDefault();
JSONObject jsonObject = null;
try {
HttpPost post = new HttpPost(url);
StringEntity stringEntity = new StringEntity(jsonParam.toString(),"utf-8");//解决中文乱码问题
stringEntity.setContentEncoding("UTF-8");
stringEntity.setContentType("application/json");
post.setEntity(stringEntity);
HttpResponse response = closeableHttpClient.execute(post);
HttpEntity httpEntity = response.getEntity();
log.info("httpEntity : " + httpEntity);
String signResponseContent = EntityUtils.toString(httpEntity, "UTF-8");
log.info("signResponseContent : " + signResponseContent);
jsonObject = JSONObject.parseObject(signResponseContent); //将字符串{“id”:1}
}catch (Exception e){
log.info(e.getMessage(),e);
}
return jsonObject;
}

调商户下单接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static String httpXmlData(String url, String xml){
CloseableHttpClient HttpClient = HttpClients.createDefault();
String xmlStr = null;
try {
HttpPost orderPost = new HttpPost(url);
orderPost.addHeader("Content-Type","text/html;charset=UTF-8");
String base64Str = GZipUtil.gzipString(xml);
StringEntity orderEntity = new StringEntity(base64Str,"utf-8");//解决中文乱码问题
orderEntity.setContentEncoding("UTF-8");
orderPost.setEntity(orderEntity);
HttpResponse orderResponse = HttpClient.execute(orderPost);
HttpEntity orderhttpEntity = orderResponse.getEntity();
log.info("orderhttpEntity : " + orderhttpEntity);
String ordersignResponseContent = EntityUtils.toString(orderhttpEntity, "UTF-8");
String s = GZipUtil.ungzipString(ordersignResponseContent);
log.info("ordersignResponseContent : " + s);
xmlStr = XmlFormatter.format(s); //转化为字符串xml
}catch (Exception e){
log.info(e.getMessage(),e);
}
return xmlStr;
}
  • controller对外提供接口类实现 WeiXinController
1
2
3
4
5
6
7
8
9
10
11
12
@ResponseBody
@RequestMapping(value = "/wxminpay", method = RequestMethod.POST)
public WXMinPayOut wxminpay(@RequestBody WXMinPayIn wxMinPayIn, HttpServletRequest request){
WXMinPayOut out = new WXMinPayOut();
String ip = IpUtils.getIpAddr(request);
try {
out = weiXinServiceImpl.Pay(wxMinPayIn,ip);
} catch (Exception e) {
e.printStackTrace();
}
return out;
}

四、小程序调用实现

小小程序端调用wx.login获取code,然后配合支付接口所需参数一同传递给后端

  • 具体实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
wx.login({
success(res) {
console.log("====code=====:" + res.code)
wx.request({
url: "http://xxxxxxx//wxminpay",
data: {
wxcode: res.code,
xxx: xxxx (下单支付所需参数,可能会有很多)
},
method: 'POST',
success(res) {
const payargs = res.data
wx.requestPayment({
timeStamp: payargs.timeStamp,
nonceStr: payargs.nonceStr,
package: payargs.package,
signType: payargs.signType,
paySign: payargs.paySign
})
}
})
}
})

五、结束

至此,微信小程序后端及小程序实现就完成了!如有疑问欢迎交流!

-------------本文结束感谢您的阅读-------------
枫林飘雪 wechat
欢迎您扫一扫上面的微信订阅号,订阅我的博客!
坚持技术分享,您的支持将鼓励我继续创作!