mh_server/lib/wxpay/wx_pay.go
2021-11-01 11:32:23 +08:00

532 lines
19 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"
"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"
wechatpayutils "github.com/wechatpay-apiv3/wechatpay-go/utils"
"io/ioutil"
"log"
"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" // 租卡
//NotifyUrl = "https://switch.deovo.com:8001/api/v1/wxpay/notice" // TODO 数据库配置 生产
NotifyUrl = "https://dev.switch.deovo.com:8004/api/v1/wxpay/notice" // TODO 测试
)
//web 微信支付
func WebPay(orderId string, totalFee uint32, openId, profitSharing, attach 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)
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: 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"`
CouponId0 string `xml:"coupon_id_0,CDATA" json:"coupon_id"`
CouponFee0 uint `xml:"coupon_fee_0,CDATA" json:"coupon_fee0"`
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"`
}
)
////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
}