微信公众号支付爬坑总结

来源:互联网 发布:联通网络报修电话 编辑:程序博客网 时间:2024/06/15 14:24
场景介绍:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1
开发步骤:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3
业务流程时序图:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_4

流程梳理:
  • 获取用户授权: -> code  -> openid
  • 统一支付下单:组参 -> sign -> 调用统一支付下单接口 ->  prepay_id
  • 唤起微信内置支付页面:组参 -> sign -> js页面唤起
     
  1. 获取openid:
    • 官方文档:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN

    • js页面请求获取 code:
      • 配置:

      • 请求微信链接
https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
      • 所需参数(5个):
        • appid: 应用唯一标识,在微信开放平台提交应用审核通过后获得
        • redirect_uri:微信回调路径(域名未备案不影响测试,域名未备案不影响测试,域名未备案不影响测试,重要的事情多说几遍,不要再怀疑域名的问题
          • 必须为 Get 请求
          • 必须用 encodeURI(redirect_uri)  处理成百分号编码对回调路径处理(在线转换)
          • 必须设置授权域名:(微信公众号平台 -> 公众号设置 -> 功能设置)
        • response_type:固定值 code
        • scope:应用授权作用域(拥有多个作用域用逗号(,)分隔)
          • snsapi_base:隐式授权,用户无感知,仅限于获取code等基本信息。
          • snsapi_login:需要用户点击确认授权,获取更多用户信息。
        • state:授权请求后原样带回给第三方。
      • 返回说明:
        • 用户允许授权后,将会重定向到redirect_uri的网址上,并且带上code和state参数。
          • redirect_uri?code=CODE&state=STATE
        • 若用户禁止授权,则重定向后不会带上code参数,仅会带上state参数
          • redirect_uri?state=STATE
      • js 代码:
var url "http://wangy0228.imwork.net/weixin/order.shtml?parkId=" $("#parkId").val();
var appid $("#wxappid").val();
var weixinUrl "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" appid "&redirect_uri=" encodeURI(url) + "&response_type=code&scope=snsapi_base&state=" plate "#wechat_redirect";
window.location.href encodeURI(weixinUrl);
    • 用code换取openid:
      • 请求的微信链接:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
      • 所需参数(4个):
        • appid:应用唯一标识,在微信开放平台提交应用审核通过后获得
        • secret:应用密钥AppSecret,在微信开放平台提交应用审核通过后获得
        • code填写第一步获取的code参数
        • grant_type:填写第一步获取的code参数
      • 返回说明:
        • access_token:接口调用凭证
        • expires_in:access_token接口调用凭证超时时间,单位(秒)
        • refresh_token:用户刷新access_token
        • openid授权用户唯一标识
        • scope:用户授权的作用域,使用逗号(,)分隔
        • unionid:当且仅当该网站应用已获得该用户的userinfo授权时,才会出现该字段。
正确返回样例:
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
错误返回样例:
{
"errcode":40029,
"errmsg":"invalid code"
}
      • 代码:
String openIdUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appid + "&secret=" + secret + "&code=" + code + "&grant_type=authorization_code";
HttpPost post = new HttpPost(openIdUrl); // 设置响应头信息
      post.addHeader("Connection", "keep-alive");
      post.addHeader("Accept", "*/*");
      post.addHeader("Content-Type", "application/json");
      post.addHeader("Host", "api.mch.weixin.qq.com");
      post.addHeader("X-Requested-With", "XMLHttpRequest");
      post.addHeader("Cache-Control", "max-age=0");
      post.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
HttpResponse response = httpclient.execute(post);
String  jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
Map<String, Object> resultMap = (Map<String, Object>) JSONObject.parse(jsonStr);
String openid = resultMap.get("openid").toString();

    2、统一下单支付:
    • 官方文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
    • 网站配置:(路径为支付路径的上一级目录)


    • 调用的微信统一下单支付接口:
https://api.mch.weixin.qq.com/pay/unifiedorder
    • 用 SortedMap 拼参数(11个)换取 prepay_id:
      • appid:微信支付分配的公众账号ID
      • mch_id:微信支付分配的商户号
      • nonce_str:随机字符串,长度要求在32位以内
UUID.randomUUID().toString().replace("-", "");
      • body:商品简单描述
      • out_trade_no:订单号
      • total_fee:总金额,单位为分
      • spbill_create_ip:APP和网页支付提交用户端ip
InetAddress ip = InetAddress.getLocalHost().getHostAddress();
      • notify_url:异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
      • trade_type:取值如下:JSAPI,NATIVE,APP等(公众号支付此值为JSAPI)
      • openidtrade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识。
      • sign:通过签名算法计算得出的签名值
        • 签名算法需要用到商户的Appsecret:( 微信商户平台(pay.weixin.qq.com)-->账户中心-->账户设置-->API安全-->密钥设置
/**
* 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
* packageParams 为除 sign 外所有参数
*/
public String createSign(SortedMap<String, String> packageParams, String appSecret) {
  StringBuffer sb = new StringBuffer();
  SortedMap<String, String> treeMap = new TreeMap<>();
  for (Map.Entry<String, String> entry : treeMap.entrySet()){
      String k = entry.getKey();
      String v = entry.getValue();
      if (null != v && !"".equals(v) && !"sign".equals(k)
            && !"key".equals(k)) {
        sb.append(k + "=" + v + "&");
      }
  }
  sb.append("key=" + appSecret);
  return MD5Util.MD5Encode(sb.toString(), this.charset).toUpperCase();
}

    • 将以上参数组参,拼接成 xml 格式字符串,调用统一支付接口换取 prepay_id。
      • 当 return_code 和 result_code 都为 SUCCESS 时返回
        • trade_type:支付类型(此处返回为JSAPI)
        • prepay_id:微信预支付会话标记
        • code_url:无
      • 其他返回参数说明见微信官方文档,此处用不到所有就不做过多分析。

     3、微信内H5调起支付:
      • 用微信浏览器内置对象 WeixinJSBridge 调起支付(WeixinJSBridge 在其他浏览器无法识别):
      • 参数(6个):
        • appId:商户id
        • timeStamp:时间戳
        • nonceStr:随机字符串
        • package:订单扩展字段(此处为前面获取的prepay_id,格式:prepay_id=***)
        • signType:签名方式(MD5)
        • paySign:签名
      • 返回结果:
        • get_brand_wcpay_request:ok:支付成功
        • get_brand_wcpay_request: cancel:用户取消支付
        • get_brand_wcpay_request:fail:支付失败
      • js代码:
if (typeof WeixinJSBridge == "undefined") {
    if (document.addEventListener) {
        document.addEventListener('WeixinJSBridgeReady'onBridgeReadyfalse);
    } else if (document.attachEvent) {
        document.attachEvent('WeixinJSBridgeReady'onBridgeReady);
        document.attachEvent('onWeixinJSBridgeReady'onBridgeReady);
    }
else {
    onBridgeReady(data.data);
}
function onBridgeReady(data) {
    var total $("#total").val();
    WeixinJSBridge.invoke(
        'getBrandWCPayRequest', {
            "appId": data.appId,     //公众号名称,由商户传入
           "timeStamp": data.timeStamp,         //时间戳,自1970年以来的秒数
           "nonceStr": data.nonceStr//随机串
           "package": data.package,
            "signType": data.signType,         //微信签名方式:
           "paySign": data.paySign //微信签名
        }, function (res) {
            if (res.err_msg == "get_brand_wcpay_request:ok") {
                window.location.href="${ctx}/weixin/success.shtml?total=" total;// 支付成功跳转到自定义页面
            } else if (res.err_msg == "get_brand_wcpay_request:cancel") {
                alert("用户取消支付!");
            } else {
                alert("支付失败!");
            }
        });
};