微信小程序开发实战11_4 微信支付退款流程

当交易发生之后一年内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付金额退还给买家,微信支付将收到退款请求并且验证成功之后,将支付款按原路退还至买家账号上。使用该接口时的一些注意事项如下:

  • 交易时间超过一年的订单无法提交退款。
  • 微信支付退款支持单笔交易分多次退款(不超50次),多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号。
  • 如果同一个用户有多笔退款,建议分不同批次进行退款,避免并发退款导致退款失败。
  • 申请退款接口的返回仅代表业务的受理情况,具体退款是否成功,需要通过退款查询接口获取结果。
    微信退款的操作流程如下:
    1)买家或者卖家发起退款请求,需要提供商户订单号、退款金额等参数。
    2)商户服务器调用退款申请接口提交退款申请。
    3)微信平台向商户服务器发送退款通知,商户服务器收到通知后更新订单的退款状态。
    4)商户在没有接收到退款通知的情况下需要主动调用退款查询接口查询订单的退款状态。

13.1退款申请

退款申请接口的请求地址为:https://api.mch.weixin.qq.com/v3/refund/domestic/refunds
退款申请接口的请求参数说明如下:

参数名 变量 描述
子商户号 sub_mchid 子商户的商户号(服务商模式下)
微信支付订单号 transaction_id 原支付交易对应的微信订单号
商户订单号 out_trade_no 原支付交易对应的商户订单号
商户退款单号 out_refund_no 商户系统内部的退款单号
退款原因 reason 若商户传入,会在下发给用户的退款消息中体现退款原因
退款结果回调url notify_url 异步接收微信支付退款结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效,优先回调当前传的这个地址。
退款资金来源 funds_account 若传递此参数则使用对应的资金账户退款,否则默认使用未结算资金退款(仅对老资金流商户适用)
金额信息 amoun t 订单金额信息
退款商品 goods_detail 指定商品退款需要传此参数,其他场景无需传递

接下来分别给出直连商户与服务商两种模式下的支付退款申请接口的相关数据结构以及函数。

//退款金额信息
type RefundCreateAmount struct {
   //退款金额,单位为分
   Refund           int       `json:"refund"`
   //原订单金额,单位为分
   Total        int       `json:"total"`
   //退款币种:CNY(人民币),境内商户号仅支持人民币。
   Currency           string    `json:"currency"`
}
//微信支付退款申请请求参数
type RefundCreateReq struct {
   //子商户的商户号(服务商)
   Sub_mchid          string `json:"sub_mchid,omitempty"`
   //商户系统内部订单号
   Out_trade_no       string `json:"out_trade_no,omitempty"`
   //微信支付订单号
   Transaction_id         string `json:"transaction_id,omitempty"`
   //商户系统内部的退款单号
   Out_refund_no      string `json:"out_refund_no"`
   //退款原因
   Reason            string `json:"reason,omitempty"`
   //退款结果回调的URL,不允许携带查询串
   Notify_url           string `json:"notify_url,omitempty"`
   //退款金额信息
   Amount     RefundCreateAmount     `json:"amount"`
}
//微信退款订单信息
type RefundOrderInfo struct {
   //服务商户号(普通商户)
   Mchid                 string `json:"mchid"`
   //服务商户号(服务商)
   Sp_mchid           string `json:"sp_mchid"`
   //子商户的商户号(服务商)
   Sub_mchid          string `json:"sub_mchid"`
   //微信支付退款单号
   Refund_id          string `json:"refund_id"`
   //商户系统内部的退款单号
   Out_refund_no      string `json:"out_refund_no"`
   //微信支付订单号
   Transaction_id         string `json:"transaction_id"`
   //商户系统内部订单号
   Out_trade_no       string `json:"out_trade_no"`
   //退款渠道 
   Channel          string `json:"channel"`
   //退款入账账户
   User_received_account    string    `json:"user_received_account"`
   //退款成功时间
   Success_time           string `json:"success_time"`
   //退款创建时间
   Create_time        string `json:"create_time"`
   //退款状态
   Status               string `json:"status"`
   //退款所使用资金对应的资金账户类型 
   Funds_account       string `json:"funds_account"`
   //订单金额信息
   Amount     RefundOrderAmount  `json:"amount"`
}
//提交退款申请(服务商模式)
func refundOrderX(ent *MchParam, data RefundCreateReq) (RefundOrderInfo, error) {
   data_body, _ := json.Marshal(data)

   var pret RefundOrderInfo
   const url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"
   result, err := WxPayPostV3(ent, url, data_body)
   if err != nil {
      return pret, err
   }
   err = json.Unmarshal([]byte(result), &pret)
   if err != nil {
      return pret, err
   }
   return pret, nil
}
//提交退款申请(普通商户模式)
func refundOrder(ent *MchParam, data RefundCreateReq) (RefundOrderInfo, error) {
   data.Sub_mchid = ""
   data_body, _ := json.Marshal(data)

   var pret RefundOrderInfo
   const url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"
   result, err := WxPayPostV3(ent, url, data_body)
   if err != nil {
      return pret, err
   }
   err = json.Unmarshal([]byte(result), &pret)
   if err != nil {
      return pret, err
   }
   return pret, nil
}

