根据微信支付的官方文档,小程序支付需要绑定商户号,并且小程序和商户号的认证主体必须一致。 目前我们的商业逻辑是小程序平台主体和支付的商户主体不一致, 那么就需要从我们的小程序跳转到支付主体小程序完成支付后,再返回我们的小程序。
曾经尝试过在小程序中通过webview的方式嵌套H5网页,使用公众号支付方式,后来发现小程序并未开放这个JSAPI。
首先我已经有了2个小程序:
- 平台小程序A,认证主体是A公司
- 支付小程序B,认证主体是B公司
接入微信支付
支付小程序B要接入微信支付,必须要先注册并认证微信公众号,然后申请服务商商户, 具体怎么申请参考微信支付服务商平台
然后登录小程序后台,点击左侧导航栏的微信支付,在页面中进行开通。
点击开通按钮后,有2种方式可以获取微信支付能力,新申请微信支付商户号或绑定一个已有的微信支付商户号, 请根据你的业务需要和具体情况选择,只能二选一。
开通指引:http://kf.qq.com/faq/140225MveaUz161230yqiIby.html
小程序支付开发注意点:
- appid 必须为最后拉起收银台的小程序appid;
- mch_id 为和appid成对绑定的支付商户号,收款资金会进入该商户号;
- trade_type 请填写JSAPI;
- openid 为appid对应的用户标识,即使用wx.login接口获得的openid
小程序跳转
参考官方文档打开小程序
wx.navigateToMiniProgram(OBJECT)
,打开同一公众号下关联的另一个小程序。(注:必须是同一公众号下,而非同个open账号下)
所以第一步要将两个小程序关联到同一个公众号上面去,这里一般都会关联到平台的公众号上去。 关联操作需要从公众号发起,请登录公众平台上公众号的管理后台,在”设置-公众号设置-相关小程序”中进行关联。
然后小程序的管理员会受到一条微信消息,点击进去同意即可。之后再进入小程序后台,就能看到小程序所关联的公众号了:
小程序A跳转到B的示例代码:
1
2
3
4
5
6
7
8
9
10
11wx.navigateToMiniProgram({
appId: 'APPID',
path: 'pages/index/index?id=123',
extraData: {
foo: 'bar'
},
envVersion: 'develop',
success(res) {
// 打开成功
}
})
同时还可以给小程序B传递参数,通过extraData
这个参数,小程序B接受参数的方法:
1 | App({ |
小程序B处理完成后返回小程序A的方式:
1
2
3
4
5
6
7
8
9console.log('支付成功啦啦啦啦。。。。。')
wx.navigateBackMiniProgram({
extraData: {
payResult: 'good'
},
success(res) {
// 打开成功
}
})
然后小程序A在App.js
中接受到返回值:
1
2
3onShow: function (options) {
wx.setStorageSync('payResult', options.referrerInfo.extraData.payResult)
},
然后在相应的页面就能处理结果值了,这里还是用onShow函数:
1
2
3
4
5onShow: function (options) {
this.setData({
'lalala': wx.getStorageSync('payResult')
})
},
小程序支付
小程序B需要实现微信支付功能,还需要有一个后端系统,我这里通过一个SpringBoot工程搭建后端系统,
另外引入一个微信支付的依赖:
1
2
3
4
5<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>${weixin-java-pay.version}</version>
</dependency>
主要的逻辑是这样:
- 小程序向服务端发送商品详情、金额、openid
- 服务端向微信统一下单
- 服务器收到返回信息二次签名发回给小程序
- 小程序发起支付
- 服务端收到回调
这里需要注意的是,小程序调用后台接口的地址需要在小程序后台配置好服务器域名:
服务器域名只能是https开头,所以需要先去给你的网站申请一个SSL证书,配置一下nginx来代理转发即可,
比如我的域名为https://aggrepay.enzhico.cn/
,这里我申请的是lets encrypted证书:
1 | server { |
设置完服务器后,就将后台应用部署上去。
回到小程序B中,先要获取到用户openid,小程序是通过登录操作获取到用户的openid的。
参考官方的小程序登录API
很重要的一张登录时序图:
1 | onLoad: function () { |
对应后台代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* get openid
*
* @return 成功标准
*/
@RequestMapping(value = "/openid", method = RequestMethod.GET)
@ResponseBody
public String openid(@RequestParam(value = "code") String code) throws Exception {
logger.info("/openid start....");
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("https://api.weixin.qq.com/sns/jscode2session?" +
"appid=APPID&secret=SECRET&js_code=" + code + "&grant_type=authorization_code");
try (CloseableHttpResponse response1 = httpclient.execute(httpGet)) {
logger.info(response1.getStatusLine().toString());
String result = EntityUtils.toString(response1.getEntity(), StandardCharsets.UTF_8);
logger.info("result=" + result);
return result;
}
}
获取到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
27
28/**生成商户订单 */
generateOrder: function (openid) {
var that = this
//统一支付
wx.request({
url: 'https://aggrepay.enzhico.cn/pay/unifiedOrder',
method: 'POST',
data: {
outTradeNo: Math.random().toString(36).substring(7),
totalFee: '7',
body: '支付测试',
attach: '云塔山香烟',
spbillCreateIp: '123.267.12.2',
notifyURL: 'https://aggrepay.enzhico.cn/pay/parseOrderNotifyResult',
tradeType: 'JSAPI',
openid: openid
},
success: function (res) {
//发起支付
var timeStamp = res.data.timeStamp;
var packages = 'prepay_id=' + res.data.prepayId;
var paySign = res.data.sign;
var nonceStr = res.data.nonceStr;
var param = { "timeStamp": timeStamp, "package": packages, "paySign": paySign, "signType": "MD5", "nonceStr": nonceStr };
that.pay(param)
},
})
},
对应后台代码,注意二次签名:
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/**
* 统一下单(详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1)
* 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识"
* 接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder
*
* @param request 请求对象,注意一些参数如appid、mchid等不用设置,方法内会自动从配置对象中获取到(前提是对应配置中已经设置)
*/
"/unifiedOrder") (
public WxPayMpOrderResultVO unifiedOrder2(@RequestBody WxPayUnifiedOrderRequest request) throws WxPayException {
WxPayUnifiedOrderResult result = this.wxService.unifiedOrder(request);
logger.info(result.getReturnMsg());
String signType = WxPayConstants.SignType.MD5;
WxPayMpOrderResultVO payResult = new WxPayMpOrderResultVO();
payResult.setAppId(result.getAppid());
payResult.setTimeStamp(String.valueOf(System.currentTimeMillis() / 1000));
payResult.setNonceStr(result.getNonceStr());
payResult.setPackageValue("prepay_id=" + result.getPrepayId());
payResult.setSignType(signType);
payResult.setPrepayId(result.getPrepayId());
// 二次签名
payResult.setSign(createSign(payResult, this.wxService.getConfig().getMchKey()));
return payResult;
}
/**
* 下面是我自己写的签名,最好调用框架的签名方法
*/
private String createSign(WxPayMpOrderResultVO p, String mchKey) {
String tosstr = "appId="+p.getAppId()+"&nonceStr="+p.getNonceStr()+"&package="+p.getPackageValue()
+"&signType=MD5&timeStamp="+p.getTimeStamp()+"&key=" + mchKey;
logger.info(tosstr);
return DigestUtils.md5Hex(tosstr).toUpperCase();
}
拿到预支付订单后,小程序B就能调用支付了,支付成功后返回到小程序A中,并将支付结果通过extraData
参数回传过去:
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/* 支付 */
pay: function (param) {
console.log("支付")
console.log(param)
wx.requestPayment({
'timeStamp': param.timeStamp,
'nonceStr': param.nonceStr,
'package': param.package,
'signType': param.signType,
'paySign': param.paySign,
success: function (res) {
console.log('支付成功啦啦啦啦。。。。。')
wx.navigateBackMiniProgram({
extraData: {
payResult: 'good'
},
success(res) {
// 打开成功
}
})
},
fail: function (res) {
// fail
},
complete: function () {
// complete
}
})
}
小程序的跳转需要上线版本才能看到效果,所以需要把代码上传后,提交上线审核。 登录小程序后台,点击”开发管理”,在下面的开发版本中点击提交审核,一般半天左右审核完成。