Compare commits

...

12 Commits

Author SHA1 Message Date
ayflying
ccc50a7dd0 荣耀支付增加验单 2025-07-29 10:51:42 +08:00
ayflying
d1a7ba8119 增加荣耀支付 2025-07-29 10:15:57 +08:00
ayflying
7c19a66cb5 修改vivo支付接口 2025-07-24 18:33:54 +08:00
ayflying
90b72129a5 增加vivo支付与登录接口 2025-07-23 17:37:17 +08:00
ayflying
58bea0c09a 晚上oppo支付回调与登录回调 2025-07-23 15:43:15 +08:00
ayflying
50fe34e1c1 增加s3文件重命名方法 2025-07-22 10:38:08 +08:00
ayflying
25c00d5072 gameact持久化保存区分跟新与添加 2025-07-21 19:20:46 +08:00
ayflying
ee32c8b83d 更新支持配置代理地址配置 2025-07-11 14:07:56 +08:00
ayflying
12a193fdee 增加oppo支付回调库 2025-07-09 11:01:38 +08:00
ayflying
ca55880beb 小米支付的回调结构体放入库 2025-07-09 10:58:27 +08:00
ayflying
ee9e399a81 分离小米支付与taptap支付到库里面 2025-07-08 16:30:44 +08:00
ayflying
ed9b0dd248 日志上传批量上传接口修改 2025-07-02 14:52:57 +08:00
29 changed files with 968 additions and 121 deletions

View File

@@ -124,9 +124,9 @@ var (
FileUrl: url[v.S3], FileUrl: url[v.S3],
}) })
if err != nil { if err != nil {
Proxy := g.Cfg().MustGet(ctx, "update_proxy", "http://192.168.50.170:10808").String()
g.Log().Debugf(ctx, "切换代理进行上传:err=%v", err) g.Log().Debugf(ctx, "切换代理进行上传:err=%v", err)
get, err = client.Proxy("http://192.168.50.114:10808"). get, err = client.Proxy(Proxy).Post(ctx, address+"/callback/update", &UpdateReq{
Post(ctx, address+"/callback/update", &UpdateReq{
FileUrl: url[v.S3], FileUrl: url[v.S3],
}) })
} }

3
go.mod
View File