13.2处理退款通知

当商户申请的退款有结果后(退款状态为:退款成功、退款关闭、退款异常),微信会把相关结果发送给商户,商户需要进行接收处理,并返回应答。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP请求头中:Wechatpay-Signature。商户应当验证签名,以确认请求来自微信,而不是其他的第三方。
退款通知HTTP应答码为200且返回状态码为SUCCESS才会当做商户接收成功。如果微信收到的应答不符合规范或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。如果在所有通知频率后没有收到微信侧回调。商户应调用退款查询接口确认退款状态。
同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
退款通知的处理逻辑如下:
1)首先从请求的header中获取签名
2)使用微信支付 的平台私钥(不是商户私钥 )进行签名验证。
3)使用APIv3密钥对支付订单数据进行解密
首先看看退款通知的相关参数说明:

参数名 变量 描述
通知ID id 通知的唯一ID
通知创建时间 create_time 通知创建的时间
通知类型 event_type 通知的类型:REFUND.SUCCESS:退款成功通知REFUND.ABNORMAL:退款异常通知REFUND.CLOSED:退款关闭通知
通知简要说明 summary 通知简要说明
通知数据类型 resource_type 通知的资源数据类型,支付成功通知为encrypt-resource
通知数据 resource 通知资源数据JSON格式

退款结果对重要的数据进行了加密,商户需要用商户密钥进行解密后才能获得结果通知的内容。商户对resource对象进行解密后,得到的通知参数,具体字段含义如下:

参数名 变量 描述
直连商户号 mchid 直连商户的商户号(直连模式下)。
服务商户号 sp_mchid 服务商户号(服务商模式下)。
子商户号 sub_mchid 子商户的商户号(服务商模式下)。
商户订单号 out_trade_no 返回的商户订单号
微信支付订单号 transaction_id 微信支付订单号
商户退款单号 out_refund_no 商户退款单号
微信支付退款单号 refund_id 微信退款单号
退款状态 refund_status 退款状态,枚举值:SUCCESS:退款成功CLOSE:退款关闭ABNORMAL:退款异常
退款成功时间 success_time 退款成功时间
退款入账账户 user_received_account 取当前退款单的退款入账方。
金额信息 amount 金额信息

退款通知相关的数据结构定义如下:

//退款金额信息
type RefundOrderAmountCB struct {
   //退款金额,单位为分
   Refund           int       `json:"refund"`
   //原订单金额,单位为分
   Total        int       `json:"total"`
   //退款币种:CNY(人民币),境内商户号仅支持人民币。
   Currency           string    `json:"currency"`
   //用户支付金额,单位为分
   Payer_total    int       `json:"payer_total,omitempty"`
   //退款给用户的金额,不包含所有优惠券金额
   Payer_refund   int       `json:"payer_refund,omitempty"`
}
//微信退款订单信息
type RefundOrderInfoCB struct {
   //服务商户号(普通商户)
   Mchid                 string `json:"mchid"`
   //服务商户号(服务商)
   Sp_mchid           string `json:"sp_mchid"`
   //子商户的商户号(服务商)
   Sub_mchid          string `json:"sub_mchid"`
   //微信支付订单号
   Transaction_id         string `json:"transaction_id"`
   //商户系统内部订单号
   Out_trade_no       string `json:"out_trade_no"`
   //微信支付退款单号
   Refund_id          string `json:"refund_id"`
   //商户系统内部的退款单号
   Out_refund_no      string `json:"out_refund_no"`
   //退款状态,SUCCESS:退款成功 CLOSE:退款关闭 ABNORMAL:退款异常
   Refund_status      string `json:"refund_status"`
   //退款成功时间
   Success_time           string `json:"success_time"`
   //退款入账账户
   User_received_account  string `json:"user_received_account"`
   //订单金额信息
   Amount        RefundOrderAmountCB `json:"amount"`
}

接下来看看一个退款通知处理的示例代码:

