- getStatusLogic 类型断言修复(*user.User) - restoreLogic 事务拆分为单条处理 + appAccountToken 解析 - attachTransactionLogic 提取 ParseProductIdDuration 共享函数 - 新增 config_helper.go 统一 Apple API 配置加载 - reconcileLogic 补充 BundleID 配置读取 - activateOrderLogic 邀请赠送天数逻辑完善 Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -191,7 +191,7 @@ func (l *ActivateOrderLogic) processOrderByType(ctx context.Context, orderInfo *
|
||||
// finalizeCouponAndOrder handles post-processing tasks including coupon updates
|
||||
// and order status finalization
|
||||
func (l *ActivateOrderLogic) finalizeCouponAndOrder(ctx context.Context, orderInfo *order.Order) {
|
||||
// Update coupon if exists
|
||||
// Update coupon if exists (non-critical, logged but not blocking)
|
||||
if orderInfo.Coupon != "" {
|
||||
if err := l.svc.CouponModel.UpdateCount(ctx, orderInfo.Coupon); err != nil {
|
||||
logger.WithContext(ctx).Error("Update coupon status failed",
|
||||
@@ -201,14 +201,14 @@ func (l *ActivateOrderLogic) finalizeCouponAndOrder(ctx context.Context, orderIn
|
||||
}
|
||||
}
|
||||
|
||||
// Update order status
|
||||
orderInfo.Status = OrderStatusFinished
|
||||
if err := l.svc.OrderModel.Update(ctx, orderInfo); err != nil {
|
||||
// Update order status using state-guarded UpdateOrderStatus to prevent double finalization
|
||||
if err := l.svc.OrderModel.UpdateOrderStatus(ctx, orderInfo.OrderNo, OrderStatusFinished); err != nil {
|
||||
logger.WithContext(ctx).Error("Update order status failed",
|
||||
logger.Field("error", err.Error()),
|
||||
logger.Field("order_no", orderInfo.OrderNo),
|
||||
)
|
||||
}
|
||||
orderInfo.Status = OrderStatusFinished
|
||||
}
|
||||
|
||||
// NewPurchase handles new subscription purchase including user creation,
|
||||
@@ -594,10 +594,28 @@ func (l *ActivateOrderLogic) handleCommission(ctx context.Context, userInfo *use
|
||||
|
||||
// Use transaction for commission updates
|
||||
err = l.svc.DB.Transaction(func(tx *gorm.DB) error {
|
||||
referer.Commission += amount
|
||||
if err = l.svc.UserModel.Update(ctx, referer, tx); err != nil {
|
||||
return err
|
||||
// Idempotency: check if commission log already exists for this order
|
||||
var existingLogCount int64
|
||||
if e := tx.Model(&log.SystemLog{}).
|
||||
Where("type = ? AND object_id = ? AND content LIKE ?",
|
||||
log.TypeCommission.Uint8(), referer.Id, fmt.Sprintf("%%\"%s\"%%", orderInfo.OrderNo)).
|
||||
Count(&existingLogCount).Error; e != nil {
|
||||
return e
|
||||
}
|
||||
if existingLogCount > 0 {
|
||||
logger.WithContext(ctx).Info("Commission already processed, skip",
|
||||
logger.Field("order_no", orderInfo.OrderNo),
|
||||
logger.Field("referer_id", referer.Id),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Atomic increment to prevent lost updates under concurrency
|
||||
if e := tx.Model(&user.User{}).Where("id = ?", referer.Id).
|
||||
UpdateColumn("commission", gorm.Expr("commission + ?", amount)).Error; e != nil {
|
||||
return e
|
||||
}
|
||||
referer.Commission += amount
|
||||
|
||||
var commissionType uint16
|
||||
switch orderInfo.Type {
|
||||
@@ -605,6 +623,8 @@ func (l *ActivateOrderLogic) handleCommission(ctx context.Context, userInfo *use
|
||||
commissionType = log.CommissionTypePurchase
|
||||
case OrderTypeRenewal:
|
||||
commissionType = log.CommissionTypeRenewal
|
||||
default:
|
||||
commissionType = log.CommissionTypePurchase
|
||||
}
|
||||
|
||||
commissionLog := &log.Commission{
|
||||
@@ -657,6 +677,23 @@ func (l *ActivateOrderLogic) grantGiftDays(ctx context.Context, u *user.User, da
|
||||
if u == nil || days <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Idempotency: check if gift log already exists for this order + user
|
||||
var existingLogCount int64
|
||||
if e := l.svc.DB.Model(&log.SystemLog{}).
|
||||
Where("type = ? AND object_id = ? AND content LIKE ?",
|
||||
log.TypeGift.Uint8(), u.Id, fmt.Sprintf("%%\"%s\"%%", orderNo)).
|
||||
Count(&existingLogCount).Error; e != nil {
|
||||
return e
|
||||
}
|
||||
if existingLogCount > 0 {
|
||||
logger.WithContext(ctx).Info("Gift days already granted, skip",
|
||||
logger.Field("order_no", orderNo),
|
||||
logger.Field("user_id", u.Id),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
activeSubscribe, err := l.svc.UserModel.FindActiveSubscribe(ctx, u.Id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -946,10 +983,28 @@ func (l *ActivateOrderLogic) Recharge(ctx context.Context, orderInfo *order.Orde
|
||||
|
||||
// Update balance in transaction
|
||||
err = l.svc.DB.Transaction(func(tx *gorm.DB) error {
|
||||
userInfo.Balance += orderInfo.Price
|
||||
if err = l.svc.UserModel.Update(ctx, userInfo, tx); err != nil {
|
||||
return err
|
||||
// Idempotency: check if balance log already exists for this order
|
||||
var existingLogCount int64
|
||||
if e := tx.Model(&log.SystemLog{}).
|
||||
Where("type = ? AND object_id = ? AND content LIKE ?",
|
||||
log.TypeBalance.Uint8(), userInfo.Id, fmt.Sprintf("%%\"%s\"%%", orderInfo.OrderNo)).
|
||||
Count(&existingLogCount).Error; e != nil {
|
||||
return e
|
||||
}
|
||||
if existingLogCount > 0 {
|
||||
logger.WithContext(ctx).Info("Recharge already processed, skip",
|
||||
logger.Field("order_no", orderInfo.OrderNo),
|
||||
logger.Field("user_id", userInfo.Id),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Atomic increment to prevent lost updates
|
||||
if e := tx.Model(&user.User{}).Where("id = ?", userInfo.Id).
|
||||
UpdateColumn("balance", gorm.Expr("balance + ?", orderInfo.Price)).Error; e != nil {
|
||||
return e
|
||||
}
|
||||
userInfo.Balance += orderInfo.Price
|
||||
|
||||
balanceLog := &log.Balance{
|
||||
Amount: orderInfo.Price,
|
||||
@@ -1068,6 +1123,9 @@ func (l *ActivateOrderLogic) buildAdminNotificationData(orderInfo *order.Order,
|
||||
|
||||
// sendUserNotifyWithTelegram sends a notification message to a user via Telegram
|
||||
func (l *ActivateOrderLogic) sendUserNotifyWithTelegram(chatId int64, text string) {
|
||||
if l.svc.TelegramBot == nil {
|
||||
return
|
||||
}
|
||||
msg := tgbotapi.NewMessage(chatId, text)
|
||||
msg.ParseMode = "markdown"
|
||||
if _, err := l.svc.TelegramBot.Send(msg); err != nil {
|
||||
@@ -1077,6 +1135,9 @@ func (l *ActivateOrderLogic) sendUserNotifyWithTelegram(chatId int64, text strin
|
||||
|
||||
// sendAdminNotifyWithTelegram sends a notification message to all admin users via Telegram
|
||||
func (l *ActivateOrderLogic) sendAdminNotifyWithTelegram(ctx context.Context, text string) {
|
||||
if l.svc.TelegramBot == nil {
|
||||
return
|
||||
}
|
||||
admins, err := l.svc.UserModel.QueryAdminUsers(ctx)
|
||||
if err != nil {
|
||||
logger.WithContext(ctx).Error("Query admin users failed", logger.Field("error", err.Error()))
|
||||
|
||||
Reference in New Issue
Block a user