mh_server/lib/wxpay/wx_pay.go

437 lines
16 KiB
Go
Raw Normal View History

2021-06-30 02:12:05 +00:00
package wxpay
import (
"bytes"
"crypto/md5"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"mh-server/config"
"mh-server/lib/utils"
"io/ioutil"
"net/http"
"sort"
"strconv"
"strings"
"time"
"github.com/codinl/go-logger"
)
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" // 租卡
)
//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: "https://switch.deovo.com:8001/api/v1/wxpay/notice",
TradeType: "JSAPI",
MchId: config.AppConfig.WxMchID,
AppId: config.AppConfig.WxAppId,
OpenId: openId,
TimeStart: strTime,
ProfitSharing: profitSharing,
Attach: attach,
}
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_fee"`
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.Error("签字符串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 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
}