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" "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 = "112.33.14.191" ) 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密钥 //) //微信商户 //商户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.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 统一预下单(JS)trade.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扫C)trade.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扫C)trade.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"` // 用户使用的交易方式,如WECHAT,ALIPAY,UNIONPAY 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 }