12.2小程序调起支付
小程序调用支付API(wx.requestPayment)来拉起微信客户端进行支付。wx.requestPayment接口的参数定义如下:
参数名 | 变量 | 描述 |
小程序id | appId | 商户申请的小程序对应的appid。若下单时传了sub_appid,可为sub_appid的值。 |
时间戳 | timeStamp | 当前的时间,其他详见时间戳规则。 |
随机字符串 | nonceStr | 随机字符串,不长于32位。 |
订单详情扩展字符串 | package | 小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=*** |
签名方式 | signType | 签名类型:RSA。 |
签名 | paySign | 签名,使用字段appId、timeStamp、nonceStr、package计算得出的签名值 |
小程序发起微信支付时首先要调用业务接口提交支付订单,业务接口中调用通过JSAPI进行支付下单,调用成功后会获取预支付交易会话标识(prepay_id),然后业务接口返回小程序支付所需的参数。小程序使用业务接口返回的支付参数调用wx.requestPayment函数来拉起微信客户端进行支付。以下是小程序发起支付的示例代码:
function weixinPay(page, ddid) {
var domain = getApp().globalData.domain;
var url = domain + '/get_pay_param';
wx.request({
url: url,
data: {ddid: ddid},
success: function (res) {
if (res.statusCode === 200) {
var param = res.data;
wx.requestPayment({
timeStamp: param.TimeStamp,
nonceStr: param.NonceStr,
package: param.Package,
signType: param.SignType,
paySign: param.PaySign,
success: function (res) {
wx.navigateBack({});
},
});
}
}
});
}
12.3处理支付通知
当用户完成支付,微信会把相关支付结果通过异步回调的方式通知商户,商户服务器需要实现接收处理,并按文档规范返回应答。支付结果通知是以POST方法给商户设置的URL发送通知,通知的数据采用JSON格式进行编码,其中含有加密的支付结果。
微信发送支付通知时会对通知数据进行签名,并将签名值放在HTTP的请求头中:Wechatpay-Signature。商户应当验证签名,以确认请求来自微信,而不是来自其他的第三方。
商户成功接收到回调通知后应返回200或204的HTTP应答码,这样微信支付平台才会认为支付通知得到了正常的处理。同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。
支付平台发送支付通知时,如果微信收到的应答不符合规范或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。以下是微信支付通知的参数:
参数名 | 变量 | 描述 |
通知ID | id | 通知的唯一ID |
通知创建时间 | create_time | 通知创建的时间 |
通知类型 | event_type | 通知的类型,支付成功通知的类型为TRANSACTION.SUCCESS |
通知数据类型 | resource_type | 通知的资源数据类型,支付成功通知为encrypt-resource |
+通知数据 | resource | 通知资源数据 |
回调摘要 | summary | 回调摘要 |
其中通知数据的定义为:
参数名 | 变量 | 描述 |
加密算法类型 | algorithm | 对开启结果数据进行加密的加密算法,目前只支持AEAD_AES_256_GCM |
数据密文 | ciphertext | Base64编码后的开启/停用结果数据密文 |
附加数据 | associated_data | 附加数据 |
原始类型 | original_type | 原始回调类型,为transaction |
随机串 | nonce | 加密使用的随机串 |
ciphertext是加密的数据,需要使用APIv3密钥、nonce和associated_data,对数据密文resource.ciphertext进行解密,得到JSON形式的资源对象。
支付通知HTTP应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx。
参数名 | 变量 | 描述 |
返回状态码 | code | 错误码,SUCCESS为清算机构接收成功,其他错误码为失败。 |
返回信息 | message | 返回信息,如非空,为错误原因。 |
支付通知的处理逻辑如下:
1)首先从请求的header中获取签名信息
2)使用微信支付 的平台私钥(不是商户私钥 )进行签名验证。
3)使用APIv3密钥对支付订单数据进行解密
首先看看支付通知的相关数据结构:
type WeixinPayNotice struct {
//通知的唯一ID
ReqId string `json:"id"`
//通知创建的时间
CreateTime string `json:"create_time"`
//通知的类型,支付成功通知的类型为TRANSACTION.SUCCESS
Event_type string `json:"event_type"`
//通知的资源数据类型,支付成功通知为encrypt-resource
ResourceType string `json:"resource_type"`
//回调摘要
Summary string `json:"summary"`
//通知资源数据
Resource NoticeResource `json:"resource"`
}
type NoticeResource struct {
//对开启结果数据进行加密的加密算法
Algorithm string `json:"algorithm"`
//数据密文
Ciphertext string `json:"ciphertext"`
//附加数据
AssociatedData string `json:"associated_data"`
//原始回调类型,为transaction
original_type string `json:"original_type"`
//加密使用的随机串
Nonce string `json:"nonce"`
}
//支付通知返回数据结构
type WxPayNotifyRet struct {
Return_code string `json:"code"`
Return_msg string `json:"message"`
}
//支付通知返回
func HttpCallBackReturn(w http.ResponseWriter, status int, code string, message string) {
var ent WxPayNotifyRet
ent.Return_code = code
ent.Return_msg = message
data, _ := json.Marshal(ent)
w.WriteHeader(status)
w.Write(data)
}
发送支付通知时,微信支付为直连商户模式以及服务商模式提供了不同的数据结构,本文中直连商户模式以及服务商模式共用一套的数据结构,并通过JSON的omitempty标签来适应两种模式的区别。以下是数据结构的定义:
//微信支付订单信息
type WxPayInfo struct {
//应用ID(普通商户)
Appid string `json:"appid"`
//服务商户号(普通商户)
Mchid string `json:"mchid"`
//服务商用ID(服务商)
Sp_appid string `json:"sp_appid"`
//服务商户号(服务商)
Sp_mchid string `json:"sp_mchid"`
//子商户用ID(服务商)
Sub_appid string `json:"sub_appid"`
//子商户的商户号(服务商)
Sub_mchid string `json:"sub_mchid"`
//商户系统内部订单号
Out_trade_no string `json:"out_trade_no"`
//微信支付订单号
Transaction_id string `json:"transaction_id"`
//交易类型,枚举值:JSAPI
Trade_type string `json:"trade_type"`
//交易状态,SUCCESS:支付成功 REFUND:转入退款 NOTPAY:未支付 CLOSED:已关闭 PAYERROR:支付失败
Trade_state string `json:"trade_state"`
//交易状态描述
Trade_state_desc string `json:"trade_state_desc"`
//付款银行
Bank_type string `json:"bank_type"`
//附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
Attach string `json:"attach"`
//支付完成时间
Success_time string `json:"success_time"`
//订单金额信息
Amount OrderAmount `json:"amount"`
//支付者信息
Payer OrderPayer `json:"payer"`
}
接下来看看一个处理支付通知的示例代码:
func HandlerCallBackV3(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
wxpay.WxPayCallBackReturn(w, "FAIL", "FAIL")
return
}
if len(body) < 1 {
err := fmt.Errorf("读取http body失败")
wxpay.WxPayCallBackReturn(w, "FAIL", "FAIL")
return
}
//读取签名验证所需的参数
var sing_param WxSignParam
err = sing_param.InitFromRequest(r, string(body))
if err != nil {
wxpay.WxPayCallBackReturn(w, "FAIL", "FAIL")
return
}
//获取平台证书,并进行签名验证
plat_certificate := GetPlatCertificate(ent, sing_param.CertSerial)
err = ResponseValidate(&sing_param, plat_certificate);
if err != nil {
wxpay.WxPayCallBackReturn(w, "FAIL", "FAIL")
return
}
//body数据解析
var ent_cb WeixinPayNotice
if err = json.Unmarshal(body, &ent_cb); err != nil {
wxpay.WxPayCallBackReturn(w, "FAIL", "FAIL")
return
}
//数据解密
decryptBytes, err := DecryptAES256GCM(
ent.MchAPIKey,
ent_cb.Resource.AssociatedData,
ent_cb.Resource.Nonce,
ent_cb.Resource.Ciphertext)
if err != nil {
wxpay.WxPayCallBackReturn(w, "FAIL", "FAIL")
return
}
//支付订单数据解析
var pay_info WxPayInfo
err = json.Unmarshal([]byte(decryptBytes), &pay_info)
if err != nil {
wxpay.WxPayCallBackReturn(w, "FAIL", "FAIL")
return
}
//其他业务逻辑开始
//..........................
//其他业务逻辑结束
wxpay.WxPayCallBackReturn(w, "SUCCESS", "SUCCESS")
}