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,101 @@
package packagesilence
import (
"context"
"strings"
"gorm.io/gorm"
agentrechargeApp "github.com/break/junhong_cmp_fiber/internal/application/agentrecharge"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
// CancelOrderCommand 是作废待支付延期订单的命令:Scope.CustomerID 非 0 为 H5 客户作废自己的订单,为 0 为后台运营作废。
type CancelOrderCommand struct {
OrderID uint
Scope Scope
Reason string
}
// CancelPendingOrder 作废未支付的在线延期订单,作废后同一套餐可以重新下单。
//
// 作废前先按冻结商户查单:渠道已支付则按支付成功处理(照常延期)并拒绝作废;
// 其余情况(未支付、渠道无此订单、查单失败)直接关闭。关闭后才到达的支付会进入异常并由恢复任务原路退款,不会吞款。
func (s *ExtensionService) CancelPendingOrder(ctx context.Context, command CancelOrderCommand) (*dto.SilenceExtensionOrderResponse, error) {
if command.Scope.CustomerID == 0 {
if err := s.requirePermission(ctx, constants.PermissionPackageSilenceExtend); err != nil {
return nil, err
}
}
order, err := postgres.NewPackageSilenceOrderStore(s.db).GetByID(ctx, command.OrderID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeNotFound, "延期订单不存在")
}
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询延期订单失败")
}
if command.Scope.CustomerID != 0 && (order.CustomerID == nil || *order.CustomerID != command.Scope.CustomerID) {
return nil, errors.New(errors.CodeNotFound, "延期订单不存在")
}
if order.Status != constants.SilenceOrderStatusPendingPayment {
return nil, errors.New(errors.CodeInvalidStatus, "只有待支付的延期订单可以作废")
}
paid, err := s.confirmIfPaid(ctx, order)
if err != nil {
return nil, err
}
if paid {
return nil, errors.New(errors.CodeInvalidStatus, "该订单已支付,已按支付成功办理延期,不能作废")
}
reason := "手动作废"
if command.Scope.CustomerID != 0 {
reason = "客户作废"
}
if extra := strings.TrimSpace(command.Reason); extra != "" {
reason += ":" + extra
}
if err := s.closeOrder(ctx, order.ID, reason); err != nil {
return nil, err
}
current, err := postgres.NewPackageSilenceOrderStore(s.db).GetByID(ctx, order.ID)
if err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询延期订单失败")
}
return s.orderResponse(ctx, current, false)
}
// confirmIfPaid 查询订单下待支付的支付单;渠道已支付时走支付确认并返回 true。查单失败不阻止作废。
func (s *ExtensionService) confirmIfPaid(ctx context.Context, order *model.PackageSilenceExtensionOrder) (bool, error) {
if order.PaymentMethod == model.PaymentByOffline {
return false, nil
}
var payments []model.Payment
if err := s.db.WithContext(ctx).Where("order_type = ? AND order_id = ? AND status = ?", constants.PaymentOrderTypeSilenceExtension, order.ID, model.PaymentRecordStatusPending).
Order("id DESC").Find(&payments).Error; err != nil {
return false, errors.Wrap(errors.CodeDatabaseError, err, "查询延期支付单失败")
}
for index := range payments {
payment := &payments[index]
state, query, err := s.queryPayment(ctx, payment)
if err != nil || state != agentrechargeApp.OnlinePaymentStatePaid {
continue
}
paidAt := s.now()
if query.PaidAt != nil {
paidAt = *query.PaidAt
}
if err := s.ConfirmPayment(ctx, PaymentConfirmCommand{
PaymentNo: payment.PaymentNo, ThirdPartyTradeNo: query.ThirdPartyTradeNo, Amount: query.Amount, PaidAt: paidAt,
}); err != nil {
return false, err
}
return true, nil
}
return false, nil
}