wx notify

This commit is contained in:
yangduo 2025-01-06 11:07:54 +08:00
parent 6f039a0d8d
commit 9e35fe6e5e
8 changed files with 468 additions and 11 deletions

View File

@ -1,10 +1,17 @@
package mainservice
import (
"crypto/sha1"
"encoding/json"
"f5"
"io"
"main/constant"
"main/service"
"payserver/model"
"payserver/mt"
"q5"
"sort"
"strings"
"github.com/gin-gonic/gin"
)
@ -36,3 +43,186 @@ func (this *MainServiceApi) RefreshToken(c *gin.Context) {
service.Wxpay.GetAccessTokenList(&rspObj.Data)
c.JSON(200, rspObj)
}
func (this *MainServiceApi) WxTNotify(c *gin.Context) {
f5.GetSysLog().Debug("wx test notify:%s", c.Request.URL.RawQuery)
signature := c.Query("signature")
timestamp := c.Query("timestamp")
nonce := c.Query("nonce")
echostr := c.Query("echostr")
strs := []string{constant.WX_NOTIFY_TOKEN, timestamp, nonce}
sort.Strings(strs)
sb := strings.Builder{}
sb.WriteString(strs[0])
sb.WriteString(strs[1])
sb.WriteString(strs[2])
m := sha1.New()
io.WriteString(m, sb.String())
sign := string(m.Sum(nil))
f5.GetSysLog().Debug("wx sign:%s, %s", sign, signature)
if sign != signature {
c.String(200, "wrong")
return
}
c.String(200, echostr)
}
func (this *MainServiceApi) WxNotifyPurchase(c *gin.Context) {
f5.GetSysLog().Debug("wx notify purchase:%s", c.Request.URL.RawQuery)
signature := c.Query("signature")
timestamp := c.Query("timestamp")
nonce := c.Query("nonce")
strs := []string{constant.WX_NOTIFY_TOKEN, timestamp, nonce}
sort.Strings(strs)
sb := strings.Builder{}
sb.WriteString(strs[0])
sb.WriteString(strs[1])
sb.WriteString(strs[2])
m := sha1.New()
io.WriteString(m, sb.String())
sign := string(m.Sum(nil))
f5.GetSysLog().Debug("wx sign:%s, %s", sign, signature)
if sign != signature {
return
}
rspObj := struct {
ErrorCode int32 `json:"ErrCode"`
ErrMsg string `json:"ErrMsg"`
}{
ErrorCode: 99999,
ErrMsg: "internal error",
}
msg_signature := c.Query("msg_signature")
if msg_signature != "" {
postObj := struct {
Encrypt string `json:"Encrypt"`
ToUserName string `json:"ToUserName"`
}{}
if err := c.ShouldBindJSON(&postObj); err != nil {
rspObj.ErrorCode = 401
rspObj.ErrMsg = "post data error"
c.JSON(200, rspObj)
return
}
smsg, appid := service.Wxpay.DecryptMsg(msg_signature, timestamp, nonce, postObj.Encrypt)
if smsg == nil || appid == nil || len(smsg) == 0 || len(appid) == 0 {
rspObj.ErrorCode = 402
rspObj.ErrMsg = "decrypt data error"
c.JSON(200, rspObj)
return
}
f5.GetSysLog().Debug("wx decrypt msg:%s", smsg)
wxnotifyobj := service.WxPurchaseNotify{}
if json.Unmarshal(smsg, &wxnotifyobj) != nil {
rspObj.ErrorCode = 403
rspObj.ErrMsg = "unmarshal data error"
c.JSON(200, rspObj)
return
}
gameid := int64(0)
appkey := ""
notifyurl := ""
mt.Table.Wxconfig.Traverse(func(w *mt.Wxconfig) bool {
if w.GetAppid() == string(appid) {
gameid = w.GetGameid()
appkey = w.GetAppkey()
notifyurl = w.GetNotifyurl()
return false
}
return true
})
if appkey == "" {
f5.GetSysLog().Error("wx app config error:%s", appid)
c.JSON(200, rspObj)
return
}
oristr := wxnotifyobj.Event + "&" + wxnotifyobj.MiniGame.Payload
sig := service.Wxpay.GenSHA256Signature(oristr, appkey)
if sig != wxnotifyobj.MiniGame.PayEventSig {
f5.GetSysLog().Error("pay event sig error:%s, %s, %s", appid, sig, wxnotifyobj.MiniGame.PayEventSig)
c.JSON(200, rspObj)
return
}
payloadobj := new(service.WxPayload)
if json.Unmarshal([]byte(wxnotifyobj.MiniGame.Payload), &payloadobj) != nil {
c.JSON(200, rspObj)
return
}
envpass := true
if f5.IsOnlineEnv() {
if payloadobj.Env != 0 {
f5.GetSysLog().Error("notify test info to prod url")
envpass = false
}
} else {
if payloadobj.Env != 1 {
f5.GetSysLog().Error("notify prod info to test url")
envpass = false
}
}
if !envpass {
c.JSON(200, rspObj)
return
}
orderModel := new(model.InAppOrder)
if err, found := orderModel.FindByOrderId(payloadobj.OutTradeNo); err != nil {
c.JSON(200, rspObj)
return
} else if !found {
c.JSON(200, rspObj)
return
}
if orderModel.Status > 1 {
rspObj.ErrorCode = 0
rspObj.ErrMsg = "Success"
c.JSON(200, rspObj)
return
}
rediskey := "ls:accountid:" + orderModel.AccountId
str, err := service.Redis.Get(constant.LOGIN_REDIS, rediskey)
if err != nil {
c.JSON(200, rspObj)
return
}
data := map[string]interface{}{}
if json.Unmarshal([]byte(str), &data) != nil {
c.JSON(200, rspObj)
return
}
openid := q5.SafeToString(data["openid"])
if openid != payloadobj.OpenId {
c.JSON(200, rspObj)
return
}
orderModel.GameId = int32(gameid)
f5.GetSysLog().Debug("notify url:%s, %s", appid, notifyurl)
}
c.JSON(200, rspObj)
}

