refund.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package pay
  2. import (
  3. "bytes"
  4. "crypto/tls"
  5. "encoding/pem"
  6. "encoding/xml"
  7. "fmt"
  8. "github.com/akikistyle/wechat/util"
  9. "golang.org/x/crypto/pkcs12"
  10. "io/ioutil"
  11. "log"
  12. "net/http"
  13. )
  14. var refundGateway = "https://api.mch.weixin.qq.com/secapi/pay/refund"
  15. //Refund Parameter
  16. type RefundParams struct {
  17. TransactionId string
  18. OutRefundNo string
  19. TotalFee string
  20. RefundFee string
  21. RefundDesc string
  22. RootCa string //ca证书
  23. }
  24. //Refund request
  25. type refundRequest struct {
  26. AppID string `xml:"appid"`
  27. MchID string `xml:"mch_id"`
  28. NonceStr string `xml:"nonce_str"`
  29. Sign string `xml:"sign"`
  30. SignType string `xml:"sign_type,omitempty"`
  31. TransactionId string `xml:"transaction_id"`
  32. OutRefundNo string `xml:"out_refund_no"`
  33. TotalFee string `xml:"total_fee"`
  34. RefundFee string `xml:"refund_fee"`
  35. RefundDesc string `xml:"refund_desc,omitempty"`
  36. //NotifyUrl string `xml:"notify_url,omitempty"`
  37. }
  38. //Refund Response
  39. type RefundResponse struct {
  40. ReturnCode string `xml:"return_code"`
  41. ReturnMsg string `xml:"return_msg"`
  42. AppID string `xml:"appid,omitempty"`
  43. MchID string `xml:"mch_id,omitempty"`
  44. NonceStr string `xml:"nonce_str,omitempty"`
  45. Sign string `xml:"sign,omitempty"`
  46. ResultCode string `xml:"result_code,omitempty"`
  47. ErrCode string `xml:"err_code,omitempty"`
  48. ErrCodeDes string `xml:"err_code_des,omitempty"`
  49. TransactionId string `xml:"transaction_id,omitempty"`
  50. OutTradeNo string `xml:"out_trade_no,omitempty"`
  51. OutRefundNo string `xml:"out_refund_no,omitempty"`
  52. RefundId string `xml:"refund_id,omitempty"`
  53. RefundFee string `xml:"refund_fee,omitempty"`
  54. SettlementRefundFee string `xml:"settlement_refund_fee,omitempty"`
  55. TotalFee string `xml:"total_fee,omitempty"`
  56. SettlementTotalFee string `xml:"settlement_total_fee,omitempty"`
  57. FeeType string `xml:"fee_type,omitempty"`
  58. CashFee string `xml:"cash_fee,omitempty"`
  59. CashFeeType string `xml:"cash_fee_type,omitempty"`
  60. }
  61. //退款申请
  62. func (pcf *Pay) Refund(p *RefundParams) (rsp RefundResponse, err error) {
  63. nonceStr := util.RandomStr(32)
  64. param := make(map[string]interface{})
  65. param["appid"] = pcf.AppID
  66. param["mch_id"] = pcf.PayMchID
  67. param["nonce_str"] = nonceStr
  68. param["out_refund_no"] = p.OutRefundNo
  69. param["refund_desc"] = p.RefundDesc
  70. param["refund_fee"] = p.RefundFee
  71. param["total_fee"] = p.TotalFee
  72. param["sign_type"] = "MD5"
  73. param["transaction_id"] = p.TransactionId
  74. bizKey := "&key=" + pcf.PayKey
  75. str := orderParam(param, bizKey)
  76. sign := util.MD5Sum(str)
  77. request := refundRequest{
  78. AppID: pcf.AppID,
  79. MchID: pcf.PayMchID,
  80. NonceStr: nonceStr,
  81. Sign: sign,
  82. SignType: "MD5",
  83. TransactionId: p.TransactionId,
  84. OutRefundNo: p.OutRefundNo,
  85. TotalFee: p.TotalFee,
  86. RefundFee: p.RefundFee,
  87. RefundDesc: p.RefundDesc,
  88. }
  89. rawRet, err := postXMLWithTLS(refundGateway, request, p.RootCa, pcf.PayMchID)
  90. if err != nil {
  91. return
  92. }
  93. err = xml.Unmarshal(rawRet, &rsp)
  94. if err != nil {
  95. return
  96. }
  97. if rsp.ReturnCode == "SUCCESS" {
  98. if rsp.ResultCode == "SUCCESS" {
  99. err = nil
  100. return
  101. }
  102. err = fmt.Errorf("refund error, errcode=%s,errmsg=%s", rsp.ErrCode, rsp.ErrCodeDes)
  103. return
  104. }
  105. err = fmt.Errorf("[msg : xmlUnmarshalError] [rawReturn : %s] [params : %s] [sign : %s]",
  106. string(rawRet), str, sign)
  107. return
  108. }
  109. //http TLS
  110. func httpWithTLS(rootCa, key string) (*http.Client, error) {
  111. var client *http.Client
  112. certData, err := ioutil.ReadFile(rootCa)
  113. if err != nil {
  114. return nil, fmt.Errorf("unable to find cert path=%s, error=%v", rootCa, err)
  115. }
  116. cert := pkcs12ToPem(certData, key)
  117. config := &tls.Config{
  118. Certificates: []tls.Certificate{cert},
  119. }
  120. tr := &http.Transport{
  121. TLSClientConfig: config,
  122. DisableCompression: true,
  123. }
  124. client = &http.Client{Transport: tr}
  125. return client, nil
  126. }
  127. //将Pkcs12转成Pem
  128. func pkcs12ToPem(p12 []byte, password string) tls.Certificate {
  129. blocks, err := pkcs12.ToPEM(p12, password)
  130. defer func() {
  131. if x := recover(); x != nil {
  132. log.Print(x)
  133. }
  134. }()
  135. if err != nil {
  136. panic(err)
  137. }
  138. var pemData []byte
  139. for _, b := range blocks {
  140. pemData = append(pemData, pem.EncodeToMemory(b)...)
  141. }
  142. cert, err := tls.X509KeyPair(pemData, pemData)
  143. if err != nil {
  144. panic(err)
  145. }
  146. return cert
  147. }
  148. //Post XML with TLS
  149. func postXMLWithTLS(uri string, obj interface{}, ca, key string) ([]byte, error) {
  150. xmlData, err := xml.Marshal(obj)
  151. if err != nil {
  152. return nil, err
  153. }
  154. body := bytes.NewBuffer(xmlData)
  155. client, err := httpWithTLS(ca, key)
  156. if err != nil {
  157. return nil, err
  158. }
  159. response, err := client.Post(uri, "application/xml;charset=utf-8", body)
  160. if err != nil {
  161. return nil, err
  162. }
  163. defer response.Body.Close()
  164. if response.StatusCode != http.StatusOK {
  165. return nil, fmt.Errorf("http code error : uri=%v , statusCode=%v", uri, response.StatusCode)
  166. }
  167. return ioutil.ReadAll(response.Body)
  168. }