diff --git a/bin/payserver/config/config.json b/bin/payserver/config/config.json index ec01ffd..39f6258 100644 --- a/bin/payserver/config/config.json +++ b/bin/payserver/config/config.json @@ -4,5 +4,7 @@ "wx_merchant_id": "1509252791", "wx_certificate_sn": "3490963E4E2767E6D8EC99CFE406A2CBEB3CD195", "wx_merchant_api_key": "fgRnUvC5Zu04ir9HQPHWesrsh49ZnfpC", - "wx_merchant_public_key_id": "PUB_KEY_ID_0115092527912025021200388000001392" -} + "wx_merchant_public_key_id": "PUB_KEY_ID_0115092527912025021200388000001392", + "hw_at_url": "https://oauth-login.cloud.huawei.com/oauth2/v3/token", + "hw_order_host": "orders-drcn.iap.cloud.huawei.com.cn|orders-dra.iap.cloud.huawei.asia|orders-dre.iap.cloud.huawei.eu" +} \ No newline at end of file diff --git a/bin/payserver/res/hwconfig@hwconfig.json b/bin/payserver/res/hwconfig@hwconfig.json new file mode 100644 index 0000000..2deff51 --- /dev/null +++ b/bin/payserver/res/hwconfig@hwconfig.json @@ -0,0 +1,9 @@ +[ + { + "gameid": "2004", + "clientid": "114061151", + "clientsecret": "bb6ebce4982201802507ac20affdaa084e3b27d30ce4cc85c9e3423349489333", + "notifyurl": "https://game2004api-test.kingsome.cn/6001/webapp/index.php", + "publickey": "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA9wju3RCKPKMG9DXmHiOFJuyjdW8/senrHH/VcFhyWZawpsozF1MeGY83pIE/pqf/lRaQ9+BoN/MJdLwVUJBtjVN6ndAjFejWchGd6PvG+C1MCSdRrJH+V5CT97BS6K3tMHuSlt+jE8iAwFhcc9t63IqfDXAJLBlMLCkp9b2RHX7rTbzTdIBsYFQYMmAAi1LB5mLIk+TECTd7rHMKVztw45X+IO6tkXVgARnI0QCdx3eeRc7yfGdiDg4GGwqBHdP9jYty9bhCw4cFhFhWeG2+E03XsTWQ0t27xHIPf1fET9oawm0GttxFScZsgOGWHpHlBnG0oS9uZ1JkAg1lxp7t3Wn7nF04Qe/jnpoCYwXw++poSDtqyfN3M4eGJemkjwhDBK/cO3/fohQW/jfcmyHMjo5uzd12RETn3iChONqT0l3t5O0RWqP/Cr/1QQY50c4rGGb+gLbD2EWUHP61Gu+QXG4knClg3tcOy8l80imVo7ZoXH8KUdXJJXvphio5EtkvAgMBAAE=" + } +] \ No newline at end of file diff --git a/server/payserver/api/v1/ingame/ingame.go b/server/payserver/api/v1/ingame/ingame.go index eedbf93..f158911 100644 --- a/server/payserver/api/v1/ingame/ingame.go +++ b/server/payserver/api/v1/ingame/ingame.go @@ -3,6 +3,7 @@ package ingame import ( "encoding/json" "f5" + "fmt" "main/constant" "main/model" "main/service" @@ -332,7 +333,7 @@ func (iga *InGameApi) OrderInfo(c *gin.Context) { } if errcode == constant.WX_ERRCODE_OK { - count, err := service.Wxpay.GetGoodsCount(gameid, int64(orderModel.ItemId)) + count, err := service.GetGoodsCount(gameid, int64(orderModel.ItemId)) if err != nil { return } @@ -531,3 +532,61 @@ func (iga *InGameApi) isNopayChannel(accountId string) (ret bool) { return strs[0] == "6000" } + +func (iga *InGameApi) HwPayDone(c *gin.Context) { + accountId := c.Query("account_id") + goodsid := c.Query("goods_id") + sessionid := c.Query("session_id") + orderid := c.Query("order_id") + token := c.Query("token") + sign := c.Query("sign") + if !strings.EqualFold(q5.Md5Str(accountId+goodsid+constant.GLOBAL_SALT+sessionid+orderid+token), strings.ToLower(sign)) { + f5.RspErr(c, 401, "sign err") + return + } + + orderModel := new(model.InAppOrder) + if err, found := orderModel.Find(accountId, orderid); err != nil { + f5.RspErr(c, 500, "server internal error") + return + } else if !found { + f5.RspErr(c, 1, "order not found") + return + } + + if orderModel.AccountId != accountId || + orderModel.ItemId != q5.SafeToInt32(goodsid) { + f5.RspErr(c, 1, "order not found 2") + return + } + + rspObj := struct { + ErrorCode int32 `json:"errcode"` + ErrMsg string `json:"errmsg"` + OrderId string `json:"order_id"` + State int32 `json:"state"` // 0 未支付;1 已支付;2 已发货 + }{ + OrderId: orderid, + } + + oldstate := orderModel.Status + updatefields := []string{"status"} + if orderModel.Status > 0 { + rspObj.State = orderModel.Status + if orderModel.Status == 1 { + orderModel.Status = 2 + } + } else { + productid := fmt.Sprintf("hw%s", goodsid) // hw productid="hw"+shopid + price := int64(0) + orderModel.Status, orderModel.SpOrderId, price = service.Hwpay.QueryOrder(int64(orderModel.GameId), token, productid, &orderid) + orderModel.Price = int32(price) + updatefields = append(updatefields, []string{"sp_orderid", "price"}...) + } + + if oldstate != orderModel.Status { + orderModel.UpdateFields(updatefields) + } + + c.JSON(0, rspObj) +} diff --git a/server/payserver/api/v1/mainservice/mainservice.go b/server/payserver/api/v1/mainservice/mainservice.go index 8250646..7037fc6 100644 --- a/server/payserver/api/v1/mainservice/mainservice.go +++ b/server/payserver/api/v1/mainservice/mainservice.go @@ -21,6 +21,10 @@ import ( type MainServiceApi struct { } +func (this *MainServiceApi) Self(c *gin.Context) { + c.String(200, c.Param("gameid")) +} + func (this *MainServiceApi) RefreshToken(c *gin.Context) { reqtime := q5.SafeToInt64(c.Query("time")) reqsign := c.Query("sign") @@ -42,7 +46,13 @@ func (this *MainServiceApi) RefreshToken(c *gin.Context) { ErrMsg string `json:"errmsg"` }{} - service.Wxpay.GetAccessTokenList(&rspObj.Data) + reftype := q5.SafeToInt64(c.Query("type")) + switch reftype { + case 0: + service.Wxpay.GetAccessTokenList(&rspObj.Data) + case 1: + service.Hwpay.GetAccessTokenList(&rspObj.Data) + } c.JSON(200, rspObj) } @@ -203,7 +213,7 @@ func (this *MainServiceApi) WxNotifyPurchase(c *gin.Context) { return } - if orderModel.Status > 1 { + if orderModel.Status > 0 { rspObj.ErrorCode = 0 rspObj.ErrMsg = "Success" c.JSON(200, rspObj) @@ -395,7 +405,7 @@ func (this *MainServiceApi) WxMsgNotify(c *gin.Context) { } thumburl := service.Wxpay.GenThumburl(int64(orderModel.GameId)) - itemname, _ := service.Wxpay.GetGoodsName(int64(orderModel.GameId), int64(orderModel.ItemId)) + itemname, _ := service.GetGoodsName(int64(orderModel.GameId), int64(orderModel.ItemId)) // 发给客服系统的clicklink clickurl := fmt.Sprintf("https://%s/wx/clicknotify?orderid=%s", cfg.GetWxPayNotifyHost(), orderid) @@ -703,7 +713,7 @@ func (this *MainServiceApi) WxPayNotify(c *gin.Context) { return } - if orderModel.Status > 1 { + if orderModel.Status > 0 { c.String(200, "") return } @@ -729,7 +739,7 @@ func (this *MainServiceApi) WxPayNotify(c *gin.Context) { f5.GetSysLog().Debug("notify url:%d, %s", gameid, notifyurl) fields := []string{"status", "sp_orderid"} - orderModel.Status = 1 + orderModel.Status = 1 orderModel.SpOrderId = resObj.TransId if len(notifyurl) > 0 { goodsidstr := q5.SafeToString(orderModel.ItemId) @@ -774,7 +784,136 @@ func (this *MainServiceApi) WxPayNotify(c *gin.Context) { } }) } else { - count, _ := service.Wxpay.GetGoodsCount(gameid, int64(orderModel.ItemId)) + count, _ := service.GetGoodsCount(gameid, int64(orderModel.ItemId)) + orderModel.SpAmount = int32(count) + fields = append(fields, "sp_amount") + } + orderModel.UpdateFields(fields) + + c.String(200, "") +} + +func (this *MainServiceApi) HwPayNotify(c *gin.Context) { + gameid := q5.SafeToInt64(c.Param("gameid")) + if gameid == 0 { + c.String(501, "") + return + } + + cfg := mt.Table.Hwconfig.GetById(gameid) + if cfg == nil { + f5.GetSysLog().Warning("error gameid cfg :%d", gameid) + return + } + + rawdata, err := c.GetRawData() + if err != nil { + c.String(501, "") + return + } + + f5.GetSysLog().Debug("hw pay notify post data:%s", rawdata) + rsp := service.HwpayNotifyObj{} + if json.Unmarshal(rawdata, &rsp) != nil { + c.String(501, "") + return + } + + if rsp.AppId != cfg.GetClientid() || + rsp.ET != "ORDER" { + c.String(200, "") + return + } + + orderrspobj := service.HwpayOrderNotifyObj{} + if json.Unmarshal(rawdata, &orderrspobj) != nil { + c.String(501, "") + return + } + + if orderrspobj.Type != 1 { + c.String(200, "") + return + } + + orderid := "" + state, sporderid, orderprice := service.Hwpay.QueryOrder(gameid, orderrspobj.PT, orderrspobj.PId, &orderid) + if state != 1 { + c.String(200, "") + return + } + + orderModel := new(model.InAppOrder) + if err, found := orderModel.FindByOrderId(orderid); err != nil || !found { + c.String(200, "") + return + } + + if orderModel.Status > 0 { + c.String(200, "") + return + } + + productid := fmt.Sprintf("hw%d", orderModel.ItemId) + if productid != orderrspobj.PId { + c.String(200, "") + return + } + + if !service.Hwpay.ConfirmOrder(gameid, orderrspobj.PT, orderrspobj.PId) { + c.String(501, "") + return + } + + notifyurl := cfg.GetNotifyurl() + f5.GetSysLog().Debug("hw notify game url:%d, %s", gameid, notifyurl) + fields := []string{"status", "sp_orderid"} + orderModel.SpOrderId = sporderid + orderModel.Status = 1 + if len(notifyurl) > 0 { + goodsidstr := q5.SafeToString(orderModel.ItemId) + totalamountstr := q5.SafeToString(orderprice) + + nowtimestr := q5.SafeToString(f5.GetApp().GetRealSeconds()) + originstr := "account_id=" + orderModel.AccountId + originstr += "&goodsid=" + goodsidstr + originstr += "&orderid=" + orderModel.OrderId + originstr += "&amount=" + totalamountstr + originstr += ":" + nowtimestr + constant.NOFITY_GAMESERVER_SALT + params := map[string]string{ + "c": "Recharge", + "a": "purchaseNotify", + "account_id": orderModel.AccountId, + "orderid": orderModel.OrderId, + "timestamp": nowtimestr, + "goodsid": goodsidstr, + "amount": totalamountstr, + "sign": q5.Md5Str(originstr), + } + f5.GetHttpCliMgr().SendGoStyleRequest( + notifyurl, + params, + func(hcr f5.HttpCliResponse) { + if hcr.GetErr() != nil { + return + } + + gamerspObj := struct { + ErrCode int64 `json:"errcode"` + ErrMsg string `json:"errmsg"` + }{} + + f5.GetSysLog().Debug("get game rsp:%s", hcr.GetRawData()) + if json.Unmarshal([]byte(hcr.GetRawData()), &gamerspObj) != nil { + return + } + + if gamerspObj.ErrCode == 0 { + orderModel.Status = 2 + } + }) + } else { + count, _ := service.GetGoodsCount(gameid, int64(orderModel.ItemId)) orderModel.SpAmount = int32(count) fields = append(fields, "sp_amount") } diff --git a/server/payserver/mt/Config.go b/server/payserver/mt/Config.go index d6a554f..72f4eee 100644 --- a/server/payserver/mt/Config.go +++ b/server/payserver/mt/Config.go @@ -39,6 +39,14 @@ func (ct *ConfigTable) GetWxMerchantPublicKeyId() string { return ct.selfConf.GetWxMerchantPublicKeyId() } +func (ct *ConfigTable) GetHwAtUrl() string { + return ct.selfConf.GetHwAtUrl() +} + +func (ct *ConfigTable) GetHwOrderHosts() []string { + return strings.Split(ct.selfConf.GetHwOrderHost(), "|") +} + func (ct *ConfigTable) PostInit1() { ct.selfConf = ct.GetById(int64(0)) if ct.selfConf == nil { diff --git a/server/payserver/mt/Hwconfig.go b/server/payserver/mt/Hwconfig.go new file mode 100644 index 0000000..7e02c62 --- /dev/null +++ b/server/payserver/mt/Hwconfig.go @@ -0,0 +1,14 @@ +package mt + +import ( + "f5" + "main/mtb" +) + +type Hwconfig struct { + mtb.Hwconfig +} + +type HwconfigTable struct { + f5.IdMetaTable[Hwconfig] +} diff --git a/server/payserver/mt/export.go b/server/payserver/mt/export.go index 09e699b..57366d7 100644 --- a/server/payserver/mt/export.go +++ b/server/payserver/mt/export.go @@ -13,6 +13,7 @@ type table struct { ConfDb *ConfDbTable Wxconfig *WxconfigTable LoginRedis *LoginRedisTable + Hwconfig *HwconfigTable } var Table = f5.New(func(this *table) { @@ -55,4 +56,9 @@ var Table = f5.New(func(this *table) { this.FileName = "../config/login.redis.json" this.PrimKey = "" }) + + this.Hwconfig = f5.New(func(this *HwconfigTable) { + this.FileName = "../res/hwconfig@hwconfig.json" + this.PrimKey = "gameid" + }) }) diff --git a/server/payserver/mtb/mtb.auto_gen.go b/server/payserver/mtb/mtb.auto_gen.go index c4f4558..0b6a4f0 100644 --- a/server/payserver/mtb/mtb.auto_gen.go +++ b/server/payserver/mtb/mtb.auto_gen.go @@ -72,6 +72,8 @@ type Config struct { wx_certificate_sn string wx_merchant_api_key string wx_merchant_public_key_id string + hw_at_url string + hw_order_host string _flags1_ uint64 _flags2_ uint64 @@ -148,6 +150,17 @@ type LoginRedis struct { _flags2_ uint64 } +type Hwconfig struct { + gameid int64 + clientid string + clientsecret string + notifyurl string + publickey string + + _flags1_ uint64 + _flags2_ uint64 +} + func (this *PayServerCluster) GetInstanceId() int32 { return this.instance_id } @@ -441,7 +454,23 @@ func (this *Config) GetWxMerchantPublicKeyId() string { } func (this *Config) HasWxMerchantPublicKeyId() bool { - return (this._flags1_ & (uint64(1) << 13)) > 0 + return (this._flags1_ & (uint64(1) << 8)) > 0 +} + +func (this *Config) GetHwAtUrl() string { + return this.hw_at_url +} + +func (this *Config) HasHwAtUrl() bool { + return (this._flags1_ & (uint64(1) << 9)) > 0 +} + +func (this *Config) GetHwOrderHost() string { + return this.hw_order_host +} + +func (this *Config) HasHwOrderHost() bool { + return (this._flags1_ & (uint64(1) << 10)) > 0 } func (this *RechargeCurrency) GetCurrencyName() string { @@ -724,6 +753,46 @@ func (this *LoginRedis) HasMaxIdleConns() bool { return (this._flags1_ & (uint64(1) << 6)) > 0 } +func (this *Hwconfig) GetGameid() int64 { + return this.gameid +} + +func (this *Hwconfig) HasGameid() bool { + return (this._flags1_ & (uint64(1) << 1)) > 0 +} + +func (this *Hwconfig) GetClientid() string { + return this.clientid +} + +func (this *Hwconfig) HasClientid() bool { + return (this._flags1_ & (uint64(1) << 2)) > 0 +} + +func (this *Hwconfig) GetClientsecret() string { + return this.clientsecret +} + +func (this *Hwconfig) HasClientsecret() bool { + return (this._flags1_ & (uint64(1) << 3)) > 0 +} + +func (this *Hwconfig) GetNotifyurl() string { + return this.notifyurl +} + +func (this *Hwconfig) HasNotifyurl() bool { + return (this._flags1_ & (uint64(1) << 4)) > 0 +} + +func (this *Hwconfig) GetPublickey() string { + return this.publickey +} + +func (this *Hwconfig) HasPublickey() bool { + return (this._flags1_ & (uint64(1) << 5)) > 0 +} + func (this *PayServerCluster) LoadFromKv(kv map[string]interface{}) { f5.ReadMetaTableField(&this.instance_id, "instance_id", &this._flags1_, 1, kv) @@ -777,7 +846,9 @@ func (this *Config) LoadFromKv(kv map[string]interface{}) { f5.ReadMetaTableField(&this.wx_merchant_id, "wx_merchant_id", &this._flags1_, 5, kv) f5.ReadMetaTableField(&this.wx_certificate_sn, "wx_certificate_sn", &this._flags1_, 6, kv) f5.ReadMetaTableField(&this.wx_merchant_api_key, "wx_merchant_api_key", &this._flags1_, 7, kv) - f5.ReadMetaTableField(&this.wx_merchant_public_key_id, "wx_merchant_public_key_id", &this._flags1_, 13, kv) + f5.ReadMetaTableField(&this.wx_merchant_public_key_id, "wx_merchant_public_key_id", &this._flags1_, 8, kv) + f5.ReadMetaTableField(&this.hw_at_url, "hw_at_url", &this._flags1_, 9, kv) + f5.ReadMetaTableField(&this.hw_order_host, "hw_order_host", &this._flags1_, 10, kv) } func (this *RechargeCurrency) LoadFromKv(kv map[string]interface{}) { @@ -832,3 +903,11 @@ func (this *LoginRedis) LoadFromKv(kv map[string]interface{}) { f5.ReadMetaTableField(&this.max_open_conns, "max_open_conns", &this._flags1_, 5, kv) f5.ReadMetaTableField(&this.max_idle_conns, "max_idle_conns", &this._flags1_, 6, kv) } + +func (this *Hwconfig) LoadFromKv(kv map[string]interface{}) { + f5.ReadMetaTableField(&this.gameid, "gameid", &this._flags1_, 1, kv) + f5.ReadMetaTableField(&this.clientid, "clientid", &this._flags1_, 2, kv) + f5.ReadMetaTableField(&this.clientsecret, "clientsecret", &this._flags1_, 3, kv) + f5.ReadMetaTableField(&this.notifyurl, "notifyurl", &this._flags1_, 4, kv) + f5.ReadMetaTableField(&this.publickey, "publickey", &this._flags1_, 5, kv) +} diff --git a/server/payserver/proto/mt.proto b/server/payserver/proto/mt.proto index 7d6bd58..66a383f 100644 --- a/server/payserver/proto/mt.proto +++ b/server/payserver/proto/mt.proto @@ -60,7 +60,9 @@ message Config optional string wx_merchant_id = 5; optional string wx_certificate_sn = 6; optional string wx_merchant_api_key = 7; - optional string wx_merchant_public_key_id = 13; + optional string wx_merchant_public_key_id = 8; + optional string hw_at_url = 9; + optional string hw_order_host = 10; } message RechargeCurrency @@ -121,3 +123,12 @@ message LoginRedis optional int32 max_open_conns = 5; optional int32 max_idle_conns = 6; } + +message Hwconfig +{ + optional int64 gameid = 1; + optional string clientid = 2; + optional string clientsecret = 3; + optional string notifyurl = 4; + optional string publickey = 5; +} \ No newline at end of file diff --git a/server/payserver/router/ingame/ingame.go b/server/payserver/router/ingame/ingame.go index 9855d9e..79db53e 100644 --- a/server/payserver/router/ingame/ingame.go +++ b/server/payserver/router/ingame/ingame.go @@ -25,4 +25,6 @@ func (this *IngameRouter) InitRouter() { api.InGameApi.PreOrder) f5.GetApp().GetGinEngine().POST("/api/ingame/querypay", api.InGameApi.QueryPay) + f5.GetApp().GetGinEngine().POST("/api/ingame/hwpaid", + api.InGameApi.HwPayDone) } diff --git a/server/payserver/router/mainservice/mainservice.go b/server/payserver/router/mainservice/mainservice.go index 0f98710..30b4342 100644 --- a/server/payserver/router/mainservice/mainservice.go +++ b/server/payserver/router/mainservice/mainservice.go @@ -9,6 +9,8 @@ type MainServiceRouter struct{} func (this *MainServiceRouter) InitRouter() { api := v1.ApiGroupApp.MainServiceApiGroup + f5.GetApp().GetGinEngine().GET("/self/:gameid", + api.Self) f5.GetApp().GetGinEngine().GET("/api/service/refresh", api.RefreshToken) f5.GetApp().GetGinEngine().GET("/wx/purnotify/:gameid", @@ -23,4 +25,6 @@ func (this *MainServiceRouter) InitRouter() { api.WxClickNotify) f5.GetApp().GetGinEngine().POST("/wx/paynotify/:gameid", api.WxPayNotify) + f5.GetApp().GetGinEngine().POST("/hw/paynotify/:gameid", + api.HwPayNotify) } diff --git a/server/payserver/service/export.go b/server/payserver/service/export.go index 83e65ab..ebd8b05 100644 --- a/server/payserver/service/export.go +++ b/server/payserver/service/export.go @@ -8,6 +8,7 @@ import ( var _serviceMgr = new(serviceMgr) var Wxpay = new(wxpay) var Redis = new(redisMap) +var Hwpay = new(hwpay) func init() { global.RegModule(constant.SERVICE_MGR_MODULE_IDX, _serviceMgr) diff --git a/server/payserver/service/hwpay.go b/server/payserver/service/hwpay.go new file mode 100644 index 0000000..b93bea9 --- /dev/null +++ b/server/payserver/service/hwpay.go @@ -0,0 +1,413 @@ +package service + +import ( + "bytes" + "crypto" + "crypto/rsa" + "crypto/sha256" + "crypto/tls" + "crypto/x509" + "encoding/base64" + "encoding/json" + "errors" + "f5" + "fmt" + "io" + "main/mt" + "net/http" + "q5" + "time" +) + +type hwpay struct { + accessTokens q5.ConcurrentMap[int64, TokenInfo] //[gameid, TokenInfo] + refreshflag bool + client http.Client +} + +type HwVerifyOrderRsp struct { + RspCode string `json:"responseCode"` //"0":OK;其它,错误 + RspMsg string `json:"responseMessage"` + PTokenData string `json:"purchaseTokenData"` //支付信息 + Sig string `json:"dataSignature"` //签名 + SigAlg string `json:"signatureAlgorithm"` //签名算法 +} + +type InappPurchaseDetails struct { + AppId int64 `json:"applicationId"` //应用id + AutoRenew bool `json:"autoRenewing"` //是否自动更新,非订阅型始终为false + OrderId string `json:"orderId"` //订单id + Kind int64 `json:"kind"` //商品类别 + PKGName string `json:"packageName"` //包名 + ProdId string `json:"productId"` //商品id + ProdName string `json:"productName"` //商品名 + PurTime int64 `json:"purchaseTime"` //购买时间 + State int64 `json:"purchaseState"` //订单状态 0 已购买 + PayLoad string `json:"developerPayload"` //保留信息,约定为游戏内orderid + ConsumeState int64 `json:"consumptionState"` //消耗状态 0 未消耗 1 已消耗 + Price int64 `json:"price"` //商品价格 分 +} + +type HwpayNotifyObj struct { + V string `json:"version"` //"0":OK;其它,错误 + ET string `json:"eventType"` //"ORDER" "SUBSCRIPTION" + Time int64 `json:"notifyTime"` //ms + AppId string `json:"applicationId"` //appid +} + +type HwpayOrderNotifyObj struct { + V string `json:"version"` //"0":OK;其它,错误 + Type int64 `json:"notificationType"` //1 支付成功 2 退款成功 + PT string `json:"purchaseToken"` //"ORDER" "SUBSCRIPTION" + PId string `json:"productId"` //hw productid +} + +func (hp *hwpay) init() { + hp.accessTokens = q5.ConcurrentMap[int64, TokenInfo]{} + + mt.Table.Hwconfig.Traverse(func(w *mt.Hwconfig) bool { + hp.accessTokens.Store(w.GetGameid(), TokenInfo{}) + return true + }) + + hp.refreshflag = true + go hp.checkAccessToken() + + hp.client = http.Client{ + Timeout: time.Second * 5, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + CipherSuites: []uint16{ + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_AES_256_GCM_SHA384, + tls.TLS_CHACHA20_POLY1305_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + }, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS13, + }, + }, + } +} + +func (hp *hwpay) unInit() { +} + +func (hp *hwpay) freshAccessToken(gameid int64) (token string) { + cfg := mt.Table.Hwconfig.GetById(gameid) + params := map[string]string{ + "grant_type": "client_credentials", + "client_id": cfg.GetClientid(), + "client_secret": cfg.GetClientsecret(), + } + url := mt.Table.Config.GetHwAtUrl() + 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:"error"` + ErrMsg string `json:"error_description"` + }{} + + f5.GetSysLog().Debug("get hw access token rsp:%s", hcr.GetRawData()) + if json.Unmarshal([]byte(hcr.GetRawData()), &rspObj) != nil { + return + } + + if rspObj.ErrCode == 0 { + tokenitem, _ := hp.accessTokens.Load(gameid) + tokenitem.GameId = gameid + tokenitem.Token = rspObj.Token + tokenitem.Expire = rspObj.Expire + f5.GetApp().GetRealSeconds() + hp.accessTokens.Store(gameid, *tokenitem) + } + + }) + + return token +} + +func (hp *hwpay) getAccessToken(gameid int64) (token string) { + cfg, exist := hp.accessTokens.Load(gameid) + nowTime := f5.GetApp().GetRealSeconds() + if exist { + if cfg.Expire > nowTime { + if cfg.Expire < nowTime+3 && !hp.refreshflag { + hp.refreshflag = true + } + return cfg.Token + } + + if !hp.refreshflag { + hp.refreshflag = true + } + return "" + } + return "" +} + +// return 0 unpaid, 1 paid, 2 consumed +func (hp *hwpay) QueryOrder(gameid int64, purchaseToken string, product_id string, orderid *string) (state int32, sporderid string, orderprice int64) { + bodyMap := map[string]string{"purchaseToken": purchaseToken, "productId": product_id} + + hosts := mt.Table.Config.GetHwOrderHosts() + queryuri := "/applications/purchases/tokens/verify" + cfg := mt.Table.Hwconfig.GetById(gameid) + if cfg == nil { + return + } + + for _, hwhost := range hosts { + url := "https://" + hwhost + queryuri + bodybytes, err := hp.sendRequest(gameid, url, bodyMap) + if err != nil { + continue + } + + rsp := HwVerifyOrderRsp{} + f5.GetSysLog().Debug("get hw verify order rsp:%s", bodybytes) + if json.Unmarshal(bodybytes, &rsp) != nil { + return + } + + if rsp.RspCode != "0" { + return + } + + if hp.verifyRsaSign(rsp.PTokenData, rsp.Sig, cfg.GetPublickey()) != nil { + return + } + + purdetails := InappPurchaseDetails{} + if json.Unmarshal([]byte(rsp.PTokenData), &purdetails) != nil { + return + } + + if q5.SafeToString(purdetails.AppId) != cfg.GetClientid() { + return + } + + if orderid == nil || (len(*orderid) > 0 && purdetails.PayLoad != *orderid) { + return + } + + if purdetails.ProdId != product_id { + return + } + + if purdetails.State != 0 { + return + } + + if purdetails.ConsumeState != 0 { + state = 2 + return + } + + sporderid = purdetails.OrderId + orderprice = purdetails.Price + if len(*orderid) > 0 { + // confirm order + url = "https://" + hwhost + "/applications/v2/purchases/confirm" + confirmbytes, err := hp.sendRequest(gameid, url, bodyMap) + if err != nil { + return + } + + f5.GetSysLog().Debug("get hw confirm order rsp:%s", confirmbytes) + confirmrsp := struct { + RspCode string `json:"responseCode"` //"0":OK;其它,错误 + RspMsg string `json:"responseMessage"` + }{} + if json.Unmarshal(confirmbytes, &confirmrsp) != nil { + return + } + + if confirmrsp.RspCode == "0" { + state = 1 + } + } else { + *orderid = purdetails.PayLoad + state = 1 + } + + return + } + + return +} + +// call queryorder first and its return is 1 +func (hp *hwpay) ConfirmOrder(gameid int64, purchaseToken string, product_id string) (ret bool) { + bodyMap := map[string]string{"purchaseToken": purchaseToken, "productId": product_id} + + hosts := mt.Table.Config.GetHwOrderHosts() + queryuri := "/applications/v2/purchases/confirm" + cfg := mt.Table.Hwconfig.GetById(gameid) + if cfg == nil { + return + } + + for _, hwhost := range hosts { + url := "https://" + hwhost + queryuri + confirmbytes, err := hp.sendRequest(gameid, url, bodyMap) + if err != nil { + continue + } + + f5.GetSysLog().Debug("get hw confirm order rsp:%s", confirmbytes) + confirmrsp := struct { + RspCode string `json:"responseCode"` //"0":OK;其它,错误 + RspMsg string `json:"responseMessage"` + }{} + if json.Unmarshal(confirmbytes, &confirmrsp) != nil { + return + } + + return confirmrsp.RspCode == "0" + } + + return +} + +func (hp *hwpay) GetAccessTokenList(data *[]TokenInfo) { + hp.accessTokens.Range(func(key int64, value TokenInfo) bool { + *data = append(*data, value) + return true + }) +} + +func (hp *hwpay) checkAccessToken() { + for { + time.Sleep(time.Second) + + // if f5.IsOnlineEnv() { + gamesGoods.Range(func(gameid int64, value map[int64]GoodsInfo) bool { + cfg, ok := hp.accessTokens.Load(gameid) + if !ok { + return true + } + if hp.refreshflag || (cfg != nil && cfg.Expire <= f5.GetApp().GetRealSeconds()) { + hp.freshAccessToken(gameid) + } + return true + }) + // } else { + // if !hp.refreshflag { + // hp.accessTokens.Range(func(key int64, value TokenInfo) bool { + // if value.Expire <= f5.GetApp().GetRealSeconds() { + // hp.refreshflag = true + // return false + // } + // return true + // }) + + // if !hp.refreshflag { + // continue + // } + // } + // url := "https://payservice.kingsome.cn/api/service/refresh" + + // nowtimestr := q5.SafeToString(f5.GetApp().GetRealSeconds()) + // params := map[string]string{ + // "type": "1", + // "time": nowtimestr, + // "sign": Wxpay.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("hp 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 { + // hp.accessTokens.Store(dataitem.GameId, dataitem) + // } + // } + // }) + // } + + hp.refreshflag = false + } +} + +func (hp *hwpay) buildAuthorization(gameid int64) (string, error) { + token := hp.getAccessToken(gameid) + if token == "" { + return "", errors.New("") + } + oriString := fmt.Sprintf("APPAT:%s", token) + var authString = base64.StdEncoding.EncodeToString([]byte(oriString)) + var authHeaderString = fmt.Sprintf("Basic %s", authString) + return authHeaderString, nil +} + +func (hp *hwpay) sendRequest(gameid int64, url string, bodyMap map[string]string) ([]byte, error) { + authHeader, err := hp.buildAuthorization(gameid) + if err != nil { + return nil, err + } + + bodyString, err := json.Marshal(bodyMap) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", url, bytes.NewReader(bodyString)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json; charset=UTF-8") + req.Header.Set("Authorization", authHeader) + response, err := hp.client.Do(req) + if err != nil { + return nil, err + } + defer response.Body.Close() + bodyBytes, err := io.ReadAll(response.Body) + if err != nil { + return nil, err + } + return bodyBytes, nil +} + +func (hp *hwpay) verifyRsaSign(content string, sign string, publicKey string) error { + publicKeyByte, err := base64.StdEncoding.DecodeString(publicKey) + if err != nil { + return err + } + pub, err := x509.ParsePKIXPublicKey(publicKeyByte) + if err != nil { + return err + } + hashed := sha256.Sum256([]byte(content)) + signature, err := base64.StdEncoding.DecodeString(sign) + if err != nil { + return err + } + return rsa.VerifyPKCS1v15(pub.(*rsa.PublicKey), crypto.SHA256, hashed[:], signature) +} diff --git a/server/payserver/service/servicemgr.go b/server/payserver/service/servicemgr.go index abdda4f..fff9e61 100644 --- a/server/payserver/service/servicemgr.go +++ b/server/payserver/service/servicemgr.go @@ -1,14 +1,78 @@ package service -type serviceMgr struct { +import ( + "encoding/json" + "errors" + "f5" + "main/mt" + "q5" +) + +var gamesGoods q5.ConcurrentMap[int64, map[int64]GoodsInfo] //[gameid, [goodsid]goodsinfo] +type serviceMgr struct { } func (this *serviceMgr) Init() { + gamesGoods = q5.ConcurrentMap[int64, map[int64]GoodsInfo]{} + Wxpay.init() Redis.init() + Hwpay.init() + + 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, + } + } + gamesGoods.Store(w.GetGameid(), gamegoods) + } + return true + }) } func (this *serviceMgr) UnInit() { Wxpay.unInit() Redis.unInit() + Hwpay.unInit() } + +func GetGoodsCount(gameid int64, goodsid int64) (count int64, err error) { + goods, ok := 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 GetGoodsName(gameid int64, goodsid int64) (name string, err error) { + goods, ok := 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 +} \ No newline at end of file diff --git a/server/payserver/service/wxpay.go b/server/payserver/service/wxpay.go index a84f668..7301390 100644 --- a/server/payserver/service/wxpay.go +++ b/server/payserver/service/wxpay.go @@ -4,7 +4,6 @@ import ( "context" "crypto/rsa" "encoding/json" - "errors" "f5" "main/constant" "main/mt" @@ -30,10 +29,9 @@ type mediaidInfo struct { 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] + 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 @@ -120,7 +118,6 @@ type GoodsInfo struct { } func (wp *wxpay) init() { - wp.gamesGoods = q5.ConcurrentMap[int64, map[int64]GoodsInfo]{} wp.accessTokens = q5.ConcurrentMap[int64, TokenInfo]{} wp.expireInfo = q5.ConcurrentMap[string, *expireSession]{} @@ -142,7 +139,6 @@ func (wp *wxpay) init() { Price: q5.SafeToInt64(data["price"]) * 100, } } - wp.gamesGoods.Store(w.GetGameid(), gamegoods) wp.accessTokens.Store(w.GetGameid(), TokenInfo{}) } return true @@ -229,34 +225,6 @@ func (wp *wxpay) getAccessToken(gameid int64) (token string) { 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 { @@ -543,7 +511,7 @@ func (wp *wxpay) checkAccessToken() { time.Sleep(time.Second) if f5.IsOnlineEnv() { - wp.gamesGoods.Range(func(gameid int64, value map[int64]GoodsInfo) bool { + 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) diff --git a/server/payserver/service/wxpay_prepare.go b/server/payserver/service/wxpay_prepare.go index b739708..b47f5b6 100644 --- a/server/payserver/service/wxpay_prepare.go +++ b/server/payserver/service/wxpay_prepare.go @@ -187,7 +187,7 @@ func (wp *wxpay) GetPrepayInfoStr(openid string, gameid int64, userip string, or svc := jsapi.JsapiApiService{Client: wp.client} // 得到prepay_id,以及调起支付所需的参数和签名 cfg := mt.Table.Wxconfig.GetById(gameid) - goods, ok := wp.gamesGoods.Load(gameid) + goods, ok := gamesGoods.Load(gameid) if !ok { return }