前提
正常情况下, 我们是可以使用微信的 NATIVE 支付来做. 但如果我们有公众号, 我们希望用户扫码付款的时候,能够进入到公众号网页中来完成. 这对于增加用户粘性是有一定好处的.
基本步骤
1. 生成一个二维码
扫码支付, 第一步肯定是生成一个二维码, 这个二维码可以是自己定义的一个链接.
例如: 我这里生成的是 woa.exti.cc/sq?p=J20220…
在这里 p 就是生成的交易单号. 这个交易单号作为主键 保存有交易数据.
2. 扫码后换取CODE
我们需要用户微信打开这个链接的时候, 自动跳转到 open.weixin.qq.com 去获取code, 微信会跳转回redirect_uri指定的路径, 这个时候新的url里面多了一个code.为了追求性能, 这一步我们就直接在nginx里面做了.
location /sq {
if ($request_uri ~* "^/sq\?p\=([\d\w]+)$" ) {
set $query_arg1 $1;
return 302 "https://open.weixin.qq.com/connect/oauth2/authorize?appid=$appid&response_type=code&scope=snsapi_base&state=$query_arg1&redirect_uri=https%3A%2F%2Fwoa.exti.cc%2fqrpay&connect_redirect=1#wechat_redirect";
}
return 204;
}
3. 获取openid
当最终跳转到付款页面的真实地址的时候, 我们使用 appid secret 去获取openid, 并将必要的数据渲染到模板页面中, 再由模板页面去调用公众号支付接口, 完成支付过程.
$state = $r->get['state']; # 单号
$code = $r->get['code']; # 用来换取openid到code
$woa = Config::get('woa'); # 公众号配置
$result = http_get("https://api.weixin.qq.com/sns/oauth2/access_token?appid={$woa['appid']}&secret={$woa['secret']}&code={$code}&grant_type=authorization_code");
if($result['code'] === 200){
$body = json_decode($result['body'], true);
return view('qrpay/index.php',[
'title'=>'APP支付页面',
'openid'=>$body['openid'],
'out_trade_no'=>$state,
]);
}
下面是模板页的内容
<!doctype html>
<html>
<head>
<title>万家APP</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover"/>
<title><?php echo $title ?></title>
<style>
</style>
</head>
<body>
</body>
<script lang="javascript">
var buyer_id = "<?php echo $openid ?>";
var trade_no = "<?php echo $out_trade_no ?>";
function onBridgeReady() {
let s = parseInt(trade_no.substr(15, 2)),n = 8400 + parseInt(trade_no.substr(17,2));
let xhr = new XMLHttpRequest();
// 单号规则, 15-17 服务器ID 17-19 计算节点ID
xhr.open('POST', 'https://fee-' + s + '.exti.cc/n-' + n +'/notify/qrpay');
xhr.onreadystatechange = function(){
if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
# 拿到支付包.
let data = JSON.parse(xhr.responseText);
if(data.success){
WeixinJSBridge.invoke('getBrandWCPayRequest', data.package, function(res) {
WeixinJSBridge.call('closewindow');
});
}else{
alert(data.message);
}
}
}
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(JSON.stringify({trade_no, buyer_id}));
}
if (typeof WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
} else {
onBridgeReady();
}
</script>
</html>
4. JSAPI 支付
使用 交易单号获取交易信息, 结合openid、appid 等信息调用下单接口.