func HandlerRefundCB(w http.ResponseWriter, r *http.Request) {
var ret_info RefundOrderInfoCB
body, err := ioutil.ReadAll(r.Body)
if err != nil {
   wxpay4go.HttpCallBackReturn(w, 500,"FAIL", "FAIL")
   return
}
if len(body) < 1 {
   wxpay4go.HttpCallBackReturn(w, 500,"FAIL", "FAIL")
   return
}
//读取签名验证所需的参数
var sing_param WxSignParam
err = sing_param.InitFromRequest(r, string(body))
if err != nil {
   wxpay4go.HttpCallBackReturn(w, 500,"FAIL", "FAIL")
   return
}
//获取平台证书,并进行签名验证
plat_certificate := GetPlatCertificate(ent, sing_param.CertSerial)
err = ResponseValidate(&sing_param, plat_certificate);
if err != nil {
   wxpay4go.HttpCallBackReturn(w, 500,"FAIL", "FAIL")
   return
}
//body数据解析
var ent_cb WeixinPayNotice
if err = json.Unmarshal(body, &ent_cb); err != nil {
   wxpay4go.HttpCallBackReturn(w, 500,"FAIL", "FAIL")
   return
}
//数据解密
decryptBytes, err := DecryptAES256GCM(
   ent.MchAPIKey,
   ent_cb.Resource.AssociatedData,
   ent_cb.Resource.Nonce,
   ent_cb.Resource.Ciphertext)
if err != nil {
   wxpay4go.HttpCallBackReturn(w, 500,"FAIL", "FAIL")
   return
}
//订单数据解析
err = json.Unmarshal([]byte(decryptBytes), &ret_info)
if err != nil {
   wxpay4go.HttpCallBackReturn(w, 500,"FAIL", "FAIL")
   return
}
//其他业务逻辑开始
//..........................
//其他业务逻辑结束
wxpay4go.HttpCallBackReturn(w, 200, "SUCCESS", "SUCCESS")
}

13.3退款查询

退款通知HTTP应答码为200且返回状态码为SUCCESS才会当做商户接收成功。如果微信收到的应答不符合规范或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。如果在所有通知频率后没有收到微信侧回调。商户应调用退款查询接口确认退款状态。
退款有一定延时,建议在提交退款申请后1分钟发起退款状态查询,一般来说零钱支付的退款5分钟内到账,银行卡支付的退款1-3个工作日到账。
微信支付为直连商户与服务商提供了不同的退款查询接口(接口的地址与参数都不相同),接下来分别介绍两种模式下的退款查询接口的使用方式。
1)服务商模式
接口的格式为:
https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/{out_refund_no}?sub_mchid={sub_mchid}
接口中的请求参数说明如下:

  • out_refund_no:商户退款单号
  • sub_mchid: 服务商商户号
    2)直连商户模式
    接口的格式为:
    https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/{out_refund_no}
    接口中的请求参数说明如下:
  • out_refund_no:商户退款单号

两种查询方式返回结果相同,返回结果的数据结构如下所示:

参数名 变量 描述
微信支付退款单号 refund_id 微信支付退款单号
商户退款单号 out_refund_no 商户系统内部的退款单号
微信支付订单号 transaction_id 微信支付交易订单号
商户订单号 out_trade_no 原支付交易对应的商户订单号
退款渠道 channel 枚举值:ORIGINAL:原路退款BALANCE:退回到余额OTHER_BALANCE:原账户异常退到其他余额账户OTHER_BANKCARD:原银行卡异常退到其他银行卡
退款入账账户 user_received_account 取当前退款单的退款入账方
退款成功时间 success_time 退款成功时间,当退款状态为退款成功时有返回。
退款创建时间 create_time 退款受理时间
退款状态 status 枚举值:SUCCESS:退款成功CLOSED:退款关闭PROCESSING:退款处理中ABNORMAL:退款异常
资金账户 funds_account 退款所使用资金对应的资金账户类型
金额信息 amount 金额详细信息
优惠退款信息 promotion_detail 优惠退款信息

以下是退款查询的服务商模式的代码以及直连商户模式的代码:

//查询单笔退款(服务商模式)
func queryRefundOrderX(ent *MchParam, out_refund_no string, mchid string) (RefundOrderInfo, error) {
   var ret_info RefundOrderInfo
   url := fmt.Sprintf("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/%s?sub_mchid=%s", out_refund_no, mchid)
   result, err := WxPayGetV3(ent, url)
   if err != nil {
      fmt.Println(err)
      return ret_info, err
   }
   err = json.Unmarshal([]byte(result), &ret_info)
   if err != nil {
      fmt.Println(err)
      return ret_info, err
   }

   return ret_info, nil
}
//查询单笔退款(直连商户模式)
func queryRefundOrder(ent *MchParam, out_refund_no string, mchid string) (RefundOrderInfo, error) {
   var ret_info RefundOrderInfo
   url := fmt.Sprintf("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/%s", out_refund_no)
   result, err := WxPayGetV3(ent, url)
   if err != nil {
      fmt.Println(err)
      return ret_info, err
   }
   err = json.Unmarshal([]byte(result), &ret_info)
   if err != nil {
      fmt.Println(err)
      return ret_info, err
   }

   return ret_info, nil
}

你可能感兴趣的:(微信小程序开发实战,微信小程序,golang,go)