diff --git a/.env.example b/.env.example index 38647ab..12b17a7 100644 --- a/.env.example +++ b/.env.example @@ -59,3 +59,4 @@ SFTPAY_ROUTE_ID= SFTPAY_APP_PRIVATE_KEY= SFTPAY_PUBLIC_KEY= SFTPAY_RETURN_URL= +SFTPAY_NOTIFY_URL= diff --git a/web/globals/shangfutong.go b/web/globals/shangfutong.go index c0dfcab..167305c 100644 --- a/web/globals/shangfutong.go +++ b/web/globals/shangfutong.go @@ -233,7 +233,7 @@ type QueryTradeResp struct { RefundState int32 `json:"refundState"` DrType *string `json:"drType"` ExtParam *string `json:"extParam"` - PayTime *int64 `json:"payTime"` + PayTime *string `json:"payTime"` Subject string `json:"subject"` TradeFee *int64 `json:"tradeFee"` CashFee *int64 `json:"cashFee"` @@ -443,3 +443,11 @@ const ( SftRefundIng SftTradeState = "REFUND_ING" SftTradeClosed SftTradeState = "TRADE_CLOSED" ) + +type SftNotifyType string + +var ( + SftNotifyTypeTrade = SftNotifyType("sft.trade.notify") + SftNotifyTypeRefund = SftNotifyType("sft.refund.notify") + SftNotifyTypeDivision = SftNotifyType("sft.division.notify") +) diff --git a/web/handlers/trade.go b/web/handlers/trade.go index f59df8d..28327b9 100644 --- a/web/handlers/trade.go +++ b/web/handlers/trade.go @@ -1,6 +1,7 @@ package handlers import ( + "encoding/json" "log/slog" "platform/web/auth" "platform/web/core" @@ -11,6 +12,7 @@ import ( "time" "github.com/gofiber/fiber/v2" + "github.com/shopspring/decimal" ) // PageTradeByAdmin 分页查询所有订单 @@ -429,3 +431,99 @@ func TradeCheck(c *fiber.Ctx) error { 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"` +} diff --git a/web/middlewares.go b/web/middlewares.go index 80ccc70..f1bd32b 100644 --- a/web/middlewares.go +++ b/web/middlewares.go @@ -30,6 +30,7 @@ func ApplyMiddlewares(app *fiber.App) { // logger app.Use(logger.New(logger.Config{ + TimeFormat: "2006-01-02 15:04:05", Next: func(c *fiber.Ctx) bool { return c.Path() == "/favicon.ico" }, diff --git a/web/routes.go b/web/routes.go index 45174ba..dd57a6f 100644 --- a/web/routes.go +++ b/web/routes.go @@ -23,6 +23,7 @@ func ApplyRouters(app *fiber.App) { // 回调 callbacks := app.Group("/callback") callbacks.Get("/identify", handlers.IdentifyCallbackNew) + callbacks.All("/trade/sft", handlers.TradeCallbackSft) // 临时 if env.RunMode == env.RunModeDev { diff --git a/web/services/trade.go b/web/services/trade.go index 5283931..34c1ac2 100644 --- a/web/services/trade.go +++ b/web/services/trade.go @@ -146,6 +146,7 @@ func (s *tradeService) Create(user *m.User, tradeData *CreateTradeData, productD Currency: "cny", ClientIp: "123.52.74.23", OrderTimeout: u.P(expireAt.Format("2006-01-02 15:04:05")), + NotifyUrl: &env.SftNotifyUrl, }) if err != nil { 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 { return core.NewServErr("处理交易失败", err) } 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. + Preload(q.Trade.User). Where(q.Trade.InnerNo.Eq(interNo)). Take() if err != nil { 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 { @@ -646,7 +671,10 @@ func (s *tradeService) CheckTrade(ref *TradeRef) (*CheckTradeResult, error) { result.Success.Acquirer = m.TradeAcquirerUnionPay } 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 + } } // 不支持的支付方式