mh_goadmin_server/app/admin/apis/pay/wx_pay.go
2023-10-14 16:19:04 +08:00

484 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package pay
import (
"context"
"crypto/md5"
"crypto/tls"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"github.com/rs/zerolog/log"
"github.com/wechatpay-apiv3/wechatpay-go/core"
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
"github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
"github.com/wechatpay-apiv3/wechatpay-go/services/transferbatch"
//"github.com/wechatpay-apiv3/wechatpay-go/utils"
wechatpayutils "github.com/wechatpay-apiv3/wechatpay-go/utils"
"go-admin/logger"
"io/ioutil"
"net/http"
"sort"
"strings"
"time"
)
const (
//公共参数
SpbilCreateIp = "113.97.33.19"
NonceStringLength = 32
WechatUnifiedOrderUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder"
WxTransferUrl = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers"
//app端微信支付
WechatAppId = "wx533b2ea6660ee3ad"
//WechatMchId = "1493640582" // 1499688752
WechatMchId = "1499688752" // 1499688752
//WechatPayKey = "cdbTz6lKwBfqUHvJ4sVrD8Idz2dVOGMe"
WechatPayKey = "wRro53HnVVlsJQpkfcs200eyaeSvQPog"
CRSTaCode = "0002" //CRS微信支付入账代码必须为消费代码
APP_TRADE = "APP" //APP支付
WxCheckName = "NO_CHECK"
//"app_id": "wxb20ee1a562e56453",
//"app_secret": "26e2a1203ad8403891a78a56a37cade5",
//"app_mchId": "1517772711",
//"app_mchSecret": "BCHkQGyEKrgHQIfMSi7E4udkt9Ua6AZV"
//"wx": {
//"app_id": "wxff888972e97ff2ef",
//"app_secret": "3d7335cf0b9fa1d70aa7eb079526ebf0",
//"app_mchId": "1494688302",
//"app_mchSecret": "aaff113c243c47e1cf09af30fe3a6d2b"
//},
//callbackUrl = "https://zouzou.api.wawazhuawawa.com/api/v1/wxpay/notice"
//WxCertFile = "/root/zouzou/configs/pro/1494688302_20190624_cert/apiclient_cert.p12"
//WxKeyFile = "/root/zouzou/configs/pro/1494688302_20190624_cert/apiclient_key.pem"
//rootCaFile = "/root/zouzou/configs/pro/1494688302_20190624_cert/apiclient_cert.pem"
WxKeyFile = "./config/merchant/apiclient_key.pem"
WxRootCaFile = "./config/merchant/apiclient_cert.pem"
//WxKeyFile = "/www/wwwroot/dev.admin.deovo.com/admin_server/go-admin/config/apiclient_key.pem"
//WxRootCaFile = "/www/wwwroot/dev.admin.deovo.com/admin_server/go-admin/config/apiclient_cert.pem"
WxAppId = "wx806c079463b5b56c"
WxAppSecret = "3d7335cf0b9fa1d70aa7eb079526ebf0"
//WxAppMchId = "1494688302"
WxAppMchId = "1609877389"
WxAppMchSecret = "DeovoMingHuiRengTianTang45675456"
)
const (
mchID string = "1609877389" // 商户号
mchCertificateSerialNumber string = "7540301D8FD52CCF7D6267DCF7CD2BC0AB467EFF" // 商户证书序列号
mchAPIv3Key string = "DeovoMingHuiRengTianTang45675123" // 商户APIv3密钥
)
//var (
// mchID string = "1609877389" // 商户号
// mchCertificateSerialNumber string = "7540301D8FD52CCF7D6267DCF7CD2BC0AB467EFF" // 商户证书序列号
// mchAPIv3Key string = "DeovoMingHuiRengTianTang45675123" // 商户APIv3密钥
//)
type WxTransferResp struct {
ReturnCode string `xml:"return_code,CDATA"`
ReturnMsg string `xml:"return_msg,CDATA"`
ResultCode string `xml:"result_code,CDATA"`
ErrCodeDes string `xml:"err_code_des,CDATA"`
PartnerTradeNo string `xml:"partner_trade_no,CDATA"`
PaymentNo string `xml:"payment_no,CDATA"`
}
// https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2
type WxTransferReq struct {
XMLName xml.Name `xml:"xml"` //xml标签
MchAppid string `xml:"mch_appid" json:"mch_appid"` //微信分配的小程序ID必须
MchId string `xml:"mchid" json:"mchid"` //微信支付分配的商户号,必须
DeviceInfo string `xml:"device_info" json:"device_info"`
NonceStr string `xml:"nonce_str" json:"nonce_str"` //随机字符串,必须
Sign string `xml:"sign" json:"sign"` //签名,必须 //"HMAC-SHA256"或者"MD5"非必须默认MD5
PartnerTradeNo string `xml:"partner_trade_no" json:"partner_trade_no"` //CRS订单号必须
OpenId string `xml:"openid" json:"openid"` //微信用户唯一标识,必须
CheckName string `xml:"check_name" json:"check_name"`
ReUserName string `xml:"re_user_name" json:"re_user_name"`
Amount string `xml:"amount" json:"amount"`
Desc string `xml:"desc" json:"desc"`
SpbillCreateIp string `xml:"spbill_create_ip" json:"spbill_create_ip"`
//SignType string `xml:"sign_type" json:"sign_type"` //"HMAC-SHA256"或者"MD5"非必须默认MD5
}
func GenTradeNo() string {
now := time.Now().Local()
strTime := fmt.Sprintf("%04d%02d%02d%02d%02d%02d", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
tradeNO := strTime + RandomNumString(100000, 999999)
return tradeNO
}
// 给用户打款
func Transfer(amount uint32, openId string, desc string) (*WxTransferResp, error) {
tradeNO := GenTradeNo()
nonce := GenRandStr(NonceStringLength)
req := WxTransferReq{
MchAppid: WxAppId,
MchId: WxAppMchId,
NonceStr: nonce,
Sign: "",
//SignType: "MD5",
PartnerTradeNo: tradeNO,
OpenId: openId,
CheckName: WxCheckName,
Amount: fmt.Sprintf("%d", amount),
//Desc: "押金退款",
Desc: desc,
SpbillCreateIp: SpbilCreateIp,
}
wxResp := WxTransferResp{}
//生成签名
m, err := struct2Map(req)
if err != nil {
logger.Error(err.Error())
return nil, err
}
sign, err := GenWechatPaySign(m, WxAppMchSecret)
if err != nil {
logger.Error(err.Error())
return nil, err
}
req.Sign = strings.ToUpper(sign)
payload, err := xml.Marshal(&req)
if err != nil {
logger.Error(err.Error())
return nil, err
}
request, err := newPostRequest(WxTransferUrl, string(payload))
if err != nil {
logger.Error(err.Error())
return nil, err
}
defer request.Body.Close()
logger.Info("证书路径:", logger.Field("WxRootCaFile", WxRootCaFile))
logger.Info("证书路径:", logger.Field("WxKeyFile", WxKeyFile))
client, err := newHttpClient(WxRootCaFile, WxKeyFile)
if err != nil {
logger.Error(err.Error())
return nil, err
}
resp, err := client.Do(request)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
logger.Error(err.Error())
return nil, err
}
//logger.Info("Transfer resp = ", string(body))
logger.Info("Transfer resp ", logger.Field("body", string(body)))
err = xml.Unmarshal(body, &wxResp)
if err != nil {
logger.Error(err.Error())
return nil, err
}
if wxResp.ReturnCode != "SUCCESS" || wxResp.ResultCode != "SUCCESS" {
return nil, errors.New("Transfer fail")
}
return &wxResp, nil
}
func newHttpClient(certFile, keyFile string) (*http.Client, error) {
//
//cert, err := ioutil.ReadFile(certFile)
//logger.Info("cert:",string(cert))
//logger.Info("err:",err)
//if err != nil {
// return nil, err
//}
//
//key, err := ioutil.ReadFile(keyFile)
//logger.Info("key:",string(key))
//logger.Info("key:",err)
//if err != nil {
// return nil, err
//}
//tlsCert, err := tls.X509KeyPair(cert, key)
//logger.Info("tlsCert:",tlsCert)
//logger.Info("err:",err)
//if err != nil {
// return nil, err
//}
tlsCert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, err
}
conf := &tls.Config{
Certificates: []tls.Certificate{tlsCert},
}
trans := &http.Transport{
TLSClientConfig: conf,
}
client := &http.Client{
Transport: trans,
}
return client, nil
}
//func newHttpsClient() (*http.Client, error) {
// tlsCert, err := tls.LoadX509KeyPair(config.AppConfig.WxCertFile, config.AppConfig.WxKeyFile)
// if err != nil {
// logger.Error(err)
// return nil, err
// }
//
// conf := &tls.Config{
// Certificates: []tls.Certificate{tlsCert},
// }
// trans := &http.Transport{
// TLSClientConfig: conf,
// }
// client := &http.Client{
// Transport: trans,
// }
//
// return client, nil
//}
func newPostRequest(url string, content string) (*http.Request, error) {
request, err := http.NewRequest("POST", url, strings.NewReader(content))
if err != nil {
return nil, err
}
request.Header.Set("Content-Type", "application/xml")
return request, nil
}
func struct2Map(r interface{}) (s map[string]string, err error) {
var temp map[string]interface{}
var result = make(map[string]string)
bin, err := json.Marshal(r)
if err != nil {
return result, err
}
if err := json.Unmarshal(bin, &temp); err != nil {
return nil, err
}
for k, v := range temp {
switch v2 := v.(type) {
case string:
result[k] = v2
case uint, int8, uint8, int, int16, uint16, int32, uint32, int64, uint64:
result[k] = fmt.Sprintf("%d", v2)
case float32, float64:
result[k] = fmt.Sprintf("%.0f", v2)
}
}
return result, nil
}
func GenWechatPaySign(m map[string]string, payKey string) (string, error) {
delete(m, "sign")
var signData []string
for k, v := range m {
if v != "" && v != "0" {
signData = append(signData, fmt.Sprintf("%s=%s", k, v))
}
}
sort.Strings(signData)
signStr := strings.Join(signData, "&")
signStr = signStr + "&key=" + payKey
logger.Info("签字符串1 :", logger.Field("sign", signStr))
c := md5.New()
_, err := c.Write([]byte(signStr))
if err != nil {
return "", err
}
signByte := c.Sum(nil)
if err != nil {
return "", err
}
return fmt.Sprintf("%x", signByte), nil
}
//const (
// mchID string = "1609877389" // 商户号
// mchCertificateSerialNumber string = "7540301D8FD52CCF7D6267DCF7CD2BC0AB467EFF" // 商户证书序列号
// mchAPIv3Key string = "DeovoMingHuiRengTianTang45675123" // 商户APIv3密钥
//)
//微信商户
//商户ID1609877389
//操作密码456755
//密钥APIDeovoMingHuiRengTianTang45675456
//密钥APIv3: DeovoMingHuiRengTianTang45675123
//证书序列号7540301D8FD52CCF7D6267DCF7CD2BC0AB467EFF
func WxSDKPay(uid uint32, wxName string) {
// 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名
//mchPrivateKey, err := utils.LoadPrivateKeyWithPath("/path/to/merchant/apiclient_key.pem")
mchPrivateKey, err := wechatpayutils.LoadPrivateKeyWithPath("/Users/li/mh/mh_admin_server/app/admin/apis/pay/merchant/apiclient_key.pem")
if err != nil {
logger.Error(err.Error())
return
}
ctx := context.Background()
// 使用商户私钥等初始化 client并使它具有自动定时获取微信支付平台证书的能力
opts := []core.ClientOption{
option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key),
}
client, err := core.NewClient(ctx, opts...)
if err != nil {
logger.Errorf("new wechat pay client err:%s", err)
return
}
//// 发送请求,以下载微信支付平台证书为例
//// https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay5_1.shtml
//svc := certificates.CertificatesApiService{Client: client}
//resp, result, err := svc.DownloadCertificates(ctx)
//logger.Errorf("status=%d resp=%s", result.Response.StatusCode, resp)
//// 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名
//mchPrivateKey, err := utils.LoadPrivateKeyWithPath("/path/to/merchant/apiclient_key.pem")
//if err != nil {
// log.Print("load merchant private key error")
//}
//ctx := context.Background()
//// 使用商户私钥等初始化 client并使它具有自动定时获取微信支付平台证书的能力
//opts := []core.ClientOption{
// option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key),
//}
//client, err := core.NewClient(ctx, opts...)
//if err != nil {
// log.Printf("new wechat pay client err:%s", err)
//}
svc := transferbatch.TransferBatchApiService{Client: client}
resp, result, err := svc.InitiateBatchTransfer(ctx,
transferbatch.InitiateBatchTransferRequest{
Appid: core.String("wxff888972e97ff2ef"),
OutBatchNo: core.String("plfk20200420131253415624"),
BatchName: core.String("押金退还"),
BatchRemark: core.String("押金退还"),
TotalAmount: core.Int64(40),
TotalNum: core.Int64(1),
TransferDetailList: []transferbatch.TransferDetailInput{transferbatch.TransferDetailInput{
Openid: core.String("ohuHh4riVVPxwKHrYHsWwZRpxVMk"),
OutDetailNo: core.String("x23zy545Bd5436123145243"),
TransferAmount: core.Int64(40),
TransferRemark: core.String("押金退款"),
//UserName: core.String("12341234"),
}},
},
)
if err != nil {
// 处理错误
logger.Errorf("call InitiateBatchTransfer err:%s", err)
} else {
// 处理返回结果
logger.Errorf("status=%d resp=%s", result.Response.StatusCode, resp)
}
}
type OrderRefund struct {
OutTradeNo string `json:"out_trade_no"`
OutRefundNo string `json:"out_refund_no"`
NotifyUrl string `json:"notify_url"`
Amount OrderRefundAmount `json:"amount"`
}
type OrderRefundAmount struct {
Refund uint32 `json:"refund"`
Total uint32 `json:"total"`
Currency string `json:"currency"`
}
func TransactionOrderRefund(orderRefund OrderRefund) error {
var (
mchID string = "1609877389" // 商户号
mchCertificateSerialNumber string = "7540301D8FD52CCF7D6267DCF7CD2BC0AB467EFF" // 商户证书序列号
mchAPIv3Key string = "DeovoMingHuiRengTianTang45675123" // 商户APIv3密钥
)
// WxKeyFile = "./config/merchant/apiclient_key.pem"
// mchPrivateKey, err := wechatpayutils.LoadPrivateKeyWithPath("/Users/li/mh/mh_admin_server/app/admin/apis/pay/merchant/apiclient_key.pem")
privatePath := "./config/merchant/apiclient_key.pem"
//privatePathTest := "/www/wwwroot/dev.admin.deovo.com/admin_server/go-admin/config/merchant/apiclient_key.pem" // TODO 测试
//privatePath := "/www/wwwroot/admin.deovo.com/admin_server/go-admin/config/merchant/apiclient_key.pem" // TODO 正式
// 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名
//mchPrivateKey, err := wechatpayutils.LoadPrivateKeyWithPath("./configs/merchant/apiclient_key.pem")
mchPrivateKey, err := wechatpayutils.LoadPrivateKeyWithPath(privatePath)
if err != nil {
//log.Print("load merchant private key error")
log.Error().Msg("load merchant private key error")
return err
}
ctx := context.Background()
// 使用商户私钥等初始化 client并使它具有自动定时获取微信支付平台证书的能力
opts := []core.ClientOption{
option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key),
}
client, err := core.NewClient(ctx, opts...)
if err != nil {
//log.Printf("new wechat pay client err:%s", err)
log.Error().Msgf("new wechat pay client err:%s", err.Error())
return err
}
svc := refunddomestic.RefundsApiService{Client: client}
resp, result, err := svc.Create(ctx,
refunddomestic.CreateRequest{
OutTradeNo: core.String(orderRefund.OutTradeNo),
OutRefundNo: core.String(orderRefund.OutRefundNo),
Reason: core.String("取消订单"),
//NotifyUrl: core.String("https://weixin.qq.com/api/v1/wxpay/notice"),
NotifyUrl: core.String(orderRefund.NotifyUrl),
Amount: &refunddomestic.AmountReq{
Currency: core.String("CNY"),
Refund: core.Int64(int64(orderRefund.Amount.Refund)),
Total: core.Int64(int64(orderRefund.Amount.Total)),
},
},
)
if err != nil {
// 处理错误
log.Printf("call Create err:%s", err)
} else {
// 处理返回结果
log.Printf("status=%d resp=%s", result.Response.StatusCode, resp)
}
return nil
}