微信支付流程图
微信支付存在多个业务流程,包括微信支付流程、退款流程等。本章节主要介绍微信的支付下单流程,图12-1是微信支付流程的交互图:
重点环节说明
- 步骤1:小程序端用户向商户服务器发起支付请求,重点是提供用户信息、商品信息、支付金额等参数。
- 步骤3:商户服务器调用支付统一下单接口生成微信支付订单,调用成功后返回预支付交易会话标识(prepay_id),该参数将用于小程序前端的接口调用。
- 步骤6:获取prepay_id后,需要再次签名,然后把签名参数以及prepay_id等参数返回给小程序。
- 步骤9:小程序调用接口:wx.requestPayment调起微信支付,发起支付请求。
- 步骤15:用户支付成功后,商户服务器可接收到微信支付支付结果通知,商户服务器收到通知后更新订单的支付状态。
- 步骤20:商户在没有接收到微信支付结果通知的情况下需要主动调用查询订单API查询支付结果。
12.1提交支付订单
商户系统调用该接口生成预支付交易单。提交支付订单的请求URL为:
https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi
提交支付订单的接口的请求参数如下:
参数名 | 变量 | 描述 |
---|---|---|
服务商应用ID | sp_appid | 必填)服务商申请的公众号appid。 |
服务商户号 | sp_mchid | (必填)服务商户号,由微信支付生成并下发 |
子商户应用ID | sub_appid | (必填)子商户申请的公众号appid。若sub_openid有传的情况下,sub_appid必填,且sub_appid需与sub_openid对应 |
子商户号 | sub_mchid | (必填)子商户的商户号,由微信支付生成并下发。 |
商品描述 | description | (必填)商品描述 |
商户订单号 | out_trade_no | (必填)商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一。 |
结束时间 | time_expire | 订单失效时间。 |
附加数据 | attach | 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 |
通知地址 | notify_url | (必填)通知URL必须为直接可访问的URL,不允许携带查询串。 |
优惠标记 | goods_tag | 订单优惠标记 |
+结算信息 | settle_info | 结算信息 |
-是否分账 | profit_sharing | 是否指定分账,枚举值true:是false:否 |
+订单金额 | amount | (必填)订单金额信息 |
-总金额 | total | (必填)订单总金额,单位为分。 |
-货币类型 | currency | CNY:人民币,境内商户号仅支持人民币。 |
+支付者 | payer | 支付者信息 |
-用户标识 | sp_openid | 用户在服务商appid下的唯一标识。 |
-用户子标识 | sub_openid | 用户在子商户appid下的唯一标识。若传sub_openid,那sub_appid必填 |
调用成功后返回:预支付交易会话标识(prepay_id),该参数用于小程序前端的接口调用中,该值的有效期为2小时。以下是实现提交支付订单的代码逻辑,首先给出的是统一下接口使用的数据结构:
//订单数据
type WxAppOrderData struct {
//子商户用ID(服务商)
Sub_appid string `json:"sub_appid,omitempty"`
//子商户的商户号(服务商)
Sub_mchid string `json:"sub_mchid,omitempty"`
//商品描述
Description string `json:"description"`
//商户系统内部订单号
Out_trade_no string `json:"out_trade_no"`
//交易结束时间
Time_expire string `json:"time_expire,omitempty"`
//附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
Attach string `json:"attach,omitempty"`
//通知URL必须为直接可访问的URL,不允许携带查询串
Notify_url string `json:"notify_url"`
//订单优惠标记
Goods_tag string `json:"goods_tag,omitempty"`
//订单金额信息
Amount OrderCreateAmount `json:"amount"`
//支付者信息
Payer OrderPayer `json:"payer"`
//统一下单结算信息
Settle_info OrderCreateSettle `json:"settle_info"`
}
//统一下单请求参数
type WxAppOrderCreateReq struct {
//应用ID(普通商户)
Appid string `json:"appid,omitempty"`
//直连商户号(普通商户)
Mchid string `json:"mchid,omitempty"`
//服务商用ID(服务商)
Sp_appid string `json:"sp_appid,omitempty"`
//服务商户号(服务商)
Sp_mchid string `json:"sp_mchid,omitempty"`
//订单数据
WxAppOrderData
}
//统一下单返回参数
type WxAppOrderCreateRet struct {
//详细错误码
Return_code string `json:"code"`
//错误描述
Return_msg string `json:"message"`
//预支付交易会话标识。用于后续接口调用中使用,该值有效期为2小时
Prepay_id string `json:"prepay_id"`
}
微信支付为直连商户模式以及服务商模式提供了不同的调用接口(具有不同的接口地址以及请求参数)。本文中直连商户模式以及服务商模式共用一套数据结构,并通过json的omitempty标签来适应两种模式的区别。接下来给出服务商模式下接口调用的实现逻辑:
//支付统一下单
//data:支付订单信息
func (ent *MchWxapp) createOrderX(data WxAppOrderData) (WxAppPayParam, error) {
var preq WxAppOrderCreateReq
preq.Sp_appid = ent.Appid
preq.Sp_mchid = ent.Mchid
preq.WxAppOrderData = data
data_body, _ := json.Marshal(preq)
var param_ent WxAppPayParam
var pret WxAppOrderCreateRet
const url = "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi"
result, err := WxPayPostV3((*MchParam)(ent), url, data_body)
if err != nil {
return param_ent, err
}
err = json.Unmarshal([]byte(result), &pret)
if err != nil {
fmt.Println(err)
return param_ent, err
}
if pret.Prepay_id == "" {
return param_ent, errors.New(pret.Return_msg)
}
//小程序客户端支付参数
param_ent.Appid = preq.Sub_appid
param_ent.TimeStamp = fmt.Sprintf("%d", time.Now().Unix())
param_ent.NonceStr, _= GenerateNonce()
param_ent.Prepay_id = pret.Prepay_id
param_ent.Package = "prepay_id=" + pret.Prepay_id
param_ent.SignType = "RSA"
param_ent.PaySign, _ = param_ent.GenPaySignV3(ent.MchPrivateKey)
return param_ent, nil
}
通过JSAPI下单成功获取预支付交易会话标识(prepay_id)后,需要通过小程序调用支付API(wx.requestPayment)来拉起微信客户端进行支付。wx.requestPayment需要将请求参数进行签名,签名计算的逻辑如下:
1)构造签名串
签名串一共有四行,每一行为一个参数。行尾以\n结束,包括最后一行。参与签名字段及格式为:
小程序appId\n时间戳\n随机字符串\n订单详情扩展字符串\n
2)计算签名值
使用商户私钥对签名串进行SHA256 with RSA签名,并对签名结果进行Base64编码得到签名值。
接下来给出小程序参数签名计算的实现代码,首先给出返回给小程序所需的支付数据的数据结构:
type WxAppPayParam struct{
Appid string
TimeStamp string
NonceStr string
Prepay_id string
Package string
SignType string
PaySign string
}
接下来使用商户私钥对签名串进行SHA256 with RSA签名,并对签名结果进行Base64编码来计算签名值:
func (ent *WxAppPayParam)GenPaySignV3(mch_pem_key *rsa.PrivateKey) (string, error) {
SignatureMessageFormat := "%s\n%s\n%s\n%s\n"
message := fmt.Sprintf(SignatureMessageFormat, ent.Appid, ent.TimeStamp, ent.NonceStr, ent.Package)
signatureResult, err := SignSHA256WithRSA(mch_pem_key, message)
if err != nil {
return "", err
}
return signatureResult, nil
}
直连商户的下单接口与服务商下单接口基本一致,除了接口地址不同外,下单时不需要提供子商户号(sub_mchid), 也不需要提供子商户子商户的应用ID(sub_appid)。直连商户下单接口的请求URL为:
https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi
直连商户下单接口的请求参数如下:
参数名 | 变量 | 描述 |
---|---|---|
应用ID | appid | body 由微信生成的应用ID,全局唯一。请求基础下单接口时请注意APPID的应用属性,例如公众号场景下,需使用应用属性为公众号的APPID |
直连商户号 | mchid | body 直连商户的商户号,由微信支付生成并下发。 |
商品描述 | description | body 商品描述 |
商户订单号 | out_trade_no | body 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一 |
交易结束时间 | time_expire body | 订单失效时间,time_expire只能第一次下单传值,不允许二次修改,二次修改系统将报错。如用户支付失败后,需再次支付,需更换原订单号重新下单。 |
附加数据 | attach | body 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 |
通知地址 | notify_url | body异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http |
订单优惠标记 | goods_tag | body 订单优惠标记 |
订单金额 | amount | body 订单金额信息 |
支付者 | payer | body 支付者信息 |
优惠功能 | detail | body 优惠功能 |
场景信息 | scene_info | body 支付场景描述 |
结算信息 | settle_info | body 结算信息 |
以下是直连商户下单接口访问的代码实现:
func (ent *MchWxapp) createOrder(data WxAppOrderData) (WxAppPayParam, error) {
var preq WxAppOrderCreateReq
preq.Appid = ent.Appid
preq.Mchid = ent.Mchid
preq.WxAppOrderData = data
data_body, _ := json.Marshal(preq)
var param_ent WxAppPayParam
var pret WxAppOrderCreateRet
const url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"
result, err := WxPayPostV3((*MchParam)(ent), url, data_body)
if err != nil {
return param_ent, err
}
err = json.Unmarshal([]byte(result), &pret)
if err != nil {
return param_ent, err
}
if pret.Prepay_id == "" {
return param_ent, errors.New(pret.Return_msg)
}
//小程序客户端支付参数
param_ent.Appid = preq.Appid
param_ent.TimeStamp = fmt.Sprintf("%d", time.Now().Unix())
param_ent.NonceStr, _= GenerateNonce()
param_ent.Prepay_id = pret.Prepay_id
param_ent.Package = "prepay_id=" + pret.Prepay_id
param_ent.SignType = "RSA"
param_ent.PaySign, _ = param_ent.GenPaySignV3(ent.MchPrivateKey)
return param_ent, nil
}