重构交易处理逻辑,合并充值与购买流程,优化交易状态管理;更新相关数据结构和接口
This commit is contained in:
@@ -2,23 +2,109 @@ package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/valyala/fasthttp/fasthttpadaptor"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"platform/web/auth"
|
||||
"platform/web/core"
|
||||
trade2 "platform/web/domains/trade"
|
||||
g "platform/web/globals"
|
||||
q "platform/web/queries"
|
||||
s "platform/web/services"
|
||||
"platform/web/tasks"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
|
||||
)
|
||||
|
||||
type TradeCreateReq struct {
|
||||
s.CreateTradeData
|
||||
Type trade2.Type `json:"type" validate:"required"`
|
||||
Resource *s.CreateResourceData `json:"resource,omitempty"`
|
||||
Recharge *s.RechargeProductInfo `json:"recharge,omitempty"`
|
||||
}
|
||||
|
||||
type TradeCreateResp struct {
|
||||
PayUrl string `json:"pay_url"`
|
||||
TradeNo string `json:"trade_no"`
|
||||
}
|
||||
|
||||
func TradeCreate(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
authCtx, err := auth.NewProtect(c).Payload(auth.PayloadUser).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 解析请求参数
|
||||
req := new(TradeCreateReq)
|
||||
if err := g.Validator.Validate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch req.Type {
|
||||
case trade2.TypePurchase:
|
||||
if req.Resource == nil {
|
||||
return core.NewBizErr("购买信息不能为空")
|
||||
}
|
||||
req.Product = req.Resource
|
||||
case trade2.TypeRecharge:
|
||||
if req.Recharge == nil {
|
||||
return core.NewBizErr("充值信息不能为空")
|
||||
}
|
||||
req.Product = req.Recharge
|
||||
}
|
||||
|
||||
// 创建交易
|
||||
result, err := s.Trade.CreateTrade(authCtx.Payload.Id, time.Now(), &req.CreateTradeData)
|
||||
if err != nil {
|
||||
slog.Error("创建交易失败", "error", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "创建交易失败"})
|
||||
}
|
||||
|
||||
return c.JSON(&TradeCreateResp{
|
||||
PayUrl: result.PaymentUrl,
|
||||
TradeNo: result.TradeNo,
|
||||
})
|
||||
}
|
||||
|
||||
type TradeCompleteReq struct {
|
||||
TradeNo string `json:"trade_no" validate:"required"`
|
||||
Method trade2.Method `json:"method" validate:"required"`
|
||||
}
|
||||
|
||||
func TradeComplete(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
_, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 解析请求参数
|
||||
req := new(TradeCompleteReq)
|
||||
if err := g.Validator.Validate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查订单状态
|
||||
result, err := s.Trade.ConfirmTradeCompleted(&s.CheckTradeData{
|
||||
TradeNo: req.TradeNo,
|
||||
Method: req.Method,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.Trade.OnTradeCompleted(&s.OnTradeCompletedData{req.TradeNo, *result})
|
||||
if err != nil {
|
||||
slog.Error("完成交易失败", "trade_no", req.TradeNo, "method", req.Method, "error", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "完成交易失败"})
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
type TradeCheckReq struct {
|
||||
tasks.CancelTradeData
|
||||
}
|
||||
|
||||
func TradeCheckSSE(c *fiber.Ctx) error {
|
||||
// 设置响应头
|
||||
c.Set("Content-Type", "text/event-stream")
|
||||
@@ -28,10 +114,6 @@ func TradeCheckSSE(c *fiber.Ctx) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type TradeCheckReq struct {
|
||||
tasks.CancelTradeData
|
||||
}
|
||||
|
||||
func TradeCancelByTask(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
_, err := auth.Protect(c, []auth.PayloadType{auth.PayloadInternalServer}, []string{})
|
||||
@@ -46,14 +128,14 @@ func TradeCancelByTask(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// 检查订单状态
|
||||
err = s.Trade.CheckTradeIfCanceled(&s.CheckTradeData{
|
||||
err = s.Trade.ConfirmTradeCanceled(&s.CheckTradeData{
|
||||
TradeNo: req.TradeNo,
|
||||
Method: req.Method,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Debug(fmt.Sprintf("订单无需取消:%s", err.Error()))
|
||||
} else {
|
||||
err = s.Trade.CancelTrade(req.TradeNo, req.Method)
|
||||
err = s.Trade.CancelTrade(req.TradeNo, req.Method, time.Now())
|
||||
if err != nil {
|
||||
slog.Warn("取消交易失败", "trade_no", req.TradeNo, "method", req.Method, "error", err)
|
||||
}
|
||||
@@ -61,152 +143,3 @@ func TradeCancelByTask(c *fiber.Ctx) error {
|
||||
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
func AlipayCallback(c *fiber.Ctx) (err error) {
|
||||
|
||||
// 解析请求
|
||||
httpRequest := new(http.Request)
|
||||
if err := fasthttpadaptor.ConvertRequest(c.Context(), httpRequest, false); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := httpRequest.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
notification, err := g.Alipay.DecodeNotification(httpRequest.Form)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
slog.Debug("支付宝支付回调", "notification", fmt.Sprintf("%+v", notification))
|
||||
|
||||
// 查询交易信息
|
||||
trade, err := q.Q.Trade.Where(q.Trade.InnerNo.Eq(notification.OutTradeNo)).Take()
|
||||
if err != nil {
|
||||
return c.SendString("success")
|
||||
}
|
||||
switch alipay.TradeStatus(notification.NotifyType) {
|
||||
|
||||
// 支付关闭
|
||||
case alipay.TradeStatusClosed:
|
||||
|
||||
// todo 退款
|
||||
|
||||
// 非退款
|
||||
switch trade2.Type(trade.Type) {
|
||||
|
||||
// 购买产品
|
||||
case trade2.TypePurchase:
|
||||
err = s.Resource.CancelResource(notification.OutTradeNo, time.Now(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 余额充值
|
||||
case trade2.TypeRecharge:
|
||||
err = s.User.RechargeCancel(notification.OutTradeNo, time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 支付成功
|
||||
case alipay.TradeStatusSuccess:
|
||||
|
||||
// 收集交易状态
|
||||
payment, err := decimal.NewFromString(notification.TotalAmount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
paidAt, err := time.Parse("2006-01-02 15:04:05", notification.GmtPayment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
verified := &s.TradeSuccessResult{
|
||||
TransId: notification.TradeNo,
|
||||
Payment: payment,
|
||||
Time: paidAt,
|
||||
}
|
||||
|
||||
// todo 退款
|
||||
|
||||
// 非退款
|
||||
switch trade2.Type(trade.Type) {
|
||||
|
||||
// 购买产品
|
||||
case trade2.TypePurchase:
|
||||
err = s.Resource.CompleteResource(notification.OutTradeNo, time.Now(), verified)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 余额充值
|
||||
case trade2.TypeRecharge:
|
||||
err := s.User.RechargeConfirm(notification.OutTradeNo, verified)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c.SendString("success")
|
||||
}
|
||||
|
||||
func WechatPayCallback(c *fiber.Ctx) error {
|
||||
|
||||
// 解析请求参数
|
||||
req := new(http.Request)
|
||||
if err := fasthttpadaptor.ConvertRequest(c.Context(), req, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content := new(payments.Transaction)
|
||||
_, err := g.WechatPay.Notify.ParseNotifyRequest(c.Context(), req, content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
slog.Debug("微信支付回调", "content", fmt.Sprintf("%+v", content))
|
||||
|
||||
// 查询交易信息
|
||||
trade, err := q.Q.Trade.Where(q.Trade.InnerNo.Eq(*content.OutTradeNo)).Take()
|
||||
if err != nil {
|
||||
// 跳过测试通知
|
||||
return nil
|
||||
}
|
||||
switch *content.TradeState {
|
||||
|
||||
// 支付成功
|
||||
case "SUCCESS":
|
||||
|
||||
// 收集交易状态
|
||||
payment := decimal.NewFromInt(*content.Amount.PayerTotal).Div(decimal.NewFromInt(100))
|
||||
paidAt, err := time.Parse(time.RFC3339, *content.SuccessTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
verified := &s.TradeSuccessResult{
|
||||
TransId: *content.TransactionId,
|
||||
Payment: payment,
|
||||
Time: paidAt,
|
||||
}
|
||||
|
||||
switch {
|
||||
|
||||
// 余额充值
|
||||
case trade.Type == int32(trade2.TypeRecharge):
|
||||
err := s.User.RechargeConfirm(*content.OutTradeNo, verified)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 购买产品
|
||||
case trade.Type == int32(trade2.TypePurchase):
|
||||
err = s.Resource.CompleteResource(*content.OutTradeNo, time.Now(), verified)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user