mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-11-16 21:23:44 +08:00
80
payment/gateway/epay/client.go
Normal file
80
payment/gateway/epay/client.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package epay
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
PayDomain string `json:"pay_domain"`
|
||||
PartnerID string `json:"partner_id"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// FormPay 表单支付
|
||||
func (c *Client) FormPay(args *PayArgs) (string, map[string]string, error) {
|
||||
formPayArgs := map[string]string{
|
||||
"type": string(args.Type),
|
||||
"pid": c.PartnerID,
|
||||
"out_trade_no": args.OutTradeNo,
|
||||
"notify_url": args.NotifyUrl,
|
||||
"return_url": args.ReturnUrl,
|
||||
"name": args.Name,
|
||||
"money": args.Money,
|
||||
// "device": string(args.Device),
|
||||
}
|
||||
|
||||
formPayArgs["sign"] = c.Sign(formPayArgs)
|
||||
formPayArgs["sign_type"] = FormArgsSignType
|
||||
|
||||
domain := strings.TrimSuffix(c.PayDomain, "/")
|
||||
|
||||
return domain + FormSubmitUrl, formPayArgs, nil
|
||||
|
||||
}
|
||||
|
||||
func (c *Client) Verify(params map[string]string) (*PaymentResult, bool) {
|
||||
sign := params["sign"]
|
||||
tradeStatus := params["trade_status"]
|
||||
|
||||
if sign == "" || tradeStatus != TradeStatusSuccess {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if sign != c.Sign(params) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var paymentResult PaymentResult
|
||||
mapstructure.Decode(params, &paymentResult)
|
||||
|
||||
return &paymentResult, true
|
||||
}
|
||||
|
||||
// Sign 签名
|
||||
func (c *Client) Sign(args map[string]string) string {
|
||||
keys := make([]string, 0, len(args))
|
||||
for k := range args {
|
||||
if k != "sign" && k != "sign_type" && args[k] != "" {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
signStrs := make([]string, len(keys))
|
||||
for i, k := range keys {
|
||||
signStrs[i] = k + "=" + args[k]
|
||||
}
|
||||
|
||||
signStr := strings.Join(signStrs, "&") + c.Key
|
||||
|
||||
h := md5.New()
|
||||
h.Write([]byte(signStr))
|
||||
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
91
payment/gateway/epay/payment.go
Normal file
91
payment/gateway/epay/payment.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package epay
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"one-api/payment/types"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Epay struct{}
|
||||
|
||||
type EpayConfig struct {
|
||||
PayType PayType `json:"pay_type"`
|
||||
Client
|
||||
}
|
||||
|
||||
func (e *Epay) Name() string {
|
||||
return "易支付"
|
||||
}
|
||||
|
||||
func (e *Epay) Pay(config *types.PayConfig, gatewayConfig string) (*types.PayRequest, error) {
|
||||
epayConfig, err := getEpayConfig(gatewayConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payArgs := &PayArgs{
|
||||
Type: epayConfig.PayType,
|
||||
OutTradeNo: config.TradeNo,
|
||||
NotifyUrl: config.NotifyURL,
|
||||
ReturnUrl: config.ReturnURL,
|
||||
Name: config.TradeNo,
|
||||
Money: strconv.FormatFloat(config.Money, 'f', 2, 64),
|
||||
}
|
||||
|
||||
formPayURL, formPayArgs, err := epayConfig.FormPay(payArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payRequest := &types.PayRequest{
|
||||
Type: 1,
|
||||
Data: types.PayRequestData{
|
||||
URL: formPayURL,
|
||||
Params: &formPayArgs,
|
||||
Method: http.MethodPost,
|
||||
},
|
||||
}
|
||||
|
||||
return payRequest, nil
|
||||
}
|
||||
|
||||
func (e *Epay) HandleCallback(c *gin.Context, gatewayConfig string) (*types.PayNotify, error) {
|
||||
queryMap := make(map[string]string)
|
||||
if err := c.ShouldBindQuery(&queryMap); err != nil {
|
||||
c.Writer.Write([]byte("fail"))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
epayConfig, err := getEpayConfig(gatewayConfig)
|
||||
if err != nil {
|
||||
c.Writer.Write([]byte("fail"))
|
||||
return nil, fmt.Errorf("tradeNo: %s, PaymentNo: %s, err: %v", queryMap["out_trade_no"], queryMap["trade_no"], err)
|
||||
}
|
||||
|
||||
paymentResult, success := epayConfig.Verify(queryMap)
|
||||
if paymentResult != nil && success {
|
||||
c.Writer.Write([]byte("success"))
|
||||
payNotify := &types.PayNotify{
|
||||
TradeNo: paymentResult.OutTradeNo,
|
||||
GatewayNo: paymentResult.TradeNo,
|
||||
}
|
||||
return payNotify, nil
|
||||
}
|
||||
|
||||
c.Writer.Write([]byte("fail"))
|
||||
return nil, fmt.Errorf("tradeNo: %s, PaymentNo: %s, Verify Sign failed", queryMap["out_trade_no"], queryMap["trade_no"])
|
||||
}
|
||||
|
||||
func getEpayConfig(gatewayConfig string) (*EpayConfig, error) {
|
||||
var epayConfig EpayConfig
|
||||
if err := json.Unmarshal([]byte(gatewayConfig), &epayConfig); err != nil {
|
||||
return nil, errors.New("config error")
|
||||
}
|
||||
|
||||
return &epayConfig, nil
|
||||
}
|
||||
45
payment/gateway/epay/type.go
Normal file
45
payment/gateway/epay/type.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package epay
|
||||
|
||||
type PayType string
|
||||
|
||||
var (
|
||||
EpayPay PayType = "" // 网关
|
||||
Alipay PayType = "alipay" // 支付宝
|
||||
Wechat PayType = "wxpay" // 微信
|
||||
QQ PayType = "qqpay" // QQ
|
||||
Bank PayType = "bank" // 银行
|
||||
JD PayType = "jdpay" // 京东
|
||||
PayPal PayType = "paypal" // PayPal
|
||||
USDT PayType = "usdt" // USDT
|
||||
)
|
||||
|
||||
// type DeviceType string
|
||||
|
||||
// var (
|
||||
// PC DeviceType = "pc" // PC
|
||||
// Mobile DeviceType = "mobile" // 移动端
|
||||
// )
|
||||
|
||||
const (
|
||||
FormArgsSignType = "MD5"
|
||||
FormSubmitUrl = "/submit.php"
|
||||
TradeStatusSuccess = "TRADE_SUCCESS"
|
||||
)
|
||||
|
||||
type PayArgs struct {
|
||||
Type PayType `json:"type,omitempty"`
|
||||
OutTradeNo string `json:"out_trade_no"`
|
||||
NotifyUrl string `json:"notify_url"`
|
||||
ReturnUrl string `json:"return_url"`
|
||||
Name string `json:"name"`
|
||||
Money string `json:"money"`
|
||||
}
|
||||
|
||||
type PaymentResult struct {
|
||||
Type PayType `mapstructure:"type"`
|
||||
TradeNo string `mapstructure:"trade_no"`
|
||||
OutTradeNo string `mapstructure:"out_trade_no"`
|
||||
Name string `mapstructure:"name"`
|
||||
Money string `mapstructure:"money"`
|
||||
TradeStatus string `mapstructure:"trade_status"`
|
||||
}
|
||||
20
payment/payment.go
Normal file
20
payment/payment.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"one-api/payment/gateway/epay"
|
||||
"one-api/payment/types"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type PaymentProcessor interface {
|
||||
Name() string
|
||||
Pay(config *types.PayConfig, gatewayConfig string) (*types.PayRequest, error)
|
||||
HandleCallback(c *gin.Context, gatewayConfig string) (*types.PayNotify, error)
|
||||
}
|
||||
|
||||
var Gateways = make(map[string]PaymentProcessor)
|
||||
|
||||
func init() {
|
||||
Gateways["epay"] = &epay.Epay{}
|
||||
}
|
||||
81
payment/service.go
Normal file
81
payment/service.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"one-api/common/config"
|
||||
"one-api/common/logger"
|
||||
"one-api/model"
|
||||
"one-api/payment/types"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type PaymentService struct {
|
||||
Payment *model.Payment
|
||||
gateway PaymentProcessor
|
||||
}
|
||||
|
||||
type PayMoney struct {
|
||||
Amount float64
|
||||
Currency model.CurrencyType
|
||||
}
|
||||
|
||||
func NewPaymentService(uuid string) (*PaymentService, error) {
|
||||
payment, err := model.GetPaymentByUUID(uuid)
|
||||
if err != nil {
|
||||
return nil, errors.New("payment not found")
|
||||
}
|
||||
|
||||
gateway, ok := Gateways[payment.Type]
|
||||
if !ok {
|
||||
return nil, errors.New("payment gateway not found")
|
||||
}
|
||||
|
||||
return &PaymentService{
|
||||
Payment: payment,
|
||||
gateway: gateway,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *PaymentService) Pay(tradeNo string, amount float64) (*types.PayRequest, error) {
|
||||
config := &types.PayConfig{
|
||||
Money: amount,
|
||||
TradeNo: tradeNo,
|
||||
NotifyURL: s.getNotifyURL(),
|
||||
ReturnURL: s.getReturnURL(),
|
||||
}
|
||||
|
||||
payRequest, err := s.gateway.Pay(config, s.Payment.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return payRequest, nil
|
||||
}
|
||||
|
||||
func (s *PaymentService) HandleCallback(c *gin.Context, gatewayConfig string) (*types.PayNotify, error) {
|
||||
payNotify, err := s.gateway.HandleCallback(c, gatewayConfig)
|
||||
if err != nil {
|
||||
logger.SysError(fmt.Sprintf("%s payment callback error: %v", s.gateway.Name(), err))
|
||||
|
||||
}
|
||||
|
||||
return payNotify, err
|
||||
}
|
||||
|
||||
func (s *PaymentService) getNotifyURL() string {
|
||||
notifyDomain := s.Payment.NotifyDomain
|
||||
if notifyDomain == "" {
|
||||
notifyDomain = config.ServerAddress
|
||||
}
|
||||
|
||||
notifyDomain = strings.TrimSuffix(notifyDomain, "/")
|
||||
return fmt.Sprintf("%s/api/payment/notify/%s", notifyDomain, s.Payment.UUID)
|
||||
}
|
||||
|
||||
func (s *PaymentService) getReturnURL() string {
|
||||
serverAdd := strings.TrimSuffix(config.ServerAddress, "/")
|
||||
return fmt.Sprintf("%s/panel/log", serverAdd)
|
||||
}
|
||||
27
payment/types/types.go
Normal file
27
payment/types/types.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package types
|
||||
|
||||
// 支付网关的通用配置
|
||||
type PayConfig struct {
|
||||
NotifyURL string `json:"notify_url"`
|
||||
ReturnURL string `json:"return_url"`
|
||||
TradeNo string `json:"trade_no"`
|
||||
Money float64 `json:"money"`
|
||||
}
|
||||
|
||||
// 请求支付时的数据结构
|
||||
type PayRequest struct {
|
||||
Type int `json:"type"` // 支付类型 1 url 2 qrcode
|
||||
Data PayRequestData `json:"data"`
|
||||
}
|
||||
|
||||
type PayRequestData struct {
|
||||
URL string `json:"url"`
|
||||
Method string `json:"method,omitempty"`
|
||||
Params any `json:"params,omitempty"`
|
||||
}
|
||||
|
||||
// 支付回调时的数据结构
|
||||
type PayNotify struct {
|
||||
TradeNo string `json:"trade_no"`
|
||||
GatewayNo string `json:"gateway_no"`
|
||||
}
|
||||
Reference in New Issue
Block a user