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

1122 lines
38 KiB
Go
Raw Normal View History

2023-09-16 02:56:39 +00:00
package pay
import (
"bytes"
2023-09-16 02:56:39 +00:00
"context"
"crypto"
2023-09-16 02:56:39 +00:00
"crypto/md5"
"crypto/rand"
"crypto/rsa"
2023-09-16 02:56:39 +00:00
"crypto/tls"
"crypto/x509"
b64 "encoding/base64"
2023-09-16 02:56:39 +00:00
"encoding/json"
"encoding/pem"
2023-09-16 02:56:39 +00:00
"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"
"io"
"os"
2023-09-16 02:56:39 +00:00
//"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"
UnifiedOrderUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder"
HmPayApiUrl = "https://hmpay.sandpay.com.cn/gateway/api"
PemBegin = "-----BEGIN RSA PRIVATE KEY-----\n"
PemEnd = "\n-----END RSA PRIVATE KEY-----"
HmPubKey = `MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDzGVH0Fxpb2M48U1BWr6lpNs2W3VHqtjO8X5RqWjtTwpQVKo8dqaiAGxVbsdnefPpsbI5l9rKquRAOJhWFU07hxSUgXZOk55QQmll03MBgRDXLgxyKfycLLQwhsCJAzDIWC7IWgok/RHV9m9AV2GbQxWBl+7iDE4prcbpgG8Z0HwIDAQAB`
HmPayMerchantId = "664403000030115"
TimeFormat = "2006-01-02 15:04:05"
clientIp = "112.33.14.191"
2023-09-16 02:56:39 +00:00
)
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
}
// Transfer 给用户打款
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
}
type (
UnifiedOrderReq struct {
XMLName xml.Name `xml:"xml"` //xml标签
AppId string `xml:"appid" json:"appid"` //微信分配的小程序ID必须
MchId string `xml:"mch_id" json:"mch_id"` //微信支付分配的商户号,必须
DeviceInfo string `xml:"device_info" json:"device_info"` //微信支付填"WEB",必须
NonceStr string `xml:"nonce_str" json:"nonce_str"` //随机字符串,必须
Sign string `xml:"sign" json:"sign"` //签名,必须
SignType string `xml:"sign_type" json:"sign_type"` //"HMAC-SHA256"或者"MD5"非必须默认MD5
Body string `xml:"body" json:"body"` //商品简单描述,必须
Detail string `xml:"detail,omitempty" json:"detail,omitempty"` //商品详细列表使用json格式
Attach string `xml:"attach" json:"attach"` //附加数据,如"贵阳分店",非必须
OutTradeNo string `xml:"out_trade_no" json:"out_trade_no"` //CRS订单号必须
FeeType string `xml:"fee_type,omitempty" json:"fee_type,omitempty"` //默认人民币CNY非必须
TotalFee string `xml:"total_fee" json:"total_fee"` //订单金额,单位分,必须
SpbillCreateIp string `xml:"spbill_create_ip" json:"spbill_create_ip"` //支付提交客户端IP如“123.123.123.123”,必须
TimeStart string `xml:"time_start,omitempty" json:"time_start,omitempty"` //订单生成时间格式为yyyyMMddHHmmss如20170324094700非必须
TimeExpire string `xml:"time_expire,omitempty" json:"time_expire,omitempty"` //订单结束时间,格式同上,非必须
GoodsTag string `xml:"goods_tag,omitempty" json:"goods_tag,omitempty"` //商品标记,代金券或立减优惠功能的参数,非必须
NotifyUrl string `xml:"notify_url" json:"notify_url"` //接收微信支付异步通知回调地址,不能携带参数,必须
TradeType string `xml:"trade_type" json:"trade_type"` //交易类型,小程序写"JSAPI",必须
LimitPay string `xml:"limit_pay,omitempty" json:"limit_pay,omitempty"` //限制某种支付方式,非必须
OpenId string `xml:"openid" json:"openid"` //微信用户唯一标识,必须
ProfitSharing string `xml:"profit_sharing" json:"profit_sharing"` //是否需要分账
}
UnifiedOrderResp struct {
ReturnCode string `xml:"return_code"`
ReturnMsg string `xml:"return_msg"`
AppId string `xml:"appid"`
MchId string `xml:"mch_id"`
DeviceInfo string `xml:"device_info"`
NonceStr string `xml:"nonce_str"`
Sign string `xml:"sign"`
ResultCode string `xml:"result_code"`
ErrCode string `xml:"err_code"`
ErrCodeDes string `xml:"err_code_des"`
TradeType string `xml:"trade_type"`
PrepayId string `xml:"prepay_id"`
}
)
type HmJsPayUnifiedOrderReq struct {
HmPayPublicPara
}
type HmPayPublicPara struct {
AppId string `json:"app_id"`
//SubAppId string `json:"sub_app_id"`
Method string `json:"method"`
//Charset string `json:"charset"`
SignType string `json:"sign_type"`
Sign string `json:"sign"`
Timestamp string `json:"timestamp"`
Nonce string `json:"nonce"`
//Version string `json:"version"`
//Format string `json:"format"`
BizContent string `json:"biz_content"`
}
// HmPayBizContent 统一预下单JStrade.create 业务请求参数
type HmPayBizContent struct {
Body string `json:"body"`
MerAppId string `json:"mer_app_id"`
//BuyerId string `json:"buyer_id"`
MerBuyerId string `json:"mer_buyer_id"`
CreateIp string `json:"create_ip"`
CreateTime string `json:"create_time"`
//DiscountInfo struct {
// DiscountableAmount float64 `json:"discountable_amount"`
//} `json:"discount_info"`
ExpireTime string `json:"expire_time"`
//ExtendParams struct {
// AccessPartyCode string `json:"access_party_code"`
//} `json:"extend_params"`
//GoodsDetails []HmPayGoodsDetails `json:"goods_details"`
//LimitPay string `json:"limit_pay"`
NotifyUrl string `json:"notify_url"`
//OperatorId string `json:"operator_id"`
OutOrderNo string `json:"out_order_no"`
PayType string `json:"pay_type"`
PayWay string `json:"pay_way"`
StoreId string `json:"store_id"`
//TerminalId string `json:"terminal_id"`
TotalAmount float64 `json:"total_amount"`
}
// HmPayBToCBizContent 刷卡支付B扫Ctrade.pay 业务请求参数
type HmPayBToCBizContent struct {
AuthCode string `json:"auth_code"`
CreateIp string `json:"create_ip"`
CreateTime string `json:"create_time"`
PayWay string `json:"pay_way"`
TotalAmount float64 `json:"total_amount"`
OutOrderNo string `json:"out_order_no"`
Body string `json:"body"`
StoreId string `json:"store_id"`
NotifyUrl string `json:"notify_url"`
}
// HmPayBToCOrderDetail 刷卡支付B扫Ctrade.pay 响应业务参数
type HmPayBToCOrderDetail struct {
SubCode string `json:"sub_code"` // 业务返回码
SubMsg string `json:"sub_msg"` // 业务返回信息
PayWay string `json:"pay_way"` // 支付方式 AUTO情况下条码的类型
BankWay string `json:"bank_way"` // 银行通道
OutOrderNo string `json:"out_order_no"` // 商户订单号
PlatTrxNo string `json:"plat_trx_no"` // 平台交易流水号
BankOrderNo string `json:"bank_order_no"` // 银行订单号,平台送给渠道的商户订单号
BankTrxNo string `json:"bank_trx_no"` // 银行流水号,渠道返回给平台的流水号,订单成功情况下存在
BuyerId string `json:"buyer_id"` // 买家ID付款方唯一标识
SuccessTime string `json:"success_time"` // 支付成功时间格式yyyyMMddHHmmss
TotalAmount float64 `json:"total_amount"` // 订单总金额
SettleAmount float64 `json:"settle_amount"` // 商户可结算金额,订单金额-商户手续费
BuyerPayAmount float64 `json:"buyer_pay_amount"` // 买家付款金额,买家实际付款的金额,订单金额-优惠金额
ReqReserved string `json:"req_reserved"` // 商户自定义字段
}
// HmPayTradeQueryContent 订单查询 trade.query 业务请求参数
type HmPayTradeQueryContent struct {
OrderCreateTime string `json:"order_create_time,omitempty"` // 订单创建时间yyyyMMddHHmmss当仅上送out_order_no需上送若不上送默认当天
OutOrderNo string `json:"out_order_no"` // 商户订单号,订单号三选一必填
PlatTrxNo string `json:"plat_trx_no,omitempty"` // 平台交易流水号
BankOrderNo string `json:"bank_order_no,omitempty"` // 银行订单号
}
// HmPayTradeQueryResp 订单查询 trade.query 业务响应参数
type HmPayTradeQueryResp struct {
SubCode string `json:"sub_code"` // 业务返回码
SubMsg string `json:"sub_msg"` // 业务返回信息
IsCancel string `json:"is_cancel"` // 是否已撤销 true/false
IsFundAuthPay string `json:"is_fund_auth_pay"` // 预授权冻结是否已完成 true/false
IsRefund string `json:"is_refund"` // 是否有退款 true/false
RefundSuccessAmount float64 `json:"refund_success_amount"` // 成功退款金额,单位元
TotalAmount float64 `json:"total_amount"` // 订单总金额
OutOrderNo string `json:"out_order_no"` // 商户订单号
PlatTrxNo string `json:"plat_trx_no"` // 平台交易流水号
BankOrderNo string `json:"bank_order_no"` // 银行订单号
BankTrxNo string `json:"bank_trx_no"` // 银行流水号
BuyerID string `json:"buyer_id"` // 买家ID
SuccessTime string `json:"success_time"` // 支付成功时间格式yyyyMMddHHmmss
SettleAmount float64 `json:"settle_amount"` // 商户可结算金额,订单金额-商户手续费
PayWayCode string `json:"pay_way_code"` // 用户使用的交易方式如WECHATALIPAYUNIONPAY
PayModeCode string `json:"pay_mode_code"` // 用户使用的支付模式如扫码支付PAY_SCAN
BankWayCode string `json:"bank_way_code"` // 银行通道
BuyerPayAmount float64 `json:"buyer_pay_amount"` // 买家付款金额,买家实际付款的金额,订单金额-优惠金额
ReqReserved string `json:"req_reserved"` // 商户自定义字段
}
// HmPayTradeCancelContent 订单撤销 trade.cancel 业务请求参数
type HmPayTradeCancelContent struct {
OutOrderNo string `json:"out_order_no"` // 商户订单号,订单号三选一必填
PlatTrxNo string `json:"plat_trx_no,omitempty"` // 平台交易流水号
BankOrderNo string `json:"bank_order_no,omitempty"` // 银行订单号
}
// HmPayTradeCancelResp 订单撤销 trade.cancel 业务响应参数
type HmPayTradeCancelResp struct {
SubCode string `json:"sub_code"` // 业务返回码
SubMsg string `json:"sub_msg"` // 业务返回信息
Action string `json:"action"` // 本次撤销触发的动作 CLOSE REFUND UNKNOW
CancelPlatTrxNo string `json:"cancel_plat_trx_no"` // 撤销平台流水号
ReqReserved string `json:"req_reserved"` // 商户自定义字段
}
type HmPayUnifiedOrderPayData struct {
TimeStamp string `json:"timeStamp"`
Package string `json:"package"`
PaySign string `json:"paySign"`
AppId string `json:"appId"`
SignType string `json:"signType"`
NonceStr string `json:"nonceStr"`
}
type HmPayUnifiedOrderDetail struct {
BankOrderNo string `json:"bank_order_no"`
BankTrxNo string `json:"bank_trx_no"`
BankWay string `json:"bank_way"`
OutOrderNo string `json:"out_order_no"`
PayData string `json:"pay_data"`
PlatTrxNo string `json:"plat_trx_no"`
PrepayId string `json:"prepay_id"`
SubCode string `json:"sub_code"`
SubMsg string `json:"sub_msg"`
}
type HmPayUnifiedOrderRsp struct {
Code string `json:"code"`
Msg string `json:"msg"`
Data string `json:"data"`
Sign string `json:"sign"`
}
func ParsePrivateKey() (*rsa.PrivateKey, error) {
fp := "/Users/max/Documents/code/deovo/mh_goadmin_server/config/hm_pay/private_key.pem"
//fp := "./configs/hm_pay/private_key.pem"
privateKey, err := os.ReadFile(fp)
if err != nil {
logger.Errorf("read file err:", err)
return nil, err
}
block, _ := pem.Decode([]byte(privateKey))
if block == nil {
return nil, errors.New("私钥信息错误!")
}
//priKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
priKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
if priKey == nil {
return nil, errors.New("pri key is nil")
}
return priKey.(*rsa.PrivateKey), nil
}
func Sha1withRsa(signContent string) (string, error) {
hash := crypto.SHA1
shaNew := hash.New()
shaNew.Write([]byte(signContent))
hashed := shaNew.Sum(nil)
priKey, err := ParsePrivateKey()
if err != nil {
logger.Errorf("parse err:", err)
return "", err
}
signature, err := rsa.SignPKCS1v15(rand.Reader, priKey, hash, hashed)
if err != nil {
logger.Errorf("sign err:", err)
return "", err
}
return b64.StdEncoding.EncodeToString(signature), nil
}
func GenHmPaySign(m map[string]string) (string, error) {
delete(m, "sign")
var signData []string
for k, v := range m {
if k == "openid" {
fmt.Println(k, ":", v)
}
if v != "" && v != "0" {
signData = append(signData, fmt.Sprintf("%s=%s", k, v))
}
}
//signDataJson, _ := json.MarshalIndent(&signData, "", " ")
//fmt.Println("signDataJson1", string(signDataJson))
sort.Strings(signData)
//signDataJson2, _ := json.MarshalIndent(&signData, "", " ")
//fmt.Println("signDataJson2", string(signDataJson2))
signStr := strings.Join(signData, "&")
//signStr = signStr + "&key=" + payKey
//logger.Info("签字符串1:", logger.Field("signStr", signStr))
fmt.Println("签字符串1:", signStr)
signature, err := Sha1withRsa(signStr)
if err != nil {
logger.Errorf("signature err:", err)
return "", err
}
return signature, nil
}
func FormatPrivateKey(privateKey string) string {
//privateKey = strings.Trim(privateKey, "\n")
if !strings.HasPrefix(privateKey, PemBegin) {
privateKey = PemBegin + privateKey
}
if !strings.HasSuffix(privateKey, PemEnd) {
privateKey = privateKey + PemEnd
}
return privateKey
}
func HmPayUnifiedOrder(r HmJsPayUnifiedOrderReq) (HmPayUnifiedOrderRsp, error) {
var hmPayUnifiedOrderRsp HmPayUnifiedOrderRsp
data, err := json.Marshal(r)
if err != nil {
logger.Error("HmPayUnifiedOrder err:", logger.Field("err", err))
return hmPayUnifiedOrderRsp, err
}
fmt.Println("data json:", string(data))
client := http.Client{}
req, err := http.NewRequest("POST", HmPayApiUrl, bytes.NewBuffer(data))
if err != nil {
logger.Error("HmPayUnifiedOrder err:", logger.Field("err", err))
return hmPayUnifiedOrderRsp, err
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
//req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
logger.Error("HmPayUnifiedOrder err:", logger.Field("err", err))
return hmPayUnifiedOrderRsp, err
}
body, err := io.ReadAll(resp.Body)
if err != nil {
logger.Error("HmPayUnifiedOrder err:", logger.Field("err", err))
return hmPayUnifiedOrderRsp, err
}
fmt.Println("body:", string(body))
defer resp.Body.Close()
err = json.Unmarshal(body, &hmPayUnifiedOrderRsp)
if err != nil {
logger.Error("hmPayUnifiedOrderRsp err:", logger.Field("err", err))
return hmPayUnifiedOrderRsp, err
}
if hmPayUnifiedOrderRsp.Code != "200" {
return hmPayUnifiedOrderRsp, errors.New(hmPayUnifiedOrderRsp.Msg)
}
return hmPayUnifiedOrderRsp, nil
}
func HmVerifySha1Rsa(signContent, signBase string) error {
//fp := "/Users/li/mh/mh_server/pack/configs/hm_pay/public_key.pme"
//publicKeyString, err := ioutil.ReadFile(fp)
//if err != nil {
// fmt.Println("read file err:", err)
// return err
//}
block, _ := pem.Decode([]byte(FormatPrivateKey(HmPubKey)))
if block == nil {
fmt.Println("decode block is nil")
return errors.New("decode block is nil")
}
publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
fmt.Println("public key err:", err)
return err
}
if publicKey == nil {
fmt.Println("public key nil:")
return err
}
hash := crypto.SHA1
shaNew := hash.New()
shaNew.Write([]byte(signContent))
hashed := shaNew.Sum(nil)
sign, err := b64.StdEncoding.DecodeString(signBase)
if err != nil {
fmt.Println("sign decode err:", err)
return err
}
err = rsa.VerifyPKCS1v15(publicKey.(*rsa.PublicKey), hash, hashed, sign)
if err != nil {
fmt.Println("verify err:", err)
return err
}
//logger.Error("验签成功")
fmt.Println("验签成功")
return nil
}
func ToSignContent(s interface{}) (string, error) {
m, err := struct2Map(s)
if err != nil {
logger.Error("ToSignContent err:", logger.Field("err", err))
return "", err
}
delete(m, "sign")
var signData []string
for k, v := range m {
if k == "openid" {
fmt.Println(k, ":", v)
}
if v != "" && v != "0" {
signData = append(signData, fmt.Sprintf("%s=%s", k, v))
}
}
sort.Strings(signData)
//signDataJson2, _ := json.MarshalIndent(&signData, "", " ")
//fmt.Println("signDataJson2", string(signDataJson2))
signStr := strings.Join(signData, "&")
return signStr, nil
}
// HmJsPayBToCOrder 河马刷卡支付B扫C
func HmJsPayBToCOrder(orderId string, totalFee float64, authCode, notifyUrl string) (*HmPayBToCOrderDetail, error) {
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())
nonce := GenRandStr(NonceStringLength)
//if notifyUrl == "" {
// logger.Error("NotifyUrl is null")
// return nil, errors.New("NotifyUrl is null")
//}
unifiedOrderReq := HmJsPayUnifiedOrderReq{}
publicPara := HmPayPublicPara{
AppId: HmPayMerchantId,
//SubAppId: HmWxSubMerchantId,
Method: "trade.pay",
//Charset: "UTF-8",
SignType: "RSA",
Sign: "",
Timestamp: now.Format(TimeFormat),
Nonce: nonce,
//Nonce: fmt.Sprintf("%d", time.Now().UnixNano()),
//Version: "1.0.0",
//Format: "JSON",
}
biz := HmPayBToCBizContent{
AuthCode: authCode,
CreateIp: clientIp,
CreateTime: strTime,
PayWay: "AUTO",
TotalAmount: totalFee,
OutOrderNo: orderId,
Body: "门店消费",
StoreId: "100001",
NotifyUrl: notifyUrl,
}
unifiedOrderReq.HmPayPublicPara = publicPara
bizString, err := json.Marshal(&biz)
if err != nil {
logger.Error("marshal biz err:", logger.Field("err", err))
return nil, err
}
unifiedOrderReq.HmPayPublicPara.BizContent = string(bizString)
m, err := struct2Map(unifiedOrderReq)
if err != nil {
logger.Error("HmJsPayUnifiedOrder struct2Map err:", logger.Field("err", err))
return nil, err
}
sign, err := GenHmPaySign(m)
if err != nil {
logger.Error("HmJsPayUnifiedOrder GenHmPaySign err:", logger.Field("err", err))
return nil, err
}
unifiedOrderReq.Sign = sign
unifiedOrderResp, err := HmPayUnifiedOrder(unifiedOrderReq)
if err != nil {
logger.Errorf("WxUnifiedOrder unified order error %#v", err)
return nil, err
}
signContent, err := ToSignContent(unifiedOrderResp)
if err != nil {
logger.Errorf("ToSignContent err:", err)
return nil, err
}
err = HmVerifySha1Rsa(signContent, unifiedOrderResp.Sign)
if err != nil {
logger.Errorf("HmVerifySha1Rsa err:", err)
return nil, err
}
//fmt.Println("unifiedOrderResp:", unifiedOrderResp.Data)
var hmPayDetail HmPayBToCOrderDetail
err = json.Unmarshal([]byte(unifiedOrderResp.Data), &hmPayDetail)
if err != nil {
logger.Errorf("hm pay unified order pay data unmarshal error %#v", err)
return nil, err
}
fmt.Println("hmPayDetail:", hmPayDetail)
return &hmPayDetail, nil
}
// HmQueryOrder 订单查询
func HmQueryOrder(orderId string) (*HmPayTradeQueryResp, error) {
now := time.Now().Local()
nonce := GenRandStr(NonceStringLength)
unifiedOrderReq := HmJsPayUnifiedOrderReq{}
publicPara := HmPayPublicPara{
AppId: HmPayMerchantId,
Method: "trade.query",
SignType: "RSA",
Sign: "",
Timestamp: now.Format(TimeFormat),
Nonce: nonce,
}
biz := HmPayTradeQueryContent{
OutOrderNo: orderId,
}
unifiedOrderReq.HmPayPublicPara = publicPara
bizString, err := json.Marshal(&biz)
if err != nil {
logger.Error("marshal biz err:", logger.Field("err", err))
return nil, err
}
unifiedOrderReq.HmPayPublicPara.BizContent = string(bizString)
m, err := struct2Map(unifiedOrderReq)
if err != nil {
logger.Error("HmJsPayUnifiedOrder struct2Map err:", logger.Field("err", err))
return nil, err
}
sign, err := GenHmPaySign(m)
if err != nil {
logger.Error("HmJsPayUnifiedOrder GenHmPaySign err:", logger.Field("err", err))
return nil, err
}
unifiedOrderReq.Sign = sign
unifiedOrderResp, err := HmPayUnifiedOrder(unifiedOrderReq)
if err != nil {
logger.Errorf("WxUnifiedOrder unified order error %#v", err)
return nil, err
}
signContent, err := ToSignContent(unifiedOrderResp)
if err != nil {
logger.Errorf("ToSignContent err:", err)
return nil, err
}
err = HmVerifySha1Rsa(signContent, unifiedOrderResp.Sign)
if err != nil {
logger.Errorf("HmVerifySha1Rsa err:", err)
return nil, err
}
fmt.Println("Resp Data:", unifiedOrderResp.Data)
var hmPayDetail HmPayTradeQueryResp
err = json.Unmarshal([]byte(unifiedOrderResp.Data), &hmPayDetail)
if err != nil {
logger.Errorf("hm pay unified order pay data unmarshal error %#v", err)
return nil, err
}
fmt.Println("hmPayDetail:", hmPayDetail)
return &hmPayDetail, nil
}
// HmCancelOrder 订单撤销
func HmCancelOrder(orderId string) (*HmPayTradeCancelResp, error) {
now := time.Now().Local()
nonce := GenRandStr(NonceStringLength)
unifiedOrderReq := HmJsPayUnifiedOrderReq{}
publicPara := HmPayPublicPara{
AppId: HmPayMerchantId,
Method: "trade.cancel",
SignType: "RSA",
Sign: "",
Timestamp: now.Format(TimeFormat),
Nonce: nonce,
}
biz := HmPayTradeCancelContent{
OutOrderNo: orderId,
}
unifiedOrderReq.HmPayPublicPara = publicPara
bizString, err := json.Marshal(&biz)
if err != nil {
logger.Error("marshal biz err:", logger.Field("err", err))
return nil, err
}
unifiedOrderReq.HmPayPublicPara.BizContent = string(bizString)
m, err := struct2Map(unifiedOrderReq)
if err != nil {
logger.Error("HmJsPayUnifiedOrder struct2Map err:", logger.Field("err", err))
return nil, err
}
sign, err := GenHmPaySign(m)
if err != nil {
logger.Error("HmJsPayUnifiedOrder GenHmPaySign err:", logger.Field("err", err))
return nil, err
}
unifiedOrderReq.Sign = sign
unifiedOrderResp, err := HmPayUnifiedOrder(unifiedOrderReq)
if err != nil {
logger.Errorf("WxUnifiedOrder unified order error %#v", err)
return nil, err
}
signContent, err := ToSignContent(unifiedOrderResp)
if err != nil {
logger.Errorf("ToSignContent err:", err)
return nil, err
}
err = HmVerifySha1Rsa(signContent, unifiedOrderResp.Sign)
if err != nil {
logger.Errorf("HmVerifySha1Rsa err:", err)
return nil, err
}
fmt.Println("Resp Data:", unifiedOrderResp.Data)
var hmPayDetail HmPayTradeCancelResp
err = json.Unmarshal([]byte(unifiedOrderResp.Data), &hmPayDetail)
if err != nil {
logger.Errorf("hm pay unified order pay data unmarshal error %#v", err)
return nil, err
}
fmt.Println("hmPayDetail:", hmPayDetail)
return &hmPayDetail, nil
}