无订阅 支付后出现两个订阅
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 8m15s
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 8m15s
This commit is contained in:
parent
ce6351368d
commit
a1ab0fefa4
@ -34,6 +34,12 @@ type AttachTransactionLogic struct {
|
|||||||
svcCtx *svc.ServiceContext
|
svcCtx *svc.ServiceContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
orderTypeSubscribe uint8 = 1
|
||||||
|
orderStatusPending uint8 = 1
|
||||||
|
orderStatusPaid uint8 = 2
|
||||||
|
)
|
||||||
|
|
||||||
func NewAttachTransactionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AttachTransactionLogic {
|
func NewAttachTransactionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AttachTransactionLogic {
|
||||||
return &AttachTransactionLogic{
|
return &AttachTransactionLogic{
|
||||||
Logger: logger.WithContext(ctx),
|
Logger: logger.WithContext(ctx),
|
||||||
@ -52,6 +58,30 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
|||||||
if err := commonLogic.DenyIfFamilyMemberReadonly(l.ctx, l.svcCtx.DB, u.Id); err != nil {
|
if err := commonLogic.DenyIfFamilyMemberReadonly(l.ctx, l.svcCtx.DB, u.Id); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if strings.TrimSpace(req.OrderNo) == "" {
|
||||||
|
l.Errorw("参数错误,orderNo 不能为空")
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "order_no is required")
|
||||||
|
}
|
||||||
|
orderInfo, orderErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo)
|
||||||
|
switch {
|
||||||
|
case errors.Is(orderErr, gorm.ErrRecordNotFound):
|
||||||
|
l.Errorw("订单不存在", logger.Field("orderNo", req.OrderNo))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.OrderNotExist), "order not exist")
|
||||||
|
case orderErr != nil:
|
||||||
|
l.Errorw("查询订单失败", logger.Field("orderNo", req.OrderNo), logger.Field("error", orderErr.Error()))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find order error: %v", orderErr.Error())
|
||||||
|
case orderInfo == nil || orderInfo.Id == 0:
|
||||||
|
l.Errorw("订单不存在", logger.Field("orderNo", req.OrderNo))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.OrderNotExist), "order not exist")
|
||||||
|
case orderInfo.UserId != u.Id:
|
||||||
|
l.Errorw("订单与当前用户不匹配", logger.Field("orderNo", req.OrderNo), logger.Field("orderUserId", orderInfo.UserId), logger.Field("userId", u.Id))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "order owner mismatch")
|
||||||
|
}
|
||||||
|
isNewPurchaseOrder := orderInfo.Type == orderTypeSubscribe
|
||||||
|
if isNewPurchaseOrder {
|
||||||
|
l.Infow("首购订单将只由订单激活流程创建订阅", logger.Field("orderNo", req.OrderNo), logger.Field("orderType", orderInfo.Type))
|
||||||
|
}
|
||||||
|
|
||||||
txPayload, err := iapapple.VerifyTransactionJWS(req.SignedTransactionJWS)
|
txPayload, err := iapapple.VerifyTransactionJWS(req.SignedTransactionJWS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorw("JWS 验签失败", logger.Field("error", err.Error()))
|
l.Errorw("JWS 验签失败", logger.Field("error", err.Error()))
|
||||||
@ -62,21 +92,6 @@ 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))
|
||||||
var orderInfo *ordermodel.Order
|
|
||||||
if req.OrderNo != "" {
|
|
||||||
ord, orderErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo)
|
|
||||||
switch {
|
|
||||||
case orderErr == nil && ord != nil && ord.Id > 0:
|
|
||||||
if ord.UserId != 0 && ord.UserId != u.Id {
|
|
||||||
l.Errorw("订单与当前用户不匹配", logger.Field("orderNo", req.OrderNo), logger.Field("orderUserId", ord.UserId), logger.Field("userId", u.Id))
|
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "order owner mismatch")
|
|
||||||
}
|
|
||||||
orderInfo = ord
|
|
||||||
case orderErr != nil && !errors.Is(orderErr, gorm.ErrRecordNotFound):
|
|
||||||
l.Errorw("查询订单失败", logger.Field("orderNo", req.OrderNo), logger.Field("error", orderErr.Error()))
|
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find order error: %v", orderErr.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析 Apple 商品ID中的单位与数量:支持 dayN / monthN / yearN
|
// 解析 Apple 商品ID中的单位与数量:支持 dayN / monthN / yearN
|
||||||
var parsedUnit string
|
var parsedUnit string
|
||||||
@ -168,13 +183,9 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
|||||||
}
|
}
|
||||||
if subscribeId == 0 {
|
if subscribeId == 0 {
|
||||||
// fallback from order_no if provided
|
// fallback from order_no if provided
|
||||||
if orderInfo != nil && orderInfo.Id > 0 {
|
duration = orderInfo.Quantity
|
||||||
duration = orderInfo.Quantity
|
subscribeId = orderInfo.SubscribeId
|
||||||
subscribeId = orderInfo.SubscribeId
|
l.Infow("使用订单信息回退", logger.Field("orderNo", req.OrderNo), logger.Field("durationDays", duration), logger.Field("subscribeId", subscribeId))
|
||||||
l.Infow("使用订单信息回退", logger.Field("orderNo", req.OrderNo), logger.Field("durationDays", duration), logger.Field("subscribeId", subscribeId))
|
|
||||||
} else if req.OrderNo != "" {
|
|
||||||
l.Infow("订单信息不可用,尝试请求参数回退", logger.Field("orderNo", req.OrderNo))
|
|
||||||
}
|
|
||||||
// final fallback: use request fields
|
// final fallback: use request fields
|
||||||
if duration <= 0 {
|
if duration <= 0 {
|
||||||
duration = req.DurationDays
|
duration = req.DurationDays
|
||||||
@ -194,7 +205,7 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
|||||||
exp := iapapple.CalcExpire(txPayload.PurchaseDate, duration)
|
exp := iapapple.CalcExpire(txPayload.PurchaseDate, duration)
|
||||||
l.Infow("计算订阅到期时间", logger.Field("expireAt", exp), logger.Field("expireUnix", exp.Unix()))
|
l.Infow("计算订阅到期时间", logger.Field("expireAt", exp), logger.Field("expireUnix", exp.Unix()))
|
||||||
var orderLinkedSub *user.Subscribe
|
var orderLinkedSub *user.Subscribe
|
||||||
if orderInfo != nil && orderInfo.SubscribeToken != "" {
|
if !isNewPurchaseOrder && orderInfo != nil && orderInfo.SubscribeToken != "" {
|
||||||
orderSub, subErr := l.svcCtx.UserModel.FindOneSubscribeByToken(l.ctx, orderInfo.SubscribeToken)
|
orderSub, subErr := l.svcCtx.UserModel.FindOneSubscribeByToken(l.ctx, orderInfo.SubscribeToken)
|
||||||
switch {
|
switch {
|
||||||
case subErr == nil && orderSub != nil && orderSub.Id > 0:
|
case subErr == nil && orderSub != nil && orderSub.Id > 0:
|
||||||
@ -211,7 +222,7 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var singleModeAnchorSub *user.Subscribe
|
var singleModeAnchorSub *user.Subscribe
|
||||||
if l.svcCtx.Config.Subscribe.SingleModel && orderLinkedSub == nil {
|
if !isNewPurchaseOrder && l.svcCtx.Config.Subscribe.SingleModel && orderLinkedSub == nil {
|
||||||
anchorSub, anchorErr := findSingleModeMergeTarget(l.ctx, l.svcCtx, u.Id, subscribeId)
|
anchorSub, anchorErr := findSingleModeMergeTarget(l.ctx, l.svcCtx, u.Id, subscribeId)
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(anchorErr, commonLogic.ErrSingleModePlanMismatch):
|
case errors.Is(anchorErr, commonLogic.ErrSingleModePlanMismatch):
|
||||||
@ -229,6 +240,15 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
|||||||
}
|
}
|
||||||
|
|
||||||
if existTx != nil && existTx.Id > 0 {
|
if existTx != nil && existTx.Id > 0 {
|
||||||
|
if isNewPurchaseOrder {
|
||||||
|
if syncErr := l.syncOrderStatusAndEnqueue(orderInfo); 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.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)
|
existSub, err := l.findIAPSubscribeByOriginalTransactionID(txPayload.OriginalTransactionId)
|
||||||
switch {
|
switch {
|
||||||
case err == nil && existSub != nil && existSub.Id > 0:
|
case err == nil && existSub != nil && existSub.Id > 0:
|
||||||
@ -286,68 +306,46 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
|||||||
}
|
}
|
||||||
l.Infow("写入事务表成功", logger.Field("id", iapTx.Id))
|
l.Infow("写入事务表成功", logger.Field("id", iapTx.Id))
|
||||||
}
|
}
|
||||||
merged := false
|
if !isNewPurchaseOrder {
|
||||||
if orderLinkedSub != nil {
|
merged := false
|
||||||
if _, e := l.extendSubscribeForIAP(orderLinkedSub, exp, subscribeId, tx); e != nil {
|
if orderLinkedSub != nil {
|
||||||
l.Errorw("更新订单关联订阅失败", logger.Field("error", e.Error()), logger.Field("userSubscribeId", orderLinkedSub.Id))
|
if _, e := l.extendSubscribeForIAP(orderLinkedSub, exp, subscribeId, tx); e != nil {
|
||||||
return e
|
l.Errorw("更新订单关联订阅失败", logger.Field("error", e.Error()), logger.Field("userSubscribeId", orderLinkedSub.Id))
|
||||||
}
|
|
||||||
merged = true
|
|
||||||
} else if singleModeAnchorSub != nil {
|
|
||||||
if _, e := l.extendSubscribeForIAP(singleModeAnchorSub, exp, subscribeId, tx); e != nil {
|
|
||||||
l.Errorw("更新单订阅锚点失败", logger.Field("error", e.Error()), logger.Field("userSubscribeId", singleModeAnchorSub.Id))
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
merged = true
|
|
||||||
}
|
|
||||||
if !merged {
|
|
||||||
userSub := user.Subscribe{
|
|
||||||
UserId: u.Id,
|
|
||||||
SubscribeId: subscribeId,
|
|
||||||
StartTime: time.Now(),
|
|
||||||
ExpireTime: exp,
|
|
||||||
Traffic: 0,
|
|
||||||
Download: 0,
|
|
||||||
Upload: 0,
|
|
||||||
Token: fmt.Sprintf("iap:%s", txPayload.OriginalTransactionId),
|
|
||||||
UUID: uuid.New().String(),
|
|
||||||
Status: 1,
|
|
||||||
}
|
|
||||||
if e := l.svcCtx.UserModel.InsertSubscribe(l.ctx, &userSub, tx); e != nil {
|
|
||||||
l.Errorw("写入用户订阅失败", logger.Field("error", e.Error()))
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
l.Infow("写入用户订阅成功", logger.Field("userId", u.Id), logger.Field("subscribeId", subscribeId), logger.Field("expireUnix", exp.Unix()))
|
|
||||||
}
|
|
||||||
// optional: mark related order as paid and enqueue activation
|
|
||||||
if req.OrderNo != "" {
|
|
||||||
if orderInfo == nil {
|
|
||||||
if ord, e := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo); e == nil && ord != nil && ord.Id > 0 {
|
|
||||||
orderInfo = ord
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if orderInfo == nil {
|
|
||||||
// do not fail transaction if order not found; just continue
|
|
||||||
l.Infow("订单不存在或查询失败,跳过订单状态更新", logger.Field("orderNo", req.OrderNo))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if orderInfo.Status == 1 {
|
|
||||||
if e := l.svcCtx.OrderModel.UpdateOrderStatus(l.ctx, req.OrderNo, 2, tx); e != nil {
|
|
||||||
l.Errorw("更新订单状态失败", logger.Field("orderNo", req.OrderNo), logger.Field("error", e.Error()))
|
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
l.Infow("更新订单状态成功", logger.Field("orderNo", req.OrderNo), logger.Field("status", 2))
|
merged = true
|
||||||
|
} else if singleModeAnchorSub != nil {
|
||||||
|
if _, e := l.extendSubscribeForIAP(singleModeAnchorSub, exp, subscribeId, tx); e != nil {
|
||||||
|
l.Errorw("更新单订阅锚点失败", logger.Field("error", e.Error()), logger.Field("userSubscribeId", singleModeAnchorSub.Id))
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
merged = true
|
||||||
}
|
}
|
||||||
// enqueue activation regardless (idempotent handler downstream)
|
if !merged {
|
||||||
payload := queueType.ForthwithActivateOrderPayload{OrderNo: req.OrderNo}
|
userSub := user.Subscribe{
|
||||||
bytes, _ := json.Marshal(payload)
|
UserId: u.Id,
|
||||||
task := asynq.NewTask(queueType.ForthwithActivateOrder, bytes)
|
SubscribeId: subscribeId,
|
||||||
if _, e := l.svcCtx.Queue.EnqueueContext(l.ctx, task); e != nil {
|
StartTime: time.Now(),
|
||||||
// non-fatal
|
ExpireTime: exp,
|
||||||
l.Errorw("enqueue activate task error", logger.Field("error", e.Error()))
|
Traffic: 0,
|
||||||
} else {
|
Download: 0,
|
||||||
l.Infow("已加入订单激活队列", logger.Field("orderNo", req.OrderNo))
|
Upload: 0,
|
||||||
|
Token: fmt.Sprintf("iap:%s", txPayload.OriginalTransactionId),
|
||||||
|
UUID: uuid.New().String(),
|
||||||
|
Status: 1,
|
||||||
|
}
|
||||||
|
if e := l.svcCtx.UserModel.InsertSubscribe(l.ctx, &userSub, tx); e != nil {
|
||||||
|
l.Errorw("写入用户订阅失败", logger.Field("error", e.Error()))
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
l.Infow("写入用户订阅成功", logger.Field("userId", u.Id), logger.Field("subscribeId", subscribeId), logger.Field("expireUnix", exp.Unix()))
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
l.Infow("首购订单跳过 attach 阶段订阅写入", logger.Field("orderNo", orderInfo.OrderNo), logger.Field("orderType", orderInfo.Type))
|
||||||
|
}
|
||||||
|
if e := l.syncOrderStatusAndEnqueue(orderInfo, tx); e != nil {
|
||||||
|
l.Errorw("同步订单状态失败", logger.Field("orderNo", req.OrderNo), logger.Field("error", e.Error()))
|
||||||
|
return e
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -362,6 +360,30 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *AttachTransactionLogic) syncOrderStatusAndEnqueue(orderInfo *ordermodel.Order, tx ...*gorm.DB) error {
|
||||||
|
if orderInfo == nil || orderInfo.OrderNo == "" {
|
||||||
|
return errors.New("order info is nil")
|
||||||
|
}
|
||||||
|
if orderInfo.Status == orderStatusPending {
|
||||||
|
if err := l.svcCtx.OrderModel.UpdateOrderStatus(l.ctx, orderInfo.OrderNo, orderStatusPaid, tx...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
orderInfo.Status = orderStatusPaid
|
||||||
|
l.Infow("更新订单状态成功", logger.Field("orderNo", orderInfo.OrderNo), logger.Field("status", orderStatusPaid))
|
||||||
|
}
|
||||||
|
// enqueue activation regardless (idempotent handler downstream)
|
||||||
|
payload := queueType.ForthwithActivateOrderPayload{OrderNo: orderInfo.OrderNo}
|
||||||
|
bytes, _ := json.Marshal(payload)
|
||||||
|
task := asynq.NewTask(queueType.ForthwithActivateOrder, bytes)
|
||||||
|
if _, err := l.svcCtx.Queue.EnqueueContext(l.ctx, task); err != nil {
|
||||||
|
// non-fatal
|
||||||
|
l.Errorw("enqueue activate task error", logger.Field("error", err.Error()))
|
||||||
|
} else {
|
||||||
|
l.Infow("已加入订单激活队列", logger.Field("orderNo", orderInfo.OrderNo))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (l *AttachTransactionLogic) findIAPSubscribeByOriginalTransactionID(originalTransactionID string) (*user.Subscribe, error) {
|
func (l *AttachTransactionLogic) findIAPSubscribeByOriginalTransactionID(originalTransactionID string) (*user.Subscribe, error) {
|
||||||
if originalTransactionID == "" {
|
if originalTransactionID == "" {
|
||||||
return nil, gorm.ErrRecordNotFound
|
return nil, gorm.ErrRecordNotFound
|
||||||
|
|||||||
@ -3165,7 +3165,7 @@ type AttachAppleTransactionRequest struct {
|
|||||||
DurationDays int64 `json:"duration_days,omitempty"`
|
DurationDays int64 `json:"duration_days,omitempty"`
|
||||||
Tier string `json:"tier,omitempty"`
|
Tier string `json:"tier,omitempty"`
|
||||||
SubscribeId int64 `json:"subscribe_id,omitempty"`
|
SubscribeId int64 `json:"subscribe_id,omitempty"`
|
||||||
OrderNo string `json:"order_no,omitempty"`
|
OrderNo string `json:"order_no" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AttachAppleTransactionResponse struct {
|
type AttachAppleTransactionResponse struct {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user