feat(iap/apple): 从Apple商品ID解析购买数量并匹配订阅折扣
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 6m8s

添加从Apple商品ID中解析购买数量(天数)的逻辑,并基于订阅列表的折扣配置进行匹配。当商品ID包含"day"时,提取后续数字作为购买数量,然后查找对应数量的订阅折扣配置。
This commit is contained in:
shanshanzhong 2025-12-17 18:48:57 -08:00
parent 9944ab7b8a
commit 5d7ca4b9bd

View File

@ -6,11 +6,14 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strconv"
"strings"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
iapmodel "github.com/perfect-panel/server/internal/model/iap/apple" iapmodel "github.com/perfect-panel/server/internal/model/iap/apple"
"github.com/perfect-panel/server/internal/model/subscribe"
"github.com/perfect-panel/server/internal/model/user" "github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types" "github.com/perfect-panel/server/internal/types"
@ -54,18 +57,67 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
var existTx *iapmodel.Transaction var existTx *iapmodel.Transaction
existTx, _ = iapmodel.NewModel(l.svcCtx.DB, l.svcCtx.Redis).FindByOriginalId(l.ctx, txPayload.OriginalTransactionId) existTx, _ = iapmodel.NewModel(l.svcCtx.DB, l.svcCtx.Redis).FindByOriginalId(l.ctx, txPayload.OriginalTransactionId)
l.Infow("幂等等检查", logger.Field("originalTransactionId", txPayload.OriginalTransactionId), logger.Field("exists", existTx != nil && existTx.Id > 0)) l.Infow("幂等等检查", logger.Field("originalTransactionId", txPayload.OriginalTransactionId), logger.Field("exists", existTx != nil && existTx.Id > 0))
pm, _ := iapapple.ParseProductMap(l.svcCtx.Config.Site.CustomData)
m, ok := pm.Items[txPayload.ProductId] // 从 Apple 商品ID中解析购买数量天数形如 com.hifastvpn.plan.day7 -> 7
l.Infow("商品映射解析", logger.Field("items_count", len(pm.Items)), logger.Field("命中映射", ok)) var parsedQuantity int64
if idx := strings.Index(strings.ToLower(txPayload.ProductId), "day"); idx >= 0 {
sub := txPayload.ProductId[idx+3:]
for i := 0; i < len(sub); i++ {
if sub[i] < '0' || sub[i] > '9' {
sub = sub[:i]
break
}
}
if q, e := strconv.ParseInt(sub, 10, 64); e == nil && q > 0 {
parsedQuantity = q
}
}
l.Infow("商品映射解析", logger.Field("productId", txPayload.ProductId), logger.Field("解析数量", parsedQuantity))
// 基于订阅列表的折扣配置做匹配UnitTime=Day 且 Discount.quantity == parsedQuantity
var duration int64 var duration int64
var tier string var tier string
var subscribeId int64 var subscribeId int64
if ok { if parsedQuantity > 0 {
duration = m.DurationDays _, subs, e := l.svcCtx.SubscribeModel.FilterList(l.ctx, &subscribe.FilterParams{
tier = m.Tier Page: 1,
subscribeId = m.SubscribeId Size: 9999,
l.Infow("命中商品映射", logger.Field("productId", txPayload.ProductId), logger.Field("durationDays", duration), logger.Field("tier", tier), logger.Field("subscribeId", subscribeId)) Show: true,
} else { Sell: true,
DefaultLanguage: true,
})
if e == nil && len(subs) > 0 {
for _, item := range subs {
if !strings.EqualFold(item.UnitTime, "Day") {
continue
}
var discounts []types.SubscribeDiscount
if item.Discount != "" {
_ = json.Unmarshal([]byte(item.Discount), &discounts)
}
for _, d := range discounts {
if int64(d.Quantity) == parsedQuantity {
duration = parsedQuantity
subscribeId = item.Id
tier = item.Name
l.Infow("订阅映射命中", logger.Field("subscribeId", subscribeId), logger.Field("name", tier), logger.Field("durationDays", duration))
break
}
}
if subscribeId > 0 {
break
}
}
} else {
l.Infow("订阅列表为空或查询失败", logger.Field("error", func() string {
if e != nil {
return e.Error()
}
return ""
}()))
}
}
if subscribeId == 0 {
// fallback from order_no if provided // fallback from order_no if provided
if req.OrderNo != "" { if req.OrderNo != "" {
if ord, e := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo); e == nil && ord != nil && ord.Id != 0 { if ord, e := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo); e == nil && ord != nil && ord.Id != 0 {