fix: 延期订单超时不关闭、无法继续支付、不能作废
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled

- 支付宝查单把 ACQ.TRADE_NOT_EXIST 当成查单失败,恢复任务永远判为未知,订单停在待支付;
  同一套餐再下单时旧单也关不掉,新单无法创建。改为按"渠道无此订单"处理,过支付截止后关闭
- 延期订单详情对未过期的待支付订单返回 qr_content,后台可展示二维码、H5 可继续支付
- 新增作废待支付订单:后台 POST /orders/:id/close、H5 POST /orders/:id/cancel;作废前先查单,已支付则按支付成功办理
This commit is contained in:
邹进
2026-11-07 20:31:45 +09:00
parent 226a15573a
commit d3face6f66
8 changed files with 244 additions and 3 deletions

View File

@@ -0,0 +1,31 @@
package payment
import (
"fmt"
"testing"
sdkalipay "github.com/smartwalle/alipay/v3"
)
func TestAlipayTradeNotExist(t *testing.T) {
notExist := sdkalipay.Error{Code: sdkalipay.Code("40004"), SubCode: alipayTradeNotExistCode}
tests := []struct {
name string
response *sdkalipay.TradeQueryRsp
err error
want bool
}{
{name: "失败响应且交易不存在", response: &sdkalipay.TradeQueryRsp{Error: notExist}, want: true},
{name: "SDK 以错误返回交易不存在", err: fmt.Errorf("wrap: %w", &notExist), want: true},
{name: "其他业务失败", response: &sdkalipay.TradeQueryRsp{Error: sdkalipay.Error{Code: sdkalipay.Code("40004"), SubCode: "ACQ.SYSTEM_ERROR"}}, want: false},
{name: "成功响应", response: &sdkalipay.TradeQueryRsp{Error: sdkalipay.Error{Code: sdkalipay.CodeSuccess}, TradeStatus: sdkalipay.TradeStatusWaitBuyerPay}, want: false},
{name: "网络错误", err: fmt.Errorf("dial tcp: timeout"), want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := alipayTradeNotExist(tt.response, tt.err); got != tt.want {
t.Fatalf("alipayTradeNotExist() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -2,6 +2,7 @@ package payment
import (
"context"
stderrors "errors"
"strconv"
"time"
@@ -54,6 +55,18 @@ func (a *AlipayWapAdapter) Query(ctx context.Context, request agentrecharge.Onli
}
startedAt := time.Now()
response, callErr := client.TradeQuery(ctx, sdkalipay.TradeQuery{OutTradeNo: request.PaymentNo})
// 客户没打开过支付链接时支付宝侧没有这笔交易(ACQ.TRADE_NOT_EXIST):按"渠道没有这笔订单"返回,
// 恢复任务在过支付截止后据此关闭订单;不能当成查单失败,否则订单永远停在待支付。
if alipayTradeNotExist(response, callErr) {
if _, err = a.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
Result: constants.IntegrationResultSuccess, ProviderCode: alipayTradeNotExistCode,
ResponseSummary: map[string]any{"state": agentrecharge.OnlinePaymentStateNotFound},
DurationMS: time.Since(startedAt).Milliseconds(),
}); err != nil {
return agentrecharge.OnlinePaymentQueryResult{}, err
}
return agentrecharge.OnlinePaymentQueryResult{State: agentrecharge.OnlinePaymentStateNotFound}, nil
}
if callErr != nil {
return agentrecharge.OnlinePaymentQueryResult{}, a.completeUnknown(ctx, attempt.IntegrationID, startedAt, callErr)
}
@@ -128,6 +141,18 @@ func (a *AlipayWapAdapter) completeUnknown(ctx context.Context, integrationID st
return apperrors.Wrap(apperrors.CodeTimeout, cause, "支付宝支付请求结果未知")
}
// alipayTradeNotExistCode 是支付宝查单"交易不存在"的业务子码。
const alipayTradeNotExistCode = "ACQ.TRADE_NOT_EXIST"
// alipayTradeNotExist 判断查单结果是否为"交易不存在":SDK 可能以失败响应返回,也可能以 *Error 返回。
func alipayTradeNotExist(response *sdkalipay.TradeQueryRsp, callErr error) bool {
if response != nil && response.IsFailure() && response.SubCode == alipayTradeNotExistCode {
return true
}
var sdkErr *sdkalipay.Error
return stderrors.As(callErr, &sdkErr) && sdkErr != nil && sdkErr.SubCode == alipayTradeNotExistCode
}
func mapAlipayTradeState(state sdkalipay.TradeStatus) string {
switch state {
case sdkalipay.TradeStatusSuccess, sdkalipay.TradeStatusFinished: