package wxminipay import ( "crypto/tls" "encoding/pem" "encoding/xml" "errors" "fmt" "github.com/shopspring/decimal" "golang.org/x/crypto/pkcs12" "io/ioutil" "log" "net/http" "recook/internal/model/aftersales" "recook/tools" "strings" "sync" ) type RefundParam struct { XMLName xml.Name `xml:"xml"` AppID string `xml:"appid"` MchId string `xml:"mch_id"` // 商户号 NonceStr string `xml:"nonce_str"` // 随机字符串 OutRefundNo string `xml:"out_refund_no"` // 商户系统内部的退款单号,商户系统内部唯一,同一退款单号多次请求只退一笔。 OutTradeNo string `xml:"out_trade_no" ` // 商户订单号 TotalFee uint `xml:"total_fee"` RefundFee uint `xml:"refund_fee"` NotifyUrl string `xml:"notify_url"` // 接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。 Sign string `xml:"sign"` } func (r *RefundParam) GenerateSign() { signStr := "appid=" + AppID signStr = signStr + "&" + "mch_id=" + MchID signStr = signStr + "&" + "nonce_str=" + r.NonceStr signStr = signStr + "&" + "notify_url=" + r.NotifyUrl signStr = signStr + "&" + "out_refund_no=" + r.OutRefundNo signStr = signStr + "&" + "out_trade_no=" + r.OutTradeNo signStr = signStr + "&" + "refund_fee=" + fmt.Sprintf("%d", r.RefundFee) signStr = signStr + "&" + "total_fee=" + fmt.Sprintf("%d", r.TotalFee) signStr = signStr + "&key=" + APIKey r.Sign = strings.ToUpper(tools.MD5(signStr)) } type RefundQueryResult struct { XMLName xml.Name `xml:"xml"` ReturnCode string `xml:"return_code"` ResultCode string `xml:"result_code"` AppID string `xml:"appid"` MchId string `xml:"mch_id"` TransactionID string `xml:"transaction_id"` // 微信支付订单号 OutTradeNo string `xml:"out_trade_no" ` // 商户订单号 TotalRefundCount uint `xml:"total_refund_count"` // 订单总共已发生的部分退款次数,当请求参数传入offset后有返回 RefundCount uint `xml:"refund_count"` // 当前返回退款笔数 TotalFee uint `xml:"total_fee"` RefundFee uint `xml:"refund_fee"` } func (r *RefundQueryResult) IsOk() bool { return r.ReturnCode == "SUCCESS" && r.ResultCode == "SUCCESS" } func Refund(asGoods *aftersales.Goods) error { param := RefundParam{ AppID: AppID, MchId: MchID, NonceStr: tools.Token(), OutTradeNo: asGoods.TradeNo, OutRefundNo: asGoods.RefundNo, TotalFee: uint(asGoods.OrderTotalAmount.Mul(decimal.NewFromInt(100)).IntPart()), RefundFee: uint(asGoods.RefundAmount.Mul(decimal.NewFromInt(100)).IntPart()), NotifyUrl: RefundCallbackUrl, } param.GenerateSign() return requestOrderRefund(¶m) } func requestOrderRefund(param *RefundParam) error { xmlStr, err := xml.MarshalIndent(param, "", "\t") if err != nil { return err } config := &tls.Config{ Certificates: []tls.Certificate{pkcs12ToPem()}, } transport := &http.Transport{ TLSClientConfig: config, DisableCompression: true, } client := &http.Client{Transport: transport} response, err := client.Post(RefundURL, "application/xml; charset=utf-8", strings.NewReader(string(xmlStr))) if err != nil { return err } defer func() { _ = response.Body.Close() }() result, err := ioutil.ReadAll(response.Body) if err != nil { return err } var r RefundQueryResult err = xml.Unmarshal(result, &r) if err != nil { return err } if r.IsOk() == false { log.Println(string(xmlStr)) log.Println("========================================") log.Println(string(result)) return errors.New("小程序退款请求结果不对") } return nil } var once sync.Once var wxRefundCert tls.Certificate func pkcs12ToPem() tls.Certificate { once.Do(func() { path := "./credentials/wxpay/refund_apiclient_cert.p12" certData, _ := ioutil.ReadFile(path) blocks, err := pkcs12.ToPEM(certData, MchID) var pemData []byte for _, b := range blocks { pemData = append(pemData, pem.EncodeToMemory(b)...) } cert, err := tls.X509KeyPair(pemData, pemData) if err != nil { panic(err) } wxRefundCert = cert }) return wxRefundCert }