package wxpay 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/codinl/go-logger" "github.com/wechatpay-apiv3/wechatpay-go/core" "github.com/wechatpay-apiv3/wechatpay-go/core/option" "github.com/wechatpay-apiv3/wechatpay-go/services/certificates" "github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi" "github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic" wechatpayutils "github.com/wechatpay-apiv3/wechatpay-go/utils" "io/ioutil" "log" mathrand "math/rand" "mh-server/config" "mh-server/lib/utils" "net/http" "sort" "strconv" "strings" "time" ) const ( //clientIp = "120.229.60.77" clientIp = "39.108.188.218" domain = "switch.deovo.com:8001" //wxPayNotifyUrl = "api/v1/wxpay/notice" wxPayNotifyUrl = "/api/v1/wxpay/notice" WxPayMember = "member_pay" // 会员 WxPayRentCard = "rent_card_pay" // 租卡 WxPayDeposit = "deposit_pay" // 押金 WxPayBuyGoods = "buy_goods" // 购买商品 WxPayUpgradeMember = "upgrade_member" // 多级会员 WxPayMemberExpireDelay = "member_expire_delay" // 会员过期滞纳金 WxPayShareCardRetrieve = "share_card_retrieve" // 收回卡 WxPayPostagePackage = "postage_package" // 运费包 //WxPayExchangeGoods = "exchange_goods" // 兑换商品 //NotifyUrl = "https://switch.deovo.com:8001/api/v1/wxpay/notice" // 数据库配置 生产 //NotifyUrl = "https://dev.switch.deovo.com:8004/api/v1/wxpay/notice" // 测试 wxPayOrderRefundsUrl = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds" HmPayMerchantId = "664403000030115" HmWxSubMerchantId = "546017470" //HmPayUnifiedOrderUrl = "https://hmpay.sandpay.com.cn/gateway/api" HmPayUnifiedOrderUrl = "https://hmpay.sandpay.com.cn/gateway/api" ) //web 微信支付 func WebPay(orderId string, totalFee uint32, openId, profitSharing, attach, notifyUrl string) (*Sextuple, 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 := utils.GenRandStr(NonceStringLength) //configInfo, err := model.PayConfigInfo() //if err != nil { // logger.Error(err) // return nil, err //} //if configInfo.NotifyUrl == "" { // logger.Error("NotifyUrl is null") // return nil, errors.New("NotifyUrl is null") //} if notifyUrl == "" { logger.Error("NotifyUrl is null") return nil, errors.New("NotifyUrl is null") } logger.Info("MchId:", config.AppConfig.WxMchID) logger.Info("AppId:", config.AppConfig.WxAppId) logger.Info("MchSecret:", config.AppConfig.WxMchSecret) unifiedOrderReq := UnifiedOrderReq{ DeviceInfo: "WEB", NonceStr: nonce, Sign: "", SignType: "MD5", Body: "创建订单", OutTradeNo: orderId, FeeType: "CNY", TotalFee: strconv.Itoa(int(totalFee)), //SpbillCreateIp: config.AppConfig.IP, SpbillCreateIp: clientIp, //NotifyUrl: "https://" + config.AppConfig.Domain + config.AppConfig.WxPayNotifyUrl, //NotifyUrl: "https://" + domain + wxPayNotifyUrl, //NotifyUrl: configInfo.NotifyUrl, NotifyUrl: notifyUrl, TradeType: "JSAPI", MchId: config.AppConfig.WxMchID, AppId: config.AppConfig.WxAppId, OpenId: openId, TimeStart: strTime, ProfitSharing: profitSharing, Attach: attach, } fmt.Println("OutTradeNo:", unifiedOrderReq.OutTradeNo) m, err := struct2Map(unifiedOrderReq) if err != nil { logger.Error(err) return nil, err } //mJson, _ := json.MarshalIndent(&m, "", " ") //fmt.Println("mJson:", string(mJson)) sign, err := GenWxPaySign(m, config.AppConfig.WxMchSecret) if err != nil { logger.Error(err) return nil, err } unifiedOrderReq.Sign = strings.ToUpper(sign) //unifiedOrderReqJson, _ := json.Marshal(&unifiedOrderReq) //fmt.Println("unifiedOrderReqJson:", string(unifiedOrderReqJson)) unifiedOrderResp, err := WxUnifiedOrder(unifiedOrderReq) if err != nil { logger.Errorf("WxUnifiedOrder unified order error %#v", err) return nil, err } var sextuple Sextuple sextuple.NonceStr = unifiedOrderResp.NonceStr sextuple.AppId = unifiedOrderResp.AppId sextuple.Timestamp = fmt.Sprintf("%d", time.Now().Unix()) sextuple.Package = "prepay_id=" + unifiedOrderResp.PrepayId sextuple.SignType = "MD5" //logger.Debugf("unified order sextuple: %#v", sextuple) m, err = struct2Map(sextuple) if err != nil { logger.Errorf("struct to map error %#v", err) return nil, err } sextuple.PaySign, err = GenWxPaySign(m, config.AppConfig.WxMchSecret) if err != nil { logger.Errorf("GenWxPaySign gen response sign error: %#v", err) return nil, err } return &sextuple, nil } const ( NonceStringLength = 32 UnifiedOrderUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder" ) type ( //根据调用微信统一支付接口返回数据组装五元组 Sextuple struct { AppId string `json:"appId"` NonceStr string `json:"nonceStr,omitempty"` Timestamp string `json:"timeStamp,omitempty"` Package string `json:"package,omitempty"` SignType string `json:"signType,omitempty"` PaySign string `json:"paySign,omitempty"` } 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"` //是否需要分账 } ZFWechatUnifiedOrderReq struct { XMLName xml.Name `xml:"xml"` //xml标签 AppId string `xml:"appid" json:"appid"` //微信分配的小程序ID,必须 MchId string `xml:"mch_id" json:"mch_id"` //微信支付分配的商户号,必须 NonceStr string `xml:"nonce_str" json:"nonce_str"` //随机字符串,必须 Sign string `xml:"sign" json:"sign"` //签名,必须 Body string `xml:"body" json:"body"` //商品简单描述,必须 OutTradeNo string `xml:"out_trade_no" json:"out_trade_no"` //CRS订单号,必须 TotalFee string `xml:"total_fee" json:"total_fee"` //订单金额,单位分,必须 SpbillCreateIp string `xml:"spbill_create_ip" json:"spbill_create_ip"` //支付提交客户端IP,如“123.123.123.123”,必须 NotifyUrl string `xml:"notify_url" json:"notify_url"` //接收微信支付异步通知回调地址,不能携带参数,必须 TradeType string `xml:"trade_type" json:"trade_type"` //交易类型,小程序写"JSAPI",必须 OpenId string `xml:"openid" json:"openid"` //微信用户唯一标识,必须 Attach string `xml:"attach" json:"attach"` } 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"` } WechatNotifyInfo struct { ReturnCode string `xml:"return_code,CDATA" json:"return_code"` ReturnMsg string `xml:"return_msg,CDATA" json:"return_msg"` Appid string `xml:"appid,CDATA" json:"appid"` MchId string `xml:"mch_id,CDATA" json:"mch_id"` DeviceInfo string `xml:"device_info,CDATA" json:"device_info"` NonceStr string `xml:"nonce_str,CDATA" json:"nonce_str"` Sign string `xml:"sign,CDATA" json:"sign"` // 默认MD5 SignType string `xml:"sign_type,CDATA" json:"sign_type"` //可选 ResultCode string `xml:"result_code,CDATA" json:"result_code"` ErrCode string `xml:"err_code,CDATA" json:"err_code"` ErrCodeDes string `xml:"err_code_des,CDATA" json:"err_code_des"` Openid string `xml:"openid,CDATA" json:"openid"` IsSubscribe string `xml:"is_subscribe,CDATA" json:"is_subscribe"` TradeType string `xml:"trade_type,CDATA" json:"trade_type"` BankType string `xml:"bank_type,CDATA" json:"bank_type"` TotalFee uint `xml:"total_fee,CDATA" json:"total_fee"` SettlementTotalFee uint `xml:"settlement_total_fee" json:"settlement_total_fee"` FeeType string `xml:"fee_type,CDATA" json:"fee_type"` CashFee uint `xml:"cash_fee,CDATA" json:"cash_fee"` CashFeeType string `xml:"cash_fee_type,CDATA" json:"cash_fee_type"` CouponFee uint `xml:"coupon_fee,CDATA" json:"coupon_fee"` CouponCount uint `xml:"coupon_count,CDATA" json:"coupon_count"` CouponType0 uint `xml:"coupon_type_0,CDATA" json:"coupon_type_0"` CouponId0 string `xml:"coupon_id_0,CDATA" json:"coupon_id_0"` CouponFee0 uint `xml:"coupon_fee_0,CDATA" json:"coupon_fee_0"` CouponFee1 uint `xml:"coupon_fee_1,CDATA" json:"coupon_fee_1"` CouponId1 string `xml:"coupon_id_1,CDATA" json:"coupon_id_1"` CouponFee2 uint `xml:"coupon_fee_2,CDATA" json:"coupon_fee_2"` CouponId2 string `xml:"coupon_id_2,CDATA" json:"coupon_id_2"` CouponFee3 uint `xml:"coupon_fee_3,CDATA" json:"coupon_fee_3"` CouponId3 string `xml:"coupon_id_3,CDATA" json:"coupon_id_3"` CouponFee4 uint `xml:"coupon_fee_4,CDATA" json:"coupon_fee_4"` CouponId4 string `xml:"coupon_id_4,CDATA" json:"coupon_id_4"` CouponFee5 uint `xml:"coupon_fee_5,CDATA" json:"coupon_fee_5"` CouponId5 string `xml:"coupon_id_5,CDATA" json:"coupon_id_5"` TransactionId string `xml:"transaction_id,CDATA" json:"transaction_id"` OutTradeNo string `xml:"out_trade_no,CDATA" json:"out_trade_no"` Attach string `xml:"attach,CDATA" json:"attach"` TimeEnd string `xml:"time_end,CDATA" json:"time_end"` } WechatNotify struct { ReturnCode string `xml:"return_code"` ReturnMsg string `xml:"return_msg"` //签名失败 或者 参数格式校验错误 } NotifyResponseObject struct { XMLName xml.Name `xml:"xml"` WechatNotify } AppWxPayRet struct { AppId string `json:"appid,omitempty"` PartnerId string `json:"partnerid,omitempty"` PrepayId string `json:"prepayid,omitempty"` Package string `json:"package,omitempty"` NonceStr string `json:"noncestr,omitempty"` Timestamp string `json:"timestamp,omitempty"` Sign string `json:"sign,omitempty"` } ) type T struct { CouponFee0 string `json:"coupon_fee_0"` CouponFee1 string `json:"coupon_fee_1"` CouponFee2 string `json:"coupon_fee_2"` CouponFee3 string `json:"coupon_fee_3"` CouponId0 string `json:"coupon_id_0"` CouponId1 string `json:"coupon_id_1"` CouponId2 string `json:"coupon_id_2"` CouponId3 string `json:"coupon_id_3"` } const ( DateTimeFormat = "2006-01-02" TimeFormat = "2006-01-02 15:04:05" ) //type T struct { // Xml struct { // Appid string `json:"appid"` // Attach string `json:"attach"` // BankType string `json:"bank_type"` // CashFee string `json:"cash_fee"` // // // CouponCount string `json:"coupon_count"` // CouponFee string `json:"coupon_fee"` // CouponFee0 string `json:"coupon_fee_0"` // CouponFee1 string `json:"coupon_fee_1"` // CouponId0 string `json:"coupon_id_0"` // CouponId1 string `json:"coupon_id_1"` // // // DeviceInfo string `json:"device_info"` // FeeType string `json:"fee_type"` // IsSubscribe string `json:"is_subscribe"` // MchId string `json:"mch_id"` // NonceStr string `json:"nonce_str"` // Openid string `json:"openid"` // OutTradeNo string `json:"out_trade_no"` // ResultCode string `json:"result_code"` // ReturnCode string `json:"return_code"` // Sign string `json:"sign"` // TimeEnd string `json:"time_end"` // TotalFee string `json:"total_fee"` // TradeType string `json:"trade_type"` // TransactionId string `json:"transaction_id"` // } `json:"xml"` //} ////app 微信支付 ////totalFee 单位是分 //func AppPay(totalFee int, openId string) (*AppWxPayRet, string, error) { // now := time.Now() // strTime := fmt.Sprintf("%04d%02d%02d%02d%02d%02d", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second()) // tradeNO := strTime + utils.RandomNumString(100000, 999999) // nonce := utils.GenRandStr(NonceStringLength) // unifiedOrderReq := UnifiedOrderReq{ // DeviceInfo: "WEB", // NonceStr: nonce, // Sign: "", // SignType: "MD5", // Body: "升级会员", // OutTradeNo: tradeNO, // FeeType: "CNY", // TotalFee: strconv.Itoa(int(totalFee)), // //SpbillCreateIp: config.AppConfig.IP, // SpbillCreateIp: clientIp, // //NotifyUrl: config.AppConfig.WxPayNotifyUrl, // TradeType: "APP", // AppId: "WechatAppId", // MchId: "WechatMchId", // OpenId: openId, // TimeStart: strTime, // } // // //生成签名 // m, err := struct2Map(unifiedOrderReq) // if err != nil { // logger.Error(err) // return nil, "", err // } // sign, err := GenWxPaySign(m, config.AppConfig.WxMchSecret) // if err != nil { // logger.Error(err) // return nil, "", err // } // unifiedOrderReq.Sign = strings.ToUpper(sign) // // unifiedOrderResp, err := WxUnifiedOrder(unifiedOrderReq) // if err != nil { // logger.Errorf("WxUnifiedOrder unified order error %#v", err) // return nil, "", err // } // // var payret AppWxPayRet // payret.AppId = unifiedOrderResp.AppId // payret.PartnerId = unifiedOrderResp.MchId // payret.PrepayId = unifiedOrderResp.PrepayId // payret.Package = "Sign=WXPay" // payret.NonceStr = unifiedOrderResp.NonceStr // payret.Timestamp = fmt.Sprintf("%d", time.Now().Unix()) // // m, err = struct2Map(payret) // if err != nil { // logger.Errorf("Pay struct to map error %#v", err) // return nil, "", err // } // // payret.Sign, err = GenWxPaySign(m, config.AppConfig.WxMchSecret) // if err != nil { // logger.Errorf("GenWxPaySign gen response sign error: %#v", err) // return nil, "", err // } // // return &payret, tradeNO, 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 { logger.Error(err) 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", v) case float32, float64: result[k] = fmt.Sprintf("%.02f", v) } } return result, nil } func GenWxPaySign(m map[string]string, payKey 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 :", signStr) c := md5.New() _, err := c.Write([]byte(signStr)) if err != nil { logger.Error(err) return "", err } signByte := c.Sum(nil) return fmt.Sprintf("%x", signByte), nil } func WxUnifiedOrder(r UnifiedOrderReq) (UnifiedOrderResp, error) { var payResp UnifiedOrderResp data, err := xml.Marshal(r) if err != nil { logger.Error(err) return payResp, err } //logger.Error(" xml data: ", string(data)) //fmt.Println("xml:", string(data)) logger.Error("xml:", string(data)) client := http.Client{} req, err := http.NewRequest("POST", UnifiedOrderUrl, bytes.NewBuffer(data)) if err != nil { logger.Error(err) return payResp, err } req.Header.Set("Content-Type", "application/xml; charset=utf-8") resp, err := client.Do(req) if err != nil { logger.Error(err) return payResp, err } //fmt.Println("err:", err) body, err := ioutil.ReadAll(resp.Body) if err != nil { logger.Error(err) return payResp, err } //fmt.Println("err:", err) fmt.Println("body:", string(body)) defer resp.Body.Close() err = xml.Unmarshal(body, &payResp) if err != nil { logger.Error(err) return payResp, err } if payResp.ReturnCode != "SUCCESS" { return payResp, errors.New(payResp.ReturnMsg) } return payResp, nil } func WxPayTransactionOrderClose(outTradeNo, mchid string) error { // url := fmt.Sprintf("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s/close", outTradeNo) // para := map[string]interface{}{ // "mchid": mchid, // } // data, err := json.Marshal(para) // logger.Error("json:", string(data)) // client := http.Client{} // req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) // if err != nil { // logger.Error(err) // return err // } // req.Header.Set("Content-Type", "application/json") // req.Header.Set("Accept", "application/json") //// Content-Type: application/json // resp, err := client.Do(req) // if err != nil { // logger.Error(err) // return err // } // // //fmt.Println("err:", err) // body, err := ioutil.ReadAll(resp.Body) // if err != nil { // logger.Error(err) // return err // } // //fmt.Println("err:", err) // fmt.Println("body:", string(body)) // // defer resp.Body.Close() var ( mchID string = "1609877389" // 商户号 mchCertificateSerialNumber string = "7540301D8FD52CCF7D6267DCF7CD2BC0AB467EFF" // 商户证书序列号 mchAPIv3Key string = "DeovoMingHuiRengTianTang45675123" // 商户APIv3密钥 ) // 微信商户 // 商户ID:1609877389 // 操作密码:456755 // 密钥API:DeovoMingHuiRengTianTang45675456 //密钥APIv3: DeovoMingHuiRengTianTang45675123 // 证书序列号:7540301D8FD52CCF7D6267DCF7CD2BC0AB467EFF // 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名 mchPrivateKey, err := wechatpayutils.LoadPrivateKeyWithPath("/Users/li/mh/mh_server/pack/configs/merchant/apiclient_key.pem") if err != nil { log.Fatal("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.Fatalf("new wechat pay client err:%s", err) } // 发送请求,以下载微信支付平台证书为例 // https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay5_1.shtml svc := certificates.CertificatesApiService{Client: client} resp, result, err := svc.DownloadCertificates(ctx) log.Printf("status=%d resp=%s", result.Response.StatusCode, resp) svcClient := jsapi.JsapiApiService{Client: client} apiResult, err := svcClient.CloseOrder(ctx, jsapi.CloseOrderRequest{ OutTradeNo: &outTradeNo, Mchid: &mchid, }) if err != nil { fmt.Println("err:", err) logger.Error(err) return err } fmt.Println("StatusCode:", (*apiResult).Response.StatusCode) //bodyCloseOrder,_ := ioutil.ReadAll((*apiResult.Response.Body)) fmt.Println("apiResult:", (*apiResult).Response.StatusCode) return nil } func PayNotifyHandle(notify WechatNotifyInfo) (string, error) { m, err := struct2Map(notify) if err != nil { logger.Error(err.Error()) return "", err } sign, err := GenWxPaySign(m, config.AppConfig.WxMchSecret) if err != nil { logger.Error(err.Error()) return "", err } sign = strings.ToUpper(sign) logger.Error("微信推送支付通知 sign : payKey", sign, config.AppConfig.WxMchSecret) return sign, err } func TransactionOrderRefund(orderRefund OrderRefund) error { var ( mchID string = "1609877389" // 商户号 mchCertificateSerialNumber string = "7540301D8FD52CCF7D6267DCF7CD2BC0AB467EFF" // 商户证书序列号 mchAPIv3Key string = "DeovoMingHuiRengTianTang45675123" // 商户APIv3密钥 ) // 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名 mchPrivateKey, err := wechatpayutils.LoadPrivateKeyWithPath("./configs/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 := 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 } func PayOrderRefund(orderRefund OrderRefund) error { para, err := json.Marshal(&orderRefund) if err != nil { logger.Error("err:", err) return err } client := http.Client{} req, err := http.NewRequest("POST", wxPayOrderRefundsUrl, bytes.NewBuffer(para)) if err != nil { logger.Error(err) return err } req.Header.Set("Content-Type", "application/json; charset=utf-8") req.Header.Set("Accept", "application/json") resp, err := client.Do(req) if err != nil { logger.Error(err) return err } //fmt.Println("err:", err) body, err := ioutil.ReadAll(resp.Body) if err != nil { logger.Error(err) return err } //fmt.Println("err:", err) fmt.Println("body:", string(body)) defer resp.Body.Close() fmt.Println("") return nil } 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 RandomNum(min int64, max int64) int64 { r := mathrand.New(mathrand.NewSource(time.Now().UnixNano())) num := min + r.Int63n(max-min+1) return num } func RandomNumString(min int64, max int64) string { num := RandomNum(min, max) return strconv.FormatInt(num, 10) } 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 } const ( KC_RAND_KIND_NUM = 0 // 纯数字 KC_RAND_KIND_LOWER = 1 // 小写字母 KC_RAND_KIND_UPPER = 2 // 大写字母 KC_RAND_KIND_ALL = 3 // 数字、大小写字母 ) // 随机字符串 func randStr(size int, kind int) string { ikind, kinds, result := kind, [][]int{[]int{10, 48}, []int{26, 97}, []int{26, 65}}, make([]byte, size) isAll := kind > 2 || kind < 0 mathrand.Seed(time.Now().UnixNano()) for i := 0; i < size; i++ { if isAll { ikind = mathrand.Intn(3) } scope, base := kinds[ikind][0], kinds[ikind][1] result[i] = uint8(base + mathrand.Intn(scope)) } return string(result) } func GenRandStr(size int) string { if size <= 0 { return "" } return randStr(size, KC_RAND_KIND_ALL) } func GenWechatPaySign(m map[string]string, payKey string) (string, error) { delete(m, "sign") var signData []string for k, v := range m { if v != "" && v != "0" { signData = append(signData, fmt.Sprintf("%s=%s", k, v)) } } sort.Strings(signData) signStr := strings.Join(signData, "&") signStr = signStr + "&key=" + payKey logger.Info("签字符串1 :", signStr) c := md5.New() _, err := c.Write([]byte(signStr)) if err != nil { return "", err } signByte := c.Sum(nil) if err != nil { return "", err } return fmt.Sprintf("%x", signByte), nil } const ( WxAppId = "wx806c079463b5b56c" WxAppSecret = "3d7335cf0b9fa1d70aa7eb079526ebf0" WxAppMchId = "1609877389" WxAppMchSecret = "DeovoMingHuiRengTianTang45675456" WxCheckName = "NO_CHECK" SpbilCreateIp = "39.108.188.218" WxTransferUrl = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers" WxKeyFile = "./configs/merchant/apiclient_key.pem" WxRootCaFile = "./configs/merchant/apiclient_cert.pem" ) //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 } 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"` } //给用户打款 func Transfer(amount uint32, openId, 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, SpbillCreateIp: SpbilCreateIp, } wxResp := WxTransferResp{} //生成签名 m, err := struct2Map(req) if err != nil { logger.Error(err) return nil, err } sign, err := GenWechatPaySign(m, WxAppMchSecret) if err != nil { logger.Error(err) return nil, err } req.Sign = strings.ToUpper(sign) payload, err := xml.Marshal(&req) if err != nil { logger.Error(err) return nil, err } request, err := newPostRequest(WxTransferUrl, string(payload)) if err != nil { logger.Error(err) return nil, err } defer request.Body.Close() logger.Info("证书路径:", WxRootCaFile) logger.Info("证书路径:", WxKeyFile) client, err := newHttpClient(WxRootCaFile, WxKeyFile) if err != nil { logger.Error(err) return nil, err } resp, err := client.Do(request) defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { logger.Error(err) return nil, err } //logger.Info("Transfer resp = ", string(body)) logger.Info("Transfer resp = ", string(body)) err = xml.Unmarshal(body, &wxResp) if err != nil { logger.Error(err) return nil, err } if wxResp.ReturnCode != "SUCCESS" || wxResp.ResultCode != "SUCCESS" { if wxResp.ErrCodeDes == "余额不足" { return nil, errors.New("account balance insufficient") } return nil, errors.New("Transfer fail") } return &wxResp, 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 newHttpClient(certFile, keyFile string) (*http.Client, error) { 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 } 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"` } type HmJsPayUnifiedOrderReq struct { HmPayPublicPara } 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"` } type HmPayGoodsDetails struct { Body string `json:"body"` GoodsId string `json:"goods_id"` GoodsName string `json:"goods_name"` Price float64 `json:"price"` Quantity int `json:"quantity"` ShowUrl string `json:"show_url"` } 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 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 HmPayUnifiedOrderRsp struct { Code string `json:"code"` Msg string `json:"msg"` Data string `json:"data"` Sign string `json:"sign"` } //hm 微信支付 func HmJsPayUnifiedOrder(orderId string, totalFee uint32, openId, profitSharing, attach, notifyUrl string) (*HmPayUnifiedOrderPayData, 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()) expireTime := now.Add(time.Hour * 3) strExpireTime := fmt.Sprintf("%04d%02d%02d%02d%02d%02d", expireTime.Year(), expireTime.Month(), expireTime.Day(), expireTime.Hour(), expireTime.Minute(), expireTime.Second()) nonce := utils.GenRandStr(NonceStringLength) if notifyUrl == "" { logger.Error("NotifyUrl is null") return nil, errors.New("NotifyUrl is null") } //logger.Info("MchId:", config.AppConfig.WxMchID) //logger.Info("AppId:", config.AppConfig.WxAppId) //logger.Info("MchSecret:", config.AppConfig.WxMchSecret) unifiedOrderReq := HmJsPayUnifiedOrderReq{} publicPara := HmPayPublicPara{ AppId: HmPayMerchantId, //SubAppId: HmWxSubMerchantId, Method: "trade.create", //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 := HmPayBizContent{ Body: "会员费", MerAppId: WxAppId, MerBuyerId: openId, CreateIp: clientIp, CreateTime: strTime, ExpireTime: strExpireTime, LimitPay: "NO_CREDIT", NotifyUrl: notifyUrl, OutOrderNo: orderId, PayType: "JSAPI", PayWay: "WECHAT", StoreId: "100001", TotalAmount: float64(totalFee) / 100, } unifiedOrderReq.HmPayPublicPara = publicPara bizString, err := json.Marshal(&biz) if err != nil { logger.Error("marshal biz err:", err) return nil, err } unifiedOrderReq.HmPayPublicPara.BizContent = string(bizString) m, err := struct2Map(unifiedOrderReq) if err != nil { logger.Error(err) return nil, err } //mJson, _ := json.MarshalIndent(&m, "", " ") //fmt.Println("mJson:", string(mJson)) sign, err := GenHmPaySign(m) if err != nil { logger.Error(err) return nil, err } unifiedOrderReq.Sign = sign //unifiedOrderReqJson, _ := json.Marshal(&unifiedOrderReq) //fmt.Println("unifiedOrderReqJson:", string(unifiedOrderReqJson)) 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.Error("ToSignContent err:", err) return nil, err } err = HmVerifySha1Rsa(signContent, unifiedOrderResp.Sign) if err != nil { logger.Error("HmVerifySha1Rsa err:", err) return nil, err } //fmt.Println("unifiedOrderResp:", unifiedOrderResp.Data) var hmPayDetail HmPayUnifiedOrderDetail 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) var hmPayData HmPayUnifiedOrderPayData err = json.Unmarshal([]byte(hmPayDetail.PayData), &hmPayData) if err != nil { logger.Errorf("hm pay unified order pay data unmarshal error %#v", err) return nil, err } //fmt.Println("hmPayData:", hmPayData) return &hmPayData, nil } func ToSignContent(s interface{}) (string, error) { m, err := struct2Map(s) if err != nil { logger.Error(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 } 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 :", signStr) signature, err := Sha1withRsa(signStr) if err != nil { logger.Error("signature err:", err) return "", err } return signature, 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.Error("parse err:", err) return "", err } signature, err := rsa.SignPKCS1v15(rand.Reader, priKey, hash, hashed) if err != nil { logger.Error("sign err:", err) return "", err } return b64.StdEncoding.EncodeToString(signature), nil } func ParsePrivateKey() (*rsa.PrivateKey, error) { //fp := "/Users/li/mh/mh_server/pack/configs/hm_pay/private_key.pem" fp := "./configs/hm_pay/private_key.pem" privateKey, err := ioutil.ReadFile(fp) if err != nil { logger.Error("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 } const ( PemBegin = "-----BEGIN RSA PRIVATE KEY-----\n" PemEnd = "\n-----END RSA PRIVATE KEY-----" ) 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 } const HmPubKey = `MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDzGVH0Fxpb2M48U1BWr6lpNs2W3VHqtjO8X5RqWjtTwpQVKo8dqaiAGxVbsdnefPpsbI5l9rKquRAOJhWFU07hxSUgXZOk55QQmll03MBgRDXLgxyKfycLLQwhsCJAzDIWC7IWgok/RHV9m9AV2GbQxWBl+7iDE4prcbpgG8Z0HwIDAQAB` 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 VerifySha1Rsa(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(publicKeyString)) 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 HmPayUnifiedOrder(r HmJsPayUnifiedOrderReq) (HmPayUnifiedOrderRsp, error) { var hmPayUnifiedOrderRsp HmPayUnifiedOrderRsp data, err := json.Marshal(r) if err != nil { logger.Error(err) return hmPayUnifiedOrderRsp, err } fmt.Println("data json:", string(data)) client := http.Client{} req, err := http.NewRequest("POST", HmPayUnifiedOrderUrl, bytes.NewBuffer(data)) if err != nil { logger.Error(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(err) return hmPayUnifiedOrderRsp, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { logger.Error(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:", err) return hmPayUnifiedOrderRsp, err } if hmPayUnifiedOrderRsp.Code != "200" { return hmPayUnifiedOrderRsp, errors.New(hmPayUnifiedOrderRsp.Msg) } return hmPayUnifiedOrderRsp, nil } //func RsaSign(signContent string, privateKey string, hash crypto.Hash) string { // shaNew := hash.New() // shaNew.Write([]byte(signContent)) // hashed := shaNew.Sum(nil) // priKey, err := ParsePrivateKey(privateKey) // if err != nil { // panic(err) // } // // signature, err := rsa.SignPKCS1v15(rand.Reader, priKey, hash, hashed) // if err != nil { // panic(err) // } // return base64.StdEncoding.EncodeToString(signature) // // digest := sha256.Sum256(data) // // signature, signErr := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, digest[:]) // // if signErr != nil { // t.Errorf("Could not sign message:%s", signErr.Error()) // } // // // just to check that we can survive to and from b64 // b64sig := base64.StdEncoding.EncodeToString(signature) // // decodedSignature, _ := base64.StdEncoding.DecodeString(b64sig) //}