Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02edbdae8d | ||
|
|
5e75094c16 | ||
|
|
140317e2df | ||
|
|
8e2301eb34 | ||
|
|
c673da89cf | ||
|
|
005cf11b2a |
@@ -1,9 +1,9 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/ayflying/utility_go"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
"sync"
|
||||
"github.com/ayflying/utility_go/pkg"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type {cfg}Cfg struct {
|
||||
@@ -23,7 +23,7 @@ func (c *{mod}Mod) Load(_cfg ...string) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.cfgArr = make([]*{cfg}Cfg, 0)
|
||||
data, err := utility_go.Config.GetFile("{file}")
|
||||
data, err := pkg.Config().GetFile("{file}")
|
||||
err = data.Scan(&c.cfgArr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
101
package/pay/taptap/sign.go
Normal file
101
package/pay/taptap/sign.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package taptap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/grand"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type pTapTap struct {
|
||||
Secret string `json:"secret" dc:"秘钥"`
|
||||
OrderId string `json:"order_id" dc:"订单唯一 ID"`
|
||||
ClientId string `json:"client_id" dc:"应用的 Client ID"`
|
||||
}
|
||||
|
||||
func New(orderId string) *pTapTap {
|
||||
return &pTapTap{
|
||||
Secret: "5AFEWnadBA0NgJK2mxeBLQEde0qyIefxLSc4XKHsx9AwkQRhxzkQ9DixsOkK6gcV",
|
||||
ClientId: "mox88lbz43edfukdgk",
|
||||
OrderId: orderId,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pTapTap) Sign(url string, body []byte) (sign string, ts int64, nonce string, err error) {
|
||||
//nolint:gosec
|
||||
secret := p.Secret
|
||||
//body := gjson.MustEncode(g.Map{})
|
||||
//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"}}`)
|
||||
//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
|
||||
}
|
||||
|
||||
// Sign signs the request.
|
||||
func Sign(req *http.Request, secret string) (string, error) {
|
||||
methodPart := req.Method
|
||||
urlPathAndQueryPart := req.URL.RequestURI()
|
||||
headersPart, err := getHeadersPart(req.Header)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
bodyPart, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
signParts := methodPart + "\n" + urlPathAndQueryPart + "\n" + headersPart + "\n" + string(bodyPart) + "\n"
|
||||
fmt.Println(signParts)
|
||||
h := hmac.New(sha256.New, []byte(secret))
|
||||
h.Write([]byte(signParts))
|
||||
rawSign := h.Sum(nil)
|
||||
sign := base64.StdEncoding.EncodeToString(rawSign)
|
||||
return sign, nil
|
||||
}
|
||||
|
||||
// getHeadersPart returns the headers part of the request.
|
||||
func getHeadersPart(header http.Header) (string, error) {
|
||||
var headerKeys []string
|
||||
for k, v := range header {
|
||||
k = strings.ToLower(k)
|
||||
if !strings.HasPrefix(k, "x-tap-") {
|
||||
continue
|
||||
}
|
||||
if k == "x-tap-sign" {
|
||||
continue
|
||||
}
|
||||
if len(v) > 1 {
|
||||
return "", fmt.Errorf("invalid header, %q has multiple values", k)
|
||||
}
|
||||
headerKeys = append(headerKeys, k)
|
||||
}
|
||||
sort.Strings(headerKeys)
|
||||
headers := make([]string, 0, len(headerKeys))
|
||||
for _, k := range headerKeys {
|
||||
headers = append(headers, fmt.Sprintf("%s:%s", k, header.Get(k)))
|
||||
}
|
||||
return strings.Join(headers, "\n"), nil
|
||||
}
|
||||
59
package/pay/taptap/taptap.go
Normal file
59
package/pay/taptap/taptap.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package taptap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/gclient"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type GetPayData struct {
|
||||
Data struct {
|
||||
Order struct {
|
||||
} `json:"order"`
|
||||
} `json:"data"`
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
//查询订单信息
|
||||
func (p *pTapTap) Info(orderId string, clientId string, token []byte) (res string, err error) {
|
||||
url := fmt.Sprintf("https://cloud-payment.tapapis.cn/order/v1/info?client_id=%v&order_id=%v", orderId, clientId)
|
||||
res, err = p.get(url, token)
|
||||
return
|
||||
}
|
||||
|
||||
//验证并核销订单
|
||||
func (p *pTapTap) Verify(orderId string, clientId string, token []byte) (res string, err error) {
|
||||
url := fmt.Sprintf("https://cloud-payment.tapapis.cn/order/v1/verify?client_id=%v", clientId)
|
||||
res, err = p.get(url, token)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *pTapTap) get(url string, token []byte, _data ...any) (res string, err error) {
|
||||
sign, ts, nonce, err := p.Sign(url, token)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
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)
|
||||
|
||||
} else {
|
||||
_get, err = g.Client().Header(map[string]string{
|
||||
"X-Tap-Sign": sign,
|
||||
"X-Tap-Nonce": nonce,
|
||||
"X-Tap-Ts": strconv.FormatInt(ts, 10),
|
||||
}).Post(gctx.New(), url, _data[0])
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res = _get.ReadAllString()
|
||||
return
|
||||
}
|
||||
40
package/pay/taptap/webhook.go
Normal file
40
package/pay/taptap/webhook.go
Normal file
@@ -0,0 +1,40 @@
|
||||
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
|
||||
}
|
||||
@@ -185,11 +185,12 @@ func (a *AdapterFile) Data(ctx context.Context) (data map[interface{}]interface{
|
||||
}
|
||||
|
||||
func (a *AdapterFile) Keys(ctx context.Context) (keys []interface{}, err error) {
|
||||
keys = make([]interface{}, 0)
|
||||
gfile.ReadLines(fileIndex, func(text string) (err error) {
|
||||
|
||||
arr := strings.Split(text, "|")
|
||||
keys = append(keys, arr[0])
|
||||
return
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
37
pkg/s3/s3.go
37
pkg/s3/s3.go
@@ -24,13 +24,14 @@ var (
|
||||
|
||||
// DataType 定义了 S3 配置的数据结构,用于存储访问 S3 所需的各种信息
|
||||
type DataType struct {
|
||||
AccessKey string `json:"access_key"` // 访问 S3 的密钥 ID
|
||||
SecretKey string `json:"secret_key"` // 访问 S3 的密钥
|
||||
Address string `json:"address"` // S3 服务的地址
|
||||
Ssl bool `json:"ssl"` // 是否使用 SSL 加密连接
|
||||
Url string `json:"url"` // S3 服务的访问 URL
|
||||
BucketName string `json:"bucket_name"` // 默认存储桶名称
|
||||
AccessKey string `json:"access_key"` // 访问 S3 的密钥 ID
|
||||
SecretKey string `json:"secret_key"` // 访问 S3 的密钥
|
||||
Address string `json:"address"` // S3 服务的地址
|
||||
Ssl bool `json:"ssl"` // 是否使用 SSL 加密连接
|
||||
Url string `json:"url"` // S3 服务的访问 URL
|
||||
BucketName string `json:"bucket_name"` // 默认存储桶名称
|
||||
BucketNameCdn string `json:"bucket_name_cdn"` // CDN 存储桶名称
|
||||
Provider string `json:"provider"` // S3 服务的提供方
|
||||
}
|
||||
|
||||
// Mod 定义了 S3 模块的结构体,包含一个 S3 客户端实例和配置信息
|
||||
@@ -64,6 +65,7 @@ func New(_name ...string) *Mod {
|
||||
&minio.Options{
|
||||
Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""),
|
||||
Secure: cfg.Ssl,
|
||||
//BucketLookup: minio.BucketLookupPath,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -133,11 +135,13 @@ func (s *Mod) ListBuckets() []minio.BucketInfo {
|
||||
func (s *Mod) PutObject(f io.Reader, name string, bucketName string, _size ...int64) (res minio.UploadInfo, err error) {
|
||||
// 初始化文件大小为 -1,表示将读取文件至结束
|
||||
var size = int64(-1)
|
||||
if len(_size) > 0 {
|
||||
size = _size[0]
|
||||
}
|
||||
//if len(_size) > 0 {
|
||||
// size = _size[0]
|
||||
//}
|
||||
// 调用 S3 客户端上传文件,设置内容类型为 "application/octet-stream"
|
||||
res, err = s.client.PutObject(ctx, bucketName, name, f, size, minio.PutObjectOptions{ContentType: "application/octet-stream"})
|
||||
res, err = s.client.PutObject(ctx, bucketName, name, f, size, minio.PutObjectOptions{
|
||||
//ContentType: "application/octet-stream",
|
||||
})
|
||||
if err != nil {
|
||||
// 记录上传错误日志
|
||||
g.Log().Error(ctx, err)
|
||||
@@ -148,6 +152,7 @@ func (s *Mod) PutObject(f io.Reader, name string, bucketName string, _size ...in
|
||||
// RemoveObject 从指定存储桶中删除指定名称的文件
|
||||
func (s *Mod) RemoveObject(name string, bucketName string) (err error) {
|
||||
opts := minio.RemoveObjectOptions{
|
||||
ForceDelete: true,
|
||||
//GovernanceBypass: true,
|
||||
//VersionID: "myversionid",
|
||||
}
|
||||
@@ -187,9 +192,15 @@ func (s *Mod) GetUrl(filePath string, defaultFile ...string) (url string) {
|
||||
filePath = defaultFile[0]
|
||||
}
|
||||
|
||||
if s.cfg.Ssl {
|
||||
url = get + filePath
|
||||
} else {
|
||||
//switch s.cfg.Provider {
|
||||
//case "qiniu":
|
||||
// url = get + path.Join(bucketName, filePath)
|
||||
//default:
|
||||
// url = get + filePath
|
||||
//}
|
||||
url = get + filePath
|
||||
|
||||
if !s.cfg.Ssl {
|
||||
url = get + path.Join(bucketName, filePath)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user