优化交易状态检查逻辑
This commit is contained in:
14
pkg/env/env.go
vendored
14
pkg/env/env.go
vendored
@@ -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
|
||||||
|
|||||||
@@ -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 '取消时间';
|
||||||
|
|||||||
@@ -38,5 +38,4 @@ const (
|
|||||||
StatusPending Status = iota // 待支付
|
StatusPending Status = iota // 待支付
|
||||||
StatusSuccess // 已支付
|
StatusSuccess // 已支付
|
||||||
StatusCanceled // 已取消
|
StatusCanceled // 已取消
|
||||||
StatusRefunded
|
|
||||||
) // 已退款
|
) // 已退款
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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=%v,message=%v", apiErr.Code, apiErr.Message),
|
fmt.Sprintf("微信上游接口异常:code=%v,message=%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("不支持的支付方式")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user