615 lines
16 KiB
Go
615 lines
16 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"crypto/rsa"
|
||
"encoding/json"
|
||
"errors"
|
||
"f5"
|
||
"main/constant"
|
||
"main/mt"
|
||
"q5"
|
||
"time"
|
||
|
||
"github.com/wechatpay-apiv3/wechatpay-go/core"
|
||
)
|
||
|
||
type TokenInfo struct {
|
||
GameId int64 `json:"gameid"`
|
||
Token string `json:"token"`
|
||
Expire int64 `json:"expire"`
|
||
}
|
||
|
||
type expireSession struct {
|
||
SessionTime int64
|
||
Time int64
|
||
}
|
||
|
||
type mediaidInfo struct {
|
||
mediaId string
|
||
time int64
|
||
}
|
||
type wxpay struct {
|
||
gamesGoods q5.ConcurrentMap[int64, map[int64]GoodsInfo] //[gameid, [goodsid]goodsinfo]
|
||
accessTokens q5.ConcurrentMap[int64, TokenInfo] //[gameid, TokenInfo]
|
||
expireInfo q5.ConcurrentMap[string, *expireSession] //[accountid, expireSession]
|
||
mediaInfo q5.ConcurrentMap[int64, mediaidInfo] //[gameid, mediainfo]
|
||
refreshflag bool
|
||
ctx context.Context
|
||
client *core.Client
|
||
payhtmlstr string
|
||
mchpubkey *rsa.PublicKey
|
||
}
|
||
|
||
type WxQuery struct {
|
||
AccessToken string `json:"access_token"`
|
||
Signature string `json:"signature"`
|
||
SigMethod string `json:"sig_method"`
|
||
PaySig string `json:"pay_sig"`
|
||
}
|
||
|
||
type WxBalanceRsp struct {
|
||
ErrCode int32 `json:"errcode"`
|
||
ErrMsg string `json:"errmsg"`
|
||
Balance int64 `json:"balance"`
|
||
GiftBalance int64 `json:"present_balance"`
|
||
SumSave int64 `json:"sum_save"`
|
||
SumGift int64 `json:"sum_present"`
|
||
SumBalance int64 `json:"sum_balance"`
|
||
SumCost int64 `json:"sum_cost"`
|
||
FirstSave bool `json:"first_save"`
|
||
}
|
||
|
||
type WxPayRsp struct {
|
||
ErrCode int32 `json:"errcode"`
|
||
ErrMsg string `json:"errmsg"`
|
||
BillNo string `json:"bill_no"`
|
||
Balance int64 `json:"balance"`
|
||
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/DVVZZdHFsYkttYmxl(PayEventSig)
|
||
IsMock bool `json:"IsMock"` //True: 模拟测试推送 False:真实推送
|
||
} `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"` //微信支付信息(仅微信支付渠道)
|
||
|
||
}
|
||
|
||
type GoodsInfo struct {
|
||
Name string
|
||
Count int64
|
||
Price int64 //
|
||
}
|
||
|
||
func (wp *wxpay) init() {
|
||
wp.gamesGoods = q5.ConcurrentMap[int64, map[int64]GoodsInfo]{}
|
||
wp.accessTokens = q5.ConcurrentMap[int64, TokenInfo]{}
|
||
wp.expireInfo = q5.ConcurrentMap[string, *expireSession]{}
|
||
|
||
mt.Table.Wxconfig.Traverse(func(w *mt.Wxconfig) bool {
|
||
filename := "../res/gamegoods/" + q5.SafeToString(w.GetGameid()) + ".json"
|
||
str, err := f5.ReadJsonFile(filename)
|
||
if err != nil {
|
||
return true
|
||
}
|
||
data := []map[string]interface{}{}
|
||
if json.Unmarshal([]byte(str), &data) != nil {
|
||
f5.GetSysLog().Error("parse [%s] error.", filename)
|
||
} else {
|
||
gamegoods := map[int64]GoodsInfo{}
|
||
for _, data := range data {
|
||
gamegoods[q5.SafeToInt64(data["id"])] = GoodsInfo{
|
||
Name: q5.SafeToString(data["name"]),
|
||
Count: q5.SafeToInt64(data["count"]),
|
||
Price: q5.SafeToInt64(data["price"]) * 100,
|
||
}
|
||
}
|
||
wp.gamesGoods.Store(w.GetGameid(), gamegoods)
|
||
wp.accessTokens.Store(w.GetGameid(), TokenInfo{})
|
||
}
|
||
return true
|
||
})
|
||
|
||
go wp.checkExpireInfo()
|
||
|
||
wp.refreshflag = true
|
||
go wp.checkAccessToken()
|
||
|
||
wp.initMch()
|
||
}
|
||
|
||
func (wp *wxpay) unInit() {
|
||
}
|
||
|
||
func (wp *wxpay) freshAccessToken(gameid int64) (token string) {
|
||
cfg := mt.Table.Wxconfig.GetById(gameid)
|
||
params := map[string]string{
|
||
"grant_type": "client_credential",
|
||
"appid": cfg.GetAppid(),
|
||
"secret": cfg.GetAppsecret(),
|
||
}
|
||
urls := mt.Table.Config.GetWxUrl()
|
||
queryuri := "/cgi-bin/token"
|
||
for _, wxhost := range urls {
|
||
url := "https://" + wxhost + queryuri
|
||
sendok := false
|
||
f5.GetHttpCliMgr().SendGoStyleRequest(
|
||
url,
|
||
params,
|
||
func(hcr f5.HttpCliResponse) {
|
||
if hcr.GetErr() != nil {
|
||
return
|
||
}
|
||
|
||
rspObj := struct {
|
||
Token string `json:"access_token"`
|
||
Expire int64 `json:"expires_in"`
|
||
ErrCode int64 `json:"errcode"`
|
||
ErrMsg string `json:"errmsg"`
|
||
}{}
|
||
|
||
sendok = true
|
||
f5.GetSysLog().Debug("wx get access token rsp:%s", hcr.GetRawData())
|
||
if json.Unmarshal([]byte(hcr.GetRawData()), &rspObj) != nil {
|
||
return
|
||
}
|
||
|
||
if rspObj.ErrCode == 0 {
|
||
tokenitem, _ := wp.accessTokens.Load(gameid)
|
||
tokenitem.GameId = gameid
|
||
tokenitem.Token = rspObj.Token
|
||
tokenitem.Expire = rspObj.Expire + f5.GetApp().GetRealSeconds()
|
||
wp.accessTokens.Store(gameid, *tokenitem)
|
||
}
|
||
|
||
})
|
||
|
||
if sendok {
|
||
break
|
||
}
|
||
}
|
||
|
||
return token
|
||
}
|
||
|
||
func (wp *wxpay) getAccessToken(gameid int64) (token string) {
|
||
cfg, exist := wp.accessTokens.Load(gameid)
|
||
nowTime := f5.GetApp().GetRealSeconds()
|
||
if exist {
|
||
if cfg.Expire > nowTime+6 {
|
||
if cfg.Expire < nowTime+30 && !wp.refreshflag {
|
||
wp.refreshflag = true
|
||
}
|
||
return cfg.Token
|
||
}
|
||
|
||
if !wp.refreshflag {
|
||
wp.refreshflag = true
|
||
}
|
||
return ""
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func (wp *wxpay) GetGoodsCount(gameid int64, goodsid int64) (count int64, err error) {
|
||
goods, ok := wp.gamesGoods.Load(gameid)
|
||
if !ok {
|
||
return 0, errors.New("no game")
|
||
}
|
||
|
||
info, ok := (*goods)[goodsid]
|
||
if !ok {
|
||
return 0, errors.New("no goods")
|
||
}
|
||
|
||
return info.Count, nil
|
||
}
|
||
|
||
func (wp *wxpay) GetGoodsName(gameid int64, goodsid int64) (name string, err error) {
|
||
goods, ok := wp.gamesGoods.Load(gameid)
|
||
if !ok {
|
||
return "", errors.New("no game")
|
||
}
|
||
|
||
info, ok := (*goods)[goodsid]
|
||
if !ok {
|
||
return "", errors.New("no goods")
|
||
}
|
||
|
||
return info.Name, nil
|
||
}
|
||
|
||
func (wp *wxpay) QueryBalance(openid string, gameid int64, userip string, sessionkey string) (balance int64, wxerrcode int32, err error) {
|
||
cfg := mt.Table.Wxconfig.GetById(gameid)
|
||
postbody := struct {
|
||
OpenId string `json:"openid"`
|
||
OfferId string `json:"offer_id"`
|
||
Ts int64 `json:"ts"`
|
||
ZoneId string `json:"zone_id"`
|
||
Env int32 `json:"env"`
|
||
UserIp string `json:"user_ip"`
|
||
}{
|
||
OpenId: openid,
|
||
OfferId: cfg.GetOfferid(),
|
||
Ts: f5.GetApp().GetRealSeconds(),
|
||
ZoneId: cfg.GetZoneid(),
|
||
UserIp: userip,
|
||
}
|
||
|
||
if !f5.IsOnlineEnv() {
|
||
postbody.Env = 1
|
||
}
|
||
|
||
poststr := q5.EncodeJson(postbody)
|
||
|
||
queryuri := "/wxa/game/getbalance"
|
||
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, ¶ms)
|
||
|
||
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 := WxBalanceRsp{}
|
||
f5.GetSysLog().Debug("wx balance 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:
|
||
balance = rspJson.Balance
|
||
case constant.WX_ERRCODE_BUSY:
|
||
sendRequest = false
|
||
default:
|
||
f5.GetSysLog().Info("err msg:%s", rspJson.ErrMsg)
|
||
wp.checkErrorCode(rspJson.ErrCode)
|
||
}
|
||
|
||
})
|
||
if sendRequest {
|
||
break
|
||
}
|
||
}
|
||
|
||
return balance, wxerrcode, err
|
||
}
|
||
|
||
func (wp *wxpay) QueryPay(openid string, gameid int64, userip string, sessionkey string, amount int32, billno string) (wxerrcode int32) {
|
||
cfg := mt.Table.Wxconfig.GetById(gameid)
|
||
postbody := struct {
|
||
OpenId string `json:"openid"`
|
||
OfferId string `json:"offer_id"`
|
||
Ts int64 `json:"ts"`
|
||
ZoneId string `json:"zone_id"`
|
||
Env int32 `json:"env"`
|
||
UserIp string `json:"user_ip"`
|
||
Amount int32 `json:"amount"`
|
||
BillNo string `json:"bill_no"`
|
||
}{
|
||
OpenId: openid,
|
||
OfferId: cfg.GetOfferid(),
|
||
Ts: f5.GetApp().GetRealSeconds(),
|
||
ZoneId: cfg.GetZoneid(),
|
||
UserIp: userip,
|
||
Amount: amount,
|
||
BillNo: billno,
|
||
}
|
||
|
||
if !f5.IsOnlineEnv() {
|
||
postbody.Env = 1
|
||
}
|
||
|
||
poststr := q5.EncodeJson(postbody)
|
||
|
||
queryuri := "/wxa/game/pay"
|
||
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, ¶ms)
|
||
|
||
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 := WxPayRsp{}
|
||
f5.GetSysLog().Debug("wx pay 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) 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, ¶ms)
|
||
|
||
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,
|
||
Time: f5.GetApp().GetRealSeconds(),
|
||
}
|
||
|
||
wp.expireInfo.Store(accountid, &info)
|
||
}
|
||
|
||
func (wp *wxpay) CheckExpireCache(accountid string, expire int64) bool {
|
||
info, exist := wp.expireInfo.Load(accountid)
|
||
if !exist {
|
||
return false
|
||
}
|
||
|
||
if (*info).SessionTime < expire {
|
||
wp.expireInfo.Delete(accountid)
|
||
return false
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
func (wp *wxpay) GetAccessTokenList(data *[]TokenInfo) {
|
||
wp.accessTokens.Range(func(key int64, value TokenInfo) bool {
|
||
*data = append(*data, value)
|
||
return true
|
||
})
|
||
}
|
||
|
||
func (wp *wxpay) checkExpireInfo() {
|
||
for {
|
||
time.Sleep(time.Minute * 30)
|
||
nowTime := f5.GetApp().GetRealSeconds()
|
||
deletelist := map[string]int64{}
|
||
wp.expireInfo.Range(func(accountid string, value *expireSession) bool {
|
||
if value.Time+3600 < nowTime {
|
||
deletelist[accountid] = value.Time
|
||
}
|
||
return true
|
||
})
|
||
|
||
for accountid := range deletelist {
|
||
wp.expireInfo.Delete(accountid)
|
||
}
|
||
}
|
||
}
|
||
|
||
func (wp *wxpay) checkAccessToken() {
|
||
for {
|
||
time.Sleep(time.Second)
|
||
|
||
if f5.IsOnlineEnv() {
|
||
wp.gamesGoods.Range(func(gameid int64, value map[int64]GoodsInfo) bool {
|
||
cfg, _ := wp.accessTokens.Load(gameid)
|
||
if wp.refreshflag || (cfg != nil && cfg.Expire <= f5.GetApp().GetRealSeconds()) {
|
||
wp.freshAccessToken(gameid)
|
||
}
|
||
return true
|
||
})
|
||
} else {
|
||
if !wp.refreshflag {
|
||
wp.accessTokens.Range(func(key int64, value TokenInfo) bool {
|
||
if value.Expire <= f5.GetApp().GetRealSeconds() {
|
||
wp.refreshflag = true
|
||
return false
|
||
}
|
||
return true
|
||
})
|
||
|
||
if !wp.refreshflag {
|
||
continue
|
||
}
|
||
}
|
||
url := "https://payservice.kingsome.cn/api/service/refresh"
|
||
|
||
nowtimestr := q5.SafeToString(f5.GetApp().GetRealSeconds())
|
||
params := map[string]string{
|
||
"time": nowtimestr,
|
||
"sign": wp.GenSHA256Signature(nowtimestr+constant.GLOBAL_SALT, constant.GLOBAL_SALT),
|
||
}
|
||
f5.GetHttpCliMgr().SendGoStyleRequest(
|
||
url,
|
||
params,
|
||
func(hcr f5.HttpCliResponse) {
|
||
if hcr.GetErr() != nil {
|
||
return
|
||
}
|
||
|
||
rspObj := struct {
|
||
Data []TokenInfo `json:"data"`
|
||
ErrCode int64 `json:"errcode"`
|
||
ErrMsg string `json:"errmsg"`
|
||
}{}
|
||
|
||
f5.GetSysLog().Debug("get online payservice rsp:%s", hcr.GetRawData())
|
||
if json.Unmarshal([]byte(hcr.GetRawData()), &rspObj) != nil {
|
||
return
|
||
}
|
||
|
||
if rspObj.ErrCode == 0 {
|
||
for _, dataitem := range rspObj.Data {
|
||
wp.accessTokens.Store(dataitem.GameId, dataitem)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
wp.refreshflag = false
|
||
}
|
||
}
|
||
|
||
func (wp *wxpay) checkErrorCode(errcode int32) {
|
||
if errcode == constant.WX_ERRCODE_EXPIRE_ACCESSTOKEN ||
|
||
errcode == constant.WX_ERRCODE_MISSING_ACCESSTOKEN ||
|
||
errcode == constant.WX_ERRCODE_UNAUTH_ACCESSTOKEN {
|
||
if !wp.refreshflag {
|
||
wp.refreshflag = true
|
||
}
|
||
|
||
}
|
||
}
|