484 lines
15 KiB
Go
484 lines
15 KiB
Go
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)
|
||
return nil, err
|
||
}
|
||
|
||
sign, err := GenWechatPaySign(m, WxAppMchSecret)
|
||
if err != nil {
|
||
logger.Error(err)
|
||
return nil, err
|
||
}
|
||
req.Sign = strings.ToUpper(sign)
|
||
|
||
payload, err := xml.Marshal(&req)
|
||
if err != nil {
|
||
logger.Error(err)
|
||
return nil, err
|
||
}
|
||
|
||
request, err := newPostRequest(WxTransferUrl, string(payload))
|
||
if err != nil {
|
||
logger.Error(err)
|
||
return nil, err
|
||
}
|
||
defer request.Body.Close()
|
||
logger.Info("证书路径:", WxRootCaFile)
|
||
logger.Info("证书路径:", WxKeyFile)
|
||
client, err := newHttpClient(WxRootCaFile, WxKeyFile)
|
||
if err != nil {
|
||
logger.Error(err)
|
||
return nil, err
|
||
}
|
||
|
||
resp, err := client.Do(request)
|
||
defer resp.Body.Close()
|
||
|
||
body, err := ioutil.ReadAll(resp.Body)
|
||
if err != nil {
|
||
logger.Error(err)
|
||
return nil, err
|
||
}
|
||
//logger.Info("Transfer resp = ", string(body))
|
||
logger.Info("Transfer resp = ", string(body))
|
||
err = xml.Unmarshal(body, &wxResp)
|
||
if err != nil {
|
||
logger.Error(err)
|
||
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 :", 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密钥
|
||
//)
|
||
|
||
//微信商户
|
||
//商户ID:1609877389
|
||
//操作密码:456755
|
||
//密钥API:DeovoMingHuiRengTianTang45675456
|
||
//密钥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:", 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
|
||
}
|