修复支付以及订单相关的问题
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 1s

This commit is contained in:
2026-05-08 10:15:03 +08:00
parent 348cb4e670
commit 7c85a8cf29
6 changed files with 204 additions and 12 deletions

View File

@@ -418,7 +418,7 @@ func (s *Service) createForceRechargeOrder(
return nil, errors.Wrap(errors.CodeDatabaseError, err, "创建充值订单失败")
}
paymentNo := generateClientRechargeNo()
paymentNo := generateClientPaymentNo()
payment := &model.Payment{
PaymentNo: paymentNo,
OrderID: rechargeOrder.ID,
@@ -433,7 +433,7 @@ func (s *Service) createForceRechargeOrder(
return nil, errors.Wrap(errors.CodeDatabaseError, err, "创建支付记录失败")
}
paymentResult, err := paymentProvider.CreateJSAPIPayment(ctx, rechargeOrderNo, "余额充值", openID, int(rechargeOrder.Amount))
paymentResult, err := paymentProvider.CreateJSAPIPayment(ctx, paymentNo, "余额充值", openID, int(rechargeOrder.Amount))
if err != nil {
return nil, err
}
@@ -809,6 +809,10 @@ func generateClientRechargeNo() string {
return fmt.Sprintf("CRCH%d%06d", time.Now().UnixNano()/1e6, rand.Intn(1000000))
}
func generateClientPaymentNo() string {
return fmt.Sprintf("PAY%d%06d", time.Now().UnixNano()/1e6, rand.Intn(1000000))
}
// ListOrders 查询 C 端订单列表。
func (s *Service) ListOrders(ctx context.Context, customerID uint, req *dto.ClientOrderListRequest) ([]dto.ClientOrderListItem, int64, error) {
skipCtx := context.WithValue(ctx, constants.ContextKeySubordinateShopIDs, []uint{})
@@ -1012,15 +1016,41 @@ func (s *Service) PayOrder(ctx context.Context, customerID uint, orderID uint, r
return nil, err
}
// 记录本次支付使用的微信配置,供支付回调验签使用
if err := s.db.WithContext(skipCtx).Model(&model.Order{}).
Where("id = ?", orderID).
Update("payment_config_id", activeConfig.ID).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "更新订单支付配置失败")
paymentNo := generateClientPaymentNo()
payment := &model.Payment{
PaymentNo: paymentNo,
OrderID: order.ID,
OrderType: model.PaymentOrderTypePackage,
PaymentMethod: model.PaymentByWechat,
Amount: order.TotalAmount,
Status: model.PaymentRecordStatusPending,
PaymentConfigID: &activeConfig.ID,
}
paymentResult, err := paymentProvider.CreateJSAPIPayment(skipCtx, order.OrderNo, "套餐购买", openID, int(order.TotalAmount))
if err := s.db.WithContext(skipCtx).Transaction(func(tx *gorm.DB) error {
// 订单仍保留最近一次支付配置,兼容旧 ORD 回调。
if err := tx.Model(&model.Order{}).
Where("id = ?", orderID).
Update("payment_config_id", activeConfig.ID).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "更新订单支付配置失败")
}
if err := s.paymentStore.CreateWithTx(skipCtx, tx, payment); err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "创建支付记录失败")
}
return nil
}); err != nil {
return nil, err
}
paymentResult, err := paymentProvider.CreateJSAPIPayment(skipCtx, paymentNo, "套餐购买", openID, int(order.TotalAmount))
if err != nil {
if updateErr := s.paymentStore.UpdateStatus(skipCtx, payment.ID, model.PaymentRecordStatusFailed); updateErr != nil {
s.logger.Warn("标记支付记录失败状态失败",
zap.Uint("payment_id", payment.ID),
zap.String("payment_no", paymentNo),
zap.Error(updateErr),
)
}
return nil, err
}
return &dto.ClientPayOrderResponse{

View File

@@ -1778,6 +1778,106 @@ func (s *Service) HandlePaymentCallback(ctx context.Context, orderNo string, pay
return nil
}
// HandlePaymentRecordCallback 通过支付单处理套餐订单第三方支付回调。
func (s *Service) HandlePaymentRecordCallback(ctx context.Context, paymentNo string, paymentMethod string, thirdPartyTradeNo string, actualPaidAmount int64) error {
payment, err := s.paymentStore.GetByPaymentNo(ctx, paymentNo)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeNotFound, "支付记录不存在")
}
return errors.Wrap(errors.CodeDatabaseError, err, "查询支付记录失败")
}
if payment.OrderType != model.PaymentOrderTypePackage {
return errors.New(errors.CodeInvalidParam, "支付记录类型不匹配")
}
order, err := s.orderStore.GetByID(ctx, payment.OrderID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeNotFound, "订单不存在")
}
return errors.Wrap(errors.CodeDatabaseError, err, "查询订单失败")
}
now := time.Now()
shouldResumeAfterPayment := false
shouldEnqueueCommission := false
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
paymentUpdates := map[string]any{
"status": model.PaymentRecordStatusPaid,
"paid_at": now,
}
if thirdPartyTradeNo != "" {
paymentUpdates["third_party_trade_no"] = thirdPartyTradeNo
}
paymentResult := tx.Model(&model.Payment{}).
Where("id = ? AND status = ?", payment.ID, model.PaymentRecordStatusPending).
Updates(paymentUpdates)
if paymentResult.Error != nil {
return errors.Wrap(errors.CodeDatabaseError, paymentResult.Error, "更新支付记录失败")
}
if paymentResult.RowsAffected == 0 {
var currentPayment model.Payment
if err := tx.First(&currentPayment, payment.ID).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "查询支付记录失败")
}
if currentPayment.Status == model.PaymentRecordStatusPaid {
return nil
}
return errors.New(errors.CodeInvalidStatus, "支付记录状态不允许处理")
}
result := tx.Model(&model.Order{}).
Where("id = ? AND payment_status = ?", order.ID, model.PaymentStatusPending).
Updates(map[string]any{
"payment_status": model.PaymentStatusPaid,
"payment_method": paymentMethod,
"paid_at": now,
"expires_at": nil,
"actual_paid_amount": actualPaidAmount,
})
if result.Error != nil {
return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新订单支付状态失败")
}
if result.RowsAffected == 0 {
var currentOrder model.Order
if err := tx.First(&currentOrder, order.ID).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "查询订单失败")
}
switch currentOrder.PaymentStatus {
case model.PaymentStatusPaid:
return nil
case model.PaymentStatusCancelled:
return errors.New(errors.CodeInvalidStatus, "订单已取消,无法支付")
case model.PaymentStatusRefunded:
return errors.New(errors.CodeInvalidStatus, "订单已退款,无法支付")
default:
return errors.New(errors.CodeInvalidStatus, "订单状态异常")
}
}
actualPaidAmountSnapshot := actualPaidAmount
order.ActualPaidAmount = &actualPaidAmountSnapshot
shouldResumeAfterPayment = true
shouldEnqueueCommission = true
return s.activatePackage(ctx, tx, order)
})
if err != nil {
return err
}
if shouldResumeAfterPayment {
s.tryResumeAfterPayment(ctx, order)
}
if shouldEnqueueCommission && order.CommissionStatus == model.CommissionStatusPending {
s.enqueueCommissionCalculation(ctx, order.ID)
}
return nil
}
// tryResumeAfterPayment 支付成功后检查是否应触发自动复机
// 仅当本次支付直接激活了主套餐status=1非排队时才调用复机避免虚流量已超但套餐排队时错误复机
func (s *Service) tryResumeAfterPayment(ctx context.Context, order *model.Order) {

View File

@@ -35,6 +35,7 @@ type Service struct {
orderStore *postgres.OrderStore
rechargeOrderStore *postgres.RechargeOrderStore
agentRechargeStore *postgres.AgentRechargeStore
paymentStore *postgres.PaymentStore
auditService AuditServiceInterface
redis *redis.Client
logger *zap.Logger
@@ -46,6 +47,7 @@ func New(
orderStore *postgres.OrderStore,
rechargeOrderStore *postgres.RechargeOrderStore,
agentRechargeStore *postgres.AgentRechargeStore,
paymentStore *postgres.PaymentStore,
auditService AuditServiceInterface,
rdb *redis.Client,
logger *zap.Logger,
@@ -55,6 +57,7 @@ func New(
orderStore: orderStore,
rechargeOrderStore: rechargeOrderStore,
agentRechargeStore: agentRechargeStore,
paymentStore: paymentStore,
auditService: auditService,
redis: rdb,
logger: logger,
@@ -512,6 +515,17 @@ func (s *Service) GetConfigForCallback(ctx context.Context, orderNo string) (*mo
// resolvePaymentConfigID 按订单号前缀从对应表中取 payment_config_id
func (s *Service) resolvePaymentConfigID(ctx context.Context, orderNo string) (*uint, error) {
if s.paymentStore != nil {
payment, err := s.paymentStore.GetByPaymentNo(ctx, orderNo)
if err == nil {
if payment.PaymentConfigID != nil {
return payment.PaymentConfigID, nil
}
} else if err != gorm.ErrRecordNotFound {
return nil, err
}
}
switch {
case len(orderNo) >= 3 && orderNo[:3] == "ORD":
order, err := s.orderStore.GetByOrderNo(ctx, orderNo)