|
|
@ -0,0 +1,337 @@ |
|
|
|
package wechat |
|
|
|
|
|
|
|
import ( |
|
|
|
"encoding/xml" |
|
|
|
"fmt" |
|
|
|
"log" |
|
|
|
"reflect" |
|
|
|
"strings" |
|
|
|
"time" |
|
|
|
|
|
|
|
"git.tetele.net/tgo/network" |
|
|
|
|
|
|
|
"git.tetele.net/tgo/crypter" |
|
|
|
|
|
|
|
"git.tetele.net/tgo/helper" |
|
|
|
) |
|
|
|
|
|
|
|
//请求支付
|
|
|
|
type PayXml struct { |
|
|
|
XMLName xml.Name `xml:"xml"` |
|
|
|
Appid string `xml:"appid"` |
|
|
|
MchId string `xml:"mch_id"` |
|
|
|
DeviceInfo string `xml:"device_info"` |
|
|
|
NonceStr string `xml:"nonce_str"` |
|
|
|
Sign string `xml:"sign"` |
|
|
|
Body string `xml:"body"` |
|
|
|
OutTradeNo string `xml:"out_trade_no"` |
|
|
|
TotalFee int `xml:"total_fee"` |
|
|
|
SpbillCreateIp string `xml:"spbill_create_ip"` |
|
|
|
AuthCode string `xml:"auth_code"` |
|
|
|
} |
|
|
|
|
|
|
|
//支付结果
|
|
|
|
type PayReturnXml 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"` |
|
|
|
Openid string `xml:"openid"` |
|
|
|
IsSubscribe string `xml:"is_subscribe"` |
|
|
|
TradeType string `xml:"trade_type"` |
|
|
|
BankType string `xml:"bank_type"` |
|
|
|
FeeType string `xml:"fee_type"` |
|
|
|
TotalFee string `xml:"total_fee"` |
|
|
|
SettlementTotalFee string `xml:"settlement_total_fee"` |
|
|
|
CouponFee string `xml:"coupon_fee"` |
|
|
|
CashFeeType string `xml:"cash_fee_type"` |
|
|
|
CashFee string `xml:"cash_fee"` |
|
|
|
TransactionId string `xml:"transaction_id"` |
|
|
|
OutTradeNo string `xml:"out_trade_no"` |
|
|
|
Attach string `xml:"attach"` |
|
|
|
TimeEnd string `xml:"time_end"` |
|
|
|
PromotionDetail string `xml:"promotion_detail"` |
|
|
|
} |
|
|
|
|
|
|
|
//查询支付
|
|
|
|
type QueryXml struct { |
|
|
|
XMLName xml.Name `xml:"xml"` |
|
|
|
Appid string `xml:"appid"` |
|
|
|
MchId string `xml:"mch_id"` |
|
|
|
OutTradeNo string `xml:"out_trade_no"` |
|
|
|
NonceStr string `xml:"nonce_str"` |
|
|
|
Sign string `xml:"sign"` |
|
|
|
} |
|
|
|
|
|
|
|
//查询结果
|
|
|
|
type QueryReturnXml struct { |
|
|
|
ReturnCode string `xml:"return_code"` |
|
|
|
ReturnMsg string `xml:"return_msg"` |
|
|
|
Appid string `xml:"appid"` |
|
|
|
MchId string `xml:"mch_id"` |
|
|
|
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"` |
|
|
|
DeviceInfo string `xml:"device_info"` |
|
|
|
Openid string `xml:"openid"` |
|
|
|
IsSubscribe string `xml:"is_subscribe"` |
|
|
|
TradeType string `xml:"trade_type"` |
|
|
|
TradeState string `xml:"trade_state"` |
|
|
|
BankType string `xml:"bank_type"` |
|
|
|
TotalFee string `xml:"total_fee"` |
|
|
|
SettlementTotalFee string `xml:"settlement_total_fee"` |
|
|
|
FeeType string `xml:"fee_type"` |
|
|
|
CashFee string `xml:"cash_fee"` |
|
|
|
CashFeeType string `xml:"cash_fee_type"` |
|
|
|
CouponFee string `xml:"coupon_fee"` |
|
|
|
CouponCount string `xml:"coupon_count"` |
|
|
|
TransactionId string `xml:"transaction_id"` |
|
|
|
OutTradeNo string `xml:"out_trade_no"` |
|
|
|
Attach string `xml:"attach"` |
|
|
|
TimeEnd string `xml:"time_end"` |
|
|
|
TradeStateDesc string `xml:"trade_state_desc"` |
|
|
|
} |
|
|
|
|
|
|
|
// 撤消
|
|
|
|
type ReverseXml struct { |
|
|
|
XMLName xml.Name `xml:"xml"` |
|
|
|
Appid string `xml:"appid"` |
|
|
|
MchId string `xml:"mch_id"` |
|
|
|
OutTradeNo string `xml:"out_trade_no"` |
|
|
|
NonceStr string `xml:"nonce_str"` |
|
|
|
Sign string `xml:"sign"` |
|
|
|
} |
|
|
|
|
|
|
|
//撤消结果
|
|
|
|
type ReverseReturnXml struct { |
|
|
|
ReturnCode string `xml:"return_code"` |
|
|
|
ReturnMsg string `xml:"return_msg"` |
|
|
|
Appid string `xml:"appid"` |
|
|
|
MchId string `xml:"mch_id"` |
|
|
|
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"` |
|
|
|
Recall string `xml:"recall"` |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 微信二维码支付 |
|
|
|
* 2022/02/07 |
|
|
|
* @param device_info 设备号 |
|
|
|
* @param body 商品描述 |
|
|
|
* @param out_trade_no 商户订单号 |
|
|
|
* @param total_fee 订单金额 |
|
|
|
* @param auth_code 付款码 |
|
|
|
*/ |
|
|
|
|
|
|
|
func MicroPay(appid, mchid, mch_key, device_info, body, out_trade_no, auth_code, total_fee string) (bool, string, PayReturnXml) { |
|
|
|
|
|
|
|
log.Println("wechat micropay") |
|
|
|
|
|
|
|
var requestxml PayXml |
|
|
|
|
|
|
|
var retXml PayReturnXml |
|
|
|
|
|
|
|
requestxml.Appid = appid |
|
|
|
requestxml.MchId = mchid |
|
|
|
requestxml.DeviceInfo = device_info |
|
|
|
requestxml.NonceStr = helper.GetRandomString(30) |
|
|
|
requestxml.Body = body |
|
|
|
requestxml.OutTradeNo = out_trade_no |
|
|
|
requestxml.TotalFee = helper.ToInt(helper.FloatMul(total_fee, 100)) |
|
|
|
requestxml.SpbillCreateIp = network.GetLocalIp() |
|
|
|
requestxml.AuthCode = auth_code |
|
|
|
|
|
|
|
signStr := helper.StringJoin("appid=", requestxml.Appid, "&auth_code=", requestxml.AuthCode, |
|
|
|
"&body=", requestxml.Body, "&device_info=", requestxml.DeviceInfo, |
|
|
|
"&mch_id=", requestxml.MchId, "&nonce_str=", requestxml.NonceStr, |
|
|
|
"&out_trade_no=", requestxml.OutTradeNo, "&spbill_create_ip=", requestxml.SpbillCreateIp, |
|
|
|
"&total_fee=", helper.ToString(requestxml.TotalFee), "&key=", mch_key) |
|
|
|
|
|
|
|
signMd5 := Md5Str(signStr) |
|
|
|
|
|
|
|
requestxml.Sign = strings.ToUpper(signMd5) |
|
|
|
|
|
|
|
reqXmlJson, err := xml.Marshal(requestxml) |
|
|
|
|
|
|
|
log.Println(requestxml, string(reqXmlJson), err) |
|
|
|
|
|
|
|
url := "https://api.mch.weixin.qq.com/pay/micropay" |
|
|
|
ret, err := network.SendXml("POST", url, requestxml) |
|
|
|
|
|
|
|
log.Println("wechat micropay pay result:", string(ret), err) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
log.Println("Err:", err) |
|
|
|
return false, "微信支付失败,请检查网络", retXml |
|
|
|
} |
|
|
|
|
|
|
|
err = xml.Unmarshal(ret, &retXml) |
|
|
|
if err != nil { |
|
|
|
log.Println("xml Unmarshal Err:", retXml, err) |
|
|
|
return false, "未知的支付结果", retXml |
|
|
|
} |
|
|
|
|
|
|
|
if retXml.ResultCode == "SUCCESS" { |
|
|
|
|
|
|
|
retMap := XmlStructToMap(retXml) |
|
|
|
|
|
|
|
//验证sign
|
|
|
|
signStr = helper.HttpBuildQuery(retMap) + "&key=" + mch_key |
|
|
|
|
|
|
|
sign := strings.ToUpper(Md5Str(signStr)) |
|
|
|
|
|
|
|
if sign != retXml.Sign { |
|
|
|
log.Println("Err:签名错误", string(ret), sign) |
|
|
|
return false, "支付结果签名错误", retXml |
|
|
|
} |
|
|
|
} else { |
|
|
|
return false, retXml.ReturnMsg, retXml |
|
|
|
} |
|
|
|
|
|
|
|
return true, "", retXml |
|
|
|
} |
|
|
|
|
|
|
|
func MicroPayQuery(appid, mchid, out_trade_no, mch_key string) (bool, string, QueryReturnXml) { |
|
|
|
|
|
|
|
log.Println("wechat micropay query", out_trade_no) |
|
|
|
|
|
|
|
var requestxml QueryXml |
|
|
|
|
|
|
|
var retXml QueryReturnXml |
|
|
|
|
|
|
|
requestxml.Appid = appid |
|
|
|
requestxml.MchId = mchid |
|
|
|
requestxml.NonceStr = helper.GetRandomString(30) |
|
|
|
requestxml.OutTradeNo = out_trade_no |
|
|
|
|
|
|
|
signStr := helper.StringJoin("appid=", requestxml.Appid, |
|
|
|
"&mch_id=", requestxml.MchId, "&nonce_str=", requestxml.NonceStr, |
|
|
|
"&out_trade_no=", requestxml.OutTradeNo, "&key=", mch_key) |
|
|
|
|
|
|
|
signMd5 := Md5Str(signStr) |
|
|
|
|
|
|
|
requestxml.Sign = strings.ToUpper(signMd5) |
|
|
|
|
|
|
|
url := "https://api.mch.weixin.qq.com/pay/orderquery" |
|
|
|
ret, err := network.SendXml("POST", url, requestxml) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
log.Println("Err:", err) |
|
|
|
return false, "微信支付失败,请检查网络", retXml |
|
|
|
} |
|
|
|
|
|
|
|
err = xml.Unmarshal(ret, &retXml) |
|
|
|
if err != nil { |
|
|
|
log.Println("xml Unmarshal Err:", retXml, err) |
|
|
|
return false, "未知的支付结果", retXml |
|
|
|
} |
|
|
|
|
|
|
|
if retXml.ReturnCode == "SUCCESS" { |
|
|
|
|
|
|
|
retMap := XmlStructToMap(retXml) |
|
|
|
|
|
|
|
//验证sign
|
|
|
|
signStr = helper.HttpBuildQuery(retMap) + "&key=" + mch_key |
|
|
|
|
|
|
|
sign := strings.ToUpper(Md5Str(signStr)) |
|
|
|
|
|
|
|
if sign != retXml.Sign { |
|
|
|
log.Println("Err:签名错误", string(ret), sign) |
|
|
|
return false, "查询支付结果签名错误", retXml |
|
|
|
} |
|
|
|
} else { |
|
|
|
return false, retXml.ReturnMsg, retXml |
|
|
|
} |
|
|
|
return true, "", retXml |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 撤消订单 |
|
|
|
* 2023/06/13 |
|
|
|
*/ |
|
|
|
func MicroPayReverse(appid, mchid, out_trade_no, mch_key string) (bool, string, QueryReturnXml) { |
|
|
|
|
|
|
|
log.Println("wechat micropay reverse", out_trade_no) |
|
|
|
|
|
|
|
var requestxml ReverseXml |
|
|
|
|
|
|
|
var retXml ReverseReturnXml |
|
|
|
|
|
|
|
requestxml.Appid = appid |
|
|
|
requestxml.MchId = mchid |
|
|
|
requestxml.NonceStr = helper.GetRandomString(30) |
|
|
|
requestxml.OutTradeNo = out_trade_no |
|
|
|
|
|
|
|
signStr := helper.StringJoin("appid=", requestxml.Appid, |
|
|
|
"&mch_id=", requestxml.MchId, "&nonce_str=", requestxml.NonceStr, |
|
|
|
"&out_trade_no=", requestxml.OutTradeNo, "&key=", mch_key) |
|
|
|
|
|
|
|
signMd5 := Md5Str(signStr) |
|
|
|
|
|
|
|
requestxml.Sign = strings.ToUpper(signMd5) |
|
|
|
|
|
|
|
url := "https://api.mch.weixin.qq.com/secapi/pay/reverse" |
|
|
|
ret, err := network.SendXml("POST", url, requestxml) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
log.Println("Err:", err) |
|
|
|
return false, "撤消微信支付失败,请检查网络", retXml |
|
|
|
} |
|
|
|
|
|
|
|
err = xml.Unmarshal(ret, &retXml) |
|
|
|
if err != nil { |
|
|
|
log.Println("xml Unmarshal Err:", retXml, err) |
|
|
|
return false, "未知的撤消结果", retXml |
|
|
|
} |
|
|
|
|
|
|
|
if retXml.ReturnCode == "SUCCESS" { |
|
|
|
|
|
|
|
retMap := XmlStructToMap(retXml) |
|
|
|
|
|
|
|
//验证sign
|
|
|
|
signStr = helper.HttpBuildQuery(retMap) + "&key=" + mch_key |
|
|
|
|
|
|
|
sign := strings.ToUpper(Md5Str(signStr)) |
|
|
|
|
|
|
|
if sign != retXml.Sign { |
|
|
|
log.Println("Err:签名错误", string(ret), sign) |
|
|
|
return false, "查询支付结果签名错误", retXml |
|
|
|
} |
|
|
|
} else { |
|
|
|
return false, retXml.ReturnMsg, retXml |
|
|
|
} |
|
|
|
return true, "", retXml |
|
|
|
} |
|
|
|
|
|
|
|
func XmlStructToMap(retXml interface{}) map[string]string { |
|
|
|
|
|
|
|
t := reflect.TypeOf(retXml) |
|
|
|
v := reflect.ValueOf(retXml) |
|
|
|
|
|
|
|
//转为map,验签
|
|
|
|
retMap := make(map[string]string) |
|
|
|
var retValue string |
|
|
|
var tag string |
|
|
|
for k := 0; k < t.NumField(); k++ { |
|
|
|
if t.Field(k).Name != "Sign" { |
|
|
|
retValue = helper.ToString(v.Field(k).Interface()) |
|
|
|
tag = fmt.Sprintf("%s", t.Field(k).Tag) |
|
|
|
tag = strings.ReplaceAll(tag, "xml:", "") |
|
|
|
tag = strings.ReplaceAll(tag, "\"", "") |
|
|
|
retMap[tag] = retValue |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return retMap |
|
|
|
} |