微信公众号扫码支付笔记

185 阅读2分钟

前提

正常情况下, 我们是可以使用微信的 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 等信息调用下单接口.