@@ -47,6 +47,8 @@ require (
github.com/gorilla/websocket v1.5.3 // indirect github.com/gorilla/websocket v1.5.3 // indirect
github.com/grokify/html-strip-tags-go v0.1.0 // indirect github.com/grokify/html-strip-tags-go v0.1.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/haxqer/gofunc v0.0.0-20210609183449-586b0cce5fe4 // indirect
github.com/haxqer/xthird v0.0.0-20210703071732-ff4f4dbb6e5d // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/magiconair/properties v1.8.9 // indirect github.com/magiconair/properties v1.8.9 // indirect
@@ -60,6 +62,7 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pelletier/go-toml v1.9.3 // indirect github.com/pelletier/go-toml v1.9.3 // indirect
github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 // indirect
github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect

7
go.sum
View File

@@ -225,6 +225,10 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/haxqer/gofunc v0.0.0-20210609183449-586b0cce5fe4 h1:QQOImHnvEkzqEbozZup+76WLv+W0QhO84GTLHppMY1M=
github.com/haxqer/gofunc v0.0.0-20210609183449-586b0cce5fe4/go.mod h1:XQaEUze5QCq3D9oenfHItzPQUD+NGg+0TDt1kJhJAAc=
github.com/haxqer/xthird v0.0.0-20210703071732-ff4f4dbb6e5d h1:e2qgvCNjMrp9fNtUQr3gQnK15B0C2CIUjPWMc7jZnbo=
github.com/haxqer/xthird v0.0.0-20210703071732-ff4f4dbb6e5d/go.mod h1:9avb74vS4djGUwtueA6V01grbpskLGisSy0jxFZIBY8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -296,6 +300,8 @@ github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZ
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 h1:xoIK0ctDddBMnc74udxJYBqlo9Ylnsp1waqjLsnef20=
github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -457,6 +463,7 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=

View File

@@ -134,7 +134,8 @@ func (s *sGameAct) Save(ctx context.Context, actId int) (err error) {
//循环获取缓存数据 //循环获取缓存数据
err = tools.Redis.RedisScanV2(cacheKey, func(keys []string) (err error) { err = tools.Redis.RedisScanV2(cacheKey, func(keys []string) (err error) {
var add []interface{} var add = make([]*entity.GameAct, 0)
var update = make([]*entity.GameAct, 0)
var delKey []string var delKey []string
for _, cacheKey = range keys { for _, cacheKey = range keys {
result := strings.Split(cacheKey, ":") result := strings.Split(cacheKey, ":")
@@ -176,25 +177,51 @@ func (s *sGameAct) Save(ctx context.Context, actId int) (err error) {
} }
actionData := cacheGet.String() actionData := cacheGet.String()
if data == nil { if data == nil {
//data = add = append(add, &entity.GameAct{
add = append(add, &do.GameAct{
ActId: actId, ActId: actId,
Uid: uid, Uid: uid,
Action: actionData, Action: actionData,
}) })
} else { } else {
//覆盖数据 //覆盖数据
data.ActId = actId
data.Uid = uid
data.Action = actionData data.Action = actionData
add = append(add, data) update = append(update, data)
} }
//最后删除key //最后删除key
delKey = append(delKey, cacheKey) delKey = append(delKey, cacheKey)
} }
//批量写入数据库 //批量写入数据库
if len(add) > 0 { if len(delKey) > 0 {
dbRes, err2 := g.Model(Name).Batch(30).Data(add).Save() for _, v := range update {
add = make([]interface{}, 0) v.UpdatedAt = gtime.Now()
_, err2 := g.Model(Name).Where(do.GameAct{
Uid: v.Uid,
ActId: v.ActId,
UpdatedAt: v.UpdatedAt,
}).Data(v).Update()
if err2 != nil {
g.Log().Error(ctx, err2)
return
}
////获取多少个数据,删除不是当前修改的数据
//count, _ := g.Model(Name).Where(do.GameAct{
// Uid: v.Uid,
// ActId: v.ActId,
//}).Count()
//if count > 1 {
// g.Model(Name).Where(do.GameAct{
// Uid: v.Uid,
// ActId: v.ActId,
// }).WhereNot("updated_at", v.UpdatedAt).Delete()
//}
}
//dbRes, err2 := g.Model(Name).Batch(50).Data(add).Update()
update = make([]*entity.GameAct, 0)
dbRes, err2 := g.Model(Name).Batch(50).Data(add).Save()
add = make([]*entity.GameAct, 0)
if err2 != nil { if err2 != nil {
g.Log().Error(ctx, err2) g.Log().Error(ctx, err2)
return return

30
package/pay/func.go Normal file
View File

@@ -0,0 +1,30 @@
package pay
import "strings"
func FormatPublicKey(publicKey string) (pKey string) {
var buffer strings.Builder
buffer.WriteString("-----BEGIN PUBLIC KEY-----\n")
rawLen := 64
keyLen := len(publicKey)
raws := keyLen / rawLen
temp := keyLen % rawLen
if temp > 0 {
raws++
}
start := 0
end := start + rawLen
for i := 0; i < raws; i++ {
if i == raws-1 {
buffer.WriteString(publicKey[start:])
} else {
buffer.WriteString(publicKey[start:end])
}
buffer.WriteByte('\n')
start += rawLen
end = start + rawLen
}
buffer.WriteString("-----END PUBLIC KEY-----\n")
pKey = buffer.String()
return
}

View File

@@ -0,0 +1,5 @@
package honor
const (
Host = "https://iap-api.cloud.honor.com"
)

View File

@@ -0,0 +1,65 @@
package honor
import (
"crypto"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"github.com/ayflying/utility_go/package/pay"
)
type Pay struct {
PubKey string `json:"pubKey"`
AppId string `json:"appId"`
}
func New(pay *Pay) *Pay {
return &Pay{
AppId: pay.AppId,
PubKey: pay.PubKey,
}
}
// VerifyRSASignature 验证RSA数字签名
// data: 原始数据字节
// sign: 签名的Base64编码字符串
// pubKey: PEM格式的公钥字符串
// 返回验证结果和可能的错误
func (p *Pay) VerifyRSASignature(data []byte, sign string) (bool, error) {
// 解码Base64格式的签名
signBytes, err := base64.StdEncoding.DecodeString(sign)
if err != nil {
return false, errors.New("签名解码失败: " + err.Error())
}
pubkey := pay.FormatPublicKey(p.PubKey)
// 解析PEM格式的公钥
block, _ := pem.Decode([]byte(pubkey))
if block == nil {
return false, errors.New("无效的PEM格式公钥")
}
// 解析公钥
publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return false, errors.New("公钥解析失败: " + err.Error())
}
// 类型断言为公钥
rsaPubKey, ok := publicKey.(*rsa.PublicKey)
if !ok {
return false, errors.New("不是有效的RSA公钥")
}
// 计算数据的SHA-256哈希
hasher := sha256.New()
hasher.Write(data)
hash := hasher.Sum(nil)
// 验证签名
err = rsa.VerifyPKCS1v15(rsaPubKey, crypto.SHA256, hash, signBytes)
return err == nil, err
}

View File

@@ -0,0 +1,45 @@
package honor
type PayCallbackHeader struct {
Charset string `json:"charset" dc:"字符集当前只支持utf-8。"`
SignType string `json:"signType" dc:"签名算法类型, 当前只支持RSA"`
Sign string `json:"sign" dc:"notificationMessage的签名已废弃请用signature。"`
Signature string `json:"signature" dc:"对data的签名。"`
AppId string `json:"appId" dc:"应用ID"`
}
type PayCallback struct {
Env string `json:"env" dc:"发送通知的环境sandbox为沙盒测试环境非sandbox为正式环境"`
EventType string `json:"eventType" dc:"事件类型,如付款成功、退款失败等"`
EventCode int `json:"eventCode" dc:"事件类型对应的code值"`
Version string `json:"version" dc:"iap版本"`
EventTime string `json:"eventTime" dc:"通知时间"`
Data PayCallbackData `json:"data" dc:"通知内容notificationMessage的json字符串"`
}
type PayCallbackData struct {
AppId string `json:"appId" dc:"应用ID"`
OrderId string `json:"orderId" dc:"订单ID"`
BizOrderNo string `json:"bizOrderNo,omitempty" dc:"max-length:64#业务订单号"`
ProductType int `json:"productType" dc:"商品类型0消耗型1非消耗型2订阅型"`
ProductId string `json:"productId" dc:"商品ID"`
ProductName string `json:"productName" dc:"商品名称"`
PurchaseTime int64 `json:"purchaseTime" dc:"购买时间UTC时间戳(毫秒)"`
PurchaseState int `json:"purchaseState" dc:"订单状态 0:已购买 1:已退款 2:付款失败 3:退款失败 4:未支付 5:退款中"`
ConsumptionState int `json:"consumptionState" dc:"消耗状态 0:未消耗 1:已消耗"`
PurchaseToken string `json:"purchaseToken" dc:"购买令牌"`
Currency string `json:"currency" dc:"币种"`
Price string `json:"price" dc:"商品价格"`
PayMoney string `json:"payMoney" dc:"实际支付金额"`
DeveloperPayload string `json:"developerPayload,omitempty" dc:"max-length:1024#商户信息"`
OriOrder string `json:"oriOrder" dc:"原订单信息"`
SandboxFlag int `json:"sandboxFlag" dc:"沙盒标识"`
AgreementNo string `json:"agreementNo,omitempty" dc:"订阅合约号"`
ExecuteTime string `json:"executeTime,omitempty" dc:"下次扣费时间(订阅)"`
SecondChargeTime int64 `json:"secondChargeTime,omitempty" dc:"第二次扣费时间(订阅升级)"`
OldProductId string `json:"oldProductId,omitempty" dc:"老商品ID(订阅升级)"`
SubStartTime string `json:"subStartTime,omitempty" dc:"订阅开始时间"`
SubEndTime string `json:"subEndTime,omitempty" dc:"订阅结束时间"`
OriginalPrice string `json:"originalPrice" dc:"原始价格"`
CancelTime string `json:"cancelTime,omitempty" dc:"订阅取消时间"`
}

View File

@@ -0,0 +1,26 @@
package honor
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"net/http"
)
func (p *Pay) Notification(r *http.Request) {
}
// ConsumeProduct 商品消耗
func (p *Pay) ConsumeProduct(purchaseToken string) (err error) {
url := Host + "/iap/server/consumeProduct"
_, err = g.Client().ContentJson().Post(gctx.New(), url, g.Map{
"purchaseToken": purchaseToken,
"developerChallenge": "",
})
if err != nil {
return
}
return
}

97
package/pay/oppo/login.go Normal file
View File

@@ -0,0 +1,97 @@
package oppo
import (
"context"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/grand"
"io"
"net/url"
"strings"
)
const host = "https://iopen.game.heytapmobi.com"
// oppo参数类型
type LoginType struct {
Token string `json:"token"`
Ssoid string `json:"ssoid"`
Channel int `json:"channel"`
AdId string `json:"adId"`
}
//登录回复
type LoginResType struct {
ResultCode string `json:"resultCode" dc:"响应码,成功为 200"`
ResultMsg string `json:"resultMsg" dc:"响应信息"`
LoginToken string `json:"loginToken" dc:"透传的token"`
Ssoid string `json:"ssoid" dc:"透传的ssoid"`
//AppKey string `json:"appKey" dc:"秘钥key,因隐私安全规范,该字段目前已不返回信息"`
UserName string `json:"userName" dc:"用户ssoid绑定的账户昵称"`
//Email string `json:"email" dc:"因隐私安全规范,该字段目前已不返回信息"`
//MobileNumber string `json:"mobileNumber" dc:"因隐私安全规范,该字段目前已不返回信息"`
//CreateTime string `json:"createTime" dc:"因隐私安全规范,该字段目前已不返回信息"`
UserStatus string `json:"userStatus" dc:"用户状态NORMAL 表示正常"`
}
func (p *OppoType) FileIdInfo(ctx context.Context, oauthToken string, ssoid string) (res *LoginResType, err error) {
url := host + "/sdkopen/user/fileIdInfo"
header := p.GetHeader(oauthToken)
getHtml, err := g.Client().Header(header).Get(ctx, url, g.Map{
"token": oauthToken,
"fileId": ssoid,
})
getRes := getHtml.ReadAllString()
gjson.DecodeTo(getRes, &res)
//g.Log().Debugf(ctx, "当前登陆请求的:%v", res)
return
}
func (p *OppoType) GenParam(oauthToken, oauthTimestamp, oauthNonce string) string {
// 注意:拼接的顺序不能有改变,不然会导致联运方验签失败
params := []string{
"oauthConsumerKey=" + url.QueryEscape(p.AppKey),
"oauthToken=" + url.QueryEscape(oauthToken),
"oauthSignatureMethod=" + url.QueryEscape("HMAC-SHA1"),
"oauthTimestamp=" + url.QueryEscape(oauthTimestamp),
"oauthNonce=" + url.QueryEscape(oauthNonce),
"oauthVersion=" + url.QueryEscape("1.0"),
}
return strings.Join(params, "&") + "&"
}
// 生成签名
func (p *OppoType) GenOauthSignature(param string) string {
oauthSignatureKey := p.AppSecret + "&"
mac := hmac.New(sha1.New, []byte(oauthSignatureKey))
io.WriteString(mac, param)
signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
return url.QueryEscape(signature)
}
func (p *OppoType) GetHeader(oauthToken string) (headers map[string]string) {
// 没有做过 urlEncode 的 token由游戏客户端调用 OPPO SDK 直接获取
//oauthToken := "TICKET_Ajnxxxxx"
oauthTimestamp := gtime.Now().TimestampStr()
oauthNonce := grand.S(5)
// 生成请求头参数和签名
param := p.GenParam(oauthToken, oauthTimestamp, oauthNonce)
oauthSignature := p.GenOauthSignature(param)
// 封装请求头
headers = map[string]string{
"param": param,
"oauthSignature": oauthSignature,
}
//fmt.Println("游戏服务端登录鉴权请求头为:", headers)
return
}

13
package/pay/oppo/model.go Normal file
View File

@@ -0,0 +1,13 @@
package oppo
// OPPO支付回调参数结构体
type PayCallback struct {
NotifyId string `json:"notifyId" dc:"回调通知单号以GC开头必填示例:GC20230314657000"`
PartnerOrder string `json:"partnerOrder" dc:"开发者订单号,必填,示例:123456"`
ProductName string `json:"productName" dc:"商品名称,必填,示例:10元宝"`
ProductDesc string `json:"productDesc" dc:"商品描述,必填,示例:10元宝等于1元"`
Price int64 `json:"price" dc:"商品价格,单位为分,需要游戏服务端做验证,必填,示例:100"`
Count int `json:"count" dc:"商品数量一般为1必填示例:1"`
Attach string `json:"attach" dc:"请求支付时上传的附加参数,可能为空,选填"`
Sign string `json:"sign" dc:"OPPO服务端签名需要游戏服务端做验证必填"`
}

41
package/pay/oppo/oppo.go Normal file
View File

@@ -0,0 +1,41 @@
package oppo
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/haxqer/xthird/oppo"
)
// 跟充值平台通信的加密key
//const PUBLIC_KEY = `dfsdfs`
type OppoType struct {
AppId string `json:"app_id"`
AppKey string `json:"app_key"`
AppSecret string `json:"app_secret"`
PublicKey string `json:"public_key"`
}
func New(cfg *OppoType) *OppoType {
return &OppoType{
AppKey: cfg.AppKey,
AppSecret: cfg.AppSecret,
PublicKey: cfg.PublicKey,
}
}
func (p *OppoType) Verify(ctx context.Context) (err error) {
// OPPO公钥. 在官方给的 demo 中. 无需修改,改了就验证不过
oppoPublicKey := "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCmreYIkPwVovKR8rLHWlFVw7YDfm9uQOJKL89Smt6ypXGVdrAKKl0wNYc3/jecAoPi2ylChfa2iRu5gunJyNmpWZzlCNRIau55fxGW0XEu553IiprOZcaw5OuYGlf60ga8QT6qToP0/dpiL/ZbmNUO9kUhosIjEu22uFgR+5cYyQIDAQAB"
//oppoPublicKey := p.PublicKey
// 解析请求参数
bodyMap, err := oppo.ParseNotifyToBodyMap(g.RequestFromCtx(ctx).Request)
if err != nil {
// 解析失败, 处理错误逻辑
return
}
err = oppo.VerifySign(oppoPublicKey, bodyMap)
return
}

View File

@@ -1,60 +1,62 @@
package taptap package taptap
import ( import (
"bytes"
"context"
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/grand"
"io" "io"
"net/http" "net/http"
"sort" "sort"
"strconv"
"strings" "strings"
) )
type pTapTap struct { type pTapTap struct {
Secret string `json:"secret" dc:"秘钥"` Secret string `json:"secret" dc:"秘钥"`
OrderId string `json:"order_id" dc:"订单唯一 ID"` //OrderId string `json:"order_id" dc:"订单唯一 ID"`
ClientId string `json:"client_id" dc:"应用的 Client ID"` ClientId string `json:"client_id" dc:"应用的 Client ID"`
} }
func New(orderId string) *pTapTap { func New(clientId string, secret string) *pTapTap {
return &pTapTap{ return &pTapTap{
Secret: "5AFEWnadBA0NgJK2mxeBLQEde0qyIefxLSc4XKHsx9AwkQRhxzkQ9DixsOkK6gcV", Secret: secret,
ClientId: "mox88lbz43edfukdgk", ClientId: clientId,
OrderId: orderId, //OrderId: orderId,
} }
} }
func (p *pTapTap) Sign(url string, body []byte) (sign string, ts int64, nonce string, err error) { // Sign signs the request.
//nolint:gosec func (p *pTapTap) Sign(req *http.Request, secret string) (string, error) {
secret := p.Secret //获取请求参数
//body := gjson.MustEncode(g.Map{}) //req := g.RequestFromCtx(ctx).Request
//body := []byte(`{"event_type":"charge.succeeded","order":{"order_id":"1790288650833465345","purchase_token":"rT2Et9p0cfzq4fwjrTsGSacq0jQExFDqf5gTy1alp+Y=","client_id":"o6nD4iNavjQj75zPQk","open_id":"4+Axcl2RFgXbt6MZwdh++w==","user_region":"US","goods_open_id":"com.goods.open_id","goods_name":"TestGoodsName","status":"charge.succeeded","amount":"19000000000","currency":"USD","create_time":"1716168000","pay_time":"1716168000","extra":"1111111111111111111"}}`) return Sign(req, secret)
//url := "https://example.com/my-service/v1/my-method"
ts = gtime.Now().Unix()
nonce = grand.S(5)
method := "POST"
header := http.Header{
"Content-Type": {"Content-Type: application/json; charset=utf-8"},
"X-Tap-Ts": {strconv.FormatInt(ts, 10)},
"X-Tap-Nonce": {nonce},
}
ctx := context.Background()
req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewBuffer(body))
req.Header = header
sign, err = Sign(req, secret)
if err != nil {
panic(err)
}
req.Header.Set("X-Tap-Sign", sign)
return
} }
//func (p *pTapTap) SignOld(ctx context.Context, method, url string, token string, data any) (sign string, ts int64, nonce string, err error) {
// //secret := p.Secret
// //ts = gtime.Now().Unix()
// //nonce = grand.S(5)
// //header := http.Header{
// // "Content-Type": {"Content-Type: application/json; charset=utf-8"},
// // "X-Tap-Ts": {strconv.FormatInt(ts, 10)},
// // "X-Tap-Nonce": {nonce},
// //}
// //if method == "POST" {
// // header.Set("Content-Type", "application/json; charset=utf-8")
// //}
// ////ctx := context.Background()
// //request := g.RequestFromCtx(ctx).Request
// //body, _ := json.Marshal(data)
// ////req, err := http.NewRequestWithContext(ctx, method, url, strings.NewReader(string(body)))
// //req.Header = header
// //sign, err = Sign(req, secret)
// //if err != nil {
// // panic(err)
// //}
// //req.Header.Set("X-Tap-Sign", sign)
// //return
//}
// Sign signs the request. // Sign signs the request.
func Sign(req *http.Request, secret string) (string, error) { func Sign(req *http.Request, secret string) (string, error) {
methodPart := req.Method methodPart := req.Method

View File

@@ -1,59 +1,129 @@
package taptap package taptap
import ( import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt" "fmt"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gclient" "github.com/gogf/gf/v2/net/gclient"
"github.com/gogf/gf/v2/os/gctx" "github.com/gogf/gf/v2/os/gctx"
"strconv" "github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/grand"
"io"
"net/http"
) )
type GetPayData struct { type GetPayData struct {
Data struct { Data struct {
Order struct { Order struct {
Order
} `json:"order"` } `json:"order"`
} `json:"data"` } `json:"data"`
Success bool `json:"success"` Success bool `json:"success"`
} }
//查询订单信息 type Order struct {
func (p *pTapTap) Info(orderId string, clientId string, token []byte) (res string, err error) { OrderID string `json:"order_id"` // 订单唯一 ID
url := fmt.Sprintf("https://cloud-payment.tapapis.cn/order/v1/info?client_id=%v&order_id=%v", orderId, clientId) PurchaseToken string `json:"purchase_token"` // 用于订单核销的 token
res, err = p.get(url, token) ClientID string `json:"client_id"` // 应用的 Client ID
return OpenID string `json:"open_id"` // 用户的开放平台 ID
UserRegion string `json:"user_region"` // 用户地区
GoodsOpenID string `json:"goods_open_id"` // 商品唯一 ID
GoodsName string `json:"goods_name"` // 商品名称
Status PaymentStatus `json:"status"` // 订单状态
Amount string `json:"amount"` // 金额(本币金额 x 1,000,000
Currency string `json:"currency"` // 币种
CreateTime string `json:"create_time"` // 创建时间
PayTime string `json:"pay_time"` // 支付时间
Extra string `json:"extra"` // 商户自定义数据,如角色信息等,长度不超过 255 UTF-8 字符
} }
type PaymentStatus string
//验证并核销订单 const (
func (p *pTapTap) Verify(orderId string, clientId string, token []byte) (res string, err error) { ChargePending PaymentStatus = "charge.pending" // 待支付
url := fmt.Sprintf("https://cloud-payment.tapapis.cn/order/v1/verify?client_id=%v", clientId) ChargeSucceeded PaymentStatus = "charge.succeeded" //支付成功
res, err = p.get(url, token) ChargeConfirmed PaymentStatus = "charge.confirmed" //已核销
return ChargeOverdue PaymentStatus = "charge.overdue" //支付超时关闭
} RefundPending PaymentStatus = "refund.pending" //退款中
RefundSucceeded PaymentStatus = "refund.succeeded" //退款成功
RefundFailed PaymentStatus = "refund.failed" //退款失败
RefundRejected PaymentStatus = "refund.rejected" //退款被拒绝
)
func (p *pTapTap) get(url string, token []byte, _data ...any) (res string, err error) { // 查询订单信息
sign, ts, nonce, err := p.Sign(url, token) func (p *pTapTap) Info(ctx context.Context, order string) (getPayData *GetPayData, err error) {
url := fmt.Sprintf("https://cloud-payment.tapapis.cn/order/v1/info?client_id=%v&order_id=%v", p.ClientId, order)
getPayData, err = p.get(ctx, url)
if err != nil { if err != nil {
return return
} }
return
}
// 验证并核销订单
func (p *pTapTap) Verify(ctx context.Context, req any) (getPayData *GetPayData, err error) {
url := fmt.Sprintf("https://cloud-payment.tapapis.cn/order/v1/verify?client_id=%v", p.ClientId)
getPayData, err = p.get(ctx, url, req)
if err != nil {
return
}
return
}
func (p *pTapTap) get(ctx context.Context, url string, _data ...any) (getPayData *GetPayData, err error) {
var _get *gclient.Response var _get *gclient.Response
if len(_data) == 0 {
_get, err = g.Client().Header(map[string]string{
"X-Tap-Sign": sign,
"X-Tap-Nonce": nonce,
"X-Tap-Ts": strconv.FormatInt(ts, 10),
}).Get(gctx.New(), url)
var header = map[string]string{
"Content-Type": "Content-Type: application/json; charset=utf-8",
"X-Tap-Nonce": grand.S(6),
"X-Tap-Ts": gtime.Now().TimestampStr(),
}
ctx2 := context.Background()
var method = "GET"
if len(_data) > 0 {
method = "POST"
}
//temp := []byte(`{"event_type":"charge.succeeded","order":{"order_id":"1790288650833465345","purchase_token":"rT2Et9p0cfzq4fwjrTsGSacq0jQExFDqf5gTy1alp+Y=","client_id":"o6nD4iNavjQj75zPQk","open_id":"4+Axcl2RFgXbt6MZwdh++w==","user_region":"US","goods_open_id":"com.goods.open_id","goods_name":"TestGoodsName","status":"charge.succeeded","amount":"19000000000","currency":"USD","create_time":"1716168000","pay_time":"1716168000","extra":"1111111111111111111"}}`)
var body io.Reader
if len(_data) > 0 {
body = bytes.NewBuffer(gjson.MustEncode(_data[0]))
} else { } else {
_get, err = g.Client().Header(map[string]string{ body = bytes.NewBuffer([]byte{})
"X-Tap-Sign": sign, }
"X-Tap-Nonce": nonce, req, _ := http.NewRequestWithContext(ctx2, method, url, body)
"X-Tap-Ts": strconv.FormatInt(ts, 10), for k, v := range header {
}).Post(gctx.New(), url, _data[0]) req.Header.Set(k, v)
}
sign, err2 := p.Sign(req, p.Secret)
if err2 != nil {
err = err2
return
}
req.Header.Set("X-Tap-Sign", sign)
header["X-Tap-Sign"] = sign
if len(_data) == 0 {
_get, err = g.Client().Header(header).ContentJson().Get(gctx.New(), url)
} else {
_get, err = g.Client().Header(header).ContentJson().Post(gctx.New(), url, _data[0])
} }
if err != nil { if err != nil {
return return
} }
res = _get.ReadAllString() getPayData = &GetPayData{}
resData := _get.ReadAll()
g.Dump(resData)
if err = json.Unmarshal(resData, &getPayData); err != nil {
return
}
if !getPayData.Success {
err = errors.New(string(resData))
}
return return
} }

View File

@@ -1,40 +0,0 @@
package taptap
import "github.com/gogf/gf/v2/encoding/gjson"
type WebhookData struct {
Order *Order `json:"order"`
EventType string `json:"event_type"`
}
// Order 订单信息结构体
type Order struct {
OrderID string `json:"order_id" dc:"订单唯一ID"`
PurchaseToken string `json:"purchase_token" dc:"用于订单核销的token"`
ClientID string `json:"client_id" dc:"应用的Client ID"`
OpenID string `json:"open_id" dc:"用户的开放平台ID"`
UserRegion string `json:"user_region" dc:"用户地区"`
GoodsOpenID string `json:"goods_open_id" dc:"商品唯一ID"`
GoodsName string `json:"goods_name" dc:"商品名称"`
Status string `json:"status" dc:"订单状态"`
Amount string `json:"amount" dc:"金额本币金额x1,000,000"`
Currency string `json:"currency" dc:"币种"`
CreateTime string `json:"create_time" dc:"创建时间"`
PayTime string `json:"pay_time" dc:"支付时间"`
Extra string `json:"extra" dc:"商户自定义数据如角色信息等长度不超过255 UTF-8字符"`
}
func (p *pTapTap) Webhook(body []byte) (res string, err error) {
var data *WebhookData
gjson.DecodeTo(body, &data)
switch data.EventType {
case "charge.succeeded": //充值成功
//todo 处理订单信息
case "refund.succeeded": //退款成功
case "refund.failed": //退款失败
}
return
}

View File

@@ -0,0 +1,15 @@
package vivo
type Pay struct {
AppId string
AppKey string
//AppSecret string
}
func New(cfg *Pay) (client *Pay) {
return &Pay{
AppId: cfg.AppId,
AppKey: cfg.AppKey,
//AppSecret: cfg.AppSecret,
}
}

View File

@@ -0,0 +1,9 @@
package vivo
const (
AuthTokenUrl = "https://joint-account.vivo.com.cn/cp/user/auth"
LocationShanghai = "Asia/Shanghai"
RSA = "RSA"
RSA2 = "RSA2"
)

34
package/pay/vivo/model.go Normal file
View File

@@ -0,0 +1,34 @@
package vivo
type TokenAuthResponse struct {
ReturnCode int `json:"retcode"`
Data *TokenAuthResponseData `json:"data,omitempty"`
}
type TokenAuthResponseData struct {
Success bool `json:"success,omitempty"`
OpenId string `json:"openid,omitempty"`
}
type LoginType struct {
Token string `json:"token"`
Ssoid string `json:"ssoid"`
Channel int `json:"channel"`
AdId string `json:"adId"`
}
type PayCallback struct {
AppId string `json:"appId"`
CpId string `json:"cpId"`
CpOrderNumber string `json:"cpOrderNumber"`
ExtInfo string `json:"extInfo"`
OrderAmount string `json:"orderAmount"`
OrderNumber string `json:"orderNumber"`
PayTime string `json:"payTime"`
RespCode string `json:"respCode"`
RespMsg string `json:"respMsg"`
SignMethod string `json:"signMethod"`
Signature string `json:"signature"`
TradeStatus string `json:"tradeStatus"`
TradeType string `json:"tradeType"`
}

View File

@@ -0,0 +1,3 @@
package vivo

48
package/pay/vivo/sign.go Normal file
View File

@@ -0,0 +1,48 @@
package vivo
import (
"errors"
"github.com/gogf/gf/v2/crypto/gmd5"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"sort"
"strings"
)
func (p *Pay) VerifySign(bm g.Map, key string) bool {
signature := bm["signature"]
delete(bm, "signature")
delete(bm, "signMethod")
sign := p.sign(bm, key)
return signature == sign
}
func (p *Pay) sign(bm g.Map, key string) string {
s, _ := p.buildSignStr(bm)
s += "&" + gmd5.MustEncrypt(key)
return gmd5.MustEncrypt(s)
}
func (p *Pay) buildSignStr(bm g.Map) (string, error) {
var (
buf strings.Builder
keyList []string
)
for k := range bm {
keyList = append(keyList, k)
}
sort.Strings(keyList)
for _, k := range keyList {
if v := bm[k]; v != "" {
buf.WriteString(k)
buf.WriteByte('=')
buf.WriteString(gconv.String(v))
buf.WriteByte('&')
}
}
if buf.Len() <= 0 {
return "", errors.New("length is error")
}
return buf.String()[:buf.Len()-1], nil
}

View File

@@ -0,0 +1,49 @@
package vivo
import (
"github.com/gogf/gf/v2/frame/g"
"testing"
)
func TestVerifySign(t *testing.T) {
type args struct {
bm g.Map
key string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "testCase-01",
args: args{
bm: map[string]interface{}{
"appId": "111",
"cpId": "11",
"cpOrderNumber": "111",
"extInfo": "扩展参数",
"orderAmount": "1",
"orderNumber": "11",
"payTime": "20210610213219",
"respCode": "200",
"respMsg": "交易成功",
"signMethod": "MD5",
"signature": "111",
"tradeStatus": "0000",
"tradeType": "01",
"uid": "111",
},
key: "1111",
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := New(nil).VerifySign(tt.args.bm, tt.args.key); got != tt.want {
t.Errorf("VerifySign() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -0,0 +1,52 @@
package vivo
import (
"encoding/json"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"net/url"
)
func (p *Pay) AuthToken(bm g.Map) (rsp *TokenAuthResponse, err error) {
if _, ok := bm["opentoken"]; !ok {
return
}
//err = bm.CheckEmptyError("opentoken")
if err != nil {
return nil, err
}
bs, err := p.doAuthToken(bm)
if err != nil {
return nil, err
}
rsp = new(TokenAuthResponse)
if err = json.Unmarshal(bs, rsp); err != nil {
return nil, fmt.Errorf("json.Unmarshal(%s)%w", string(bs), err)
}
return rsp, nil
}
func (p *Pay) doAuthToken(bm g.Map) (bs []byte, err error) {
param := p.FormatURLParam(bm)
//httpClient := xhttp.NewClient()
//res, bs, errs := httpClient.Type(xhttp.TypeFormData).Post(AuthTokenUrl).SendString(param).EndBytes()
res, err := g.Client().Post(gctx.New(), AuthTokenUrl, param)
if err != nil {
return nil, err
}
if res.StatusCode != 200 {
return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode)
}
return res.ReadAll(), nil
}
// 格式化请求URL参数
func (p *Pay) FormatURLParam(body g.Map) (urlParam string) {
v := url.Values{}
for key, value := range body {
v.Add(key, value.(string))
}
return v.Encode()
}

View File

@@ -0,0 +1,70 @@
package vivo
import (
"encoding/json"
"github.com/gogf/gf/v2/frame/g"
"reflect"
"testing"
)
func TestAuthToken(t *testing.T) {
type args struct {
bm g.Map
}
tests := []struct {
name string
args args
wantRsp *TokenAuthResponse
wantErr bool
}{
{
name: "testCase-01",
args: args{
bm: map[string]interface{}{
"opentoken": "_STV1_797e3324f7e3f1a3_797e3324f7e3f1a3_8db97942_Awykia3hpb90kcu3l",
},
},
wantRsp: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotRsp, err := New(nil).AuthToken(tt.args.bm)
if (err != nil) != tt.wantErr {
t.Errorf("AuthToken() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(gotRsp, tt.wantRsp) {
if gotRsp != nil {
marshal, _ := json.Marshal(gotRsp)
println(string(marshal))
}
t.Errorf("AuthToken() gotRsp = %v, want %v", gotRsp, tt.wantRsp)
}
})
}
}
func TestFormatURLParam(t *testing.T) {
type args struct {
body g.Map
}
tests := []struct {
name string
args args
wantUrlParam string
}{
{name: "testCase-01", args: args{body: map[string]interface{}{
"opentoken": "_STV1_797e3324f7e3f1a3_797e3324f7e3f1a3_8db97942_Abbccayhpb90kvd3m",
"123": "123",
}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if gotUrlParam := New(nil).FormatURLParam(tt.args.body); gotUrlParam != tt.wantUrlParam {
t.Errorf("FormatURLParam() = %v, want %v", gotUrlParam, tt.wantUrlParam)
}
})
}
}

View File

@@ -0,0 +1,63 @@
package xiaomi
import (
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"fmt"
"sort"
"strings"
)
// SignatureHelper 签名辅助类
type SignatureHelper struct{}
// hmacSHA1 计算HMAC-SHA1哈希值
func hmacSHA1(data, key string) string {
h := hmac.New(sha1.New, []byte(key))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
// Sign 计算hmac-sha1签名
func (m *MiPay) Sign(params map[string]string, secretKey string) string {
if _, ok := params["signature"]; ok {
delete(params, "signature")
}
for k, v := range params {
if v == "" || v == "0" {
delete(params, k)
}
}
sortString := m.buildSortString(params)
signature := hmacSHA1(sortString, secretKey)
return signature
}
// VerifySignature 验证签名
func (m *MiPay) VerifySignature(params map[string]string, signature, secretKey string) bool {
tmpSign := m.Sign(params, secretKey)
return tmpSign == signature
}
// buildSortString 构造排序字符串
func (m *MiPay) buildSortString(params map[string]string) string {
if len(params) == 0 {
return ""
}
// 按键排序
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
// 构建排序字符串
var fields []string
for _, k := range keys {
fields = append(fields, fmt.Sprintf("%s=%s", k, params[k]))
}
return strings.Join(fields, "&")
}

View File

@@ -0,0 +1,20 @@
package xiaomi
import "github.com/gogf/gf/v2/os/gtime"
type PayCallback struct {
AppID string `json:"appId" dc:"游戏ID" required:"true"`
CPOrderID string `json:"cpOrderId" dc:"开发商订单ID" required:"true"`
CPUserInfo string `json:"cpUserInfo" dc:"开发商透传信息" required:"false"`
OrderConsumeType int `json:"orderConsumeType" dc:"订单类型10普通订单 11直充直消订单" required:"false"`
OrderID string `json:"orderId" dc:"游戏平台订单ID" required:"true"`
OrderStatus string `json:"orderStatus" dc:"订单状态TRADE_SUCCESS代表成功" required:"true"`
PayFee int `json:"payFee" dc:"支付金额单位为分即0.01米币。请务必使用payFee字段值与游戏发起订单金额做校验确保订单金额一致性" required:"true"`
PayTime *gtime.Time `json:"payTime" dc:"支付时间格式yyyy-MM-dd HH:mm:ss" required:"true"`
ProductCode string `json:"productCode" dc:"商品代码" required:"true"`
ProductCount int `json:"productCount" dc:"商品数量" required:"true"`
ProductName string `json:"productName" dc:"商品名称" required:"true"`
UID string `json:"uid" dc:"用户ID" required:"true"`
PartnerGiftConsume int64 `json:"partnerGiftConsume" dc:"使用游戏券金额如果订单使用游戏券则有long型如果有则参与签名" required:"false"`
Signature string `json:"signature" dc:"签名,签名方法见后面说明" required:"true"`
}

View File

@@ -0,0 +1,29 @@
package xiaomi
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
)
// Config 小米支付配置信息
type Config struct {
AppID string `json:"app_id"` // 应用ID
AppSecret string `json:"app_secret"` // 应用密钥
//PrivateKey string // 商户私钥(如需证书)
//MIAPIURL string // 小米支付API基础地址
//IsSandbox bool // 是否沙箱环境
}
// Miipay 小米支付客户端
type MiPay struct {
config *Config
}
func New() *MiPay {
_cfg, _ := g.Cfg().Get(gctx.New(), "pay.xiaomi")
var cfg *Config
_cfg.Scan(&cfg)
return &MiPay{
config: cfg,
}
}

View File

@@ -270,7 +270,7 @@ func (s *Mod) GetPath(url string) (filePath string) {
return url[len(get+bucketName)+1:] return url[len(get+bucketName)+1:]
} }
// 复制文件 // CopyObject 复制文件
func (s *Mod) CopyObject(bucketName string, dstStr string, srcStr string) (err error) { func (s *Mod) CopyObject(bucketName string, dstStr string, srcStr string) (err error) {
// 原始文件 // 原始文件
@@ -288,3 +288,15 @@ func (s *Mod) CopyObject(bucketName string, dstStr string, srcStr string) (err e
_, err = s.client.CopyObject(ctx, dst, src) _, err = s.client.CopyObject(ctx, dst, src)
return return
} }
// Rename 重命名文件
func (s *Mod) Rename(bucketName string, name string, newName string) (err error) {
// 复制文件到新的名称
err = s.CopyObject(bucketName, name, newName)
if err != nil {
return
}
// 删除原始文件
err = s.RemoveObject(bucketName, name)
return
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/elastic/go-elasticsearch/v8/typedapi/types" "github.com/elastic/go-elasticsearch/v8/typedapi/types"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx" "github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/util/gconv"
) )
var ( var (
@@ -23,6 +24,13 @@ type Elastic struct {
name string name string
} }
type elkBulk struct {
Index struct {
Index string `json:"_index"`
Id string `json:"_id"`
} `json:"index"`
}
func NewV1(name string) *Elastic { func NewV1(name string) *Elastic {
var cfg elasticsearch.Config var cfg elasticsearch.Config
_cfg := g.Cfg().MustGetWithEnv(gctx.New(), "elasticsearch") _cfg := g.Cfg().MustGetWithEnv(gctx.New(), "elasticsearch")
@@ -52,7 +60,7 @@ func NewV1(name string) *Elastic {
// fmt.Printf("index:%#v\n", resp.Index) // fmt.Printf("index:%#v\n", resp.Index)
//} //}
// Set 索引文档 // Set 添加文档索引文档
func (s *Elastic) Set(ctx context.Context, key string, data interface{}) (err error) { func (s *Elastic) Set(ctx context.Context, key string, data interface{}) (err error) {
// 添加文档 // 添加文档
_, err = s.client.Index(s.name).Id(key).Document(data).Do(ctx) _, err = s.client.Index(s.name).Id(key).Document(data).Do(ctx)
@@ -61,11 +69,20 @@ func (s *Elastic) Set(ctx context.Context, key string, data interface{}) (err er
// SetBulk 批量添加文档 // SetBulk 批量添加文档
func (s *Elastic) SetBulk(ctx context.Context, data []any) (err error) { func (s *Elastic) SetBulk(ctx context.Context, data []any) (err error) {
var save *bulk.Request var save bulk.Request
save = &bulk.Request{ save = make(bulk.Request, 0)
data, for _, v := range data {
val := gconv.Map(v)
var saveIndex = elkBulk{}
saveIndex.Index.Index = s.name
if _, ok := val["uuid"]; ok {
saveIndex.Index.Id = val["uuid"].(string)
} }
s.client.Bulk().Index(s.name).Request(save).Do(ctx) save = append(save, saveIndex)
save = append(save, v)
}
//save = data
_, err = s.client.Bulk().Index(s.name).Request(&save).Do(ctx)
return return
} }

View File

@@ -150,6 +150,7 @@ func (s *Mod) PutObject(f io.Reader, name string, bucketName string, _size ...in
} }
// RemoveObject 从指定存储桶中删除指定名称的文件 // RemoveObject 从指定存储桶中删除指定名称的文件
// Deprecation: to新方法 RemoveObjectV2
func (s *Mod) RemoveObject(name string, bucketName string) (err error) { func (s *Mod) RemoveObject(name string, bucketName string) (err error) {
opts := minio.RemoveObjectOptions{ opts := minio.RemoveObjectOptions{
ForceDelete: true, ForceDelete: true,
@@ -161,6 +162,18 @@ func (s *Mod) RemoveObject(name string, bucketName string) (err error) {
return return
} }
// RemoveObjectV2 从指定存储桶中删除指定名称的文件
func (s *Mod) RemoveObjectV2(bucketName string, name string) (err error) {
opts := minio.RemoveObjectOptions{
ForceDelete: true,
//GovernanceBypass: true,
//VersionID: "myversionid",
}
// 调用 S3 客户端删除文件
err = s.client.RemoveObject(ctx, bucketName, name, opts)
return
}
// ListObjects 获取指定存储桶中指定前缀的文件列表 // ListObjects 获取指定存储桶中指定前缀的文件列表
// 返回一个包含文件信息的通道 // 返回一个包含文件信息的通道
func (s *Mod) ListObjects(bucketName string, prefix string) (res <-chan minio.ObjectInfo, err error) { func (s *Mod) ListObjects(bucketName string, prefix string) (res <-chan minio.ObjectInfo, err error) {
@@ -216,21 +229,43 @@ func (s *Mod) GetPath(url string) (filePath string) {
} }
// CopyObject 在指定存储桶内复制文件 // CopyObject 在指定存储桶内复制文件
// 支持指定源文件和目标文件路径 // bucketName 存储桶名称
// dstStr 目标文件路径
// srcStr 源文件路径
// 返回操作过程中可能出现的错误
func (s *Mod) CopyObject(bucketName string, dstStr string, srcStr string) (err error) { func (s *Mod) CopyObject(bucketName string, dstStr string, srcStr string) (err error) {
// 定义目标文件选项 // 定义目标文件的复制选项,包含存储桶名称和目标文件路径
var dst = minio.CopyDestOptions{ var dst = minio.CopyDestOptions{
Bucket: bucketName, Bucket: bucketName,
Object: dstStr, Object: dstStr,
} }
// 定义源文件选项 // 定义源文件的复制选项,包含存储桶名称和源文件路径
var src = minio.CopySrcOptions{ var src = minio.CopySrcOptions{
Bucket: bucketName, Bucket: bucketName,
Object: srcStr, Object: srcStr,
} }
// 调用 S3 客户端复制文件 // 调用 S3 客户端的 CopyObject 方法,将源文件复制到目标位置
// 忽略返回的复制信息,仅关注是否发生错误
_, err = s.client.CopyObject(ctx, dst, src) _, err = s.client.CopyObject(ctx, dst, src)
return return
} }
// Rename 重命名文件
func (s *Mod) Rename(bucketName string, oldName string, newName string) (err error) {
// 复制文件到新的名称
g.Log().Debugf(nil, "仓库=%v,rename %s to %s", bucketName, oldName, newName)
err = s.CopyObject(bucketName, newName, oldName)
if err != nil {
g.Log().Error(ctx, err)
return
}
// 删除原始文件
err = s.RemoveObjectV2(bucketName, oldName)
if err != nil {
g.Log().Error(ctx, err)
return
}
return
}