优化交易状态检查逻辑

This commit is contained in:
2025-06-24 11:36:27 +08:00
parent 2fd17dde22
commit 065a7c77c3
10 changed files with 218 additions and 283 deletions

14
pkg/env/env.go vendored
View File

@@ -148,8 +148,7 @@ func loadRedis() {
// region log // region log
var ( var (
LogLevel = slog.LevelDebug LogLevel = slog.LevelDebug
LogHttpDump = false // 是否打印HTTP请求和响应的原始数据
) )
func loadLog() { func loadLog() {
@@ -164,17 +163,6 @@ func loadLog() {
case "error": case "error":
LogLevel = slog.LevelError LogLevel = slog.LevelError
} }
_LogHttpDump := os.Getenv("LOG_HTTP_DUMP")
if _LogHttpDump != "" {
value, err := strconv.ParseBool(_LogHttpDump)
if err != nil {
panic("环境变量 LOG_HTTP_DUMP 的值不是布尔值")
}
LogHttpDump = value
} else {
LogHttpDump = false // 默认不打印HTTP请求和响应的原始数据
}
} }
// endregion // endregion

View File

@@ -910,7 +910,7 @@ comment on column trade.payment is '支付金额';
comment on column trade.method is '支付方式1-支付宝2-微信3-商福通渠道支付宝4-商福通渠道微信'; comment on column trade.method is '支付方式1-支付宝2-微信3-商福通渠道支付宝4-商福通渠道微信';
comment on column trade.platform is '支付平台1-电脑网站2-手机网站'; comment on column trade.platform is '支付平台1-电脑网站2-手机网站';
comment on column trade.acquirer is '收单机构1-支付宝2-微信3-银联'; comment on column trade.acquirer is '收单机构1-支付宝2-微信3-银联';
comment on column trade.status is '订单状态0-待支付1-已支付2-已取消3-已退款'; comment on column trade.status is '订单状态0-待支付1-已支付2-已取消';
comment on column trade.pay_url is '支付链接'; comment on column trade.pay_url is '支付链接';
comment on column trade.paid_at is '支付时间'; comment on column trade.paid_at is '支付时间';
comment on column trade.cancel_at is '取消时间'; comment on column trade.cancel_at is '取消时间';

View File

@@ -38,5 +38,4 @@ const (
StatusPending Status = iota // 待支付 StatusPending Status = iota // 待支付
StatusSuccess // 已支付 StatusSuccess // 已支付
StatusCanceled // 已取消 StatusCanceled // 已取消
StatusRefunded
) // 已退款 ) // 已退款

View File

