mirror of
https://github.com/GMWalletApp/epusdt.git
synced 2026-07-07 18:26:16 +00:00
add epay route support
This commit is contained in:
@@ -64,3 +64,8 @@ tron_grid_api_key=
|
|||||||
solana_rpc_url=
|
solana_rpc_url=
|
||||||
|
|
||||||
ethereum_ws_url=wss://ethereum.publicnode.com
|
ethereum_ws_url=wss://ethereum.publicnode.com
|
||||||
|
|
||||||
|
|
||||||
|
# epay
|
||||||
|
epay_pid=
|
||||||
|
epay_key=
|
||||||
|
|||||||
@@ -340,3 +340,11 @@ func GetSolanaRpcUrl() string {
|
|||||||
func GetEthereumWsUrl() string {
|
func GetEthereumWsUrl() string {
|
||||||
return strings.TrimSpace(viper.GetString("ethereum_ws_url"))
|
return strings.TrimSpace(viper.GetString("ethereum_ws_url"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetEpayPid() int {
|
||||||
|
return viper.GetInt("epay_pid")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEpayKey() string {
|
||||||
|
return viper.GetString("epay_key")
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package comm
|
package comm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/assimon/luuu/model/request"
|
"github.com/assimon/luuu/model/request"
|
||||||
"github.com/assimon/luuu/model/service"
|
"github.com/assimon/luuu/model/service"
|
||||||
"github.com/assimon/luuu/util/constant"
|
"github.com/assimon/luuu/util/constant"
|
||||||
@@ -22,3 +25,29 @@ func (c *BaseCommController) CreateTransaction(ctx echo.Context) (err error) {
|
|||||||
}
|
}
|
||||||
return c.SucJson(ctx, resp)
|
return c.SucJson(ctx, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *BaseCommController) CreateTransactionAndRedirect(ctx echo.Context) (err error) {
|
||||||
|
req := new(request.CreateTransactionRequest)
|
||||||
|
if err = ctx.Bind(req); err != nil {
|
||||||
|
log.Println("bind request error:", err)
|
||||||
|
return c.FailJson(ctx, constant.ParamsMarshalErr)
|
||||||
|
}
|
||||||
|
if err = c.ValidateStruct(ctx, req); err != nil {
|
||||||
|
log.Println("validate request error:", err)
|
||||||
|
return c.FailJson(ctx, err)
|
||||||
|
}
|
||||||
|
resp, err := service.CreateTransaction(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("create transaction error:", err)
|
||||||
|
return c.FailJson(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("create transaction response: %+v\n", resp)
|
||||||
|
|
||||||
|
tradeID := resp.TradeId
|
||||||
|
|
||||||
|
ctx.Redirect(302, "/pay/checkout-counter/"+tradeID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,9 +8,13 @@ const (
|
|||||||
CallBackConfirmNo = 2
|
CallBackConfirmNo = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PaymentTypeEpay = "Epay"
|
||||||
|
)
|
||||||
|
|
||||||
type Orders struct {
|
type Orders struct {
|
||||||
TradeId string `gorm:"column:trade_id;uniqueIndex:orders_trade_id_uindex" json:"trade_id"`
|
TradeId string `gorm:"column:trade_id;uniqueIndex:orders_trade_id_uindex" json:"trade_id"`
|
||||||
OrderId string `gorm:"column:order_id;uniqueIndex:orders_order_id_uindex" json:"order_id"`
|
OrderId string `gorm:"column:order_id;uniqueIndex:orders_order_id_uindex" json:"order_id"` // the order id is generated by client, and will notify client when order is paid
|
||||||
BlockTransactionId string `gorm:"index:orders_block_transaction_id_index;column:block_transaction_id" json:"block_transaction_id"`
|
BlockTransactionId string `gorm:"index:orders_block_transaction_id_index;column:block_transaction_id" json:"block_transaction_id"`
|
||||||
Amount float64 `gorm:"column:amount" json:"amount"`
|
Amount float64 `gorm:"column:amount" json:"amount"`
|
||||||
Currency string `gorm:"column:currency" json:"currency"`
|
Currency string `gorm:"column:currency" json:"currency"`
|
||||||
@@ -21,8 +25,10 @@ type Orders struct {
|
|||||||
Status int `gorm:"column:status;default:1" json:"status"`
|
Status int `gorm:"column:status;default:1" json:"status"`
|
||||||
NotifyUrl string `gorm:"column:notify_url" json:"notify_url"`
|
NotifyUrl string `gorm:"column:notify_url" json:"notify_url"`
|
||||||
RedirectUrl string `gorm:"column:redirect_url" json:"redirect_url"`
|
RedirectUrl string `gorm:"column:redirect_url" json:"redirect_url"`
|
||||||
|
Name string `gorm:"column:name" json:"name"`
|
||||||
CallbackNum int `gorm:"column:callback_num;default:0" json:"callback_num"`
|
CallbackNum int `gorm:"column:callback_num;default:0" json:"callback_num"`
|
||||||
CallBackConfirm int `gorm:"column:callback_confirm;default:2" json:"callback_confirm"`
|
CallBackConfirm int `gorm:"column:callback_confirm;default:2" json:"callback_confirm"`
|
||||||
|
PaymentType string `gorm:"column:payment_type" json:"payment_type"`
|
||||||
BaseModel
|
BaseModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ type CreateTransactionRequest struct {
|
|||||||
NotifyUrl string `json:"notify_url" validate:"required"`
|
NotifyUrl string `json:"notify_url" validate:"required"`
|
||||||
Signature string `json:"signature" validate:"required"`
|
Signature string `json:"signature" validate:"required"`
|
||||||
RedirectUrl string `json:"redirect_url"`
|
RedirectUrl string `json:"redirect_url"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
PaymentType string `json:"payment_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r CreateTransactionRequest) Translates() map[string]string {
|
func (r CreateTransactionRequest) Translates() map[string]string {
|
||||||
|
|||||||
@@ -25,3 +25,16 @@ type OrderNotifyResponse struct {
|
|||||||
Signature string `json:"signature"` // 签名
|
Signature string `json:"signature"` // 签名
|
||||||
Status int `json:"status"` // 1:等待支付,2:支付成功,3:已过期
|
Status int `json:"status"` // 1:等待支付,2:支付成功,3:已过期
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OrderNotifyResponseEpay epay订单异步回调结构体
|
||||||
|
type OrderNotifyResponseEpay struct {
|
||||||
|
PID int `json:"pid"` // 商户ID
|
||||||
|
TradeNo string `json:"trade_no"` // 平台订单号
|
||||||
|
OutTradeNo string `json:"out_trade_no"` // 商户订单号
|
||||||
|
Type string `json:"type"` // 订单类型
|
||||||
|
Name string `json:"name"` // 商品名称
|
||||||
|
Money string `json:"money"` // 订单金额,保留4位小数
|
||||||
|
Sign string `json:"sign"` // 签名
|
||||||
|
SignType string `json:"sign_type"` // 签名类型 // MD5
|
||||||
|
TradeStatus string `json:"trade_status"` // 订单状态 // only has "TRADE_SUCCESS" for now
|
||||||
|
}
|
||||||
|
|||||||
@@ -95,6 +95,8 @@ func CreateTransaction(req *request.CreateTransactionRequest) (*response.CreateT
|
|||||||
Status: mdb.StatusWaitPay,
|
Status: mdb.StatusWaitPay,
|
||||||
NotifyUrl: req.NotifyUrl,
|
NotifyUrl: req.NotifyUrl,
|
||||||
RedirectUrl: req.RedirectUrl,
|
RedirectUrl: req.RedirectUrl,
|
||||||
|
Name: req.Name,
|
||||||
|
PaymentType: req.PaymentType,
|
||||||
}
|
}
|
||||||
if err = data.CreateOrderWithTransaction(tx, order); err != nil {
|
if err = data.CreateOrderWithTransaction(tx, order); err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
|
|||||||
+81
-28
@@ -2,7 +2,10 @@ package mq
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -162,36 +165,86 @@ func processCallback(tradeID string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sendOrderCallback(order *mdb.Orders) error {
|
func sendOrderCallback(order *mdb.Orders) error {
|
||||||
client := http_client.GetHttpClient()
|
|
||||||
orderResp := response.OrderNotifyResponse{
|
|
||||||
TradeId: order.TradeId,
|
|
||||||
OrderId: order.OrderId,
|
|
||||||
Amount: order.Amount,
|
|
||||||
ActualAmount: order.ActualAmount,
|
|
||||||
ReceiveAddress: order.ReceiveAddress,
|
|
||||||
Token: order.Token,
|
|
||||||
BlockTransactionId: order.BlockTransactionId,
|
|
||||||
Status: mdb.StatusPaySuccess,
|
|
||||||
}
|
|
||||||
signature, err := sign.Get(orderResp, config.GetApiAuthToken())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
orderResp.Signature = signature
|
|
||||||
|
|
||||||
resp, err := client.R().
|
switch order.PaymentType {
|
||||||
SetHeader("powered-by", "Epusdt(https://github.com/GMwalletApp/epusdt)").
|
case mdb.PaymentTypeEpay:
|
||||||
SetBody(orderResp).
|
// 构造 EPay 标准回调参数
|
||||||
Post(order.NotifyUrl)
|
notifyData := response.OrderNotifyResponseEpay{
|
||||||
if err != nil {
|
PID: config.GetEpayPid(),
|
||||||
return err
|
TradeNo: order.TradeId, // epusdt 订单号作为 EPay 平台订单号
|
||||||
}
|
OutTradeNo: order.OrderId, // 注意:EPay 回调要求商户订单号使用 out_trade_no 参数
|
||||||
if resp.StatusCode() != http.StatusOK {
|
|
||||||
return errors.New(resp.Status())
|
Type: "alipay",
|
||||||
}
|
Name: order.Name,
|
||||||
if string(resp.Body()) != "ok" {
|
Money: fmt.Sprintf("%.4f", order.Amount),
|
||||||
return errors.New("not ok")
|
TradeStatus: "TRADE_SUCCESS",
|
||||||
|
}
|
||||||
|
|
||||||
|
signstr2, err := sign.Get(notifyData, config.GetEpayKey())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 form-encoded POST(EPay 标准协议格式)
|
||||||
|
formData := url.Values{
|
||||||
|
"pid": {fmt.Sprintf("%d", notifyData.PID)},
|
||||||
|
"trade_no": {notifyData.TradeNo},
|
||||||
|
"out_trade_no": {notifyData.OutTradeNo},
|
||||||
|
"type": {notifyData.Type},
|
||||||
|
"name": {notifyData.Name},
|
||||||
|
"money": {notifyData.Money},
|
||||||
|
"trade_status": {notifyData.TradeStatus},
|
||||||
|
"sign": {signstr2},
|
||||||
|
"sign_type": {"MD5"},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.PostForm(order.NotifyUrl, formData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
responseBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("notify_url response status: %d, body: %s\n", resp.StatusCode, string(responseBody))
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
client := http_client.GetHttpClient()
|
||||||
|
orderResp := response.OrderNotifyResponse{
|
||||||
|
TradeId: order.TradeId,
|
||||||
|
OrderId: order.OrderId,
|
||||||
|
Amount: order.Amount,
|
||||||
|
ActualAmount: order.ActualAmount,
|
||||||
|
ReceiveAddress: order.ReceiveAddress,
|
||||||
|
Token: order.Token,
|
||||||
|
BlockTransactionId: order.BlockTransactionId,
|
||||||
|
Status: mdb.StatusPaySuccess,
|
||||||
|
}
|
||||||
|
signature, err := sign.Get(orderResp, config.GetApiAuthToken())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
orderResp.Signature = signature
|
||||||
|
|
||||||
|
resp, err := client.R().
|
||||||
|
SetHeader("powered-by", "Epusdt(https://github.com/GMwalletApp/epusdt)").
|
||||||
|
SetBody(orderResp).
|
||||||
|
Post(order.NotifyUrl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.StatusCode() != http.StatusOK {
|
||||||
|
return errors.New(resp.Status())
|
||||||
|
}
|
||||||
|
if string(resp.Body()) != "ok" {
|
||||||
|
return errors.New("not ok")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,17 @@ package route
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/assimon/luuu/config"
|
||||||
"github.com/assimon/luuu/controller/comm"
|
"github.com/assimon/luuu/controller/comm"
|
||||||
"github.com/assimon/luuu/middleware"
|
"github.com/assimon/luuu/middleware"
|
||||||
|
"github.com/assimon/luuu/model/mdb"
|
||||||
|
"github.com/assimon/luuu/util/constant"
|
||||||
|
"github.com/assimon/luuu/util/sign"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,6 +55,7 @@ func RegisterRoute(e *echo.Echo) {
|
|||||||
return comm.Ctrl.FailJson(ctx, err)
|
return comm.Ctrl.FailJson(ctx, err)
|
||||||
}
|
}
|
||||||
ctx.Request().Body = io.NopCloser(bytes.NewBuffer(jsonBytes))
|
ctx.Request().Body = io.NopCloser(bytes.NewBuffer(jsonBytes))
|
||||||
|
ctx.Request().ContentLength = int64(len(jsonBytes))
|
||||||
|
|
||||||
return comm.Ctrl.CreateTransaction(ctx)
|
return comm.Ctrl.CreateTransaction(ctx)
|
||||||
}, middleware.CheckApiSign())
|
}, middleware.CheckApiSign())
|
||||||
@@ -64,4 +71,66 @@ func RegisterRoute(e *echo.Echo) {
|
|||||||
walletV1.GET("/:id", comm.Ctrl.GetWallet)
|
walletV1.GET("/:id", comm.Ctrl.GetWallet)
|
||||||
walletV1.POST("/:id/status", comm.Ctrl.ChangeWalletStatus)
|
walletV1.POST("/:id/status", comm.Ctrl.ChangeWalletStatus)
|
||||||
walletV1.POST("/:id/delete", comm.Ctrl.DeleteWallet)
|
walletV1.POST("/:id/delete", comm.Ctrl.DeleteWallet)
|
||||||
|
|
||||||
|
// epay v1 routes
|
||||||
|
epayV1 := paymentRoute.Group("/epay/v1")
|
||||||
|
epayV1.GET("/order/create-transaction/submit.php", func(ctx echo.Context) error {
|
||||||
|
money := ctx.QueryParam("money")
|
||||||
|
name := ctx.QueryParam("name")
|
||||||
|
notifyURL := ctx.QueryParam("notify_url")
|
||||||
|
outTradeNo := ctx.QueryParam("out_trade_no")
|
||||||
|
returnURL := ctx.QueryParam("return_url")
|
||||||
|
signstr := ctx.QueryParam("sign")
|
||||||
|
// signType := ctx.QueryParam("sign_type")
|
||||||
|
|
||||||
|
m := map[string]interface{}{
|
||||||
|
"money": money,
|
||||||
|
"name": name,
|
||||||
|
"notify_url": notifyURL,
|
||||||
|
"out_trade_no": outTradeNo,
|
||||||
|
"pid": config.GetEpayPid(), // 注意:验签时需要包含 pid 参数
|
||||||
|
"return_url": returnURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSignature, err := sign.Get(m, config.GetApiAuthToken())
|
||||||
|
if err != nil {
|
||||||
|
return constant.SignatureErr
|
||||||
|
}
|
||||||
|
if checkSignature != signstr {
|
||||||
|
return constant.SignatureErr
|
||||||
|
}
|
||||||
|
|
||||||
|
amountFloat, err := strconv.ParseFloat(money, 64)
|
||||||
|
if err != nil {
|
||||||
|
return comm.Ctrl.FailJson(ctx, fmt.Errorf("invalid money value: %s", money))
|
||||||
|
}
|
||||||
|
|
||||||
|
body := map[string]interface{}{
|
||||||
|
"token": "usdt",
|
||||||
|
"currency": "cny",
|
||||||
|
"network": "tron",
|
||||||
|
"amount": amountFloat,
|
||||||
|
"notify_url": notifyURL,
|
||||||
|
"order_id": outTradeNo,
|
||||||
|
"redirect_url": returnURL,
|
||||||
|
"signature": signstr,
|
||||||
|
"name": name,
|
||||||
|
"payment_type": mdb.PaymentTypeEpay,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Set("request_body", body)
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return comm.Ctrl.FailJson(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Request().Body = io.NopCloser(bytes.NewBuffer(jsonBytes))
|
||||||
|
ctx.Request().ContentLength = int64(len(jsonBytes))
|
||||||
|
ctx.Request().Method = http.MethodPost
|
||||||
|
ctx.Request().Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
return comm.Ctrl.CreateTransactionAndRedirect(ctx)
|
||||||
|
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user