mh_server/lib/wxpay/wx_pay.go
2022-10-13 17:20:52 +08:00

943 lines
31 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 wxpay
import (
"bytes"
"context"
"crypto/md5"
"crypto/tls"
"encoding/json"
"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"
//"mh-server/model"
"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"
)
//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"`
}
//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", v2)
case float32, float64:
result[k] = fmt.Sprintf("%.0f", v2)
}
}
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密钥
)
// 微信商户
// 商户ID1609877389
// 操作密码456755
// 密钥APIDeovoMingHuiRengTianTang45675456
//密钥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
}