@@ -107,23 +107,24 @@ func (s *SftClient) QueryTrade(req *QueryTradeReq) (*QueryTradeResp, error) {
} }
type PaymentScanPayReq struct { type PaymentScanPayReq struct {
Subject string `json:"subject"` Subject string `json:"subject"`
Body string `json:"body"` Body string `json:"body"`
Amount int64 `json:"amount"` Amount int64 `json:"amount"`
Currency string `json:"currency"` Currency string `json:"currency"`
ClientIp string `json:"clientIp"` PayType SftPayType `json:"payType"`
MchOrderNo string `json:"mchOrderNo"` ClientIp string `json:"clientIp"`
StoreId *string `json:"storeId,omitempty"` MchOrderNo string `json:"mchOrderNo"`
RouteNo *string `json:"routeNo,omitempty"` StoreId *string `json:"storeId,omitempty"`
HbFqNum *int `json:"hbFqNum,omitempty"` RouteNo *string `json:"routeNo,omitempty"`
HbFqPercent *int `json:"hbFqPercent,omitempty"` HbFqNum *int `json:"hbFqNum,omitempty"`
BuyerRemark *string `json:"buyerRemark,omitempty"` HbFqPercent *int `json:"hbFqPercent,omitempty"`
NotifyUrl *string `json:"notifyUrl,omitempty"` BuyerRemark *string `json:"buyerRemark,omitempty"`
ReturnUrl *string `json:"returnUrl,omitempty"` NotifyUrl *string `json:"notifyUrl,omitempty"`
ExpiredTime *int `json:"expiredTime,omitempty"` ReturnUrl *string `json:"returnUrl,omitempty"`
OrderTimeout *string `json:"orderTimeout,omitempty"` ExpiredTime *int `json:"expiredTime,omitempty"`
ExtParam *string `json:"extParam,omitempty"` OrderTimeout *string `json:"orderTimeout,omitempty"`
LimitPay *int `json:"limitPay,omitempty"` ExtParam *string `json:"extParam,omitempty"`
LimitPay *int `json:"limitPay,omitempty"`
} }
type PaymentH5PayReq struct { type PaymentH5PayReq struct {
@@ -269,7 +270,7 @@ func call[T any](s *SftClient, url string, req any) (*T, error) {
} }
request.Header.Set("Content-Type", "application/json") request.Header.Set("Content-Type", "application/json")
if env.LogHttpDump == true { if env.DebugHttpDump == true {
reqDump, err := httputil.DumpRequest(request, true) reqDump, err := httputil.DumpRequest(request, true)
if err != nil { if err != nil {
return nil, fmt.Errorf("请求内容转储失败:%w", err) return nil, fmt.Errorf("请求内容转储失败:%w", err)
@@ -282,7 +283,7 @@ func call[T any](s *SftClient, url string, req any) (*T, error) {
return nil, fmt.Errorf("请求失败:%w", err) return nil, fmt.Errorf("请求失败:%w", err)
} }
if env.LogHttpDump == true { if env.DebugHttpDump == true {
respDump, err := httputil.DumpResponse(response, true) respDump, err := httputil.DumpResponse(response, true)
if err != nil { if err != nil {
return nil, fmt.Errorf("响应内容转储失败:%w", err) return nil, fmt.Errorf("响应内容转储失败:%w", err)
@@ -431,7 +432,7 @@ type SftTradeState string
const ( const (
SftInit SftTradeState = "INIT" SftInit SftTradeState = "INIT"
SftTradeAWAIT SftTradeState = "TRADE_WAIT" SftTradeAwait SftTradeState = "TRADE_WAIT"
SftTradeSuccess SftTradeState = "TRADE_SUCCESS" SftTradeSuccess SftTradeState = "TRADE_SUCCESS"
SftTradeFail SftTradeState = "TRADE_FAIL" SftTradeFail SftTradeState = "TRADE_FAIL"
SftTradeCancel SftTradeState = "TRADE_CANCEL" SftTradeCancel SftTradeState = "TRADE_CANCEL"

View File

@@ -508,12 +508,7 @@ func ResourcePrice(c *fiber.Ctx) error {
return err return err
} }
data, err := req.ToData()
if err != nil {
return err
}
return c.JSON(fiber.Map{ return c.JSON(fiber.Map{
"price": data.GetPrice().StringFixed(2), "price": req.GetPrice().StringFixed(2),
}) })
} }

View File

@@ -45,10 +45,18 @@ func TradeCancelByTask(c *fiber.Ctx) error {
return err return err
} }
// 取消支付 // 检查订单状态
err = s.Trade.CancelTrade(req.TradeNo, req.Method) err = s.Trade.CheckTradeIfCanceled(&s.CheckTradeData{
TradeNo: req.TradeNo,
Method: req.Method,
})
if err != nil { if err != nil {
slog.Warn("取消交易失败", "trade_no", req.TradeNo, "method", req.Method, "error", err) slog.Debug(fmt.Sprintf("订单无需取消:%s", err.Error()))
} else {
err = s.Trade.CancelTrade(req.TradeNo, req.Method)
if err != nil {
slog.Warn("取消交易失败", "trade_no", req.TradeNo, "method", req.Method, "error", err)
}
} }
return c.SendStatus(fiber.StatusNoContent) return c.SendStatus(fiber.StatusNoContent)

View File

@@ -163,154 +163,6 @@ type RechargeConfirmResp struct {
Status string `json:"status"` Status string `json:"status"`
} }
func RechargePrepareAlipay(c *fiber.Ctx) error {
// 检查权限
authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{})
if err != nil {
return err
}
// 解析请求参数
req := new(RechargePrepareReq)
if err := c.BodyParser(req); err != nil {
return err
}
// 保存交易信息
var now = time.Now()
amount, err := decimal.NewFromString(req.Amount)
if err != nil {
return err
}
var result *s.TradeCreateResult
err = q.Q.Transaction(func(tx *q.Query) error {
result, err = s.Trade.CreateTrade(tx, authContext.Payload.Id, now, &s.TradeCreateData{
Subject: "账户充值 - " + amount.StringFixed(2) + "元",
Amount: amount,
ExpireAt: time.Now().Add(30 * time.Minute),
Type: trade2.TypeRecharge,
Method: trade2.MethodAlipay,
Platform: req.Platform,
})
return err
})
if err != nil {
return err
}
// 返回结果
return c.JSON(RechargePrepareResp{
TradeNo: result.TradeNo,
PayURL: result.PayURL,
})
}
func RechargeConfirmAlipay(c *fiber.Ctx) error {
// 检查权限
_, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{})
if err != nil {
return err
}
// 解析请求参数
req := new(RechargeConfirmReq)
if err := c.BodyParser(req); err != nil {
return err
}
// 验证支付结果
result, err := s.Trade.VerifyTrade(&s.TradeVerifyData{
TradeNo: req.TradeNo,
Method: trade2.MethodAlipay,
})
if err != nil {
return err
}
// 更新数据库
err = s.User.RechargeConfirm(req.TradeNo, result)
if err != nil {
return err
}
return c.JSON(fiber.Map{"status": "success"})
}
func RechargePrepareWechat(c *fiber.Ctx) error {
// 检查权限
authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{})
if err != nil {
return err
}
// 解析请求参数
req := new(RechargePrepareReq)
if err := c.BodyParser(req); err != nil {
return err
}
// 保存交易信息
var now = time.Now()
amount, err := decimal.NewFromString(req.Amount)
if err != nil {
return err
}
var result *s.TradeCreateResult
err = q.Q.Transaction(func(tx *q.Query) error {
result, err = s.Trade.CreateTrade(tx, authContext.Payload.Id, now, &s.TradeCreateData{
Subject: "账户充值 - " + amount.StringFixed(2) + "元",
Amount: amount,
ExpireAt: now.Add(30 * time.Minute),
Type: trade2.TypeRecharge,
Method: trade2.MethodWeChat,
Platform: req.Platform,
})
return err
})
if err != nil {
return err
}
// 返回结果
return c.JSON(RechargePrepareResp{
TradeNo: result.TradeNo,
PayURL: result.PayURL,
})
}
func RechargeConfirmWechat(c *fiber.Ctx) error {
// 检查权限
_, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{})
if err != nil {
return err
}
// 解析请求参数
req := new(struct {
TradeNo string `json:"trade_no" validate:"required"`
})
if err := c.BodyParser(req); err != nil {
return err
}
// 验证支付结果
result, err := s.Trade.VerifyTrade(&s.TradeVerifyData{
TradeNo: req.TradeNo,
Method: trade2.MethodWeChat,
})
if err != nil {
return err
}
// 更新数据库
err = s.User.RechargeConfirm(req.TradeNo, result)
if err != nil {
return err
}
return c.JSON(fiber.Map{"status": "success"})
}
func RechargePrepare(c *fiber.Ctx) error { func RechargePrepare(c *fiber.Ctx) error {
// 检查权限 // 检查权限
authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{})
@@ -367,7 +219,7 @@ func RechargeComplete(c *fiber.Ctx) error {
} }
// 验证支付结果 // 验证支付结果
result, err := s.Trade.VerifyTrade(&s.TradeVerifyData{ result, err := s.Trade.CheckTradeIfCreated(&s.CheckTradeData{
TradeNo: req.TradeNo, TradeNo: req.TradeNo,
Method: trade2.MethodSft, Method: trade2.MethodSft,
}) })

View File

@@ -90,20 +90,15 @@ func (s *resourceService) CreateResource(uid int32, now time.Time, ser *CreateRe
func (s *resourceService) PrepareResource(uid int32, now time.Time, ser *PrepareResourceData) (*TradeCreateResult, error) { func (s *resourceService) PrepareResource(uid int32, now time.Time, ser *PrepareResourceData) (*TradeCreateResult, error) {
data, err := ser.ToData() name := ser.GetName()
if err != nil { amount := ser.GetPrice()
return nil, err
}
name := data.GetName()
amount := data.GetPrice()
method := ser.PaymentMethod method := ser.PaymentMethod
platform := ser.PaymentPlatform platform := ser.PaymentPlatform
// 保存到数据库 // 保存到数据库
var result *TradeCreateResult var result *TradeCreateResult
err = q.Q.Transaction(func(q *q.Query) error { err := q.Q.Transaction(func(q *q.Query) error {
var err error var err error
// 生成交易订单 // 生成交易订单
@@ -120,16 +115,11 @@ func (s *resourceService) PrepareResource(uid int32, now time.Time, ser *Prepare
} }
// 保存请求缓存 // 保存请求缓存
resourceSerializer := new(PrepareResourceData)
if err := resourceSerializer.ByData(data); err != nil {
return err
}
err = g.Redis.Set(context.Background(), resPrepareKey(result.TradeNo), &PrepareResourceCache{ err = g.Redis.Set(context.Background(), resPrepareKey(result.TradeNo), &PrepareResourceCache{
Uid: uid, Uid: uid,
TradeId: result.Trade.ID, TradeId: result.Trade.ID,
BillId: result.Bill.ID, BillId: result.Bill.ID,
PrepareResourceData: resourceSerializer, PrepareResourceData: ser,
}, 30*time.Minute).Err() }, 30*time.Minute).Err()
if err != nil { if err != nil {
return err return err
@@ -162,7 +152,7 @@ func (s *resourceService) CompleteResource(tradeNo string, now time.Time, opResu
rs = opResult[0] rs = opResult[0]
} else { } else {
var err error var err error
rs, err = Trade.VerifyTrade(&TradeVerifyData{ rs, err = Trade.CheckTradeIfCreated(&CheckTradeData{
TradeNo: tradeNo, TradeNo: tradeNo,
Method: cache.PaymentMethod, Method: cache.PaymentMethod,
}) })
@@ -196,9 +186,8 @@ func (s *resourceService) CompleteResource(tradeNo string, now time.Time, opResu
// 更新账单 // 更新账单
_, err = q.Bill.Debug(). _, err = q.Bill.Debug().
Select(q.Bill.ResourceID). Where(q.Bill.ID.Eq(cache.BillId)).
Updates(&m.Bill{ Updates(&m.Bill{
ID: cache.BillId,
ResourceID: &resource.ID, ResourceID: &resource.ID,
}) })
if err != nil { if err != nil {
@@ -416,6 +405,28 @@ type CreateResourceData struct {
Long *CreateLongResourceData `json:"long,omitempty"` Long *CreateLongResourceData `json:"long,omitempty"`
} }
func (data *CreateResourceData) GetName() string {
switch data.Type {
case resource2.TypeShort:
return data.Short.GetName()
case resource2.TypeLong:
return data.Long.GetName()
default:
panic("未处理的 resource type 枚举值")
}
}
func (data *CreateResourceData) GetPrice() decimal.Decimal {
switch data.Type {
case resource2.TypeShort:
return data.Short.GetPrice()
case resource2.TypeLong:
return data.Long.GetPrice()
default:
panic("未处理的 resource type 枚举值")
}
}
func (s *CreateResourceData) ToData() (CreateTypeResourceDataInter, error) { func (s *CreateResourceData) ToData() (CreateTypeResourceDataInter, error) {
switch s.Type { switch s.Type {
case resource2.TypeShort: case resource2.TypeShort:
@@ -423,21 +434,8 @@ func (s *CreateResourceData) ToData() (CreateTypeResourceDataInter, error) {
case resource2.TypeLong: case resource2.TypeLong:
return s.Long, nil return s.Long, nil
} }
return nil, fmt.Errorf("不支持的套餐类型")
}
func (s *CreateResourceData) ByData(data CreateTypeResourceDataInter) error { return nil, fmt.Errorf("不支持的套餐类型")
switch data := data.(type) {
case *CreateShortResourceData:
s.Type = resource2.TypeShort
s.Short = data
case *CreateLongResourceData:
s.Type = resource2.TypeLong
s.Long = data
default:
return fmt.Errorf("不支持的套餐类型")
}
return nil
} }
type PrepareResourceData struct { type PrepareResourceData struct {

View File

@@ -146,12 +146,22 @@ func (s *tradeService) CreateTrade(q *q.Query, uid int32, now time.Time, data *T
payUrl = *resp.CodeUrl payUrl = *resp.CodeUrl
// 商福通 + 电脑网站 // 商福通 + 电脑网站
case method == trade2.MethodSft && platform == trade2.PlatformDesktop: case (method == trade2.MethodSftAlipay || method == trade2.MethodSftWeChat) && platform == trade2.PlatformDesktop:
var payType g.SftPayType
switch method {
case trade2.MethodSftAlipay:
payType = g.SftAlipay
case trade2.MethodSftWeChat:
payType = g.SftWeChat
default:
panic("unhandled default case")
}
resp, err := g.SFTPay.PaymentScanPay(&g.PaymentScanPayReq{ resp, err := g.SFTPay.PaymentScanPay(&g.PaymentScanPayReq{
MchOrderNo: tradeNo, MchOrderNo: tradeNo,
Subject: subject, Subject: subject,
Body: subject, Body: subject,
Amount: amountReal.Mul(decimal.NewFromInt(100)).Round(0).IntPart(), Amount: amountReal.Mul(decimal.NewFromInt(100)).Round(0).IntPart(),
PayType: payType,
Currency: "cny", Currency: "cny",
ClientIp: "123.52.74.23", ClientIp: "123.52.74.23",
OrderTimeout: u.P(expire.Format("2006-01-02 15:04:05")), OrderTimeout: u.P(expire.Format("2006-01-02 15:04:05")),
@@ -164,10 +174,13 @@ func (s *tradeService) CreateTrade(q *q.Query, uid int32, now time.Time, data *T
// 商福通 + 手机网站 // 商福通 + 手机网站
case (method == trade2.MethodSftAlipay || method == trade2.MethodSftWeChat) && platform == trade2.PlatformMobile: case (method == trade2.MethodSftAlipay || method == trade2.MethodSftWeChat) && platform == trade2.PlatformMobile:
var payType g.SftPayType var payType g.SftPayType
if method == trade2.MethodSftAlipay { switch method {
case trade2.MethodSftAlipay:
payType = g.SftAlipay payType = g.SftAlipay
} else { case trade2.MethodSftWeChat:
payType = g.SftWeChat payType = g.SftWeChat
default:
panic("unhandled default case")
} }
resp, err := g.SFTPay.PaymentH5Pay(&g.PaymentH5PayReq{ resp, err := g.SFTPay.PaymentH5Pay(&g.PaymentH5PayReq{
MchOrderNo: tradeNo, MchOrderNo: tradeNo,
@@ -266,9 +279,11 @@ func (s *tradeService) OnTradeCreated(q *q.Query, data *OnTradeCreateData) (*m.T
// 检查交易状态 // 检查交易状态
switch trade2.Status(trade.Status) { switch trade2.Status(trade.Status) {
// 如果已退款或取消,则返回错误 case trade2.StatusCanceled:
case trade2.StatusCanceled, trade2.StatusRefunded: return nil, core.NewBizErr("交易已取消")
return nil, errors.New("交易已取消或已退款")
case trade2.StatusSuccess:
return nil, core.NewBizErr("交易已完成")
// 如果是未支付,则更新支付状态 // 如果是未支付,则更新支付状态
case trade2.StatusPending: case trade2.StatusPending:
@@ -284,7 +299,6 @@ func (s *tradeService) OnTradeCreated(q *q.Query, data *OnTradeCreateData) (*m.T
if err != nil { if err != nil {
return nil, err return nil, err
} }
case trade2.StatusSuccess:
} }
return trade, nil return trade, nil
@@ -320,7 +334,7 @@ func (s *tradeService) CancelTrade(tradeNo string, method trade2.Method) error {
return errors.New("交易取消失败") return errors.New("交易取消失败")
} }
case trade2.MethodSft: case trade2.MethodSft, trade2.MethodSftAlipay, trade2.MethodSftWeChat:
resp, err := g.SFTPay.OrderClose(&g.OrderCloseReq{ resp, err := g.SFTPay.OrderClose(&g.OrderCloseReq{
MchOrderNo: &tradeNo, MchOrderNo: &tradeNo,
}) })
@@ -361,18 +375,18 @@ func (s *tradeService) OnTradeRefunded(q *q.Query, tradeNo string, now time.Time
panic("todo") panic("todo")
} }
func (s *tradeService) VerifyTrade(data *TradeVerifyData) (*TradeSuccessResult, error) { func (s *tradeService) CheckTrade(data *CheckTradeData) (*CheckTradeResult, error) {
var tradeNo = data.TradeNo var tradeNo = data.TradeNo
var method = data.Method var method = data.Method
// 检查交易号是否存在 // 检查交易号是否存在
var transId string var result = new(CheckTradeResult)
var paidAt time.Time
var payment decimal.Decimal
switch method { switch method {
// 检查支付宝交易 // 支付宝
case trade2.MethodAlipay: case trade2.MethodAlipay:
// 查询交易状态
resp, err := g.Alipay.TradeQuery(context.Background(), alipay.TradeQuery{ resp, err := g.Alipay.TradeQuery(context.Background(), alipay.TradeQuery{
OutTradeNo: tradeNo, OutTradeNo: tradeNo,
}) })
@@ -383,22 +397,34 @@ func (s *tradeService) VerifyTrade(data *TradeVerifyData) (*TradeSuccessResult,
slog.Warn("支付宝交易查询失败", "code", resp.Code, "sub_code", resp.SubCode, "msg", resp.Msg) slog.Warn("支付宝交易查询失败", "code", resp.Code, "sub_code", resp.SubCode, "msg", resp.Msg)
return nil, errors.New("交易查询失败") return nil, errors.New("交易查询失败")
} }
if resp.TradeStatus != alipay.TradeStatusSuccess {
return nil, ErrTransactionNotPaid // 填充返回值
result.TransId = resp.TradeNo
switch resp.TradeStatus {
case alipay.TradeStatusWaitBuyerPay:
result.Status = trade2.StatusPending
case alipay.TradeStatusClosed:
result.Status = trade2.StatusCanceled
case alipay.TradeStatusSuccess, alipay.TradeStatusFinished:
result.Status = trade2.StatusSuccess
result.Success.Acquirer = trade2.AcquirerAlipay
result.Success.Payment, err = decimal.NewFromString(resp.TotalAmount)
if err != nil {
return nil, err
}
result.Success.Time, err = time.Parse("2006-01-02 15:04:05", resp.SendPayDate)
if err != nil {
return nil, err
}
} }
transId = resp.TradeNo // 微信
payment, err = decimal.NewFromString(resp.TotalAmount)
if err != nil {
return nil, err
}
paidAt, err = time.Parse("2006-01-02 15:04:05", resp.SendPayDate)
if err != nil {
return nil, err
}
// 检查微信交易
case trade2.MethodWeChat: case trade2.MethodWeChat:
// 查询交易状态
resp, _, err := g.WechatPay.Native.QueryOrderByOutTradeNo(context.Background(), native.QueryOrderByOutTradeNoRequest{ resp, _, err := g.WechatPay.Native.QueryOrderByOutTradeNo(context.Background(), native.QueryOrderByOutTradeNoRequest{
OutTradeNo: &tradeNo, OutTradeNo: &tradeNo,
Mchid: &env.WechatPayMchId, Mchid: &env.WechatPayMchId,
@@ -407,51 +433,76 @@ func (s *tradeService) VerifyTrade(data *TradeVerifyData) (*TradeSuccessResult,
var apiErr *wecahtpaycore.APIError var apiErr *wecahtpaycore.APIError
if errors.As(err, &apiErr) { if errors.As(err, &apiErr) {
if apiErr.Code == "ORDER_NOT_EXIST" { if apiErr.Code == "ORDER_NOT_EXIST" {
return nil, ErrTransactionNotPaid return nil, core.NewBizErr("订单不存在")
} }
return nil, core.NewServErr( return nil, core.NewServErr(
fmt.Sprintf("微信上游接口异常code=%vmessage=%v", apiErr.Code, apiErr.Message), fmt.Sprintf("微信上游接口异常code=%vmessage=%v", apiErr.Code, apiErr.Message),
apiErr, apiErr,
) )
} }
return nil, err return nil, core.NewServErr(fmt.Sprintf("微信上游支付接口异常:%s", err.Error()))
}
if *resp.TradeState != "SUCCESS" {
return nil, core.NewServErr(
fmt.Sprintf("预期之外的支付未完成state=%v, stateDesc=%v", resp.TradeState, resp.TradeStateDesc),
)
} }
transId = *resp.TransactionId // 填充返回值
payment = decimal.NewFromInt(*resp.Amount.PayerTotal).Div(decimal.NewFromInt(100)) result.TransId = *resp.TransactionId
paidAt, err = time.Parse(time.RFC3339, *resp.SuccessTime) switch *resp.TradeState {
if err != nil {
return nil, err case "NOTPAY":
result.Status = trade2.StatusPending
case "CLOSED":
result.Status = trade2.StatusCanceled
case "SUCCESS", "REFUND":
result.Status = trade2.StatusSuccess
result.Success.Acquirer = trade2.AcquirerWeChat
result.Success.Payment = decimal.NewFromInt(*resp.Amount.PayerTotal).Div(decimal.NewFromInt(100))
result.Success.Time, err = time.Parse(time.RFC3339, *resp.SuccessTime)
if err != nil {
return nil, err
}
} }
// 检查商福通交易 // 商福通
case trade2.MethodSft, trade2.MethodSftAlipay, trade2.MethodSftWeChat: case trade2.MethodSft, trade2.MethodSftAlipay, trade2.MethodSftWeChat:
// 查询交易状态
resp, err := g.SFTPay.QueryTrade(&g.QueryTradeReq{ resp, err := g.SFTPay.QueryTrade(&g.QueryTradeReq{
MchOrderNo: &tradeNo, MchOrderNo: &tradeNo,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
if resp.State != g.SftTradeSuccess {
return nil, ErrTransactionNotPaid
}
if resp.PayOrderId == nil { if resp.PayOrderId == nil {
return nil, errors.New("商福通交易号不存在") return nil, errors.New("商福通交易号不存在")
} }
if resp.PayTime == nil {
return nil, errors.New("商福通交易时间不存在") // 填充返回值
} result.TransId = *resp.PayOrderId
transId = *resp.PayOrderId switch resp.State {
payment = decimal.NewFromInt(resp.Amount).Div(decimal.NewFromInt(100))
paidAt, err = time.Parse("2006-01-02 15:04:05", *resp.PayTime) case g.SftInit, g.SftTradeAwait, g.SftTradeFail:
if err != nil { result.Status = trade2.StatusPending
return nil, err
case g.SftTradeClosed, g.SftTradeCancel:
result.Status = trade2.StatusCanceled
case g.SftTradeSuccess, g.SftTradeRefund, g.SftRefundIng:
result.Status = trade2.StatusSuccess
switch resp.PayType {
case "WECHAT":
result.Success.Acquirer = trade2.AcquirerWeChat
case "ALIPAY":
result.Success.Acquirer = trade2.AcquirerAlipay
case "UNIONPAY":
result.Success.Acquirer = trade2.AcquirerUnionPay
}
result.Success.Payment = decimal.NewFromInt(resp.Amount).Div(decimal.NewFromInt(100))
result.Success.Time, err = time.Parse("2006-01-02 15:04:05", *resp.PayTime)
if err != nil {
return nil, err
}
} }
// 不支持的支付方式 // 不支持的支付方式
@@ -459,11 +510,47 @@ func (s *tradeService) VerifyTrade(data *TradeVerifyData) (*TradeSuccessResult,
return nil, ErrTransactionNotSupported return nil, ErrTransactionNotSupported
} }
return &TradeSuccessResult{ return result, nil
TransId: transId, }
Payment: payment, func (s *tradeService) CheckTradeIfCreated(data *CheckTradeData) (*TradeSuccessResult, error) {
Time: paidAt,
}, nil rs, err := Trade.CheckTrade(&CheckTradeData{
TradeNo: data.TradeNo,
Method: data.Method,
})
if err != nil {
return nil, err
}
switch rs.Status {
case trade2.StatusPending:
return nil, core.NewBizErr("订单未支付")
case trade2.StatusCanceled:
return nil, core.NewBizErr("订单已关闭")
case trade2.StatusSuccess:
// pass
}
return rs.Success, nil
}
func (s *tradeService) CheckTradeIfCanceled(data *CheckTradeData) error {
rs, err := Trade.CheckTrade(&CheckTradeData{
TradeNo: data.TradeNo,
Method: data.Method,
})
if err != nil {
return err
}
switch rs.Status {
case trade2.StatusPending:
return core.NewBizErr("订单未支付")
case trade2.StatusSuccess:
return core.NewBizErr("订单已关闭")
case trade2.StatusCanceled:
// pass
}
return nil
} }
type TradeCreateData struct { type TradeCreateData struct {
@@ -483,16 +570,22 @@ type TradeCreateResult struct {
Trade *m.Trade Trade *m.Trade
} }
type TradeVerifyData struct { type CheckTradeData struct {
TradeNo string TradeNo string
Method trade2.Method Method trade2.Method
} }
type CheckTradeResult struct {
TransId string
Status trade2.Status
Success *TradeSuccessResult
}
type TradeSuccessResult struct { type TradeSuccessResult struct {
TransId string TransId string
Acquirer trade2.Acquirer
Payment decimal.Decimal Payment decimal.Decimal
Time time.Time Time time.Time
Acquirer trade2.Acquirer
} }
type OnTradeCreateData struct { type OnTradeCreateData struct {
@@ -507,6 +600,5 @@ func (e TradeErr) Error() string {
} }
var ( var (
ErrTransactionNotPaid = core.NewBizErr("交易未支付")
ErrTransactionNotSupported = core.NewBizErr("不支持的支付方式") ErrTransactionNotSupported = core.NewBizErr("不支持的支付方式")
) )

View File

@@ -158,7 +158,9 @@ func newLogger() fiber.Handler {
} }
func newRecover() fiber.Handler { func newRecover() fiber.Handler {
return recover.New() return recover.New(recover.Config{
EnableStackTrace: true,
})
} }
// endregion // endregion