mh_goadmin_server/app/admin/apis/pay/wx_pay.go
chenlin 0a5fe58bbe 优化生产反馈缺陷:
1.小程序调用erp登录接口不判断验证码;
2.修改原接口的翻页相关字段;
3.修复float64转int32精度丢失的缺陷;
4.注释库存导入时采购价需大于0的校验;
5.相关域名改成生产环境域名;
2024-07-03 18:56:33 +08:00

1250 lines
42 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 (
"bytes"
"context"
"crypto"
"crypto/md5"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
b64 "encoding/base64"
"encoding/json"
"encoding/pem"
"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"
"go-admin/tools/config"
"io"
"os"
//"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 = "39.108.188.218" // 小程序服务器
clientIpDev = "112.33.14.191" // 移动云服务器
HmPayMerchantIdDeovo = "664403000021193"
HmPubKeyDeovo = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCDA4g8VFWIxEbOzxYC8ZIOgaOsLWK4Y5k9D8GwJ1Gige79LbTxbe3PH12KMc59DpCR1PnIDwlYWjIE7mZZAHgImXs0pSFihvlNS9srWk2uPlEXXQjjIZ3mnPoXtNhU0x5cYdkB8jtijcYMSGwKmdrIvpvPX3MrDKOX6dJ1T4ll+QIDAQAB"
)
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
}
// Transfer 给用户打款
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
}
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 ParsePrivateKeyDeovo() (*rsa.PrivateKey, error) {
//fp := "/Users/max/Documents/code/deovo/mh_goadmin_server/config/hm_pay/private_key.pem"
fp := "./config/hm_pay/deovo_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 Sha1withRsaDeovo(signContent string) (string, error) {
hash := crypto.SHA1
shaNew := hash.New()
shaNew.Write([]byte(signContent))
hashed := shaNew.Sum(nil)
priKey, err := ParsePrivateKeyDeovo()
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 GenHmPaySignDeovo(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 := Sha1withRsaDeovo(signStr)
if err != nil {
logger.Errorf("signature err:", err)
return "", err
}
return signature, nil
}
func ParsePrivateKey() (*rsa.PrivateKey, error) {
//fp := "/Users/max/Documents/code/deovo/mh_goadmin_server/config/hm_pay/private_key.pem"
fp := "./config/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 HmVerifySha1RsaDeovo(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(HmPubKeyDeovo)))
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 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: HmPayMerchantIdDeovo,
//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,
}
if config.ApplicationConfig.Mode == "dev" {
biz.CreateIp = clientIpDev
}
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 := GenHmPaySignDeovo(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 = HmVerifySha1RsaDeovo(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
}
logger.Info("刷卡付响应参数::", logger.Field("HmPayBToCOrderDetail", 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
}
logger.Info("订单查询响应参数::", logger.Field("HmPayTradeQueryResp", 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
}