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  }