github.com/chanxuehong/wechat@v0.0.0-20230222024006-36f0325263cd/mch/pay/refund.go (about) 1 package pay 2 3 import ( 4 "fmt" 5 "strconv" 6 7 "github.com/chanxuehong/wechat/mch/core" 8 wechatutil "github.com/chanxuehong/wechat/util" 9 ) 10 11 // Refund 申请退款. 12 // 13 // NOTE: 请求需要双向证书. 14 func Refund(clt *core.Client, req map[string]string) (resp map[string]string, err error) { 15 return clt.PostXML(core.APIBaseURL()+"/secapi/pay/refund", req) 16 } 17 18 type RefundRequest struct { 19 XMLName struct{} `xml:"xml" json:"-"` 20 21 // 必选参数, TransactionId 和 OutTradeNo 二选一即可. 22 TransactionId string `xml:"transaction_id"` // 微信生成的订单号,在支付通知中有返回 23 OutTradeNo string `xml:"out_trade_no"` // 商户侧传给微信的订单号 24 OutRefundNo string `xml:"out_refund_no"` // 商户系统内部的退款单号,商户系统内部唯一,同一退款单号多次请求只退一笔 25 TotalFee int64 `xml:"total_fee"` // 订单总金额,单位为分,只能为整数,详见支付金额 26 RefundFee int64 `xml:"refund_fee"` // 退款总金额,订单总金额,单位为分,只能为整数,详见支付金额 27 28 // 可选参数 29 NonceStr string `xml:"nonce_str"` // 随机字符串,不长于32位。NOTE: 如果为空则系统会自动生成一个随机字符串。 30 SignType string `xml:"sign_type"` // 签名类型,目前支持HMAC-SHA256和MD5,默认为MD5 31 RefundFeeType string `xml:"refund_fee_type"` // 货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 32 RefundDesc string `xml:"refund_desc"` // 若商户传入,会在下发给用户的退款消息中体现退款原因 33 RefundAccount string `xml:"refund_account"` // 退款资金来源 34 NotifyUrl string `xml:"notify_url"` // 通知地址url 35 } 36 37 type RefundResponse struct { 38 XMLName struct{} `xml:"xml" json:"-"` 39 40 // 必选返回 41 TransactionId string `xml:"transaction_id"` // 微信订单号 42 OutTradeNo string `xml:"out_trade_no"` // 商户系统内部的订单号 43 OutRefundNo string `xml:"out_refund_no"` // 商户退款单号 44 RefundId string `xml:"refund_id"` // 微信退款单号 45 RefundFee int64 `xml:"refund_fee"` // 退款总金额,单位为分,可以做部分退款 46 TotalFee int64 `xml:"total_fee"` // 订单总金额,单位为分,只能为整数,详见支付金额 47 CashFee int64 `xml:"cash_fee"` // 现金支付金额,单位为分,只能为整数,详见支付金额 48 49 // 下面字段都是可选返回的(详细见微信支付文档), 为空值表示没有返回, 程序逻辑里需要判断 50 SettlementRefundFee *int64 `xml:"settlement_refund_fee"` // 退款金额=申请退款金额-非充值代金券退款金额,退款金额<=申请退款金额 51 SettlementTotalFee *int64 `xml:"settlement_total_fee"` // 应结订单金额=订单金额-非充值代金券金额,应结订单金额<=订单金额。 52 FeeType string `xml:"fee_type"` // 订单金额货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 53 CashFeeType string `xml:"cash_fee_type"` // 货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 54 CashRefundFee *int64 `xml:"cash_refund_fee"` // 现金退款金额,单位为分,只能为整数,详见支付金额 55 } 56 57 // Refund2 申请退款. 58 // 59 // NOTE: 60 // 1. 请求需要双向证书. 61 // 2. 该函数不支持 代金券 功能, 如果有 代金券 功能请使用 Refund 函数. 62 func Refund2(clt *core.Client, req *RefundRequest) (resp *RefundResponse, err error) { 63 m1 := make(map[string]string, 16) 64 if req.TransactionId != "" { 65 m1["transaction_id"] = req.TransactionId 66 } 67 if req.OutTradeNo != "" { 68 m1["out_trade_no"] = req.OutTradeNo 69 } 70 m1["out_refund_no"] = req.OutRefundNo 71 m1["total_fee"] = strconv.FormatInt(req.TotalFee, 10) 72 m1["refund_fee"] = strconv.FormatInt(req.RefundFee, 10) 73 if req.NonceStr != "" { 74 m1["nonce_str"] = req.NonceStr 75 } else { 76 m1["nonce_str"] = wechatutil.NonceStr() 77 } 78 if req.SignType != "" { 79 m1["sign_type"] = req.SignType 80 } 81 if req.RefundFeeType != "" { 82 m1["refund_fee_type"] = req.RefundFeeType 83 } 84 if req.RefundDesc != "" { 85 m1["refund_desc"] = req.RefundDesc 86 } 87 if req.RefundAccount != "" { 88 m1["refund_account"] = req.RefundAccount 89 } 90 91 if req.NotifyUrl != "" { 92 m1["notify_url"] = req.NotifyUrl 93 } 94 95 m2, err := Refund(clt, m1) 96 if err != nil { 97 return nil, err 98 } 99 100 resp = &RefundResponse{ 101 TransactionId: m2["transaction_id"], 102 OutTradeNo: m2["out_trade_no"], 103 OutRefundNo: m2["out_refund_no"], 104 RefundId: m2["refund_id"], 105 FeeType: m2["fee_type"], 106 CashFeeType: m2["cash_fee_type"], 107 } 108 109 if str := m2["refund_fee"]; str != "" { 110 if n, err := strconv.ParseInt(str, 10, 64); err != nil { 111 err = fmt.Errorf("parse refund_fee:%q to int64 failed: %s", str, err.Error()) 112 return nil, err 113 } else { 114 resp.RefundFee = n 115 } 116 } 117 if str := m2["total_fee"]; str != "" { 118 if n, err := strconv.ParseInt(str, 10, 64); err != nil { 119 err = fmt.Errorf("parse total_fee:%q to int64 failed: %s", str, err.Error()) 120 return nil, err 121 } else { 122 resp.TotalFee = n 123 } 124 } 125 if str := m2["cash_fee"]; str != "" { 126 if n, err := strconv.ParseInt(str, 10, 64); err != nil { 127 err = fmt.Errorf("parse cash_fee:%q to int64 failed: %s", str, err.Error()) 128 return nil, err 129 } else { 130 resp.CashFee = n 131 } 132 } 133 134 if str := m2["settlement_refund_fee"]; str != "" { 135 if n, err := strconv.ParseInt(str, 10, 64); err != nil { 136 err = fmt.Errorf("parse settlement_refund_fee:%q to int64 failed: %s", str, err.Error()) 137 return nil, err 138 } else { 139 resp.SettlementRefundFee = wechatutil.Int64(n) 140 } 141 } 142 if str := m2["settlement_total_fee"]; str != "" { 143 if n, err := strconv.ParseInt(str, 10, 64); err != nil { 144 err = fmt.Errorf("parse settlement_total_fee:%q to int64 failed: %s", str, err.Error()) 145 return nil, err 146 } else { 147 resp.SettlementTotalFee = wechatutil.Int64(n) 148 } 149 } 150 if str := m2["cash_refund_fee"]; str != "" { 151 if n, err := strconv.ParseInt(str, 10, 64); err != nil { 152 err = fmt.Errorf("parse cash_refund_fee:%q to int64 failed: %s", str, err.Error()) 153 return nil, err 154 } else { 155 resp.CashRefundFee = wechatutil.Int64(n) 156 } 157 } 158 159 // 校验返回参数 160 if req.TransactionId != "" && resp.TransactionId != "" && req.TransactionId != resp.TransactionId { 161 err = fmt.Errorf("transaction_id mismatch, have: %s, want: %s", resp.TransactionId, req.TransactionId) 162 return nil, err 163 } 164 if req.OutTradeNo != "" && resp.OutTradeNo != "" && req.OutTradeNo != resp.OutTradeNo { 165 err = fmt.Errorf("out_trade_no mismatch, have: %s, want: %s", resp.OutTradeNo, req.OutTradeNo) 166 return nil, err 167 } 168 if req.OutRefundNo != "" && resp.OutRefundNo != "" && req.OutRefundNo != resp.OutRefundNo { 169 err = fmt.Errorf("out_refund_no mismatch, have: %s, want: %s", resp.OutRefundNo, req.OutRefundNo) 170 return nil, err 171 } 172 if req.TotalFee != resp.TotalFee { 173 err = fmt.Errorf("total_fee mismatch, have: %d, want: %d", resp.TotalFee, req.TotalFee) 174 return nil, err 175 } 176 if req.RefundFee != resp.RefundFee { 177 err = fmt.Errorf("refund_fee mismatch, have: %d, want: %d", resp.RefundFee, req.RefundFee) 178 return nil, err 179 } 180 181 return resp, nil 182 }