You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

185 lines
5.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package wechat
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/domain"
"recook/internal/v2/model/recook/after"
"recook/tools"
"strings"
"sync"
)
const (
AppID = "wx21724a42aebe20cc"
APIKey = "83f8932eb742257316e3168ba9e920dk"
MchID = "1545449631"
)
var (
PayCallbackUrl = domain.GetName() + "/api/v1/pay/wxpay/callback"
RefundCallbackUrl = domain.GetName() + "/api/v1/pay/wxpay/refund/callback"
)
const (
CreateOrderURL = "https://api.mch.weixin.qq.com/pay/unifiedorder" // 统一下单
OrderQueryURL = "https://api.mch.weixin.qq.com/pay/orderquery" // 订单查询
RefundURL = "https://api.mch.weixin.qq.com/secapi/pay/refund" // 退款
)
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))
}
/*=========================================================================================================
<xml>
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
<mch_id><![CDATA[10000100]]></mch_id>
<nonce_str><![CDATA[TeqClE3i0mvn3DrK]]></nonce_str>
<out_refund_no_0><![CDATA[1415701182]]></out_refund_no_0>
<out_trade_no><![CDATA[1415757673]]></out_trade_no>
<refund_count>1</refund_count>
<refund_fee_0>1</refund_fee_0>
<refund_id_0><![CDATA[2008450740201411110000174436]]></refund_id_0>
<refund_status_0><![CDATA[PROCESSING]]></refund_status_0>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<sign><![CDATA[1F2841558E233C33ABA71A961D27561C]]></sign>
<transaction_id><![CDATA[1008450740201411110005820873]]></transaction_id>
</xml>
*/
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 *after.RecookAfterSalesGoodsModel) 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(&param)
}
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 {
log.Println(string(result))
log.Println(err)
return err
}
var r RefundQueryResult
err = xml.Unmarshal(result, &r)
if err != nil {
log.Println(string(result))
log.Println(err)
log.Println(r)
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
}