This commit is contained in:
@@ -6,12 +6,9 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hibiken/asynq"
|
||||
commonLogic "github.com/perfect-panel/server/internal/logic/common"
|
||||
@@ -100,12 +97,10 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
||||
existingOrderNo, validateErr := l.validateOrderTradeNoBinding(orderInfo, tradeNoCandidates)
|
||||
if validateErr != nil {
|
||||
l.Errorw("Apple 交易绑定校验失败", logger.Field("orderNo", req.OrderNo), logger.Field("tradeNoCandidates", tradeNoCandidates), logger.Field("error", validateErr.Error()))
|
||||
l.sendIAPAttachTraceToTelegram("REJECT_BINDING_ERROR", orderInfo, u.Id, orderInfo.SubscribeId, "", orderInfo.Quantity, txPayload.PurchaseDate, txPayload.TransactionId, txPayload.OriginalTransactionId, validateErr.Error())
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "apple transaction binding error")
|
||||
}
|
||||
if existingOrderNo != "" {
|
||||
l.Errorw("Apple 交易重复绑定,返回已绑定订单", logger.Field("orderNo", req.OrderNo), logger.Field("existingOrderNo", existingOrderNo), logger.Field("tradeNoCandidates", tradeNoCandidates))
|
||||
l.sendIAPAttachTraceToTelegram("REJECT_DUPLICATE_TRANSACTION", orderInfo, u.Id, orderInfo.SubscribeId, "", orderInfo.Quantity, txPayload.PurchaseDate, txPayload.TransactionId, txPayload.OriginalTransactionId, "already used by "+existingOrderNo)
|
||||
// 关闭当前 pending 订单,避免产生孤儿订单
|
||||
if orderInfo.Status == orderStatusPending {
|
||||
if closeErr := l.svcCtx.DB.Model(&ordermodel.Order{}).
|
||||
@@ -282,7 +277,6 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
||||
l.Errorw("同订单幂等同步失败", logger.Field("orderNo", req.OrderNo), logger.Field("error", syncErr.Error()))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "sync order status failed: %v", syncErr.Error())
|
||||
}
|
||||
l.sendIAPAttachTraceToTelegram("IDEMPOTENT_SAME_ORDER", orderInfo, u.Id, subscribeId, tier, duration, txPayload.PurchaseDate, txPayload.TransactionId, txPayload.OriginalTransactionId, "")
|
||||
l.Infow("事务已处理,同订单幂等返回", logger.Field("orderNo", req.OrderNo), logger.Field("expiresAt", expiresAt))
|
||||
return &types.AttachAppleTransactionResponse{ExpiresAt: expiresAt, Tier: tier}, nil
|
||||
}
|
||||
@@ -296,7 +290,6 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
||||
l.Errorw("事务已处理但同步订单状态失败", logger.Field("orderNo", req.OrderNo), logger.Field("error", syncErr.Error()))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "sync order status failed: %v", syncErr.Error())
|
||||
}
|
||||
l.sendIAPAttachTraceToTelegram("SUCCESS_NEW_PURCHASE_QUEUE", orderInfo, u.Id, subscribeId, tier, duration, txPayload.PurchaseDate, txPayload.TransactionId, txPayload.OriginalTransactionId, "")
|
||||
l.Infow("事务已处理,首购订单等待激活队列发放订阅", logger.Field("orderNo", req.OrderNo), logger.Field("expiresAt", exp.Unix()))
|
||||
return &types.AttachAppleTransactionResponse{ExpiresAt: exp.Unix(), Tier: tier}, nil
|
||||
}
|
||||
@@ -316,7 +309,6 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
||||
l.Errorw("同步订单状态失败(existSub)", logger.Field("orderNo", req.OrderNo), logger.Field("error", syncErr.Error()))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "sync order status failed: %v", syncErr.Error())
|
||||
}
|
||||
l.sendIAPAttachTraceToTelegram("SUCCESS_RENEW_EXIST_SUB", orderInfo, u.Id, subscribeId, tier, duration, txPayload.PurchaseDate, txPayload.TransactionId, txPayload.OriginalTransactionId, "")
|
||||
l.Infow("事务已处理,刷新订阅到期时间", logger.Field("originalTransactionId", txPayload.OriginalTransactionId), logger.Field("tier", tier), logger.Field("expiresAt", newExpire.Unix()))
|
||||
return &types.AttachAppleTransactionResponse{
|
||||
ExpiresAt: newExpire.Unix(),
|
||||
@@ -337,7 +329,6 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
||||
l.Errorw("同步订单状态失败(orderLinkedSub)", logger.Field("orderNo", req.OrderNo), logger.Field("error", syncErr.Error()))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "sync order status failed: %v", syncErr.Error())
|
||||
}
|
||||
l.sendIAPAttachTraceToTelegram("SUCCESS_RENEW_ORDER_LINKED_SUB", orderInfo, u.Id, subscribeId, tier, duration, txPayload.PurchaseDate, txPayload.TransactionId, txPayload.OriginalTransactionId, "")
|
||||
l.Infow("事务已处理,刷新订单关联订阅到期时间", logger.Field("orderNo", req.OrderNo), logger.Field("userSubscribeId", orderLinkedSub.Id), logger.Field("expiresAt", newExpire.Unix()))
|
||||
return &types.AttachAppleTransactionResponse{ExpiresAt: newExpire.Unix(), Tier: tier}, nil
|
||||
}
|
||||
@@ -355,7 +346,6 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
||||
l.Errorw("同步订单状态失败(singleModeAnchorSub)", logger.Field("orderNo", req.OrderNo), logger.Field("error", syncErr.Error()))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "sync order status failed: %v", syncErr.Error())
|
||||
}
|
||||
l.sendIAPAttachTraceToTelegram("SUCCESS_RENEW_SINGLE_MODE_ANCHOR", orderInfo, u.Id, subscribeId, tier, duration, txPayload.PurchaseDate, txPayload.TransactionId, txPayload.OriginalTransactionId, "")
|
||||
l.Infow("事务已处理,刷新单订阅锚点到期时间", logger.Field("userSubscribeId", singleModeAnchorSub.Id), logger.Field("expiresAt", newExpire.Unix()))
|
||||
return &types.AttachAppleTransactionResponse{ExpiresAt: newExpire.Unix(), Tier: tier}, nil
|
||||
}
|
||||
@@ -394,6 +384,7 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
||||
userSub := user.Subscribe{
|
||||
UserId: entitlement.EffectiveUserID,
|
||||
SubscribeId: subscribeId,
|
||||
OrderId: orderInfo.Id,
|
||||
StartTime: time.Now(),
|
||||
ExpireTime: exp,
|
||||
Traffic: 0,
|
||||
@@ -426,7 +417,6 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
||||
l.Errorw("绑定事务提交失败", logger.Field("error", err.Error()))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert error: %v", err.Error())
|
||||
}
|
||||
l.sendIAPAttachTraceToTelegram("SUCCESS_COMMIT", orderInfo, u.Id, subscribeId, tier, duration, txPayload.PurchaseDate, txPayload.TransactionId, txPayload.OriginalTransactionId, "")
|
||||
|
||||
// 事务提交后立即清除订阅缓存,避免 App 查到旧数据(激活队列异步执行,存在竞态)
|
||||
if orderLinkedSub != nil {
|
||||
@@ -600,94 +590,3 @@ func containsString(candidates []string, target string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *AttachTransactionLogic) sendIAPAttachTraceToTelegram(status string, orderInfo *ordermodel.Order, userID int64, subscribeID int64, subscribeName string, quantity int64, purchaseAt time.Time, transactionID string, originalTransactionID string, note string) {
|
||||
if l.svcCtx == nil {
|
||||
return
|
||||
}
|
||||
orderNo := ""
|
||||
if orderInfo != nil {
|
||||
orderNo = orderInfo.OrderNo
|
||||
}
|
||||
if subscribeName == "" {
|
||||
subscribeName = "-"
|
||||
}
|
||||
message := fmt.Sprintf(
|
||||
"IAP Attach Log [%s]\n订单号: %s\n购买时间: %s\n购买人ID: %d\n订阅信息: %s (subscribe_id=%d, quantity=%d)\ntransaction: %s\noriginal_transaction: %s",
|
||||
status,
|
||||
orderNo,
|
||||
purchaseAt.Format("2006-01-02 15:04:05"),
|
||||
userID,
|
||||
subscribeName,
|
||||
subscribeID,
|
||||
quantity,
|
||||
transactionID,
|
||||
originalTransactionID,
|
||||
)
|
||||
if note != "" {
|
||||
message += "\n备注: " + note
|
||||
}
|
||||
|
||||
overrideBotToken := strings.TrimSpace(os.Getenv("TG_BOT_TOKEN"))
|
||||
overrideChatID := strings.TrimSpace(os.Getenv("TG_CHAT_ID"))
|
||||
if overrideBotToken != "" && overrideChatID != "" {
|
||||
if chatID, err := strconv.ParseInt(overrideChatID, 10, 64); err == nil && chatID != 0 {
|
||||
bot := l.svcCtx.TelegramBot
|
||||
if bot == nil || strings.TrimSpace(l.svcCtx.Config.Telegram.BotToken) != overrideBotToken {
|
||||
overrideBot, newErr := tgbotapi.NewBotAPI(overrideBotToken)
|
||||
if newErr == nil {
|
||||
bot = overrideBot
|
||||
} else {
|
||||
l.Errorw("初始化 TG 覆盖 Bot 失败", logger.Field("error", newErr.Error()))
|
||||
}
|
||||
}
|
||||
if bot != nil {
|
||||
msg := tgbotapi.NewMessage(chatID, message)
|
||||
if _, sendErr := bot.Send(msg); sendErr != nil {
|
||||
l.Errorw("发送 IAP TG 覆盖通道消息失败", logger.Field("error", sendErr.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if l.svcCtx.TelegramBot == nil || !l.svcCtx.Config.Telegram.EnableNotify {
|
||||
return
|
||||
}
|
||||
|
||||
if groupChatID, err := strconv.ParseInt(strings.TrimSpace(l.svcCtx.Config.Telegram.GroupChatID), 10, 64); err == nil && groupChatID != 0 {
|
||||
msg := tgbotapi.NewMessage(groupChatID, message)
|
||||
if _, sendErr := l.svcCtx.TelegramBot.Send(msg); sendErr != nil {
|
||||
l.Errorw("发送 IAP TG 群消息失败", logger.Field("error", sendErr.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
admins, err := l.svcCtx.UserModel.QueryAdminUsers(l.ctx)
|
||||
if err != nil {
|
||||
l.Errorw("查询管理员失败(IAP TG日志)", logger.Field("error", err.Error()))
|
||||
return
|
||||
}
|
||||
for _, admin := range admins {
|
||||
if telegramID, ok := findTelegramAuth(admin); ok {
|
||||
msg := tgbotapi.NewMessage(telegramID, message)
|
||||
if _, sendErr := l.svcCtx.TelegramBot.Send(msg); sendErr != nil {
|
||||
l.Errorw("发送 IAP TG 管理员消息失败", logger.Field("error", sendErr.Error()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func findTelegramAuth(u *user.User) (int64, bool) {
|
||||
if u == nil {
|
||||
return 0, false
|
||||
}
|
||||
for _, item := range u.AuthMethods {
|
||||
if item.AuthType == "telegram" {
|
||||
if telegramID, err := strconv.ParseInt(item.AuthIdentifier, 10, 64); err == nil {
|
||||
return telegramID, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
@@ -111,6 +111,26 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P
|
||||
}
|
||||
}
|
||||
|
||||
// 单订阅模式下,若已有同套餐 pending 订单,直接返回,防止重复创建
|
||||
if l.svcCtx.Config.Subscribe.SingleModel && orderType == 1 {
|
||||
var existPending order.Order
|
||||
if e := l.svcCtx.DB.WithContext(l.ctx).
|
||||
Model(&order.Order{}).
|
||||
Where("user_id = ? AND subscribe_id = ? AND status = 1", u.Id, targetSubscribeID).
|
||||
Order("id DESC").
|
||||
First(&existPending).Error; e == nil && existPending.Id > 0 {
|
||||
l.Infow("[Purchase] single mode pending order exists, returning existing",
|
||||
logger.Field("user_id", u.Id),
|
||||
logger.Field("order_no", existPending.OrderNo),
|
||||
logger.Field("subscribe_id", targetSubscribeID),
|
||||
)
|
||||
return &types.PurchaseOrderResponse{
|
||||
OrderNo: existPending.OrderNo,
|
||||
AppAccountToken: existPending.AppAccountToken,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// find subscribe plan
|
||||
sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, targetSubscribeID)
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ func (m *defaultUserModel) FindSingleModeAnchorSubscribe(ctx context.Context, us
|
||||
var data Subscribe
|
||||
err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, _ interface{}) error {
|
||||
return conn.Model(&Subscribe{}).
|
||||
Where("user_id = ? AND order_id > 0 AND token != '' AND `status` IN ?", userId, []int64{0, 1, 2, 3, 5}).
|
||||
Where("user_id = ? AND token != '' AND (order_id > 0 OR token LIKE 'iap:%') AND `status` IN ?", userId, []int64{0, 1, 2, 3, 5}).
|
||||
Order("expire_time DESC").
|
||||
Order("updated_at DESC").
|
||||
Order("id DESC").
|
||||
|
||||
Reference in New Issue
Block a user