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")
}