add epay route support
This commit is contained in:
@@ -64,3 +64,8 @@ tron_grid_api_key=
|
||||
solana_rpc_url=
|
||||
|
||||
ethereum_ws_url=wss://ethereum.publicnode.com
|
||||
|
||||
|
||||
# epay
|
||||
epay_pid=
|
||||
epay_key=
|
||||
|
||||
@@ -340,3 +340,11 @@ func GetSolanaRpcUrl() string {
|
||||
func GetEthereumWsUrl() string {
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/assimon/luuu/model/request"
|
||||
"github.com/assimon/luuu/model/service"
|
||||
"github.com/assimon/luuu/util/constant"
|
||||
@@ -22,3 +25,29 @@ func (c *BaseCommController) CreateTransaction(ctx echo.Context) (err error) {
|
||||
}
|
||||
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
|
||||
)
|
||||
|
||||
const (
|
||||
PaymentTypeEpay = "Epay"
|
||||
)
|
||||
|
||||
type Orders struct {
|
||||
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"`
|
||||
Amount float64 `gorm:"column:amount" json:"amount"`
|
||||
Currency string `gorm:"column:currency" json:"currency"`
|
||||
@@ -21,8 +25,10 @@ type Orders struct {
|
||||
Status int `gorm:"column:status;default:1" json:"status"`
|
||||
NotifyUrl string `gorm:"column:notify_url" json:"notify_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"`
|
||||
CallBackConfirm int `gorm:"column:callback_confirm;default:2" json:"callback_confirm"`
|
||||
PaymentType string `gorm:"column:payment_type" json:"payment_type"`
|
||||
BaseModel
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ type CreateTransactionRequest struct {
|
||||
NotifyUrl string `json:"notify_url" validate:"required"`
|
||||
Signature string `json:"signature" validate:"required"`
|
||||
RedirectUrl string `json:"redirect_url"`
|
||||
Name string `json:"name"`
|
||||
PaymentType string `json:"payment_type"`
|
||||
}
|
||||
|
||||
func (r CreateTransactionRequest) Translates() map[string]string {
|
||||
|
||||
@@ -25,3 +25,16 @@ type OrderNotifyResponse struct {
|
||||
Signature string `json:"signature"` // 签名
|
||||
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,
|
||||
NotifyUrl: req.NotifyUrl,
|
||||
RedirectUrl: req.RedirectUrl,
|
||||
Name: req.Name,
|
||||
PaymentType: req.PaymentType,
|
||||
}
|
||||
if err = data.CreateOrderWithTransaction(tx, order); err != nil {
|
||||
tx.Rollback()
|
||||
|
||||
+81
-28
@@ -2,7 +2,10 @@ package mq
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -162,36 +165,86 @@ func processCallback(tradeID string) {
|
||||
}
|
||||
|
||||
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().
|
||||
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")
|
||||
switch order.PaymentType {
|
||||
case mdb.PaymentTypeEpay:
|
||||
// 构造 EPay 标准回调参数
|
||||
notifyData := response.OrderNotifyResponseEpay{
|
||||
PID: config.GetEpayPid(),
|
||||
TradeNo: order.TradeId, // epusdt 订单号作为 EPay 平台订单号
|
||||
OutTradeNo: order.OrderId, // 注意:EPay 回调要求商户订单号使用 out_trade_no 参数
|
||||
|
||||
Type: "alipay",
|
||||
Name: order.Name,
|
||||
Money: fmt.Sprintf("%.4f", order.Amount),
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,17 @@ package route
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/assimon/luuu/config"
|
||||
"github.com/assimon/luuu/controller/comm"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -49,6 +55,7 @@ func RegisterRoute(e *echo.Echo) {
|
||||
return comm.Ctrl.FailJson(ctx, err)
|
||||
}
|
||||
ctx.Request().Body = io.NopCloser(bytes.NewBuffer(jsonBytes))
|
||||
ctx.Request().ContentLength = int64(len(jsonBytes))
|
||||
|
||||
return comm.Ctrl.CreateTransaction(ctx)
|
||||
}, middleware.CheckApiSign())
|
||||
@@ -64,4 +71,66 @@ func RegisterRoute(e *echo.Echo) {
|
||||
walletV1.GET("/:id", comm.Ctrl.GetWallet)
|
||||
walletV1.POST("/:id/status", comm.Ctrl.ChangeWalletStatus)
|
||||
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