完善支付流程,实现异步事件
This commit is contained in:
@@ -59,3 +59,4 @@ SFTPAY_ROUTE_ID=
|
|||||||
SFTPAY_APP_PRIVATE_KEY=
|
SFTPAY_APP_PRIVATE_KEY=
|
||||||
SFTPAY_PUBLIC_KEY=
|
SFTPAY_PUBLIC_KEY=
|
||||||
SFTPAY_RETURN_URL=
|
SFTPAY_RETURN_URL=
|
||||||
|
SFTPAY_NOTIFY_URL=
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ type QueryTradeResp struct {
|
|||||||
RefundState int32 `json:"refundState"`
|
RefundState int32 `json:"refundState"`
|
||||||
DrType *string `json:"drType"`
|
DrType *string `json:"drType"`
|
||||||
ExtParam *string `json:"extParam"`
|
ExtParam *string `json:"extParam"`
|
||||||
PayTime *int64 `json:"payTime"`
|
PayTime *string `json:"payTime"`
|
||||||
Subject string `json:"subject"`
|
Subject string `json:"subject"`
|
||||||
TradeFee *int64 `json:"tradeFee"`
|
TradeFee *int64 `json:"tradeFee"`
|
||||||
CashFee *int64 `json:"cashFee"`
|
CashFee *int64 `json:"cashFee"`
|
||||||
@@ -443,3 +443,11 @@ const (
|
|||||||
SftRefundIng SftTradeState = "REFUND_ING"
|
SftRefundIng SftTradeState = "REFUND_ING"
|
||||||
SftTradeClosed SftTradeState = "TRADE_CLOSED"
|
SftTradeClosed SftTradeState = "TRADE_CLOSED"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SftNotifyType string
|
||||||
|
|
||||||
|
var (
|
||||||
|
SftNotifyTypeTrade = SftNotifyType("sft.trade.notify")
|
||||||
|
SftNotifyTypeRefund = SftNotifyType("sft.refund.notify")
|
||||||
|
SftNotifyTypeDivision = SftNotifyType("sft.division.notify")
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"platform/web/auth"
|
"platform/web/auth"
|
||||||
"platform/web/core"
|
"platform/web/core"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PageTradeByAdmin 分页查询所有订单
|
// PageTradeByAdmin 分页查询所有订单
|
||||||
@@ -429,3 +431,99 @@ func TradeCheck(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
return c.JSON(result)
|
return c.JSON(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// 交易回调
|
||||||
|
func TradeCallbackSft(c *fiber.Ctx) error {
|
||||||
|
|
||||||
|
// 解析请求参数
|
||||||
|
var req TradeCallbackSftReq
|
||||||
|
if err := g.Validator.ParseBody(c, &req); err != nil {
|
||||||
|
return core.NewServErr("解析请求失败", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var bizData TradeCallbackSftReqBizData
|
||||||
|
if err := json.Unmarshal([]byte(*req.BizData), &bizData); err != nil {
|
||||||
|
return core.NewServErr("解析业务数据失败", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查通知
|
||||||
|
if bizData.MsgType != string(g.SftNotifyTypeTrade) {
|
||||||
|
return core.NewBizErr("消息类型处理异常")
|
||||||
|
}
|
||||||
|
|
||||||
|
if bizData.State != string(g.SftTradeSuccess) {
|
||||||
|
return core.NewBizErr("订单状态处理异常")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新订单状态
|
||||||
|
|
||||||
|
if bizData.PayType == nil {
|
||||||
|
return core.NewBizErr("支付渠道解析错误")
|
||||||
|
}
|
||||||
|
|
||||||
|
var acquirer m.TradeAcquirer
|
||||||
|
switch *bizData.PayType {
|
||||||
|
case string(g.SftAlipay):
|
||||||
|
acquirer = m.TradeAcquirerAlipay
|
||||||
|
case string(g.SftWeChat):
|
||||||
|
acquirer = m.TradeAcquirerWechat
|
||||||
|
case string(g.SftUnionPay):
|
||||||
|
acquirer = m.TradeAcquirerUnionPay
|
||||||
|
default:
|
||||||
|
return core.NewBizErr("支付渠道类型异常")
|
||||||
|
}
|
||||||
|
|
||||||
|
var time, err = time.Parse("2006-01-02 15:04:05", *bizData.PayTime)
|
||||||
|
if err != nil {
|
||||||
|
return core.NewBizErr("时间解析错误")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.Trade.CompleteTradeByNotify(bizData.MchOrderNo, bizData.PayOrderId, &s.TradeSuccessResult{
|
||||||
|
Acquirer: acquirer,
|
||||||
|
Actual: decimal.NewFromInt(bizData.Amount).Div(decimal.NewFromInt(100)),
|
||||||
|
Time: time,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return core.NewServErr("完成交易失败", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Send([]byte("SUCCESS"))
|
||||||
|
}
|
||||||
|
|
||||||
|
type TradeCallbackSftReq struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Msg *string `json:"msg"`
|
||||||
|
Sign *string `json:"sign"`
|
||||||
|
BizData *string `json:"bizData"`
|
||||||
|
SignType *string `json:"signType"`
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TradeCallbackSftReqBizData struct {
|
||||||
|
MsgType string `json:"msgType"`
|
||||||
|
PayOrderId string `json:"payOrderId"`
|
||||||
|
State string `json:"state"`
|
||||||
|
MercNo string `json:"mercNo"`
|
||||||
|
StoreId string `json:"storeId"`
|
||||||
|
MchOrderNo string `json:"mchOrderNo"`
|
||||||
|
IfCode string `json:"ifCode"`
|
||||||
|
Amount int64 `json:"amount"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
ChannelTradeNo *string `json:"channelTradeNo"`
|
||||||
|
ChannelSendNo *string `json:"channelSendNo"`
|
||||||
|
UserId *string `json:"userId"`
|
||||||
|
ExtParam *string `json:"extParam"`
|
||||||
|
PayTime *string `json:"payTime"`
|
||||||
|
Note *string `json:"note"`
|
||||||
|
PayType *string `json:"payType"`
|
||||||
|
DrType *string `json:"drType"`
|
||||||
|
TradeFee *int64 `json:"tradeFee"`
|
||||||
|
CashFee *int64 `json:"cashFee"`
|
||||||
|
RefundAmt *int64 `json:"refundAmt"`
|
||||||
|
RefundState *int32 `json:"refundState"`
|
||||||
|
SettlementType *string `json:"settlementType"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ func ApplyMiddlewares(app *fiber.App) {
|
|||||||
|
|
||||||
// logger
|
// logger
|
||||||
app.Use(logger.New(logger.Config{
|
app.Use(logger.New(logger.Config{
|
||||||
|
TimeFormat: "2006-01-02 15:04:05",
|
||||||
Next: func(c *fiber.Ctx) bool {
|
Next: func(c *fiber.Ctx) bool {
|
||||||
return c.Path() == "/favicon.ico"
|
return c.Path() == "/favicon.ico"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ func ApplyRouters(app *fiber.App) {
|
|||||||
// 回调
|
// 回调
|
||||||
callbacks := app.Group("/callback")
|
callbacks := app.Group("/callback")
|
||||||
callbacks.Get("/identify", handlers.IdentifyCallbackNew)
|
callbacks.Get("/identify", handlers.IdentifyCallbackNew)
|
||||||
|
callbacks.All("/trade/sft", handlers.TradeCallbackSft)
|
||||||
|
|
||||||
// 临时
|
// 临时
|
||||||
if env.RunMode == env.RunModeDev {
|
if env.RunMode == env.RunModeDev {
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ func (s *tradeService) Create(user *m.User, tradeData *CreateTradeData, productD
|
|||||||
Currency: "cny",
|
Currency: "cny",
|
||||||
ClientIp: "123.52.74.23",
|
ClientIp: "123.52.74.23",
|
||||||
OrderTimeout: u.P(expireAt.Format("2006-01-02 15:04:05")),
|
OrderTimeout: u.P(expireAt.Format("2006-01-02 15:04:05")),
|
||||||
|
NotifyUrl: &env.SftNotifyUrl,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -238,23 +239,47 @@ func (s *tradeService) CompleteTrade(user *m.User, ref *TradeRef) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取交易信息
|
||||||
|
trade, err := q.Trade.
|
||||||
|
Preload(q.Trade.User).
|
||||||
|
Where(q.Trade.InnerNo.Eq(ref.TradeNo)).
|
||||||
|
Take()
|
||||||
|
if err != nil {
|
||||||
|
return core.NewBizErr("获取交易信息失败", err)
|
||||||
|
}
|
||||||
|
if trade.User == nil || trade.User.ID != user.ID {
|
||||||
|
return core.NewBizErr("交易信息不存在")
|
||||||
|
}
|
||||||
|
|
||||||
// 更新交易状态
|
// 更新交易状态
|
||||||
err = s.OnCompleteTrade(user, ref.TradeNo, result.TransId, &result.Success)
|
err = s.OnCompleteTrade(trade, ref.TradeNo, result.TransId, &result.Success)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return core.NewServErr("处理交易失败", err)
|
return core.NewServErr("处理交易失败", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (s *tradeService) OnCompleteTrade(user *m.User, interNo string, outerNo string, result *TradeSuccessResult) error {
|
func (s *tradeService) CompleteTradeByNotify(interNo string, outerNo string, result *TradeSuccessResult) error {
|
||||||
|
|
||||||
// 获取交易信息
|
|
||||||
trade, err := q.Trade.
|
trade, err := q.Trade.
|
||||||
|
Preload(q.Trade.User).
|
||||||
Where(q.Trade.InnerNo.Eq(interNo)).
|
Where(q.Trade.InnerNo.Eq(interNo)).
|
||||||
Take()
|
Take()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return core.NewBizErr("获取交易信息失败", err)
|
return core.NewBizErr("获取交易信息失败", err)
|
||||||
}
|
}
|
||||||
|
if trade.User == nil {
|
||||||
|
return core.NewBizErr("交易信息不存在")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.OnCompleteTrade(trade, interNo, outerNo, result)
|
||||||
|
if err != nil {
|
||||||
|
return core.NewServErr("处理交易失败", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *tradeService) OnCompleteTrade(trade *m.Trade, interNo string, outerNo string, result *TradeSuccessResult) error {
|
||||||
|
user := trade.User
|
||||||
|
|
||||||
// 检查交易状态
|
// 检查交易状态
|
||||||
switch trade.Status {
|
switch trade.Status {
|
||||||
@@ -646,7 +671,10 @@ func (s *tradeService) CheckTrade(ref *TradeRef) (*CheckTradeResult, error) {
|
|||||||
result.Success.Acquirer = m.TradeAcquirerUnionPay
|
result.Success.Acquirer = m.TradeAcquirerUnionPay
|
||||||
}
|
}
|
||||||
result.Success.Actual = decimal.NewFromInt(resp.Amount).Div(decimal.NewFromInt(100))
|
result.Success.Actual = decimal.NewFromInt(resp.Amount).Div(decimal.NewFromInt(100))
|
||||||
result.Success.Time = time.UnixMilli(*resp.PayTime)
|
result.Success.Time, err = time.Parse("2006-01-02 15:04:05", *resp.PayTime)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 不支持的支付方式
|
// 不支持的支付方式
|
||||||
|
|||||||
Reference in New Issue
Block a user