Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8491f0aba | ||
|
|
999f1f6a84 | ||
|
|
039acea0af | ||
|
|
d54de73e11 | ||
|
|
61d69159ea | ||
|
|
fa659d87ae |
3
go.mod
3
go.mod
@@ -47,8 +47,6 @@ 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
|
||||||
@@ -62,7 +60,6 @@ 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
7
go.sum
@@ -225,10 +225,6 @@ 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=
|
||||||
@@ -300,8 +296,6 @@ 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=
|
||||||
@@ -463,7 +457,6 @@ 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=
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Boot() (err error) {
|
func Boot() (err error) {
|
||||||
err = service.SystemCron().StartCron()
|
// 启动计划任务定时器,预防debug工具激活计划任务造成重复执行,此处不执行计划任务
|
||||||
|
//err = service.SystemCron().StartCron()
|
||||||
|
|
||||||
//用户活动持久化
|
//用户活动持久化
|
||||||
service.SystemCron().AddCronV2(v1.CronType_DAILY, func(ctx context.Context) error {
|
service.SystemCron().AddCronV2(v1.CronType_DAILY, func(ctx context.Context) error {
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package ip2region
|
package ip2region
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/ayflying/utility_go/service"
|
"github.com/ayflying/utility_go/service"
|
||||||
"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/os/gfile"
|
"github.com/gogf/gf/v2/os/gfile"
|
||||||
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -15,6 +16,8 @@ var (
|
|||||||
wait = false
|
wait = false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const IpDbPath = "runtime/library/ip2region.xdb"
|
||||||
|
|
||||||
type sIp2region struct {
|
type sIp2region struct {
|
||||||
searcher *xdb.Searcher
|
searcher *xdb.Searcher
|
||||||
}
|
}
|
||||||
@@ -35,16 +38,17 @@ func init() {
|
|||||||
// Load 加载到内存中
|
// Load 加载到内存中
|
||||||
//
|
//
|
||||||
// @Description: 加载ip2region数据库到内存中。
|
// @Description: 加载ip2region数据库到内存中。
|
||||||
|
|
||||||
// @receiver s *sIp2region: sIp2region的实例。
|
// @receiver s *sIp2region: sIp2region的实例。
|
||||||
func (s *sIp2region) Load() {
|
func (s *sIp2region) Load() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
var dbPath = "runtime/library/ip2region.xdb"
|
//var dbPath = "runtime/library/ip2region.xdb"
|
||||||
var url = "https://github.com/ayflying/resource/raw/refs/heads/main/attachment/ip2region.xdb"
|
var url = "https://github.com/ayflying/resource/raw/refs/heads/master/attachment/ip2region.xdb"
|
||||||
if wait {
|
if wait {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if gfile.IsEmpty(dbPath) {
|
if gfile.IsEmpty(IpDbPath) {
|
||||||
wait = true
|
wait = true
|
||||||
defer func() {
|
defer func() {
|
||||||
wait = false
|
wait = false
|
||||||
@@ -55,9 +59,9 @@ func (s *sIp2region) Load() {
|
|||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = gfile.PutBytes(dbPath, putData.ReadAll())
|
err = gfile.PutBytes(IpDbPath, putData.ReadAll())
|
||||||
}
|
}
|
||||||
cBuff := gfile.GetBytes(dbPath)
|
cBuff := gfile.GetBytes(IpDbPath)
|
||||||
/*
|
/*
|
||||||
var cBuff []byte
|
var cBuff []byte
|
||||||
if gres.Contains(dbPath) {
|
if gres.Contains(dbPath) {
|
||||||
@@ -78,7 +82,9 @@ func (s *sIp2region) Load() {
|
|||||||
|
|
||||||
func (s *sIp2region) GetIp(ip string) (res []string) {
|
func (s *sIp2region) GetIp(ip string) (res []string) {
|
||||||
//初始化加载
|
//初始化加载
|
||||||
s.Load()
|
if s.searcher == nil {
|
||||||
|
s.Load()
|
||||||
|
}
|
||||||
|
|
||||||
res = make([]string, 5)
|
res = make([]string, 5)
|
||||||
if s.searcher == nil {
|
if s.searcher == nil {
|
||||||
|
|||||||
32
package/pay/chongchong/chongchong.go
Normal file
32
package/pay/chongchong/chongchong.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package chongchong
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/ayflying/utility_go/package/pay/common"
|
||||||
|
"github.com/gogf/gf/v2/crypto/gmd5"
|
||||||
|
)
|
||||||
|
|
||||||
|
//验单
|
||||||
|
func (p *Pay) Verify(req *CallbackData, sign string) (isOk bool, err error) {
|
||||||
|
//req := g.RequestFromCtx(ctx).Request
|
||||||
|
//data, err := common.ParseNotifyToBodyMap(req)
|
||||||
|
|
||||||
|
var data = map[string]interface{}{
|
||||||
|
"orderPrice": req.OrderPrice,
|
||||||
|
"packageId": req.PackageId,
|
||||||
|
"partnerTransactionNo": req.PartnerTransactionNo,
|
||||||
|
"productId": req.ProductId,
|
||||||
|
"statusCode": req.StatusCode,
|
||||||
|
"transactionNo": req.TransactionNo,
|
||||||
|
}
|
||||||
|
|
||||||
|
dataStr, err := common.BuildSignStr(data)
|
||||||
|
|
||||||
|
var SingStr = fmt.Sprintf("%v&%v", dataStr, p.ApiKey)
|
||||||
|
sign2, err := gmd5.EncryptString(SingStr)
|
||||||
|
|
||||||
|
if sign == sign2 {
|
||||||
|
isOk = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
25
package/pay/chongchong/model.go
Normal file
25
package/pay/chongchong/model.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package chongchong
|
||||||
|
|
||||||
|
type Pay struct {
|
||||||
|
ApiKey string `json:"api_key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(pay *Pay) *Pay {
|
||||||
|
return &Pay{
|
||||||
|
ApiKey: pay.ApiKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallbackData 用于处理回调数据的结构体
|
||||||
|
type CallbackData struct {
|
||||||
|
TransactionNo string `json:"transactionNo" dc:"平台交易单号,唯一标识一笔交易"`
|
||||||
|
PartnerTransactionNo string `json:"partnerTransactionNo" dc:"合作方交易单号,由合作方生成"`
|
||||||
|
StatusCode string `json:"statusCode" dc:"交易状态码,SUCCESS表示成功,FAIL表示失败"`
|
||||||
|
ProductId int `json:"productId" dc:"产品ID,对应后台配置的商品"`
|
||||||
|
OrderPrice float64 `json:"orderPrice" dc:"订单金额,单位为元"`
|
||||||
|
PackageId int `json:"packageId" dc:"套餐ID,可选字段,部分商品有套餐区分"`
|
||||||
|
ProductName string `json:"productName" dc:"产品名称,展示用"`
|
||||||
|
ExtParam string `json:"extParam" dc:"扩展参数,回调时原样返回"`
|
||||||
|
UserId int `json:"userId" dc:"用户ID,标识购买者"`
|
||||||
|
Sign string `json:"sign" dc:"签名,用于验证请求合法性"`
|
||||||
|
}
|
||||||
105
package/pay/common/func.go
Normal file
105
package/pay/common/func.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FormatPublicKey 将原始公钥字符串格式化为标准PEM格式的公钥
|
||||||
|
// 功能:为原始公钥添加PEM头部和尾部,并按64字符长度拆分换行,符合PKCS#8标准格式要求
|
||||||
|
// 参数 publicKey: 原始未格式化的公钥字符串(通常为Base64编码且无换行)
|
||||||
|
// 返回值: 格式化后的PEM格式公钥字符串
|
||||||
|
func FormatPublicKey(publicKey string) (pKey string) {
|
||||||
|
var buffer strings.Builder
|
||||||
|
// 写入PEM格式头部
|
||||||
|
buffer.WriteString("-----BEGIN PUBLIC KEY-----\n")
|
||||||
|
|
||||||
|
// 定义每行公钥的标准长度(PEM格式要求64字符/行)
|
||||||
|
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 {
|
||||||
|
// 最后一行取剩余所有字符(处理不足64字符的情况)
|
||||||
|
buffer.WriteString(publicKey[start:])
|
||||||
|
} else {
|
||||||
|
// 非最后行取固定64字符
|
||||||
|
buffer.WriteString(publicKey[start:end])
|
||||||
|
}
|
||||||
|
buffer.WriteByte('\n') // 每行结束添加换行符
|
||||||
|
start += rawLen
|
||||||
|
end = start + rawLen
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入PEM格式尾部
|
||||||
|
buffer.WriteString("-----END PUBLIC KEY-----\n")
|
||||||
|
pKey = buffer.String()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseNotifyToBodyMap 将HTTP请求中的表单数据解析为键值对映射
|
||||||
|
// 功能:解析请求表单数据,提取单值字段并转换为map[string]interface{}格式
|
||||||
|
// 参数 req: 包含表单数据的HTTP请求对象
|
||||||
|
// 返回值: 解析后的键值对映射(bm)和可能的错误(err)
|
||||||
|
func ParseNotifyToBodyMap(req *http.Request) (bm map[string]interface{}, err error) {
|
||||||
|
// 解析请求表单数据,若失败则返回错误
|
||||||
|
if err = req.ParseForm(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 获取解析后的表单数据(key为字段名,value为字符串切片形式的字段值)
|
||||||
|
var form map[string][]string = req.Form
|
||||||
|
// 初始化结果映射,预分配容量(表单字段数+1,预留扩展空间)
|
||||||
|
bm = make(map[string]interface{}, len(form)+1)
|
||||||
|
// 遍历表单字段,仅保留单值字段(忽略多值字段)
|
||||||
|
for k, v := range form {
|
||||||
|
if len(v) == 1 {
|
||||||
|
bm[k] = v[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildSignStr 根据传入的g.Map构建签名字符串
|
||||||
|
// 规则:对所有非空值的键进行字母排序后,按"key=value&"格式拼接,最后去除末尾的"&"
|
||||||
|
// 参数 bm: 包含键值对的g.Map
|
||||||
|
// 返回值: 构建好的签名字符串和可能的错误
|
||||||
|
func 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
|
||||||
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package honor
|
package honor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
@@ -8,7 +9,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/ayflying/utility_go/package/pay"
|
"github.com/ayflying/utility_go/package/pay/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Pay struct {
|
type Pay struct {
|
||||||
@@ -28,14 +29,18 @@ func New(pay *Pay) *Pay {
|
|||||||
// sign: 签名的Base64编码字符串
|
// sign: 签名的Base64编码字符串
|
||||||
// pubKey: PEM格式的公钥字符串
|
// pubKey: PEM格式的公钥字符串
|
||||||
// 返回验证结果和可能的错误
|
// 返回验证结果和可能的错误
|
||||||
func (p *Pay) VerifyRSASignature(data []byte, sign string) (bool, error) {
|
func (p *Pay) VerifyRSASignature(ctx context.Context, data []byte, sign string) (bool, error) {
|
||||||
|
//req := g.RequestFromCtx(ctx).Request
|
||||||
|
//post, err := common.ParseNotifyToBodyMap(req)
|
||||||
|
//var data = gjson.MustEncode(post)
|
||||||
|
|
||||||
// 解码Base64格式的签名
|
// 解码Base64格式的签名
|
||||||
signBytes, err := base64.StdEncoding.DecodeString(sign)
|
signBytes, err := base64.StdEncoding.DecodeString(sign)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.New("签名解码失败: " + err.Error())
|
return false, errors.New("签名解码失败: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
pubkey := pay.FormatPublicKey(p.PubKey)
|
pubkey := common.FormatPublicKey(p.PubKey)
|
||||||
// 解析PEM格式的公钥
|
// 解析PEM格式的公钥
|
||||||
block, _ := pem.Decode([]byte(pubkey))
|
block, _ := pem.Decode([]byte(pubkey))
|
||||||
if block == nil {
|
if block == nil {
|
||||||
|
|||||||
25
package/pay/huawei/consts.go
Normal file
25
package/pay/huawei/consts.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package huawei
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TokenUrl = "https://oauth-login.cloud.huawei.com/oauth2/v3/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getOrderUrl(accountFlag int) string {
|
||||||
|
if accountFlag == 1 {
|
||||||
|
// site for telecom carrier
|
||||||
|
//return "https://orders-at-dre.iap.dbankcloud.com"
|
||||||
|
return "https://orders-drcn.iap.cloud.huawei.com.cn"
|
||||||
|
} else {
|
||||||
|
// TODO: replace the (ip:port) to the real one
|
||||||
|
return "http://exampleserver/_mockserver_"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// default http client with 5 seconds timeout
|
||||||
|
var RequestHttpClient = http.Client{Timeout: time.Second * 5}
|
||||||
166
package/pay/huawei/huawei.go
Normal file
166
package/pay/huawei/huawei.go
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
package huawei
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gogf/gf/v2/encoding/gjson"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/os/gctx"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Pay struct {
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
ClientId string `json:"client_id"`
|
||||||
|
//TokenUrl string `json:"token_url"`
|
||||||
|
ApplicationPublicKey string `json:"application_public_key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *Pay) *Pay {
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfirmPurchase 发货后确认购买接口(华为支付)消耗商品
|
||||||
|
// 功能:通知华为支付平台当前订单已完成发货,触发支付完成流程(需在商品实际发货后调用)
|
||||||
|
// 参数说明:
|
||||||
|
// purchaseToken: 华为支付返回的购买令牌(唯一标识一笔具体的购买交易,由客户端支付成功后返回)
|
||||||
|
// productId: 应用内商品的唯一标识(需与客户端发起支付时使用的productId一致)
|
||||||
|
// accountFlag: 账户标识(用于区分不同账户体系/环境,如0-普通用户、1-企业用户,具体值由业务定义)
|
||||||
|
func (p *Pay) ConfirmPurchase(purchaseToken, productId string, accountFlag int) {
|
||||||
|
// 构造请求体参数(包含购买令牌和产品ID)
|
||||||
|
bodyMap := map[string]string{
|
||||||
|
"purchaseToken": purchaseToken, // 华为支付返回的购买凭证
|
||||||
|
"productId": productId, // 对应应用内商品的唯一标识
|
||||||
|
}
|
||||||
|
url := getOrderUrl(accountFlag) + "/applications/v2/purchases/confirm"
|
||||||
|
bodyBytes, err := p.SendRequest(url, bodyMap)
|
||||||
|
if err != nil {
|
||||||
|
// 请求失败时记录错误日志(实际业务中建议增加重试或异常处理逻辑)
|
||||||
|
log.Printf("err is %s", err)
|
||||||
|
}
|
||||||
|
// 打印响应结果(实际业务中需替换为具体处理逻辑,如更新订单状态、校验响应数据等)
|
||||||
|
// TODO: 建议根据华为支付文档解析响应数据(如检查responseCode是否为0表示成功)
|
||||||
|
log.Printf("%s", bodyBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyToken 验证回调订单
|
||||||
|
//您可以调用本接口向华为应用内支付服务器校验支付结果中的购买令牌,确认支付结果的准确性。
|
||||||
|
func (p *Pay) VerifyToken(purchaseToken, productId string, accountFlag int) (res *PurchaseTokenData, err error) {
|
||||||
|
bodyMap := map[string]string{"purchaseToken": purchaseToken, "productId": productId}
|
||||||
|
url := getOrderUrl(accountFlag) + "/applications/purchases/tokens/verify"
|
||||||
|
bodyBytes, err := p.SendRequest(url, bodyMap)
|
||||||
|
if err != nil {
|
||||||
|
g.Log().Error(gctx.New(), "err is %s", err)
|
||||||
|
}
|
||||||
|
var data *VerifyTokenRes
|
||||||
|
err = gjson.DecodeTo(bodyBytes, &data)
|
||||||
|
err = gjson.DecodeTo(data.PurchaseTokenData, &res)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pay) SendRequest(url string, bodyMap map[string]string) (string, error) {
|
||||||
|
authHeaderString, err := p.BuildAuthorization()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
bodyString, err := json.Marshal(bodyMap)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", url, bytes.NewReader(bodyString))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
req.Header.Set("Authorization", authHeaderString)
|
||||||
|
response, err := RequestHttpClient.Do(req)
|
||||||
|
defer response.Body.Close()
|
||||||
|
bodyBytes, err := io.ReadAll(response.Body)
|
||||||
|
|
||||||
|
//req, err := g.Client().Header(g.MapStrStr{
|
||||||
|
// "Content-Type": "application/json; charset=UTF-8",
|
||||||
|
// "Authorization": authHeaderString,
|
||||||
|
//}).Post(gctx.New(), url, bodyString)
|
||||||
|
//defer req.Close()
|
||||||
|
//var bodyBytes = req.ReadAll()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(bodyBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pay) VerifyRsaSign(content string, sign string, publicKey string) error {
|
||||||
|
//publicKey = common.FormatPublicKey(publicKey)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pay) GetAppAt() (string, error) {
|
||||||
|
//demoConfig := GetDefaultConfig()
|
||||||
|
urlValue := url.Values{
|
||||||
|
"grant_type": {"client_credentials"},
|
||||||
|
"client_secret": {p.ClientSecret},
|
||||||
|
"client_id": {p.ClientId},
|
||||||
|
}
|
||||||
|
resp, err := RequestHttpClient.PostForm(TokenUrl, urlValue)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
bodyBytes, err := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
//post := g.MapStrStr{
|
||||||
|
// "grant_type": "client_credentials",
|
||||||
|
// "client_secret": p.ClientSecret,
|
||||||
|
// "client_id": p.ClientId,
|
||||||
|
//}
|
||||||
|
//resp, err := g.Client().PostForm(gctx.New(), p.TokenUrl, post)
|
||||||
|
//if err != nil {
|
||||||
|
// return "", err
|
||||||
|
//}
|
||||||
|
//resp.Close()
|
||||||
|
//bodyBytes := resp.ReadAll()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var atResponse AtResponse
|
||||||
|
json.Unmarshal(bodyBytes, &atResponse)
|
||||||
|
if atResponse.AccessToken != "" {
|
||||||
|
return atResponse.AccessToken, nil
|
||||||
|
} else {
|
||||||
|
return "", errors.New("Get token fail, " + string(bodyBytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pay) BuildAuthorization() (string, error) {
|
||||||
|
appAt, err := p.GetAppAt()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
oriString := fmt.Sprintf("APPAT:%s", appAt)
|
||||||
|
var authString = base64.StdEncoding.EncodeToString([]byte(oriString))
|
||||||
|
var authHeaderString = fmt.Sprintf("Basic %s", authString)
|
||||||
|
return authHeaderString, nil
|
||||||
|
}
|
||||||
82
package/pay/huawei/model.go
Normal file
82
package/pay/huawei/model.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package huawei
|
||||||
|
|
||||||
|
type CallbackType struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
NotifyTime int64 `json:"notifyTime"`
|
||||||
|
EventType string `json:"eventType"`
|
||||||
|
ApplicationId string `json:"applicationId"`
|
||||||
|
OrderNotification *OrderNotification `json:"orderNotification"`
|
||||||
|
SubNotification *SubNotification `json:"subNotification"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderNotification struct {
|
||||||
|
Version string `json:"version" dc:"通知版本:v2"`
|
||||||
|
NotificationType int `json:"notificationType" dc:"通知事件的类型,取值如下:1:支付成功 2:退款成功"`
|
||||||
|
PurchaseToken string `json:"purchaseToken" dc:"待下发商品的购买Token"`
|
||||||
|
ProductId string `json:"productId" dc:"商品ID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubNotification struct {
|
||||||
|
StatusUpdateNotification *StatusUpdateNotification `json:"statusUpdateNotification" dc:"通知消息"`
|
||||||
|
NotificationSignature string `json:"notificationSignature" dc:"对statusUpdateNotification字段的签名字符串,签名算法为signatureAlgorithm表示的签名算法。"`
|
||||||
|
Version string `json:"version" dc:"通知版本:v2"`
|
||||||
|
SignatureAlgorithm string `json:"signatureAlgorithm" dc:"签名算法。"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusUpdateNotification 订阅状态更新通知
|
||||||
|
type StatusUpdateNotification struct {
|
||||||
|
Environment string `json:"environment" dc:"发送通知的环境。PROD:正式环境;Sandbox:沙盒测试"`
|
||||||
|
NotificationType int `json:"notificationType" dc:"通知事件的类型,具体定义需参考相关文档说明"`
|
||||||
|
SubscriptionID string `json:"subscriptionId" dc:"订阅ID"`
|
||||||
|
CancellationDate int64 `json:"cancellationDate" dc:"撤销订阅时间或退款时间,UTC时间戳,以毫秒为单位,仅在notificationType取值为CANCEL的场景下会传入"`
|
||||||
|
OrderID string `json:"orderId" dc:"订单ID,唯一标识一笔需要收费的收据,由华为应用内支付服务器在创建订单以及订阅型商品续费时生成。每一笔新的收据都会使用不同的orderId。通知类型为NEW_RENEWAL_PREF时不存在"`
|
||||||
|
LatestReceipt string `json:"latestReceipt" dc:"最近的一笔收据的token,仅在notificationType取值为INITIAL_BUY 、RENEWAL或INTERACTIVE_RENEWAL并且续期成功情况下传入"`
|
||||||
|
LatestReceiptInfo string `json:"latestReceiptInfo" dc:"最近的一笔收据,JSON字符串格式,包含的参数请参见InappPurchaseDetails,在notificationType取值为CANCEL时无值"`
|
||||||
|
LatestReceiptInfoSignature string `json:"latestReceiptInfoSignature" dc:"对latestReceiptInfo的签名字符串,签名算法为statusUpdateNotification中的signatureAlgorithm。服务器在收到签名字符串后,需要参见对返回结果验签使用IAP公钥对latestReceiptInfo的JSON字符串进行验签。公钥获取请参见查询支付服务信息"`
|
||||||
|
LatestExpiredReceipt string `json:"latestExpiredReceipt" dc:"最近的一笔过期收据的token"`
|
||||||
|
LatestExpiredReceiptInfo string `json:"latestExpiredReceiptInfo" dc:"最近的一笔过期收据,JSON字符串格式,在notificationType取值为RENEWAL或INTERACTIVE_RENEWAL时有值"`
|
||||||
|
LatestExpiredReceiptInfoSignature string `json:"latestExpiredReceiptInfoSignature" dc:"对latestExpiredReceiptInfo的签名字符串,签名算法为statusUpdateNotification中的signatureAlgorithm。服务器在收到签名字符串后,需要参见对返回结果验签使用IAP公钥对latestExpiredReceiptInfo的JSON字符串进行验签。公钥获取请参见查询支付服务信息"`
|
||||||
|
AutoRenewStatus int `json:"autoRenewStatus" dc:"续期状态。取值说明:1:当前周期到期后正常续期;0:用户已终止续期"`
|
||||||
|
RefundPayOrderId string `json:"refundPayOrderId" dc:"退款交易号,在notificationType取值为CANCEL时有值"`
|
||||||
|
ProductID string `json:"productId" dc:"订阅型商品ID"`
|
||||||
|
ApplicationID string `json:"applicationId" dc:"应用ID"`
|
||||||
|
ExpirationIntent int `json:"expirationIntent" dc:"超期原因,仅在notificationType为RENEWAL或INTERACTIVE_RENEWAL时并且续期失败情况下有值"`
|
||||||
|
PurchaseToken string `json:"purchaseToken" dc:"订阅token,与上述订阅ID字段subscriptionId对应。"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AtResponse struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VerifyTokenRes struct {
|
||||||
|
ResponseCode string `json:"responseCode"`
|
||||||
|
PurchaseTokenData string `json:"purchaseTokenData"`
|
||||||
|
DataSignature string `json:"dataSignature"`
|
||||||
|
SignatureAlgorithm string `json:"signatureAlgorithm"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PurchaseTokenData struct {
|
||||||
|
AutoRenewing bool `json:"autoRenewing" dc:"表示订阅是否自动续费"`
|
||||||
|
OrderId string `json:"orderId" dc:"订单ID,唯一标识一笔订单"`
|
||||||
|
PackageName string `json:"packageName" dc:"应用的包名"`
|
||||||
|
ApplicationId int `json:"applicationId" dc:"应用ID,以整数形式表示"`
|
||||||
|
ApplicationIdString string `json:"applicationIdString" dc:"应用ID的字符串形式"`
|
||||||
|
Kind int `json:"kind" dc:"购买类型的某种标识,具体含义可能取决于业务逻辑"`
|
||||||
|
ProductId string `json:"productId" dc:"商品ID,用于标识购买的商品"`
|
||||||
|
ProductName string `json:"productName" dc:"商品名称"`
|
||||||
|
PurchaseTime int64 `json:"purchaseTime" dc:"购买时间,可能是某种特定格式的时间表示"`
|
||||||
|
PurchaseTimeMillis int64 `json:"purchaseTimeMillis" dc:"购买时间,以毫秒为单位的时间戳"`
|
||||||
|
PurchaseState int `json:"purchaseState" dc:"购买状态,不同的整数值代表不同的状态,具体含义取决于业务逻辑"`
|
||||||
|
DeveloperPayload string `json:"developerPayload" dc:"开发者自定义负载数据"`
|
||||||
|
PurchaseToken string `json:"purchaseToken" dc:"购买令牌"`
|
||||||
|
ResponseCode string `json:"responseCode" dc:"响应代码,用于表示购买操作的响应结果"`
|
||||||
|
ConsumptionState int `json:"consumptionState" dc:"消费状态,不同的整数值代表不同的消费状态,具体含义取决于业务逻辑"`
|
||||||
|
Confirmed int `json:"confirmed" dc:"确认状态,不同的整数值代表不同的确认情况,具体含义取决于业务逻辑"`
|
||||||
|
PurchaseType int `json:"purchaseType" dc:"购买类型,不同的整数值代表不同的购买类型,具体含义取决于业务逻辑"`
|
||||||
|
Currency string `json:"currency" dc:"货币类型"`
|
||||||
|
Price int `json:"price" dc:"商品价格"`
|
||||||
|
Country string `json:"country" dc:"购买所在国家"`
|
||||||
|
PayOrderId string `json:"payOrderId" dc:"支付订单ID"`
|
||||||
|
PayType string `json:"payType" dc:"支付类型"`
|
||||||
|
SdkChannel string `json:"sdkChannel" dc:"SDK渠道"`
|
||||||
|
}
|
||||||
107
package/pay/huawei/notification.go
Normal file
107
package/pay/huawei/notification.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package huawei
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
//import "encoding/json"
|
||||||
|
|
||||||
|
const (
|
||||||
|
INITIAL_BUY = 0
|
||||||
|
CANCEL = 1
|
||||||
|
RENEWAL = 2
|
||||||
|
INTERACTIVE_RENEWAL = 3
|
||||||
|
NEW_RENEWAL_PREF = 4
|
||||||
|
RENEWAL_STOPPED = 5
|
||||||
|
RENEWAL_RESTORED = 6
|
||||||
|
RENEWAL_RECURRING = 7
|
||||||
|
ON_HOLD = 9
|
||||||
|
PAUSED = 10
|
||||||
|
PAUSE_PLAN_CHANGED = 11
|
||||||
|
PRICE_CHANGE_CONFIRMED = 12
|
||||||
|
DEFERRED = 13
|
||||||
|
)
|
||||||
|
|
||||||
|
//type NotificationServer struct {
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//var NotificationDemo = &NotificationServer{}
|
||||||
|
|
||||||
|
//type NotificationRequest struct {
|
||||||
|
// StatusUpdateNotification string `json:"statusUpdateNotification"`
|
||||||
|
// NotificationSignature string `json:"notifycationSignature"`
|
||||||
|
//}
|
||||||
|
|
||||||
|
//
|
||||||
|
//type NotificationResponse struct {
|
||||||
|
// ErrorCode string `json:"errorCode"`
|
||||||
|
// ErrorMsg string `json:"errorMsg"`
|
||||||
|
//}
|
||||||
|
|
||||||
|
//type StatusUpdateNotification struct {
|
||||||
|
// Environment string `json:"environment"`
|
||||||
|
// NotificationType int `json:"notificationType"`
|
||||||
|
// SubscriptionID string `json:"subscriptionId"`
|
||||||
|
// CancellationDate int64 `json:"cancellationDate"`
|
||||||
|
// OrderID string `json:"orderId"`
|
||||||
|
// LatestReceipt string `json:"latestReceipt"`
|
||||||
|
// LatestReceiptInfo string `json:"latestReceiptInfo"`
|
||||||
|
// LatestReceiptInfoSignature string `json:"latestReceiptInfoSignature"`
|
||||||
|
// LatestExpiredReceipt string `json:"latestExpiredReceipt"`
|
||||||
|
// LatestExpiredReceiptInfo string `json:"latestExpiredReceiptInfo"`
|
||||||
|
// LatestExpiredReceiptInfoSignature string `json:"latestExpiredReceiptInfoSignature"`
|
||||||
|
// AutoRenewStatus int `json:"autoRenewStatus"`
|
||||||
|
// RefundPayOrderId string `json:"refundPayOrderId"`
|
||||||
|
// ProductID string `json:"productId"`
|
||||||
|
// ApplicationID string `json:"applicationId"`
|
||||||
|
// ExpirationIntent int `json:"expirationIntent"`
|
||||||
|
//}
|
||||||
|
|
||||||
|
func (p *Pay) DealNotification(information string) (err error) {
|
||||||
|
//var request PayCallback
|
||||||
|
//err = json.Unmarshal([]byte(information), &request)
|
||||||
|
//if err != nil {
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
//err = p.VerifyRsaSign(request.StatusUpdateNotification, request.NotificationSignature, DefaultConfig.ApplicationPublicKey)
|
||||||
|
//if err != nil {
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//var info = request.StatusUpdateNotification
|
||||||
|
////json.Unmarshal([]byte(request.StatusUpdateNotification), &info)
|
||||||
|
//switch notificationType := info.NotificationType; notificationType {
|
||||||
|
//case INITIAL_BUY:
|
||||||
|
//case CANCEL:
|
||||||
|
//case RENEWAL:
|
||||||
|
//case INTERACTIVE_RENEWAL:
|
||||||
|
//case NEW_RENEWAL_PREF:
|
||||||
|
//case RENEWAL_STOPPED:
|
||||||
|
//case RENEWAL_RESTORED:
|
||||||
|
//case RENEWAL_RECURRING:
|
||||||
|
//case ON_HOLD:
|
||||||
|
//case PAUSED:
|
||||||
|
//case PAUSE_PLAN_CHANGED:
|
||||||
|
//case PRICE_CHANGE_CONFIRMED:
|
||||||
|
//case DEFERRED:
|
||||||
|
//default:
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
////response := NotificationResponse{ErrorCode: "0"}
|
||||||
|
////return &response, nil
|
||||||
|
//return
|
||||||
|
return
|
||||||
|
}
|
||||||
9
package/pay/oppo/const.go
Normal file
9
package/pay/oppo/const.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package oppo
|
||||||
|
|
||||||
|
const (
|
||||||
|
LoginUrl = "https://iopen.game.oppomobile.com/sdkopen/user/fileIdInfo?fileId=%s&token=%s"
|
||||||
|
LocationShanghai = "Asia/Shanghai"
|
||||||
|
|
||||||
|
RSA = "RSA"
|
||||||
|
RSA2 = "RSA2"
|
||||||
|
)
|
||||||
32
package/pay/oppo/notify.go
Normal file
32
package/pay/oppo/notify.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package oppo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *OppoType) ParseNotifyToBodyMap(req *http.Request) (bm map[string]interface{}, err error) {
|
||||||
|
if err = req.ParseForm(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var form map[string][]string = req.Form
|
||||||
|
bm = make(map[string]interface{}, len(form)+1)
|
||||||
|
for k, v := range form {
|
||||||
|
if len(v) == 1 {
|
||||||
|
bm[k] = v[0]
|
||||||
|
//bm.Set(k, v[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OppoType) ParseNotifyByURLValues(value url.Values) (bm map[string]interface{}, err error) {
|
||||||
|
bm = make(map[string]interface{}, len(value)+1)
|
||||||
|
for k, v := range value {
|
||||||
|
if len(v) == 1 {
|
||||||
|
bm[k] = v[0]
|
||||||
|
//bm.Set(k, v[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ package oppo
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/haxqer/xthird/oppo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 跟充值平台通信的加密key
|
// 跟充值平台通信的加密key
|
||||||
@@ -30,12 +29,12 @@ func (p *OppoType) Verify(ctx context.Context) (err error) {
|
|||||||
oppoPublicKey := "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCmreYIkPwVovKR8rLHWlFVw7YDfm9uQOJKL89Smt6ypXGVdrAKKl0wNYc3/jecAoPi2ylChfa2iRu5gunJyNmpWZzlCNRIau55fxGW0XEu553IiprOZcaw5OuYGlf60ga8QT6qToP0/dpiL/ZbmNUO9kUhosIjEu22uFgR+5cYyQIDAQAB"
|
oppoPublicKey := "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCmreYIkPwVovKR8rLHWlFVw7YDfm9uQOJKL89Smt6ypXGVdrAKKl0wNYc3/jecAoPi2ylChfa2iRu5gunJyNmpWZzlCNRIau55fxGW0XEu553IiprOZcaw5OuYGlf60ga8QT6qToP0/dpiL/ZbmNUO9kUhosIjEu22uFgR+5cYyQIDAQAB"
|
||||||
//oppoPublicKey := p.PublicKey
|
//oppoPublicKey := p.PublicKey
|
||||||
// 解析请求参数
|
// 解析请求参数
|
||||||
bodyMap, err := oppo.ParseNotifyToBodyMap(g.RequestFromCtx(ctx).Request)
|
bodyMap, err := p.ParseNotifyToBodyMap(g.RequestFromCtx(ctx).Request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 解析失败, 处理错误逻辑
|
// 解析失败, 处理错误逻辑
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = oppo.VerifySign(oppoPublicKey, bodyMap)
|
err = p.VerifySign(oppoPublicKey, bodyMap)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
80
package/pay/oppo/sign.go
Normal file
80
package/pay/oppo/sign.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package oppo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/ayflying/utility_go/package/pay/common"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
"hash"
|
||||||
|
"math/rand"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *OppoType) GenLoginBaseStr(bm map[string]interface{}, appKey, appSecret string) (string, string) {
|
||||||
|
baseStr := fmt.Sprintf("oauthConsumerKey=%s&oauthToken=%s&oauthSignatureMethod=HMAC-SHA1&oauthTimestamp=%d&oauthNonce=%d&oauthVersion=1.0&",
|
||||||
|
appKey, url.QueryEscape(gconv.String(bm["token"])), time.Now().Unix(), rand.Int31n(100000000))
|
||||||
|
|
||||||
|
var h hash.Hash
|
||||||
|
h = hmac.New(sha1.New, []byte(appSecret+"&"))
|
||||||
|
h.Write([]byte(baseStr))
|
||||||
|
|
||||||
|
sign := url.QueryEscape(base64.StdEncoding.EncodeToString(h.Sum(nil)))
|
||||||
|
return baseStr, sign
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OppoType) VerifySign(oppoPayPublicKey string, bm map[string]interface{}) (err error) {
|
||||||
|
if oppoPayPublicKey == "" || bm == nil {
|
||||||
|
return errors.New("oppoPayPublicKey or bm is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
bodySign := bm["sign"].(string)
|
||||||
|
bodySignType := RSA
|
||||||
|
signData := fmt.Sprintf("notifyId=%s&partnerOrder=%s&productName=%s&productDesc=%s&price=%s&count=%s&attach=%s",
|
||||||
|
bm["notifyId"], bm["partnerOrder"], bm["productName"],
|
||||||
|
bm["productDesc"], bm["price"], bm["count"], bm["attach"])
|
||||||
|
pKey := common.FormatPublicKey(oppoPayPublicKey)
|
||||||
|
if err = p.verifySign(signData, bodySign, bodySignType, pKey); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OppoType) verifySign(signData, sign, signType, oppoPayPublicKey string) (err error) {
|
||||||
|
var (
|
||||||
|
h hash.Hash
|
||||||
|
hashs crypto.Hash
|
||||||
|
block *pem.Block
|
||||||
|
pubKey interface{}
|
||||||
|
publicKey *rsa.PublicKey
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
signBytes, _ := base64.StdEncoding.DecodeString(sign)
|
||||||
|
if block, _ = pem.Decode([]byte(oppoPayPublicKey)); block == nil {
|
||||||
|
return errors.New("OPPO公钥Decode错误")
|
||||||
|
}
|
||||||
|
if pubKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
|
||||||
|
return fmt.Errorf("x509.ParsePKIXPublicKey:%w", err)
|
||||||
|
}
|
||||||
|
if publicKey, ok = pubKey.(*rsa.PublicKey); !ok {
|
||||||
|
return errors.New("OPPO公钥转换错误")
|
||||||
|
}
|
||||||
|
switch signType {
|
||||||
|
case RSA:
|
||||||
|
hashs = crypto.SHA1
|
||||||
|
case RSA2:
|
||||||
|
hashs = crypto.SHA256
|
||||||
|
default:
|
||||||
|
hashs = crypto.SHA256
|
||||||
|
}
|
||||||
|
h = hashs.New()
|
||||||
|
h.Write([]byte(signData))
|
||||||
|
return rsa.VerifyPKCS1v15(publicKey, hashs, h.Sum(nil), signBytes)
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package vivo
|
package vivo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/ayflying/utility_go/package/pay/common"
|
||||||
"github.com/gogf/gf/v2/crypto/gmd5"
|
"github.com/gogf/gf/v2/crypto/gmd5"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
@@ -10,7 +12,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *Pay) VerifySign(bm g.Map, key string) bool {
|
func (p *Pay) VerifySign(ctx context.Context, key string) bool {
|
||||||
|
bm, _ := common.ParseNotifyToBodyMap(g.RequestFromCtx(ctx).Request)
|
||||||
signature := bm["signature"]
|
signature := bm["signature"]
|
||||||
delete(bm, "signature")
|
delete(bm, "signature")
|
||||||
delete(bm, "signMethod")
|
delete(bm, "signMethod")
|
||||||
|
|||||||
Reference in New Issue
Block a user