微信支付详解

469 阅读5分钟

一.概论

  1. Native支付:

商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式

返回说明:微信接口返回 url 链接(wxpay://) 前端根据url生成对应的二维码,用户扫码支付

  1. 付款码支付:

用户出示微信钱包中的条码、二维码,商家通过扫描用户条码即可完成收款

返回说明:(客户出示微信的条码,商家通过扫码机扫码完成支付)

备注:条码扫码机原理:扫码机在扫码时会识别条码里包含的数字+回车键事件(举例:input输入框监听回车键按键事件)

  1. H5支付:

商户在微信客户端外的移动端网页展示商品或服务,用户在前述页面确认使用微信支付时,商户发起本服务呼起微信客户端进行支付。

返回支付H5 url链接,用户跳转到url界面去唤醒支付

  1. APP支付:

商户通过在移动端应用APP中集成开放SDK调起微信支付模块来完成支付。

在APP内直接通过activity唤醒微信模块内支付模块

(每一个APP软件的每一个界面都是一个activity,另一个APP可以通过)

  1. 小程序支付:

在微信内打开小程序时,可以调用微信支付完成下单购买的流程

微信小程序内调起微信支付接口

二.详情

以下已小程序支付为例说明

①V2版本

V2版本介绍:(本公司项目目前皆使用的是V2版本)

*V2版本默认是不需要配置证书的 ,但是配置了证书后必须使用证书方式进行调用

接口说明:

第一步:(前端小程序功能)小程序调起支付

wx.requestPayment

(

{

//时间戳

"timeStamp": "1414561699",

//随机字符串,不长于32位。

"nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",

//小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=*** 

//该参数由微信支付统一下单接口返回(如附注一)

"package": "prepay_id=wx201410272009395522657a690389285100",

//签名类型,默认为HMAC256和MD5。

"signType": "MD5",

//该sign通过加密算法生成

"paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq/xDg==",

"success":function(res){},

"fail":function(res){},

"complete":function(res){}

}

)

附注一:

统一下单接口获取prepay_id

统一下单接口url:api.mch.weixin.qq.com/pay/unified…

参数说明:

'appid' => $this->appid, //小程序 ID

'mch_id' => $this->mch_id, //商户号

nonce_str' => $this->createNoncestr(), //随机字符串

'body' => $this->body,

'out_trade_no' => '2018013106125348', //商户订单号

'total_fee' =>100, //总金额 单位 分

'spbill_create_ip' => '192.168.0.161', //终端 IP

'notify_url' =>‘’, //通知地址 确保外网能正常访问

'openid' => $this->openid, //用户 id

'trade_type' => 'JSAPI'//交易类型

‘sign’=>根据以上参数生成

(如果配置了证书:

ssl_key_addr:‘’,

ssl_cert_addr:‘’ 记得是证书的绝对地址而不是证书里面的内容 v3版本使用的是证书里的内容)

备注:①sign的生成算法(注意这个sign并不是小程序所需要的paysign而是统一下单接口需要的sign)

将以上参数去掉sign参数后 进行按字典序排序后去掉空格 在转为appid=123&mch_id=123123&…&trade_type=JSAPI形式的字符串str;再在字符串str后拼接key 得到新的字符串str(appid=123&mch_id=123123&…&trade_type=JSAPI&key=12312313)

最后将得到的str进行md5加密 得到所谓的sign

②注意v2版本 提交给微信的并非是我们常见的json形式的数据而是xml格式

(xml教程:www.w3school.com.cn/xml/index.a…

以上接口成功后会拿到所需要的prepay_id

有了prepay_id后 我们就还剩一个参数没有 那便是小程序调起支付所需要的paysign

此时需要在根据上面sign的生成算法 根据值生成一个小程序使用的paysign

学名把它叫做 二次验签 参数如下

'appId' => $this->appid, //小程序 ID

'timeStamp' => '' . time() . '', //时间戳

'nonceStr' => $this->createNoncestr(), //随机串

'package' => 'prepay_id=', //数据包(上面拿到的prepay_id)

'signType' => 'MD5'//签名方式

到此:小程序所需要的所有参数已经全部获取 直接调用即可拉起微信支付

②V3

V3接口是最近出来的新的方式,v3的sign生成算法和v2的逻辑上有着本质的区别

只要一个一样的 那就是都需要二次验签,具体区别如下图:

可以看到 ①v3 已经放弃了你们常见的MD5 而改用最新的RSA+SHA256(俗称RSA2加密方式)②v2的所有返回结果都是明文的xml v3改成了加密后的json但是需要解密后才能拿到真正的返回值(解密算法AES-256-GCM)

具体流程如下:

第一步:(前端小程序功能)小程序调起支付

wx.requestPayment

(

{

//时间戳

"timeStamp": "1414561699",

//随机字符串,不长于32位。

"nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",

//小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=*** 

//该参数由微信支付统一下单接口

"package": "prepay_id=wx201410272009395522657a690389285100",

//签名类型

"signType": "RSA",(只能RSA)

//该sign通过加密算法生成

"paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq/xDg==",

"success":function(res){},

"fail":function(res){},

"complete":function(res){}

}

)

这个功能 其实和v2的参数大相径庭 重点依旧是获取prepay_id 和 paysign

也就是重点是从后面开始 会有截然不同的体现

jsapi下单接口url:api.mch.weixin.qq.com/v3/pay/tran…

参数:

'appid' => $this->appid, //小程序 ID

'mch_id' => $this->mch_id, //商户号

'description' => 商品描述,

'out_trade_no' => '2018013106125348', //商户订单号

'notify_url' =>‘’, //通知地址 确保外网能正常访问

‘payer’:{'openid' => $this->openid}, //用户 id

‘amount:{'total' => 100}, //金额

例如:

{

"mchid": "1900006XXX",

"out_trade_no": "1217752501201407033233368318",

"appid": "wxdace645e0bc2cXXX",

"description": "Image形象店-深圳腾大-QQ公仔",

"notify_url": "weixin.qq.com/",

"amount": {

"total": 1,

"currency": "CNY"

},

"payer": {

"openid": "o4GgauInH_RCEdvrrNGrntXDuXXX"

}

}

这就是所有要传入的参数 细心的你们会发现 参数里并没有发现sign

那么v3的接口到底如何调用 我们需要的sign又是如何生成的呢???

具体的如下:

  1. 第一步:

我们有了以上的参数后 就可以进行签名(第一次签名)

第一次签名 并不再是单纯的依靠我们的参数进行签名 ,而是已一种http协议格式的字符串进行签名:例如

HTTP请求方法\n URL\n 请求时间戳\n 请求随机串\n 请求报文主体\n

以上的 请求方法:get/post

url 网关

请求时间戳(自主生成)/请求随机串(自主生成)/报文主体:我们的参数

对这个字符串先进行sha256编码在对编码后的字符串进行rsa加密

得到的第一次签名的sign

备注:这个sign并不需要加入到参数中 ,而是在我们向微信提交请求时 放在请求头中

auth_info = 'WECHATPAY2-SHA256-RSA2048 {}'.format(sign)

headers['Authorization'] = auth_info

如此我们便能得到prepay_id,那么就还是小程序所需要的paysign

那么小程序所需要的paysign 在v3的接口里是怎么生成的呢

如下:当我们拿到prepay_id以后

我们需要拼接一个这样格式的字符串:

'{}\n{}\n{}\n{}\n'.format(self.mch_appid, ts, nonce, prepay_id)

举例: Appid\n时间戳\随机字符串\nprepay_id=******

对上面所诉的字符串在进行签名得到小程序调起支付所需要的paysign

返给前端:至此v3版本支付功能结束

由于V3版本设计到了RSA和sha256等算法,这两种算法都封装于openssl里面

Sha256 把一个字符串进行sha256后会成为一个256bit的哈希值(跟md5一样不可逆)

比如:A7FCFC6B5269BDCCE571798D618EA219A68B96CB87A0E21080C2E758D23E4CE9

Rsa ①则是一种对称加密算法 之然而然 就会有加密的密钥 和 解密的密钥(rsa的密钥存放在证书里))②传统的rsa 最开始是1028位的 ,最新的暴力破解已经尝试到了986位,所以 微信使用的全是最新的rsa256(密钥长度为 2056位)(这一点跟支付宝一样,支付宝提供个人沙箱环境 只支持rsa256加密))