mh_goadmin_server/app/admin/apis/pay/wx_pay.go

484 lines
16 KiB
Go
Raw Normal View History

2023-09-16 02:56:39 +00:00
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"`
}
2023-10-14 08:19:04 +00:00
// https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2
2023-09-16 02:56:39 +00:00
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
}
2023-10-14 08:19:04 +00:00
// 给用户打款
2023-09-16 02:56:39 +00:00
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 {
2023-10-14 08:19:04 +00:00
logger.Error(err.Error())
2023-09-16 02:56:39 +00:00
return nil, err
}
sign, err := GenWechatPaySign(m, WxAppMchSecret)
if err != nil {
2023-10-14 08:19:04 +00:00
logger.Error(err.Error())
2023-09-16 02:56:39 +00:00
return nil, err
}
req.Sign = strings.ToUpper(sign)
payload, err := xml.Marshal(&req)
if err != nil {
2023-10-14 08:19:04 +00:00
logger.Error(err.Error())
2023-09-16 02:56:39 +00:00
return nil, err
}
request, err := newPostRequest(WxTransferUrl, string(payload))
if err != nil {
2023-10-14 08:19:04 +00:00
logger.Error(err.Error())
2023-09-16 02:56:39 +00:00
return nil, err
}
defer request.Body.Close()
2023-10-14 08:19:04 +00:00
logger.Info("证书路径:", logger.Field("WxRootCaFile", WxRootCaFile))
logger.Info("证书路径:", logger.Field("WxKeyFile", WxKeyFile))
2023-09-16 02:56:39 +00:00
client, err := newHttpClient(WxRootCaFile, WxKeyFile)
if err != nil {
2023-10-14 08:19:04 +00:00
logger.Error(err.Error())
2023-09-16 02:56:39 +00:00
return nil, err
}
resp, err := client.Do(request)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
2023-10-14 08:19:04 +00:00
logger.Error(err.Error())
2023-09-16 02:56:39 +00:00
return nil, err
}
//logger.Info("Transfer resp = ", string(body))
2023-10-14 08:19:04 +00:00
logger.Info("Transfer resp ", logger.Field("body", string(body)))
2023-09-16 02:56:39 +00:00
err = xml.Unmarshal(body, &wxResp)
if err != nil {
2023-10-14 08:19:04 +00:00
logger.Error(err.Error())
2023-09-16 02:56:39 +00:00
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
2023-10-14 08:19:04 +00:00
logger.Info("签字符串1 :", logger.Field("sign", signStr))
2023-09-16 02:56:39 +00:00
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 {
2023-10-14 08:19:04 +00:00
logger.Error(err.Error())
2023-09-16 02:56:39 +00:00
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
}