110 lines
3.3 KiB
Go
110 lines
3.3 KiB
Go
|
package notify
|
|||
|
|
|||
|
import (
|
|||
|
"fmt"
|
|||
|
"reflect"
|
|||
|
"sort"
|
|||
|
"strings"
|
|||
|
|
|||
|
"github.com/fatih/structs"
|
|||
|
"github.com/silenceper/wechat/v2/util"
|
|||
|
"github.com/spf13/cast"
|
|||
|
)
|
|||
|
|
|||
|
// doc: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7&index=8
|
|||
|
|
|||
|
// PaidResult 下单回调
|
|||
|
type PaidResult struct {
|
|||
|
ReturnCode *string `xml:"return_code"`
|
|||
|
ReturnMsg *string `xml:"return_msg"`
|
|||
|
|
|||
|
AppID *string `xml:"appid" json:"appid"`
|
|||
|
MchID *string `xml:"mch_id"`
|
|||
|
DeviceInfo *string `xml:"device_info"`
|
|||
|
NonceStr *string `xml:"nonce_str"`
|
|||
|
Sign *string `xml:"sign"`
|
|||
|
SignType *string `xml:"sign_type"`
|
|||
|
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"`
|
|||
|
TradeState *string `xml:"trade_state"`
|
|||
|
BankType *string `xml:"bank_type"`
|
|||
|
TotalFee *int `xml:"total_fee"`
|
|||
|
SettlementTotalFee *int `xml:"settlement_total_fee"`
|
|||
|
FeeType *string `xml:"fee_type"`
|
|||
|
CashFee *string `xml:"cash_fee"`
|
|||
|
CashFeeType *string `xml:"cash_fee_type"`
|
|||
|
CouponFee *int `xml:"coupon_fee"`
|
|||
|
CouponCount *int `xml:"coupon_count"`
|
|||
|
|
|||
|
// coupon_type_$n 这里只声明 3 个,如果有更多的可以自己组合
|
|||
|
CouponType0 *string `xml:"coupon_type_0"`
|
|||
|
CouponType1 *string `xml:"coupon_type_1"`
|
|||
|
CouponType2 *string `xml:"coupon_type_2"`
|
|||
|
CouponID0 *string `xml:"coupon_id_0"`
|
|||
|
CouponID1 *string `xml:"coupon_id_1"`
|
|||
|
CouponID2 *string `xml:"coupon_id_2"`
|
|||
|
CouponFee0 *string `xml:"coupon_fee_0"`
|
|||
|
CouponFee1 *string `xml:"coupon_fee_1"`
|
|||
|
CouponFee2 *string `xml:"coupon_fee_2"`
|
|||
|
|
|||
|
TransactionID *string `xml:"transaction_id"`
|
|||
|
OutTradeNo *string `xml:"out_trade_no"`
|
|||
|
Attach *string `xml:"attach"`
|
|||
|
TimeEnd *string `xml:"time_end"`
|
|||
|
}
|
|||
|
|
|||
|
// PaidResp 消息通知返回
|
|||
|
type PaidResp struct {
|
|||
|
ReturnCode string `xml:"return_code"`
|
|||
|
ReturnMsg string `xml:"return_msg"`
|
|||
|
}
|
|||
|
|
|||
|
// PaidVerifySign 支付成功结果验签
|
|||
|
func (notify *Notify) PaidVerifySign(notifyRes PaidResult) bool {
|
|||
|
// STEP1, 转换 struct 为 map,并对 map keys 做排序
|
|||
|
resMap := structs.Map(notifyRes)
|
|||
|
|
|||
|
sortedKeys := make([]string, 0, len(resMap))
|
|||
|
for k := range resMap {
|
|||
|
sortedKeys = append(sortedKeys, k)
|
|||
|
}
|
|||
|
sort.Strings(sortedKeys)
|
|||
|
|
|||
|
// STEP2, 对key=value的键值对用&连接起来,略过空值 & sign
|
|||
|
var signStrings string
|
|||
|
for _, k := range sortedKeys {
|
|||
|
value := fmt.Sprintf("%v", cast.ToString(resMap[k]))
|
|||
|
if value != "" && strings.ToLower(k) != "sign" {
|
|||
|
signStrings = signStrings + getTagKeyName(k, ¬ifyRes) + "=" + value + "&"
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// STEP3, 在键值对的最后加上key=API_KEY
|
|||
|
signStrings = signStrings + "key=" + notify.Key
|
|||
|
|
|||
|
// STEP4, 根据SignType计算出签名
|
|||
|
var signType string
|
|||
|
if notifyRes.SignType != nil {
|
|||
|
signType = *notifyRes.SignType
|
|||
|
}
|
|||
|
sign, err := util.CalculateSign(signStrings, signType, notify.Key)
|
|||
|
if err != nil {
|
|||
|
return false
|
|||
|
}
|
|||
|
if sign != *notifyRes.Sign {
|
|||
|
return false
|
|||
|
}
|
|||
|
return true
|
|||
|
}
|
|||
|
|
|||
|
func getTagKeyName(key string, notifyRes *PaidResult) string {
|
|||
|
s := reflect.TypeOf(notifyRes).Elem()
|
|||
|
f, _ := s.FieldByName(key)
|
|||
|
name := f.Tag.Get("xml")
|
|||
|
return name
|
|||
|
}
|