|
|
|
@@ -6,10 +6,12 @@ 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"
|
|
|
|
@@ -38,6 +40,7 @@ const (
|
|
|
|
|
orderTypeSubscribe uint8 = 1
|
|
|
|
|
orderStatusPending uint8 = 1
|
|
|
|
|
orderStatusPaid uint8 = 2
|
|
|
|
|
orderStatusFinished uint8 = 5
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func NewAttachTransactionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AttachTransactionLogic {
|
|
|
|
@@ -88,6 +91,17 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
|
|
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "invalid jws")
|
|
|
|
|
}
|
|
|
|
|
l.Infow("JWS 验签成功", logger.Field("productId", txPayload.ProductId), logger.Field("originalTransactionId", txPayload.OriginalTransactionId), logger.Field("purchaseAt", txPayload.PurchaseDate))
|
|
|
|
|
tradeNoCandidates := l.getAppleTradeNoCandidates(txPayload)
|
|
|
|
|
if err = l.validateOrderTradeNoBinding(orderInfo, tradeNoCandidates); err != nil {
|
|
|
|
|
l.Errorw("Apple 交易重复绑定,拒绝处理", logger.Field("orderNo", req.OrderNo), logger.Field("tradeNoCandidates", tradeNoCandidates), logger.Field("error", err.Error()))
|
|
|
|
|
l.sendIAPAttachTraceToTelegram("REJECT_DUPLICATE_TRANSACTION", orderInfo, u.Id, orderInfo.SubscribeId, "", orderInfo.Quantity, txPayload.PurchaseDate, txPayload.TransactionId, txPayload.OriginalTransactionId, err.Error())
|
|
|
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "apple transaction already used")
|
|
|
|
|
}
|
|
|
|
|
tradeNo := ""
|
|
|
|
|
if len(tradeNoCandidates) > 0 {
|
|
|
|
|
tradeNo = tradeNoCandidates[0]
|
|
|
|
|
}
|
|
|
|
|
orderAlreadyBound := containsString(tradeNoCandidates, orderInfo.TradeNo)
|
|
|
|
|
// idempotency: check existing transaction by original id
|
|
|
|
|
var existTx *iapmodel.Transaction
|
|
|
|
|
existTx, _ = iapmodel.NewModel(l.svcCtx.DB, l.svcCtx.Redis).FindByOriginalId(l.ctx, txPayload.OriginalTransactionId)
|
|
|
|
@@ -203,6 +217,11 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
exp := iapapple.CalcExpire(txPayload.PurchaseDate, duration)
|
|
|
|
|
shouldAccumulate := duration > 0 && (strings.EqualFold(parsedUnit, "Day") || strings.EqualFold(txPayload.Type, "Consumable"))
|
|
|
|
|
accumulateDuration := int64(0)
|
|
|
|
|
if shouldAccumulate {
|
|
|
|
|
accumulateDuration = duration
|
|
|
|
|
}
|
|
|
|
|
l.Infow("计算订阅到期时间", logger.Field("expireAt", exp), logger.Field("expireUnix", exp.Unix()))
|
|
|
|
|
var orderLinkedSub *user.Subscribe
|
|
|
|
|
if !isNewPurchaseOrder && orderInfo != nil && orderInfo.SubscribeToken != "" {
|
|
|
|
@@ -239,60 +258,106 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
existSub, existSubErr := l.findIAPSubscribeByOriginalTransactionID(txPayload.OriginalTransactionId)
|
|
|
|
|
if existSubErr != nil && !errors.Is(existSubErr, gorm.ErrRecordNotFound) {
|
|
|
|
|
l.Errorw("查询 IAP 订阅失败", logger.Field("error", existSubErr.Error()), logger.Field("originalTransactionId", txPayload.OriginalTransactionId))
|
|
|
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find iap subscribe error: %v", existSubErr.Error())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if existTx != nil && existTx.Id > 0 {
|
|
|
|
|
if orderAlreadyBound {
|
|
|
|
|
expiresAt := exp.Unix()
|
|
|
|
|
if existSub != nil && existSub.Id > 0 {
|
|
|
|
|
expiresAt = existSub.ExpireTime.Unix()
|
|
|
|
|
}
|
|
|
|
|
if orderLinkedSub != nil && orderLinkedSub.Id > 0 {
|
|
|
|
|
expiresAt = orderLinkedSub.ExpireTime.Unix()
|
|
|
|
|
}
|
|
|
|
|
if singleModeAnchorSub != nil && singleModeAnchorSub.Id > 0 {
|
|
|
|
|
expiresAt = singleModeAnchorSub.ExpireTime.Unix()
|
|
|
|
|
}
|
|
|
|
|
if bindErr := l.bindOrderTradeNo(orderInfo, tradeNo); bindErr != nil {
|
|
|
|
|
l.Errorw("回填订单交易号失败", logger.Field("orderNo", req.OrderNo), logger.Field("tradeNo", tradeNo), logger.Field("error", bindErr.Error()))
|
|
|
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "bind order trade_no failed: %v", bindErr.Error())
|
|
|
|
|
}
|
|
|
|
|
if syncErr := l.syncOrderStatusAndEnqueue(orderInfo, 0); syncErr != nil {
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if isNewPurchaseOrder {
|
|
|
|
|
if bindErr := l.bindOrderTradeNo(orderInfo, tradeNo); bindErr != nil {
|
|
|
|
|
l.Errorw("写入订单交易号失败", logger.Field("orderNo", req.OrderNo), logger.Field("tradeNo", tradeNo), logger.Field("error", bindErr.Error()))
|
|
|
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "bind order trade_no failed: %v", bindErr.Error())
|
|
|
|
|
}
|
|
|
|
|
if syncErr := l.syncOrderStatusAndEnqueue(orderInfo, 0); syncErr != nil {
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
existSub, err := l.findIAPSubscribeByOriginalTransactionID(txPayload.OriginalTransactionId)
|
|
|
|
|
switch {
|
|
|
|
|
case err == nil && existSub != nil && existSub.Id > 0:
|
|
|
|
|
newExpire, updateErr := l.extendSubscribeForIAP(existSub, exp, subscribeId)
|
|
|
|
|
case existSubErr == nil && existSub != nil && existSub.Id > 0:
|
|
|
|
|
newExpire, updateErr := l.extendSubscribeForIAP(existSub, exp, accumulateDuration, subscribeId)
|
|
|
|
|
if updateErr != nil {
|
|
|
|
|
l.Errorw("刷新 IAP 订阅失败", logger.Field("error", updateErr.Error()), logger.Field("subscribeId", existSub.Id))
|
|
|
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update iap subscribe failed: %v", updateErr.Error())
|
|
|
|
|
}
|
|
|
|
|
if bindErr := l.bindOrderTradeNo(orderInfo, tradeNo); bindErr != nil {
|
|
|
|
|
l.Errorw("写入订单交易号失败", logger.Field("orderNo", req.OrderNo), logger.Field("tradeNo", tradeNo), logger.Field("error", bindErr.Error()))
|
|
|
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "bind order trade_no failed: %v", bindErr.Error())
|
|
|
|
|
}
|
|
|
|
|
if syncErr := l.syncOrderStatusAndEnqueue(orderInfo, newExpire.Unix()); syncErr != nil {
|
|
|
|
|
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(),
|
|
|
|
|
Tier: tier,
|
|
|
|
|
}, nil
|
|
|
|
|
case err != nil && !errors.Is(err, gorm.ErrRecordNotFound):
|
|
|
|
|
l.Errorw("查询 IAP 订阅失败", logger.Field("error", err.Error()), logger.Field("originalTransactionId", txPayload.OriginalTransactionId))
|
|
|
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find iap subscribe error: %v", err.Error())
|
|
|
|
|
}
|
|
|
|
|
if orderLinkedSub != nil {
|
|
|
|
|
newExpire, updateErr := l.extendSubscribeForIAP(orderLinkedSub, exp, subscribeId)
|
|
|
|
|
newExpire, updateErr := l.extendSubscribeForIAP(orderLinkedSub, exp, accumulateDuration, subscribeId)
|
|
|
|
|
if updateErr != nil {
|
|
|
|
|
l.Errorw("刷新订单关联订阅失败", logger.Field("error", updateErr.Error()), logger.Field("subscribeId", orderLinkedSub.Id))
|
|
|
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update order subscribe failed: %v", updateErr.Error())
|
|
|
|
|
}
|
|
|
|
|
if bindErr := l.bindOrderTradeNo(orderInfo, tradeNo); bindErr != nil {
|
|
|
|
|
l.Errorw("写入订单交易号失败", logger.Field("orderNo", req.OrderNo), logger.Field("tradeNo", tradeNo), logger.Field("error", bindErr.Error()))
|
|
|
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "bind order trade_no failed: %v", bindErr.Error())
|
|
|
|
|
}
|
|
|
|
|
if syncErr := l.syncOrderStatusAndEnqueue(orderInfo, newExpire.Unix()); syncErr != nil {
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
if singleModeAnchorSub != nil {
|
|
|
|
|
newExpire, updateErr := l.extendSubscribeForIAP(singleModeAnchorSub, exp, subscribeId)
|
|
|
|
|
newExpire, updateErr := l.extendSubscribeForIAP(singleModeAnchorSub, exp, accumulateDuration, subscribeId)
|
|
|
|
|
if updateErr != nil {
|
|
|
|
|
l.Errorw("刷新单订阅锚点订阅失败", logger.Field("error", updateErr.Error()), logger.Field("subscribeId", singleModeAnchorSub.Id))
|
|
|
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update single mode anchor subscribe failed: %v", updateErr.Error())
|
|
|
|
|
}
|
|
|
|
|
if bindErr := l.bindOrderTradeNo(orderInfo, tradeNo); bindErr != nil {
|
|
|
|
|
l.Errorw("写入订单交易号失败", logger.Field("orderNo", req.OrderNo), logger.Field("tradeNo", tradeNo), logger.Field("error", bindErr.Error()))
|
|
|
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "bind order trade_no failed: %v", bindErr.Error())
|
|
|
|
|
}
|
|
|
|
|
if syncErr := l.syncOrderStatusAndEnqueue(orderInfo, newExpire.Unix()); syncErr != nil {
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
@@ -349,6 +414,10 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
|
|
|
|
} else {
|
|
|
|
|
l.Infow("首购订单跳过 attach 阶段订阅写入", logger.Field("orderNo", orderInfo.OrderNo), logger.Field("orderType", orderInfo.Type))
|
|
|
|
|
}
|
|
|
|
|
if e := l.bindOrderTradeNo(orderInfo, tradeNo, tx); e != nil {
|
|
|
|
|
l.Errorw("写入订单交易号失败", logger.Field("orderNo", req.OrderNo), logger.Field("tradeNo", tradeNo), logger.Field("error", e.Error()))
|
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
if e := l.syncOrderStatusAndEnqueue(orderInfo, exp.Unix(), tx); e != nil {
|
|
|
|
|
l.Errorw("同步订单状态失败", logger.Field("orderNo", req.OrderNo), logger.Field("error", e.Error()))
|
|
|
|
|
return e
|
|
|
|
@@ -359,6 +428,7 @@ 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, "")
|
|
|
|
|
l.Infow("绑定完成", logger.Field("userId", u.Id), logger.Field("tier", tier), logger.Field("expiresAt", exp.Unix()))
|
|
|
|
|
return &types.AttachAppleTransactionResponse{
|
|
|
|
|
ExpiresAt: exp.Unix(),
|
|
|
|
@@ -407,14 +477,11 @@ func (l *AttachTransactionLogic) findIAPSubscribeByOriginalTransactionID(origina
|
|
|
|
|
return nil, gorm.ErrRecordNotFound
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *AttachTransactionLogic) extendSubscribeForIAP(userSub *user.Subscribe, exp time.Time, subscribeId int64, tx ...*gorm.DB) (time.Time, error) {
|
|
|
|
|
func (l *AttachTransactionLogic) extendSubscribeForIAP(userSub *user.Subscribe, exp time.Time, durationDays int64, subscribeId int64, tx ...*gorm.DB) (time.Time, error) {
|
|
|
|
|
if userSub == nil {
|
|
|
|
|
return time.Time{}, errors.New("user subscribe is nil")
|
|
|
|
|
}
|
|
|
|
|
newExpire := userSub.ExpireTime
|
|
|
|
|
if exp.After(newExpire) {
|
|
|
|
|
newExpire = exp
|
|
|
|
|
}
|
|
|
|
|
newExpire := l.calcIAPRenewalExpire(userSub.ExpireTime, exp, durationDays)
|
|
|
|
|
userSub.ExpireTime = newExpire
|
|
|
|
|
if subscribeId > 0 && userSub.SubscribeId != subscribeId {
|
|
|
|
|
userSub.SubscribeId = subscribeId
|
|
|
|
@@ -426,3 +493,176 @@ func (l *AttachTransactionLogic) extendSubscribeForIAP(userSub *user.Subscribe,
|
|
|
|
|
}
|
|
|
|
|
return newExpire, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *AttachTransactionLogic) calcIAPRenewalExpire(currentExpire time.Time, fallbackExpire time.Time, durationDays int64) time.Time {
|
|
|
|
|
if durationDays > 0 {
|
|
|
|
|
base := currentExpire
|
|
|
|
|
now := time.Now()
|
|
|
|
|
if base.Before(now) {
|
|
|
|
|
base = now
|
|
|
|
|
}
|
|
|
|
|
return base.Add(time.Duration(durationDays) * 24 * time.Hour)
|
|
|
|
|
}
|
|
|
|
|
if fallbackExpire.After(currentExpire) {
|
|
|
|
|
return fallbackExpire
|
|
|
|
|
}
|
|
|
|
|
return currentExpire
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *AttachTransactionLogic) getAppleTradeNoCandidates(txPayload *iapapple.TransactionPayload) []string {
|
|
|
|
|
if txPayload == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
candidates := make([]string, 0, 2)
|
|
|
|
|
if txPayload.OriginalTransactionId != "" {
|
|
|
|
|
candidates = append(candidates, txPayload.OriginalTransactionId)
|
|
|
|
|
}
|
|
|
|
|
if txPayload.TransactionId != "" && txPayload.TransactionId != txPayload.OriginalTransactionId {
|
|
|
|
|
candidates = append(candidates, txPayload.TransactionId)
|
|
|
|
|
}
|
|
|
|
|
return candidates
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *AttachTransactionLogic) validateOrderTradeNoBinding(orderInfo *ordermodel.Order, tradeNoCandidates []string) error {
|
|
|
|
|
if orderInfo == nil || len(tradeNoCandidates) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if orderInfo.TradeNo != "" && !containsString(tradeNoCandidates, orderInfo.TradeNo) {
|
|
|
|
|
return errors.New("order already bound to another apple transaction")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var boundOrder ordermodel.Order
|
|
|
|
|
err := l.svcCtx.DB.WithContext(l.ctx).
|
|
|
|
|
Model(&ordermodel.Order{}).
|
|
|
|
|
Where("trade_no IN ? AND order_no <> ? AND status IN ?", tradeNoCandidates, orderInfo.OrderNo, []uint8{orderStatusPaid, orderStatusFinished}).
|
|
|
|
|
Order("id DESC").
|
|
|
|
|
First(&boundOrder).Error
|
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return errors.Errorf("apple transaction already used by order %s", boundOrder.OrderNo)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *AttachTransactionLogic) bindOrderTradeNo(orderInfo *ordermodel.Order, tradeNo string, tx ...*gorm.DB) error {
|
|
|
|
|
if orderInfo == nil || tradeNo == "" {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if orderInfo.TradeNo == tradeNo {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if orderInfo.TradeNo != "" && orderInfo.TradeNo != tradeNo {
|
|
|
|
|
return errors.New("order already bound to another apple transaction")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
orderInfo.TradeNo = tradeNo
|
|
|
|
|
if err := l.svcCtx.OrderModel.Update(l.ctx, orderInfo, tx...); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func containsString(candidates []string, target string) bool {
|
|
|
|
|
if target == "" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
for _, item := range candidates {
|
|
|
|
|
if item == target {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|