mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-07 16:16:37 +08:00
压缩历史至 95081aff
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
Copyright 2025 linux.do
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
package payment
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/linux-do/credit/internal/apps/oauth"
|
||||
"github.com/linux-do/credit/internal/common"
|
||||
"github.com/linux-do/credit/internal/db"
|
||||
"github.com/linux-do/credit/internal/model"
|
||||
"github.com/linux-do/credit/internal/service"
|
||||
"github.com/linux-do/credit/internal/util"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// HandleParseOrderNoError 处理 ParseOrderNo 返回的错误,返回对应的 HTTP 响应
|
||||
func HandleParseOrderNoError(c *gin.Context, err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
errMsg := err.Error()
|
||||
switch errMsg {
|
||||
case OrderNotFound:
|
||||
c.JSON(http.StatusNotFound, util.Err(errMsg))
|
||||
case MerchantInfoNotFound, PayConfigNotFound:
|
||||
c.JSON(http.StatusInternalServerError, util.Err(errMsg))
|
||||
case common.CannotPaySelf, common.TestModeCannotProcessOrder, OrderNoFormatError:
|
||||
c.JSON(http.StatusBadRequest, util.Err(errMsg))
|
||||
case common.UnAuthorized:
|
||||
c.JSON(http.StatusUnauthorized, util.Err(errMsg))
|
||||
default:
|
||||
c.JSON(http.StatusInternalServerError, util.Err(errMsg))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// OrderContext 订单上下文信息
|
||||
type OrderContext struct {
|
||||
OrderID uint64
|
||||
MerchantUser *model.User
|
||||
CurrentUser *model.User
|
||||
PayerPayConfig *model.UserPayConfig
|
||||
MerchantPayConfig *model.UserPayConfig
|
||||
MerchantAPIKey *model.MerchantAPIKey
|
||||
}
|
||||
|
||||
// ParseOrderNo 解析订单号,获取订单上下文信息
|
||||
func ParseOrderNo(c *gin.Context, orderNo string) (*OrderContext, error) {
|
||||
merchantIDStr, errGet := db.Redis.Get(c.Request.Context(), db.PrefixedKey(fmt.Sprintf(OrderMerchantIDCacheKeyFormat, orderNo))).Result()
|
||||
if errGet != nil {
|
||||
if errors.Is(errGet, redis.Nil) {
|
||||
return nil, errors.New(OrderNotFound)
|
||||
}
|
||||
return nil, errGet
|
||||
}
|
||||
|
||||
merchantID, errParse := strconv.ParseUint(merchantIDStr, 10, 64)
|
||||
if errParse != nil {
|
||||
return nil, errors.New(OrderNoFormatError)
|
||||
}
|
||||
|
||||
// 获取商户用户信息
|
||||
var merchantUser model.User
|
||||
if err := db.DB(c.Request.Context()).Where("id = ? AND is_active = ?", merchantID, true).First(&merchantUser).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New(MerchantInfoNotFound)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
currentUser, _ := util.GetFromContext[*model.User](c, oauth.UserObjKey)
|
||||
|
||||
orderNoStr, errDecrypt := util.Decrypt(merchantUser.SignKey, orderNo)
|
||||
if errDecrypt != nil {
|
||||
return nil, errors.New(OrderNoFormatError)
|
||||
}
|
||||
|
||||
orderID, errParse := strconv.ParseUint(orderNoStr, 10, 64)
|
||||
if errParse != nil {
|
||||
return nil, errors.New(OrderNoFormatError)
|
||||
}
|
||||
|
||||
var apiKey model.MerchantAPIKey
|
||||
if err := db.DB(c.Request.Context()).
|
||||
Where("client_id = (SELECT client_id FROM orders WHERE id = ?)", orderID).
|
||||
First(&apiKey).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New(OrderNotFound)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 验证测试模式下的支付权限
|
||||
if err := service.ValidateTestModePayment(currentUser.ID, merchantUser.ID, apiKey.TestMode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx := &OrderContext{
|
||||
OrderID: orderID,
|
||||
MerchantUser: &merchantUser,
|
||||
CurrentUser: currentUser,
|
||||
MerchantAPIKey: &apiKey,
|
||||
}
|
||||
|
||||
// 获取付款用户的支付配置(用于限额检查)
|
||||
var payerPayConfig model.UserPayConfig
|
||||
if err := payerPayConfig.GetByPayScore(db.DB(c.Request.Context()), currentUser.PayScore); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New(PayConfigNotFound)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
ctx.PayerPayConfig = &payerPayConfig
|
||||
|
||||
// 获取商家的支付配置(用于手续费倍率)
|
||||
var merchantPayConfig model.UserPayConfig
|
||||
if err := merchantPayConfig.GetByPayScore(db.DB(c.Request.Context()), merchantUser.PayScore); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New(PayConfigNotFound)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
ctx.MerchantPayConfig = &merchantPayConfig
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// GenerateSignature 生成签名
|
||||
func GenerateSignature(params map[string]string, secret string, isMD5 bool) string {
|
||||
// 按key排序
|
||||
keys := make([]string, 0, len(params))
|
||||
for k := range params {
|
||||
if k == "sign" || k == "sign_type" {
|
||||
continue
|
||||
}
|
||||
// 空值不参与签名
|
||||
if params[k] == "" {
|
||||
continue
|
||||
}
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
// 拼接签名字符串
|
||||
var builder strings.Builder
|
||||
builder.Grow(256)
|
||||
for i, k := range keys {
|
||||
if i > 0 {
|
||||
builder.WriteByte('&')
|
||||
}
|
||||
builder.WriteString(k)
|
||||
builder.WriteByte('=')
|
||||
builder.WriteString(params[k])
|
||||
}
|
||||
builder.WriteString(secret)
|
||||
|
||||
if isMD5 {
|
||||
// MD5加密
|
||||
hash := md5.Sum([]byte(builder.String()))
|
||||
return fmt.Sprintf("%x", hash)
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// VerifySignatureMD5 验证MD5签名
|
||||
func VerifySignatureMD5(c *gin.Context, apiKey *model.MerchantAPIKey) (*CreateOrderRequest, error) {
|
||||
var req EPayRequest
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 验证金额
|
||||
if err := util.ValidateAmount(req.Amount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := apiKey.GetByClientID(db.DB(c.Request.Context()), req.ClientID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 构建签名参数
|
||||
params := map[string]string{
|
||||
"pid": req.ClientID,
|
||||
"type": req.PayType,
|
||||
"out_trade_no": util.DerefString(req.MerchantOrderNo),
|
||||
"notify_url": req.NotifyURL,
|
||||
"return_url": req.ReturnURL,
|
||||
"name": req.OrderName,
|
||||
"device": req.Device,
|
||||
}
|
||||
|
||||
params["money"] = req.Amount.Truncate(2).StringFixed(2)
|
||||
expectedSignFixed := GenerateSignature(params, apiKey.ClientSecret, true)
|
||||
|
||||
params["money"] = req.Amount.Truncate(2).String()
|
||||
expectedSignTrimmed := GenerateSignature(params, apiKey.ClientSecret, true)
|
||||
|
||||
matchFixed := subtle.ConstantTimeCompare([]byte(strings.ToLower(expectedSignFixed)), []byte(strings.ToLower(req.Sign))) == 1
|
||||
matchTrimmed := subtle.ConstantTimeCompare([]byte(strings.ToLower(expectedSignTrimmed)), []byte(strings.ToLower(req.Sign))) == 1
|
||||
|
||||
if !matchFixed && !matchTrimmed {
|
||||
return nil, errors.New("签名验证失败")
|
||||
}
|
||||
|
||||
return NewCreateOrderRequest(req.OrderName, req.MerchantOrderNo, req.Amount, req.PayType, req.NotifyURL, req.ReturnURL), nil
|
||||
}
|
||||
|
||||
// VerifySignatureEd25519 验证 Ed25519 签名
|
||||
func VerifySignatureEd25519(c *gin.Context, apiKey *model.MerchantAPIKey) (*CreateOrderRequest, error) {
|
||||
var req LDCPayRequest
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 验证金额
|
||||
if err := util.ValidateAmount(req.Amount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := apiKey.GetByClientID(db.DB(c.Request.Context()), req.ClientID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(apiKey.PublicKey) == 0 {
|
||||
return nil, errors.New("商户未配置公钥")
|
||||
}
|
||||
|
||||
signatureBytes, err := util.Base64Decode(req.Sign)
|
||||
if err != nil {
|
||||
return nil, errors.New("签名格式错误")
|
||||
}
|
||||
|
||||
// 构建签名参数
|
||||
params := map[string]string{
|
||||
"client_id": req.ClientID,
|
||||
"type": req.PayType,
|
||||
"out_trade_no": util.DerefString(req.MerchantOrderNo),
|
||||
"order_name": req.OrderName,
|
||||
"notify_url": req.NotifyURL,
|
||||
"return_url": req.ReturnURL,
|
||||
"money": req.Amount.Truncate(2).StringFixed(2),
|
||||
}
|
||||
|
||||
signatureParam := GenerateSignature(params, apiKey.ClientSecret, false)
|
||||
validTrimmed := util.Ed25519Verify(apiKey.PublicKey, []byte(signatureParam), signatureBytes)
|
||||
|
||||
if !validTrimmed {
|
||||
return nil, errors.New("签名验证失败")
|
||||
}
|
||||
|
||||
return NewCreateOrderRequest(req.OrderName, req.MerchantOrderNo, req.Amount, req.PayType, req.NotifyURL, req.ReturnURL), nil
|
||||
}
|
||||
Reference in New Issue
Block a user