View File

@ -56,3 +56,12 @@ const (
const (
GLOBAL_SALT = "f3a6a9a5-217a-4079-ab99-b5d69b8212be"
)
const (
WX_NOTIFY_TOKEN = "dV93f4FwSGMwkYcvsRHD8egdW5egPMhF" //必须32位
WX_NOTIFY_ENCODING_AES_KEY = "H60uFIXjyd431hLVhlsKyus3U28RVIzWncey424DqpY"
WX_AESKEY_SIZE = 32
WX_ENCODING_KEY_SIZE = 43
WX_RANDENCRYPT_STRLEN = 16
WX_KMSG_LEN = 4
)

View File

@ -23,7 +23,7 @@ type InAppOrder struct {
TryCount int32 `gorm:"column:try_count;comment:补单次数"`
Price int32 `gorm:"column:price;comment:price"`
IP string `gorm:"column:ipv4;comment:ipv4地址"`
Status int32 `gorm:"column:status;comment:0: 新添加订单 1:已经完成订单"`
Status int32 `gorm:"column:status;comment:0: 新添加订单 1:已支付 2已发货"`
ConfirmTime int32 `gorm:"column:confirmtime;comment:GameServer订单确认时间"`
CreateTime int32 `gorm:"column:createtime;<-:create"`
ModifyTime int32 `gorm:"column:modifytime"`

View File

@ -119,6 +119,7 @@ type Wxconfig struct {
appsecret string
zoneid string
offerid string
notifyurl string
_flags1_ uint64
_flags2_ uint64
@ -568,6 +569,14 @@ func (this *Wxconfig) HasOfferid() bool {
return (this._flags1_ & (uint64(1) << 6)) > 0
}
func (this *Wxconfig) GetNotifyurl() string {
return this.notifyurl
}
func (this *Wxconfig) HasNotifyurl() bool {
return (this._flags1_ & (uint64(1) << 7)) > 0
}
func (this *LoginRedis) GetHost() string {
return this.host
}
@ -702,6 +711,7 @@ func (this *Wxconfig) LoadFromKv(kv map[string]interface{}) {
f5.ReadMetaTableField(&this.appsecret, "appsecret", &this._flags1_, 4, kv)
f5.ReadMetaTableField(&this.zoneid, "zoneid", &this._flags1_, 5, kv)
f5.ReadMetaTableField(&this.offerid, "offerid", &this._flags1_, 6, kv)
f5.ReadMetaTableField(&this.notifyurl, "notifyurl", &this._flags1_, 7, kv)
}
func (this *LoginRedis) LoadFromKv(kv map[string]interface{}) {

View File

@ -98,6 +98,7 @@ message Wxconfig
optional string appsecret = 4;
optional string zoneid = 5;
optional string offerid = 6;
optional string notifyurl = 7;
}
message LoginRedis

View File

@ -11,4 +11,6 @@ func (this *MainServiceRouter) InitRouter() {
api := v1.ApiGroupApp.MainServiceApiGroup
f5.GetApp().GetGinEngine().GET("/api/service/refresh",
api.RefreshToken)
f5.GetApp().GetGinEngine().GET("/wx/tnotify",
api.WxTNotify)
}

View File

@ -1,16 +1,12 @@
package service
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"f5"
"main/constant"
"main/mt"
"q5"
"strings"
"time"
)
@ -58,6 +54,50 @@ type WxPayRsp struct {
UsedGift int64 `json:"used_present_amount"`
}
type WxQueryOrderRsp struct {
ErrCode int32 `json:"errcode"`
ErrMsg string `json:"errmsg"`
ProductId string `json:"product_id"` //道具id
PayState int32 `json:"pay_state"` //1:未支付;2:已支付
DeliverState int32 `json:"deliver_state"` //1:未发货;2:已发货
PayTime int64 `json:"pay_finish_time"` //支付完成时间
TrandNo string `json:"out_trade_no"` //用户订单号
MchOrderNo string `json:"mch_order_no"` //微信支付商户单
Trans string `json:"transaction_id"` //微信支付订单号
}
type WxPurchaseNotify struct {
ToUserName string `json:"ToUserName"` //小游戏原始ID
FromUserName string `json:"FromUserName"` //该事件消息的openid道具发货场景固定为微信官方的openid
CreateTime int64 `json:"CreateTime"` //消息发送时间
MsgType string `json:"MsgType"` //消息类型道具发货场景固定为event
Event string `json:"Event"` //事件类型 商城道具场景固定为minigame_h5_goods_deliver_notify 道具直购游戏内场景固定为minigame_game_pay_goods_deliver_notify
MiniGame struct {
Payload string `json:"Payload"` // 携带的具体内容格式为json具体内容如下表格Payload因为这里需要对消息内容统一签名所以统一把消息内容设计成json格式
PayEventSig string `json:"PayEventSig"` //见https://docs.qq.com/doc/DVVZZdHFsYkttYmxlPayEventSig
} `json:"MiniGame"` //道具直购发货参数
}
type WxPayload struct {
OpenId string `json:"OpenId"` //接收道具的玩家openid
Env int32 `json:"Env"` //环境配置 0现网环境也叫正式环境 1沙箱环境
OutTradeNo string `json:"OutTradeNo"` // 订单号
GoodsInfo struct {
ProductId string `json:"ProductId"` //游戏道具id标识
Quantity int64 `json:"Quantity"` //购买道具数量
ZoneId string `json:"ZoneId"` //分区
OrigPrice int64 `json:"OrigPrice"` //物品原始价格 (单位:分)
ActualPrice int64 `json:"ActualPrice"` //物品实际支付价格(单位:分)
Attach string `json:"Attach"` //透传数据
OrderSource int64 `json:"OrderSource"` // 1 游戏内 2 商城下单 3 商城测试下单
} `json:"GoodsInfo"` //发货道具
WeChatPayInfo struct {
MchOrderNo string `json:"MchOrderNo"` // 微信支付商户单号
TransactionId string `json:"TransactionId"` // 交易单号(微信支付订单号)
} `json:"WeChatPayInfo"` //微信支付信息(仅微信支付渠道)
}
func (wp *wxpay) init() {
wp.gamesGoods = q5.ConcurrentMap[int64, map[int64]int64]{}
wp.accessTokens = q5.ConcurrentMap[int64, TokenInfo]{}
@ -333,6 +373,82 @@ func (wp *wxpay) QueryPay(openid string, gameid int64, userip string, sessionkey
return wxerrcode
}
func (wp *wxpay) QueryPurchase(openid string, gameid int64, userip string, sessionkey string, amount int32, tradeno string) (wxerrcode int32) {
cfg := mt.Table.Wxconfig.GetById(gameid)
postbody := struct {
OpenId string `json:"openid"`
Ts int64 `json:"ts"`
Env int32 `json:"env"`
TradeNo string `json:"out_trade_no"`
BizId int32 `json:"biz_id"`
OfferId string `json:"offer_id"`
}{
OpenId: openid,
Ts: f5.GetApp().GetRealSeconds(),
BizId: 2, //1代币 2道具直购
TradeNo: tradeno,
OfferId: cfg.GetOfferid(),
}
if !f5.IsOnlineEnv() {
postbody.Env = 1
}
poststr := q5.EncodeJson(postbody)
queryuri := "/wxa/game/queryorderinfo"
query := WxQuery{
AccessToken: wp.getAccessToken(gameid),
Signature: wp.GenSHA256Signature(poststr, sessionkey),
SigMethod: "hmac_sha256",
PaySig: wp.GenSHA256Signature(queryuri+"&"+poststr, cfg.GetAppkey()),
}
params := map[string]string{}
data, _ := json.Marshal(query)
json.Unmarshal(data, &params)
sendRequest := false
urls := mt.Table.Config.GetWxUrl()
for _, wxhost := range urls {
url := "https://" + wxhost + queryuri
f5.GetHttpCliMgr().SendGoStylePost(
url,
params,
"Content-Type: application/json",
poststr,
func(rsp f5.HttpCliResponse) {
if rsp.GetErr() != nil {
return
}
sendRequest = true
rspJson := WxQueryOrderRsp{}
f5.GetSysLog().Debug("wx query order rsp:%s", rsp.GetRawData())
err := q5.DecodeJson(rsp.GetRawData(), &rspJson)
if err != nil {
return
}
wxerrcode = rspJson.ErrCode
switch rspJson.ErrCode {
case constant.WX_ERRCODE_OK:
case constant.WX_ERRCODE_BUSY:
sendRequest = false
default:
f5.GetSysLog().Info("err msg:%s", rspJson.ErrMsg)
wp.checkErrorCode(rspJson.ErrCode)
}
})
if sendRequest {
break
}
}
return wxerrcode
}
func (wp *wxpay) AddExpireInfo(accountid string, sessiontime int64) {
info := expireSession{
SessionTime: sessiontime,
@ -356,12 +472,6 @@ func (wp *wxpay) CheckExpireCache(accountid string, expire int64) bool {
return true
}
func (wp *wxpay) GenSHA256Signature(str string, key string) string {
mac := hmac.New(sha256.New, []byte(key))
_, _ = mac.Write([]byte(str))
return strings.ToLower(hex.EncodeToString(mac.Sum(nil)))
}
func (wp *wxpay) GetAccessTokenList(data *[]TokenInfo) {
wp.accessTokens.Range(func(key int64, value TokenInfo) bool {
*data = append(*data, value)

View File

@ -0,0 +1,135 @@
package service
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"main/constant"
"sort"
"strings"
)
func (wp *wxpay) GenSHA256Signature(str string, key string) string {
mac := hmac.New(sha256.New, []byte(key))
_, _ = mac.Write([]byte(str))
return strings.ToLower(hex.EncodeToString(mac.Sum(nil)))
}
func (wp *wxpay) DecryptMsg(sMsgSignature string, sTimeStamp string, sNonce string, sEncryptMsg string) (sMsg []byte, msgappid []byte) {
// 2.validate signature
if !wp.ValidateSignature(sMsgSignature, sTimeStamp, sNonce, sEncryptMsg) {
return
}
//3.decode base64
sAesData, err := base64.StdEncoding.DecodeString(sEncryptMsg)
if err != nil {
return
}
//4.decode aes
sAesKey := wp.GenAesKeyFromEncodingKey(constant.WX_NOTIFY_ENCODING_AES_KEY)
if sAesKey == "" {
return
}
sNoEncryptData := wp.AES_CBCDecrypt(sAesData, sAesKey)
// 5. remove kRandEncryptStrLen str
if len(sNoEncryptData) <= constant.WX_RANDENCRYPT_STRLEN+constant.WX_KMSG_LEN {
return
}
netlenbyte := sNoEncryptData[constant.WX_RANDENCRYPT_STRLEN : constant.WX_RANDENCRYPT_STRLEN+constant.WX_KMSG_LEN]
buf := bytes.NewReader(netlenbyte)
iMsgLen := int(0) //ntohl(iNetLen);
binary.Read(buf, binary.BigEndian, &iMsgLen)
if len(sNoEncryptData) <= constant.WX_RANDENCRYPT_STRLEN+constant.WX_KMSG_LEN+iMsgLen {
return
}
sMsg = sNoEncryptData[constant.WX_RANDENCRYPT_STRLEN+constant.WX_KMSG_LEN : constant.WX_RANDENCRYPT_STRLEN+constant.WX_KMSG_LEN+iMsgLen]
//6. validate appid
msgappid = sNoEncryptData[constant.WX_RANDENCRYPT_STRLEN+constant.WX_KMSG_LEN+iMsgLen:]
return
}
func (wp *wxpay) ValidateSignature(sMsgSignature string, sTimeStamp string, sNonce string, sEncryptMsg string) bool {
sSignature := wp.ComputeSignature(constant.WX_NOTIFY_TOKEN, sTimeStamp, sNonce, sEncryptMsg)
if sSignature == "" {
return false
}
return sMsgSignature == sSignature
}
func (wp *wxpay) ComputeSignature(sToken string, sTimeStamp string, sNonce string, sMessage string) string {
if sToken == "" || sNonce == "" || sMessage == "" || sTimeStamp == "" {
return ""
}
//sort
strs := []string{}
strs = append(strs, sToken)
strs = append(strs, sTimeStamp)
strs = append(strs, sNonce)
strs = append(strs, sMessage)
sort.Strings(strs)
sStr := strs[0] + strs[1] + strs[2] + strs[3]
//compute
sha1crypto := sha1.New()
_, err := sha1crypto.Write([]byte(sStr))
if err != nil {
return ""
}
return hex.EncodeToString(sha1crypto.Sum(nil))
}
func (wp *wxpay) GenAesKeyFromEncodingKey(sEncodingKey string) string {
if len(sEncodingKey) != len(constant.WX_NOTIFY_ENCODING_AES_KEY) {
return ""
}
sBase64 := sEncodingKey + "="
data, err := base64.StdEncoding.DecodeString(sBase64)
if err != nil {
return ""
}
return string(data)
}
func (wp *wxpay) AES_CBCDecrypt(sSource []byte, sKey string) []byte {
if len(sSource) < constant.WX_AESKEY_SIZE || len(sSource)%constant.WX_AESKEY_SIZE != 0 {
return []byte{}
}
key := []byte(sKey)
if len(sKey) > constant.WX_AESKEY_SIZE {
key = key[0:constant.WX_AESKEY_SIZE]
}
return aesDecryptCBC(sSource, key)
}
func aesDecryptCBC(encrypted []byte, key []byte) (decrypted []byte) {
block, _ := aes.NewCipher(key) // 分组秘钥
blockSize := block.BlockSize() // 获取秘钥块的长度
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) // 加密模式
decrypted = make([]byte, len(encrypted)) // 创建数组
blockMode.CryptBlocks(decrypted, encrypted) // 解密
decrypted = pkcs5UnPadding(decrypted) // 去除补全码
return decrypted
}
func pkcs5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}