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

4 years ago
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 (
3 years ago
AppID = "wx21724a42aebe20cc"
APIKey = "83f8932eb742257316e3168ba9e920dk"
MchID = "1545449631"
4 years ago
)
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 {
3 years ago
log.Println(string(result))
log.Println(err)
4 years ago
return err
}
var r RefundQueryResult
err = xml.Unmarshal(result, &r)
if err != nil {
3 years ago
log.Println(string(result))
log.Println(err)
log.Println(r)
4 years ago
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
}