This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
ordermodel "github.com/perfect-panel/server/internal/model/order"
|
||||
usermodel "github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
SubscriptionTraceType = "subscription_flow"
|
||||
SubscriptionTraceFlowOrder = "order_subscription"
|
||||
SubscriptionTraceFlowEmailBind = "email_bind_subscription"
|
||||
)
|
||||
|
||||
func SubscriptionTraceFields(flow string, stage string, fields ...logger.LogField) []logger.LogField {
|
||||
base := []logger.LogField{
|
||||
logger.Field("trace_type", SubscriptionTraceType),
|
||||
logger.Field("flow", flow),
|
||||
logger.Field("stage", stage),
|
||||
}
|
||||
|
||||
return append(base, fields...)
|
||||
}
|
||||
|
||||
func SubscriptionTraceInfo(log logger.Logger, flow string, stage string, msg string, fields ...logger.LogField) {
|
||||
log.Infow(msg, SubscriptionTraceFields(flow, stage, fields...)...)
|
||||
}
|
||||
|
||||
func SubscriptionTraceError(log logger.Logger, flow string, stage string, msg string, fields ...logger.LogField) {
|
||||
log.Errorw(msg, SubscriptionTraceFields(flow, stage, fields...)...)
|
||||
}
|
||||
|
||||
func OrderTraceFields(orderInfo *ordermodel.Order) []logger.LogField {
|
||||
if orderInfo == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
effectiveUserID := orderInfo.UserId
|
||||
if orderInfo.SubscriptionUserId > 0 {
|
||||
effectiveUserID = orderInfo.SubscriptionUserId
|
||||
}
|
||||
|
||||
fields := []logger.LogField{
|
||||
logger.Field("order_id", orderInfo.Id),
|
||||
logger.Field("order_no", orderInfo.OrderNo),
|
||||
logger.Field("order_type", orderInfo.Type),
|
||||
logger.Field("order_status", orderInfo.Status),
|
||||
logger.Field("user_id", orderInfo.UserId),
|
||||
logger.Field("subscription_user_id", orderInfo.SubscriptionUserId),
|
||||
logger.Field("effective_user_id", effectiveUserID),
|
||||
logger.Field("order_subscribe_id", orderInfo.SubscribeId),
|
||||
logger.Field("payment_id", orderInfo.PaymentId),
|
||||
logger.Field("payment_method", orderInfo.Method),
|
||||
logger.Field("parent_order_id", orderInfo.ParentId),
|
||||
logger.Field("quantity", orderInfo.Quantity),
|
||||
logger.Field("is_new_order", orderInfo.IsNew),
|
||||
}
|
||||
|
||||
if tail := SensitiveTail(orderInfo.SubscribeToken); tail != "" {
|
||||
fields = append(fields, logger.Field("subscribe_token_tail", tail))
|
||||
}
|
||||
if tail := SensitiveTail(orderInfo.TradeNo); tail != "" {
|
||||
fields = append(fields, logger.Field("trade_no_tail", tail))
|
||||
}
|
||||
if tail := SensitiveTail(orderInfo.AppAccountToken); tail != "" {
|
||||
fields = append(fields, logger.Field("app_account_token_tail", tail))
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
func UserSubscribeTraceFields(userSub *usermodel.Subscribe) []logger.LogField {
|
||||
if userSub == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fields := []logger.LogField{
|
||||
logger.Field("user_subscribe_id", userSub.Id),
|
||||
logger.Field("subscribe_owner_user_id", userSub.UserId),
|
||||
logger.Field("user_subscribe_plan_id", userSub.SubscribeId),
|
||||
logger.Field("subscribe_order_id", userSub.OrderId),
|
||||
logger.Field("subscribe_status", userSub.Status),
|
||||
logger.Field("expire_time", userSub.ExpireTime),
|
||||
}
|
||||
|
||||
if tail := SensitiveTail(userSub.Token); tail != "" {
|
||||
fields = append(fields, logger.Field("subscribe_token_tail", tail))
|
||||
}
|
||||
if tail := SensitiveTail(userSub.UUID); tail != "" {
|
||||
fields = append(fields, logger.Field("subscribe_uuid_tail", tail))
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
func SensitiveTail(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
if len(value) <= 8 {
|
||||
return value
|
||||
}
|
||||
|
||||
return value[len(value)-8:]
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
commonLogic "github.com/perfect-panel/server/internal/logic/common"
|
||||
"github.com/perfect-panel/server/pkg/constant"
|
||||
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
@@ -56,6 +57,12 @@ func (l *AlipayNotifyLogic) AlipayNotify(r *http.Request) error {
|
||||
l.Logger.Error("[AlipayNotify] Decode notification failed", logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "payment_notify_received",
|
||||
"[SubscriptionFlow] alipay notify received",
|
||||
logger.Field("order_no", notify.OrderNo),
|
||||
logger.Field("payment_platform", data.Platform),
|
||||
logger.Field("notify_status", string(notify.Status)),
|
||||
)
|
||||
if notify.Status == alipay.Success {
|
||||
orderInfo, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, notify.OrderNo)
|
||||
if err != nil {
|
||||
@@ -73,6 +80,12 @@ func (l *AlipayNotifyLogic) AlipayNotify(r *http.Request) error {
|
||||
l.Logger.Error("[AlipayNotify] Update order status failed", logger.Field("error", err.Error()), logger.Field("orderNo", notify.OrderNo))
|
||||
return err
|
||||
}
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "payment_settled",
|
||||
"[SubscriptionFlow] alipay notify marked order as paid",
|
||||
append(commonLogic.OrderTraceFields(orderInfo),
|
||||
logger.Field("payment_platform", data.Platform),
|
||||
)...,
|
||||
)
|
||||
l.Logger.Info("[AlipayNotify] Notify status success", logger.Field("orderNo", notify.OrderNo))
|
||||
payload := types.ForthwithActivateOrderPayload{
|
||||
OrderNo: notify.OrderNo,
|
||||
@@ -88,6 +101,13 @@ func (l *AlipayNotifyLogic) AlipayNotify(r *http.Request) error {
|
||||
l.Logger.Error("[AlipayNotify] Enqueue task failed", logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "activation_task_enqueued",
|
||||
"[SubscriptionFlow] activation task enqueued from alipay notify",
|
||||
append(commonLogic.OrderTraceFields(orderInfo),
|
||||
logger.Field("payment_platform", data.Platform),
|
||||
logger.Field("queue_task_id", taskInfo.ID),
|
||||
)...,
|
||||
)
|
||||
l.Logger.Info("[AlipayNotify] Enqueue task success", logger.Field("taskInfo", taskInfo))
|
||||
} else {
|
||||
l.Logger.Error("[AlipayNotify] Notify status failed", logger.Field("status", string(notify.Status)))
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
commonLogic "github.com/perfect-panel/server/internal/logic/common"
|
||||
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"
|
||||
@@ -57,6 +58,13 @@ func (l *AppleIAPNotifyLogic) Handle(signedPayload string) error {
|
||||
}
|
||||
// 验签通过,记录通知类型与关键交易标识
|
||||
l.Infow("iap notify verified", logger.Field("type", ntype), logger.Field("productId", txPayload.ProductId), logger.Field("originalTransactionId", txPayload.OriginalTransactionId))
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "payment_notify_received",
|
||||
"[SubscriptionFlow] apple iap server notification received",
|
||||
logger.Field("notify_type", ntype),
|
||||
logger.Field("product_id", txPayload.ProductId),
|
||||
logger.Field("original_transaction_tail", commonLogic.SensitiveTail(txPayload.OriginalTransactionId)),
|
||||
logger.Field("transaction_id_tail", commonLogic.SensitiveTail(txPayload.TransactionId)),
|
||||
)
|
||||
return l.svcCtx.DB.Transaction(func(db *gorm.DB) error {
|
||||
var existing *iapmodel.Transaction
|
||||
existing, _ = iapmodel.NewModel(l.svcCtx.DB, l.svcCtx.Redis).FindByOriginalId(l.ctx, txPayload.OriginalTransactionId)
|
||||
@@ -201,6 +209,13 @@ func (l *AppleIAPNotifyLogic) Handle(signedPayload string) error {
|
||||
return err
|
||||
}
|
||||
l.Infow("iap notify fallback updated subscribe", logger.Field("userSubscribeId", candidate.Id), logger.Field("status", candidate.Status))
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "subscription_updated_from_notify",
|
||||
"[SubscriptionFlow] apple iap notify updated fallback subscription candidate",
|
||||
append(commonLogic.UserSubscribeTraceFields(candidate),
|
||||
logger.Field("notify_type", ntype),
|
||||
logger.Field("product_id", txPayload.ProductId),
|
||||
)...,
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -226,6 +241,13 @@ func (l *AppleIAPNotifyLogic) Handle(signedPayload string) error {
|
||||
}
|
||||
// 更新成功,输出订阅状态
|
||||
l.Infow("iap notify updated subscribe", logger.Field("userSubscribeId", sub.Id), logger.Field("status", sub.Status))
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "subscription_updated_from_notify",
|
||||
"[SubscriptionFlow] apple iap notify updated subscription",
|
||||
append(commonLogic.UserSubscribeTraceFields(sub),
|
||||
logger.Field("notify_type", ntype),
|
||||
logger.Field("product_id", txPayload.ProductId),
|
||||
)...,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
|
||||
commonLogic "github.com/perfect-panel/server/internal/logic/common"
|
||||
"github.com/perfect-panel/server/pkg/constant"
|
||||
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
@@ -44,12 +45,18 @@ func (l *EPayNotifyLogic) EPayNotify(req *types.EPayNotifyRequest) error {
|
||||
l.Logger.Error("[EPayNotify] Payment not found in context")
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "payment config not found")
|
||||
}
|
||||
l.Infof("[EPayNotify] Payment config: %+v", data)
|
||||
orderInfo, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OutTradeNo)
|
||||
if err != nil {
|
||||
l.Logger.Error("[EPayNotify] Find order failed", logger.Field("error", err.Error()), logger.Field("orderNo", req.OutTradeNo))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.OrderNotExist), "order not exist: %v", req.OutTradeNo)
|
||||
}
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "payment_notify_received",
|
||||
"[SubscriptionFlow] epay notify received",
|
||||
append(commonLogic.OrderTraceFields(orderInfo),
|
||||
logger.Field("payment_platform", data.Platform),
|
||||
logger.Field("trade_status", req.TradeStatus),
|
||||
)...,
|
||||
)
|
||||
|
||||
var config payment.EPayConfig
|
||||
if err := json.Unmarshal([]byte(data.Config), &config); err != nil {
|
||||
@@ -75,6 +82,12 @@ func (l *EPayNotifyLogic) EPayNotify(req *types.EPayNotifyRequest) error {
|
||||
l.Logger.Error("[EPayNotify] Update order status failed", logger.Field("error", err.Error()), logger.Field("orderNo", req.OutTradeNo))
|
||||
return err
|
||||
}
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "payment_settled",
|
||||
"[SubscriptionFlow] epay notify marked order as paid",
|
||||
append(commonLogic.OrderTraceFields(orderInfo),
|
||||
logger.Field("payment_platform", data.Platform),
|
||||
)...,
|
||||
)
|
||||
// Create activate order task
|
||||
payload := queueType.ForthwithActivateOrderPayload{
|
||||
OrderNo: req.OutTradeNo,
|
||||
@@ -90,6 +103,13 @@ func (l *EPayNotifyLogic) EPayNotify(req *types.EPayNotifyRequest) error {
|
||||
l.Logger.Error("[EPayNotify] Enqueue task failed", logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "activation_task_enqueued",
|
||||
"[SubscriptionFlow] activation task enqueued from epay notify",
|
||||
append(commonLogic.OrderTraceFields(orderInfo),
|
||||
logger.Field("payment_platform", data.Platform),
|
||||
logger.Field("queue_task_id", taskInfo.ID),
|
||||
)...,
|
||||
)
|
||||
l.Logger.Info("[EPayNotify] Enqueue task success", logger.Field("taskInfo", taskInfo))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
commonLogic "github.com/perfect-panel/server/internal/logic/common"
|
||||
"github.com/perfect-panel/server/pkg/constant"
|
||||
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
@@ -67,6 +68,13 @@ func (l *StripeNotifyLogic) StripeNotify(r *http.Request, w http.ResponseWriter)
|
||||
l.Logger.Error("[StripeNotify] Find order failed", logger.Field("error", err.Error()), logger.Field("orderNo", notify.OrderNo))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.OrderNotExist), "order not exist: %v", notify.OrderNo)
|
||||
}
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "payment_notify_received",
|
||||
"[SubscriptionFlow] stripe notify received",
|
||||
append(commonLogic.OrderTraceFields(orderInfo),
|
||||
logger.Field("payment_platform", stripeConfig.Platform),
|
||||
logger.Field("stripe_event_type", notify.EventType),
|
||||
)...,
|
||||
)
|
||||
if notify.EventType == "payment_intent.succeeded" {
|
||||
if orderInfo.Status == 5 {
|
||||
return nil
|
||||
@@ -76,6 +84,13 @@ func (l *StripeNotifyLogic) StripeNotify(r *http.Request, w http.ResponseWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "payment_settled",
|
||||
"[SubscriptionFlow] stripe notify marked order as paid",
|
||||
append(commonLogic.OrderTraceFields(orderInfo),
|
||||
logger.Field("payment_platform", stripeConfig.Platform),
|
||||
logger.Field("stripe_event_type", notify.EventType),
|
||||
)...,
|
||||
)
|
||||
// create ActivateOrder task
|
||||
payload := types.ForthwithActivateOrderPayload{
|
||||
OrderNo: notify.OrderNo,
|
||||
@@ -86,11 +101,19 @@ func (l *StripeNotifyLogic) StripeNotify(r *http.Request, w http.ResponseWriter)
|
||||
return err
|
||||
}
|
||||
task := asynq.NewTask(types.ForthwithActivateOrder, bytes, asynq.MaxRetry(5))
|
||||
_, err = l.svcCtx.Queue.Enqueue(task)
|
||||
taskInfo, err := l.svcCtx.Queue.Enqueue(task)
|
||||
if err != nil {
|
||||
l.Errorw("[StripeNotify] Enqueue error", logger.Field("errors", err.Error()))
|
||||
return err
|
||||
}
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "activation_task_enqueued",
|
||||
"[SubscriptionFlow] activation task enqueued from stripe notify",
|
||||
append(commonLogic.OrderTraceFields(orderInfo),
|
||||
logger.Field("payment_platform", stripeConfig.Platform),
|
||||
logger.Field("stripe_event_type", notify.EventType),
|
||||
logger.Field("queue_task_id", taskInfo.ID),
|
||||
)...,
|
||||
)
|
||||
l.Infow("[StripeNotify] success", logger.Field("orderNo", notify.OrderNo))
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -82,6 +82,13 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
||||
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")
|
||||
}
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "iap_attach_start",
|
||||
"[SubscriptionFlow] apple iap attach flow started",
|
||||
append(commonLogic.OrderTraceFields(orderInfo),
|
||||
logger.Field("request_user_id", u.Id),
|
||||
logger.Field("effective_user_id", entitlement.EffectiveUserID),
|
||||
)...,
|
||||
)
|
||||
isNewPurchaseOrder := orderInfo.Type == orderTypeSubscribe
|
||||
if isNewPurchaseOrder {
|
||||
l.Infow("首购订单将只由订单激活流程创建订阅", logger.Field("orderNo", req.OrderNo), logger.Field("orderType", orderInfo.Type))
|
||||
@@ -93,6 +100,14 @@ 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))
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "iap_attach_verified",
|
||||
"[SubscriptionFlow] apple iap transaction verified",
|
||||
append(commonLogic.OrderTraceFields(orderInfo),
|
||||
logger.Field("product_id", txPayload.ProductId),
|
||||
logger.Field("original_transaction_tail", commonLogic.SensitiveTail(txPayload.OriginalTransactionId)),
|
||||
logger.Field("transaction_id_tail", commonLogic.SensitiveTail(txPayload.TransactionId)),
|
||||
)...,
|
||||
)
|
||||
tradeNoCandidates := l.getAppleTradeNoCandidates(txPayload)
|
||||
existingOrderNo, validateErr := l.validateOrderTradeNoBinding(orderInfo, tradeNoCandidates)
|
||||
if validateErr != nil {
|
||||
@@ -390,6 +405,12 @@ func (l *AttachTransactionLogic) Attach(req *types.AttachAppleTransactionRequest
|
||||
return e
|
||||
}
|
||||
l.Infow("写入用户订阅成功", logger.Field("userId", u.Id), logger.Field("subscribeId", subscribeId), logger.Field("expireUnix", exp.Unix()))
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "subscription_created",
|
||||
"[SubscriptionFlow] apple iap attach created a subscription placeholder before queue activation",
|
||||
append(commonLogic.OrderTraceFields(orderInfo),
|
||||
commonLogic.UserSubscribeTraceFields(&userSub)...,
|
||||
)...,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
l.Infow("首购订单跳过 attach 阶段订阅写入", logger.Field("orderNo", orderInfo.OrderNo), logger.Field("orderType", orderInfo.Type))
|
||||
@@ -453,6 +474,12 @@ func (l *AttachTransactionLogic) syncOrderStatusAndEnqueue(orderInfo *ordermodel
|
||||
}
|
||||
orderInfo.Status = orderStatusPaid
|
||||
l.Infow("更新订单状态成功", logger.Field("orderNo", orderInfo.OrderNo), logger.Field("status", orderStatusPaid))
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "payment_settled",
|
||||
"[SubscriptionFlow] apple iap attach marked order as paid",
|
||||
append(commonLogic.OrderTraceFields(orderInfo),
|
||||
logger.Field("iap_expire_at", iapExpireAt),
|
||||
)...,
|
||||
)
|
||||
}
|
||||
// enqueue activation regardless (idempotent handler downstream)
|
||||
payload := queueType.ForthwithActivateOrderPayload{OrderNo: orderInfo.OrderNo, IAPExpireAt: iapExpireAt}
|
||||
@@ -463,6 +490,12 @@ func (l *AttachTransactionLogic) syncOrderStatusAndEnqueue(orderInfo *ordermodel
|
||||
l.Errorw("enqueue activate task error", logger.Field("error", err.Error()))
|
||||
} else {
|
||||
l.Infow("已加入订单激活队列", logger.Field("orderNo", orderInfo.OrderNo))
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "activation_task_enqueued",
|
||||
"[SubscriptionFlow] apple iap attach enqueued activation task",
|
||||
append(commonLogic.OrderTraceFields(orderInfo),
|
||||
logger.Field("iap_expire_at", iapExpireAt),
|
||||
)...,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -63,6 +63,17 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P
|
||||
return nil, entErr
|
||||
}
|
||||
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "order_create_start",
|
||||
"[SubscriptionFlow] purchase order creation started",
|
||||
logger.Field("order_kind", "purchase"),
|
||||
logger.Field("user_id", u.Id),
|
||||
logger.Field("effective_user_id", entitlement.EffectiveUserID),
|
||||
logger.Field("requested_subscribe_id", req.SubscribeId),
|
||||
logger.Field("quantity", req.Quantity),
|
||||
logger.Field("payment_id", req.Payment),
|
||||
logger.Field("coupon", req.Coupon),
|
||||
)
|
||||
|
||||
if req.Quantity <= 0 {
|
||||
l.Debugf("[Purchase] Quantity is less than or equal to 0, setting to 1")
|
||||
req.Quantity = 1
|
||||
@@ -102,12 +113,15 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P
|
||||
parentOrderID = decision.Anchor.OrderId
|
||||
subscribeToken = decision.Anchor.Token
|
||||
anchorUserSubscribeID = decision.Anchor.Id
|
||||
l.Infow("[Purchase] single mode purchase routed to renewal",
|
||||
logger.Field("mode", "single"),
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "order_route_selected",
|
||||
"[SubscriptionFlow] purchase routed to renewal before order creation",
|
||||
logger.Field("route_mode", "single"),
|
||||
logger.Field("route", "purchase_to_renewal"),
|
||||
logger.Field("anchor_user_subscribe_id", decision.Anchor.Id),
|
||||
logger.Field("order_no", "pending"),
|
||||
logger.Field("user_id", u.Id),
|
||||
logger.Field("effective_user_id", entitlement.EffectiveUserID),
|
||||
logger.Field("requested_subscribe_id", req.SubscribeId),
|
||||
logger.Field("resolved_subscribe_id", targetSubscribeID),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -126,11 +140,15 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P
|
||||
orderType = 2
|
||||
parentOrderID = existSub.OrderId
|
||||
subscribeToken = existSub.Token
|
||||
l.Infow("[Purchase] purchase routed to renewal/change plan (existing subscription found)",
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "order_route_selected",
|
||||
"[SubscriptionFlow] purchase routed to renewal because an existing subscription was found",
|
||||
logger.Field("route_mode", "global_single_subscription"),
|
||||
logger.Field("route", "purchase_to_existing_subscription"),
|
||||
logger.Field("existing_subscribe_id", existSub.Id),
|
||||
logger.Field("existing_status", existSub.Status),
|
||||
logger.Field("user_id", u.Id),
|
||||
logger.Field("subscribe_id", targetSubscribeID),
|
||||
logger.Field("effective_user_id", entitlement.EffectiveUserID),
|
||||
logger.Field("resolved_subscribe_id", targetSubscribeID),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -301,13 +319,13 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P
|
||||
AppAccountToken: uuid.New().String(),
|
||||
}
|
||||
if isSingleModeRenewal {
|
||||
l.Infow("[Purchase] single mode purchase order created as renewal",
|
||||
logger.Field("mode", "single"),
|
||||
logger.Field("route", "purchase_to_renewal"),
|
||||
logger.Field("anchor_user_subscribe_id", anchorUserSubscribeID),
|
||||
logger.Field("order_no", orderInfo.OrderNo),
|
||||
logger.Field("parent_id", orderInfo.ParentId),
|
||||
logger.Field("user_id", u.Id),
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "order_created",
|
||||
"[SubscriptionFlow] purchase order persisted as renewal",
|
||||
append(commonLogic.OrderTraceFields(orderInfo),
|
||||
logger.Field("route_mode", "single"),
|
||||
logger.Field("route", "purchase_to_renewal"),
|
||||
logger.Field("anchor_user_subscribe_id", anchorUserSubscribeID),
|
||||
)...,
|
||||
)
|
||||
}
|
||||
// Database transaction
|
||||
@@ -404,6 +422,16 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P
|
||||
}
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert order error: %v", err.Error())
|
||||
}
|
||||
|
||||
if !isSingleModeRenewal {
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "order_created",
|
||||
"[SubscriptionFlow] purchase order persisted",
|
||||
append(commonLogic.OrderTraceFields(orderInfo),
|
||||
logger.Field("route_mode", "standard"),
|
||||
logger.Field("resolved_subscribe_id", targetSubscribeID),
|
||||
)...,
|
||||
)
|
||||
}
|
||||
// Deferred task
|
||||
payload := queue.DeferCloseOrderPayload{
|
||||
OrderNo: orderInfo.OrderNo,
|
||||
|
||||
@@ -54,6 +54,17 @@ func (l *RenewalLogic) Renewal(req *types.RenewalOrderRequest) (resp *types.Rene
|
||||
if entErr != nil {
|
||||
return nil, entErr
|
||||
}
|
||||
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "order_create_start",
|
||||
"[SubscriptionFlow] renewal order creation started",
|
||||
logger.Field("order_kind", "renewal"),
|
||||
logger.Field("user_id", u.Id),
|
||||
logger.Field("effective_user_id", entitlement.EffectiveUserID),
|
||||
logger.Field("requested_user_subscribe_id", req.UserSubscribeID),
|
||||
logger.Field("quantity", req.Quantity),
|
||||
logger.Field("payment_id", req.Payment),
|
||||
logger.Field("coupon", req.Coupon),
|
||||
)
|
||||
if req.Quantity <= 0 {
|
||||
l.Debugf("[Renewal] Quantity is less than or equal to 0, setting to 1")
|
||||
req.Quantity = 1
|
||||
@@ -180,22 +191,22 @@ func (l *RenewalLogic) Renewal(req *types.RenewalOrderRequest) (resp *types.Rene
|
||||
UserId: u.Id,
|
||||
SubscriptionUserId: entitlement.EffectiveUserID,
|
||||
ParentId: userSubscribe.OrderId,
|
||||
OrderNo: orderNo,
|
||||
Type: 2,
|
||||
Quantity: req.Quantity,
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
GiftAmount: deductionAmount,
|
||||
Discount: discountAmount,
|
||||
Coupon: req.Coupon,
|
||||
CouponDiscount: coupon,
|
||||
PaymentId: payment.Id,
|
||||
Method: canonicalOrderMethod(payment.Platform),
|
||||
FeeAmount: feeAmount,
|
||||
Status: 1,
|
||||
SubscribeId: userSubscribe.SubscribeId,
|
||||
SubscribeToken: userSubscribe.Token,
|
||||
AppAccountToken: uuid.New().String(),
|
||||
OrderNo: orderNo,
|
||||
Type: 2,
|
||||
Quantity: req.Quantity,
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
GiftAmount: deductionAmount,
|
||||
Discount: discountAmount,
|
||||
Coupon: req.Coupon,
|
||||
CouponDiscount: coupon,
|
||||
PaymentId: payment.Id,
|
||||
Method: canonicalOrderMethod(payment.Platform),
|
||||
FeeAmount: feeAmount,
|
||||
Status: 1,
|
||||
SubscribeId: userSubscribe.SubscribeId,
|
||||
SubscribeToken: userSubscribe.Token,
|
||||
AppAccountToken: uuid.New().String(),
|
||||
}
|
||||
// Database transaction
|
||||
err = l.svcCtx.DB.Transaction(func(db *gorm.DB) error {
|
||||
@@ -235,6 +246,14 @@ func (l *RenewalLogic) Renewal(req *types.RenewalOrderRequest) (resp *types.Rene
|
||||
l.Errorw("[Renewal] Database insert error", logger.Field("error", err.Error()), logger.Field("order", orderInfo))
|
||||
return nil, errors.Wrapf(err, "insert order error: %v", err.Error())
|
||||
}
|
||||
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "order_created",
|
||||
"[SubscriptionFlow] renewal order persisted",
|
||||
append(commonLogic.OrderTraceFields(&orderInfo),
|
||||
logger.Field("requested_user_subscribe_id", req.UserSubscribeID),
|
||||
logger.Field("resolved_user_subscribe_id", userSubscribe.Id),
|
||||
)...,
|
||||
)
|
||||
// Deferred task
|
||||
payload := queue.DeferCloseOrderPayload{
|
||||
OrderNo: orderInfo.OrderNo,
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
commonLogic "github.com/perfect-panel/server/internal/logic/common"
|
||||
"github.com/perfect-panel/server/internal/model/log"
|
||||
"github.com/perfect-panel/server/internal/report"
|
||||
"github.com/perfect-panel/server/pkg/constant"
|
||||
@@ -75,6 +76,14 @@ func (l *PurchaseCheckoutLogic) PurchaseCheckout(req *types.CheckoutOrderRequest
|
||||
l.Logger.Error("[PurchaseCheckout] Database query error", logger.Field("error", err.Error()), logger.Field("payment", orderInfo.Method))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find payment method error: %v", err.Error())
|
||||
}
|
||||
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "checkout_start",
|
||||
"[SubscriptionFlow] checkout started",
|
||||
append(commonLogic.OrderTraceFields(orderInfo),
|
||||
logger.Field("payment_platform", paymentConfig.Platform),
|
||||
logger.Field("has_return_url", req.ReturnUrl != ""),
|
||||
)...,
|
||||
)
|
||||
// Route to appropriate payment handler based on payment platform
|
||||
switch paymentPlatform.ParsePlatform(orderInfo.Method) {
|
||||
case paymentPlatform.AppleIAP:
|
||||
@@ -83,6 +92,14 @@ func (l *PurchaseCheckoutLogic) PurchaseCheckout(req *types.CheckoutOrderRequest
|
||||
Type: "apple_iap",
|
||||
ProductIds: []string{productId},
|
||||
}
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "checkout_response_ready",
|
||||
"[SubscriptionFlow] checkout response prepared",
|
||||
append(commonLogic.OrderTraceFields(orderInfo),
|
||||
logger.Field("payment_platform", paymentConfig.Platform),
|
||||
logger.Field("checkout_type", resp.Type),
|
||||
logger.Field("product_ids", resp.ProductIds),
|
||||
)...,
|
||||
)
|
||||
return resp, nil
|
||||
case paymentPlatform.EPay:
|
||||
// Process EPay payment - generates payment URL for redirect
|
||||
@@ -157,6 +174,16 @@ func (l *PurchaseCheckoutLogic) PurchaseCheckout(req *types.CheckoutOrderRequest
|
||||
l.Errorw("[PurchaseCheckout] payment method not found", logger.Field("method", orderInfo.Method))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "payment method not found")
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "checkout_response_ready",
|
||||
"[SubscriptionFlow] checkout response prepared",
|
||||
append(commonLogic.OrderTraceFields(orderInfo),
|
||||
logger.Field("payment_platform", paymentConfig.Platform),
|
||||
logger.Field("checkout_type", resp.Type),
|
||||
)...,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -503,6 +530,9 @@ func (l *PurchaseCheckoutLogic) queryExchangeRate(to string, src int64) (amount
|
||||
func (l *PurchaseCheckoutLogic) balancePayment(u *user.User, o *order.Order) error {
|
||||
var userInfo user.User
|
||||
var err error
|
||||
var giftUsed int64
|
||||
var balanceUsed int64
|
||||
paymentPath := "balance"
|
||||
if o.Amount == 0 {
|
||||
// No payment required for zero-amount orders
|
||||
l.Logger.Info(
|
||||
@@ -518,6 +548,13 @@ func (l *PurchaseCheckoutLogic) balancePayment(u *user.User, o *order.Order) err
|
||||
logger.Field("userId", u.Id))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Update order status error: %s", err.Error())
|
||||
}
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "payment_settled",
|
||||
"[SubscriptionFlow] order marked paid without external payment",
|
||||
append(commonLogic.OrderTraceFields(o),
|
||||
logger.Field("payment_path", "zero_amount"),
|
||||
)...,
|
||||
)
|
||||
paymentPath = "zero_amount"
|
||||
goto activation
|
||||
}
|
||||
|
||||
@@ -536,7 +573,6 @@ func (l *PurchaseCheckoutLogic) balancePayment(u *user.User, o *order.Order) err
|
||||
}
|
||||
|
||||
// Calculate payment distribution: prioritize gift amount first
|
||||
var giftUsed, balanceUsed int64
|
||||
remainingAmount := o.Amount
|
||||
|
||||
if userInfo.GiftAmount >= remainingAmount {
|
||||
@@ -621,6 +657,15 @@ func (l *PurchaseCheckoutLogic) balancePayment(u *user.User, o *order.Order) err
|
||||
return err
|
||||
}
|
||||
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "payment_settled",
|
||||
"[SubscriptionFlow] balance payment settled and order marked paid",
|
||||
append(commonLogic.OrderTraceFields(o),
|
||||
logger.Field("payment_path", "balance"),
|
||||
logger.Field("gift_used", giftUsed),
|
||||
logger.Field("balance_used", balanceUsed),
|
||||
)...,
|
||||
)
|
||||
|
||||
activation:
|
||||
// Enqueue order activation task for immediate processing
|
||||
payload := queueType.ForthwithActivateOrderPayload{
|
||||
@@ -639,6 +684,13 @@ activation:
|
||||
return err
|
||||
}
|
||||
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "activation_task_enqueued",
|
||||
"[SubscriptionFlow] activation task enqueued after checkout payment",
|
||||
append(commonLogic.OrderTraceFields(o),
|
||||
logger.Field("payment_path", paymentPath),
|
||||
)...,
|
||||
)
|
||||
|
||||
l.Logger.Info("[PurchaseCheckout] Balance payment completed successfully",
|
||||
logger.Field("orderNo", o.OrderNo),
|
||||
logger.Field("userId", u.Id))
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
commonLogic "github.com/perfect-panel/server/internal/logic/common"
|
||||
"github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
@@ -43,6 +44,12 @@ func (l *BindEmailWithVerificationLogic) BindEmailWithVerification(req *types.Bi
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
|
||||
}
|
||||
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowEmailBind, "bind_start",
|
||||
"[SubscriptionFlow] email bind with verification started",
|
||||
logger.Field("device_user_id", u.Id),
|
||||
logger.Field("email", req.Email),
|
||||
)
|
||||
|
||||
type payload struct {
|
||||
Code string `json:"code"`
|
||||
LastAt int64 `json:"lastAt"`
|
||||
@@ -69,6 +76,12 @@ func (l *BindEmailWithVerificationLogic) BindEmailWithVerification(req *types.Bi
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error or expired")
|
||||
}
|
||||
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowEmailBind, "bind_code_verified",
|
||||
"[SubscriptionFlow] email verification code accepted",
|
||||
logger.Field("device_user_id", u.Id),
|
||||
logger.Field("email", req.Email),
|
||||
)
|
||||
|
||||
familyHelper := newFamilyBindingHelper(l.ctx, l.svcCtx)
|
||||
currentEmailMethod, err := familyHelper.getUserEmailMethod(u.Id)
|
||||
if err != nil {
|
||||
@@ -115,6 +128,13 @@ func (l *BindEmailWithVerificationLogic) BindEmailWithVerification(req *types.Bi
|
||||
return nil, txErr
|
||||
}
|
||||
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowEmailBind, "email_owner_created",
|
||||
"[SubscriptionFlow] new email owner account created for bind flow",
|
||||
logger.Field("device_user_id", u.Id),
|
||||
logger.Field("owner_user_id", emailUser.Id),
|
||||
logger.Field("email", req.Email),
|
||||
)
|
||||
|
||||
// Join family: email user as owner, device user as member
|
||||
if err = familyHelper.validateJoinFamily(emailUser.Id, u.Id); err != nil {
|
||||
return nil, err
|
||||
@@ -123,11 +143,32 @@ func (l *BindEmailWithVerificationLogic) BindEmailWithVerification(req *types.Bi
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowEmailBind, "family_joined",
|
||||
"[SubscriptionFlow] device user joined email owner family",
|
||||
logger.Field("device_user_id", u.Id),
|
||||
logger.Field("owner_user_id", emailUser.Id),
|
||||
logger.Field("family_id", joinResult.FamilyId),
|
||||
logger.Field("email", req.Email),
|
||||
)
|
||||
token, err := l.refreshBindSessionToken(u.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowEmailBind, "trial_grant_requested",
|
||||
"[SubscriptionFlow] evaluating trial grant after email bind",
|
||||
logger.Field("device_user_id", u.Id),
|
||||
logger.Field("owner_user_id", emailUser.Id),
|
||||
logger.Field("family_id", joinResult.FamilyId),
|
||||
logger.Field("email", req.Email),
|
||||
)
|
||||
tryGrantTrialOnEmailBind(l.ctx, l.svcCtx, l.Logger, emailUser.Id, req.Email)
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowEmailBind, "bind_complete",
|
||||
"[SubscriptionFlow] email bind with verification completed",
|
||||
logger.Field("device_user_id", u.Id),
|
||||
logger.Field("owner_user_id", emailUser.Id),
|
||||
logger.Field("family_id", joinResult.FamilyId),
|
||||
logger.Field("email", req.Email),
|
||||
)
|
||||
return &types.BindEmailWithVerificationResponse{
|
||||
Success: true,
|
||||
Message: "email user created and joined family",
|
||||
@@ -146,16 +187,44 @@ func (l *BindEmailWithVerificationLogic) BindEmailWithVerification(req *types.Bi
|
||||
if err = familyHelper.validateJoinFamily(existingMethod.UserId, u.Id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowEmailBind, "email_owner_resolved",
|
||||
"[SubscriptionFlow] existing email owner resolved for bind flow",
|
||||
logger.Field("device_user_id", u.Id),
|
||||
logger.Field("owner_user_id", existingMethod.UserId),
|
||||
logger.Field("email", req.Email),
|
||||
)
|
||||
joinResult, err := familyHelper.joinFamily(existingMethod.UserId, u.Id, "bind_email_with_verification")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowEmailBind, "family_joined",
|
||||
"[SubscriptionFlow] device user joined existing email owner family",
|
||||
logger.Field("device_user_id", u.Id),
|
||||
logger.Field("owner_user_id", existingMethod.UserId),
|
||||
logger.Field("family_id", joinResult.FamilyId),
|
||||
logger.Field("email", req.Email),
|
||||
)
|
||||
token, err := l.refreshBindSessionToken(u.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowEmailBind, "trial_grant_requested",
|
||||
"[SubscriptionFlow] evaluating trial grant after existing email owner bind",
|
||||
logger.Field("device_user_id", u.Id),
|
||||
logger.Field("owner_user_id", existingMethod.UserId),
|
||||
logger.Field("family_id", joinResult.FamilyId),
|
||||
logger.Field("email", req.Email),
|
||||
)
|
||||
tryGrantTrialOnEmailBind(l.ctx, l.svcCtx, l.Logger, existingMethod.UserId, req.Email)
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowEmailBind, "bind_complete",
|
||||
"[SubscriptionFlow] email bind with verification completed",
|
||||
logger.Field("device_user_id", u.Id),
|
||||
logger.Field("owner_user_id", existingMethod.UserId),
|
||||
logger.Field("family_id", joinResult.FamilyId),
|
||||
logger.Field("email", req.Email),
|
||||
)
|
||||
|
||||
return &types.BindEmailWithVerificationResponse{
|
||||
Success: true,
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/server/internal/logic/auth"
|
||||
commonLogic "github.com/perfect-panel/server/internal/logic/common"
|
||||
"github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
@@ -14,18 +15,28 @@ import (
|
||||
|
||||
func tryGrantTrialOnEmailBind(ctx context.Context, svcCtx *svc.ServiceContext, log logger.Logger, ownerUserId int64, email string) {
|
||||
rc := svcCtx.Config.Register
|
||||
commonLogic.SubscriptionTraceInfo(log, commonLogic.SubscriptionTraceFlowEmailBind, "trial_grant_evaluating",
|
||||
"[SubscriptionFlow] evaluating email bind trial grant",
|
||||
logger.Field("owner_user_id", ownerUserId),
|
||||
logger.Field("email", email),
|
||||
logger.Field("trial_subscribe_id", rc.TrialSubscribe),
|
||||
)
|
||||
if !auth.ShouldAutoGrantTrialOnPublicEmailFlows(rc) {
|
||||
log.Infow("auto trial on email flow disabled, skip",
|
||||
commonLogic.SubscriptionTraceInfo(log, commonLogic.SubscriptionTraceFlowEmailBind, "trial_grant_skipped",
|
||||
"[SubscriptionFlow] auto trial on public email flow disabled",
|
||||
logger.Field("email", email),
|
||||
logger.Field("owner_user_id", ownerUserId),
|
||||
logger.Field("skip_reason", "public_email_trial_disabled"),
|
||||
)
|
||||
return
|
||||
}
|
||||
if !auth.ShouldGrantTrialForEmail(rc, email) {
|
||||
if rc.EnableTrial && rc.EnableTrialEmailWhitelist {
|
||||
log.Infow("email domain not in trial whitelist, skip",
|
||||
commonLogic.SubscriptionTraceInfo(log, commonLogic.SubscriptionTraceFlowEmailBind, "trial_grant_skipped",
|
||||
"[SubscriptionFlow] email domain not in trial whitelist",
|
||||
logger.Field("email", email),
|
||||
logger.Field("owner_user_id", ownerUserId),
|
||||
logger.Field("skip_reason", "trial_whitelist_rejected"),
|
||||
)
|
||||
}
|
||||
return
|
||||
@@ -36,12 +47,20 @@ func tryGrantTrialOnEmailBind(ctx context.Context, svcCtx *svc.ServiceContext, l
|
||||
Model(&user.Subscribe{}).
|
||||
Where("user_id = ? AND subscribe_id = ?", ownerUserId, rc.TrialSubscribe).
|
||||
Count(&count).Error; err != nil {
|
||||
log.Errorw("failed to check existing trial", logger.Field("error", err.Error()))
|
||||
commonLogic.SubscriptionTraceError(log, commonLogic.SubscriptionTraceFlowEmailBind, "trial_grant_error",
|
||||
"[SubscriptionFlow] failed to query existing trial subscription",
|
||||
logger.Field("owner_user_id", ownerUserId),
|
||||
logger.Field("email", email),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return
|
||||
}
|
||||
if count > 0 {
|
||||
log.Infow("trial already granted, skip",
|
||||
commonLogic.SubscriptionTraceInfo(log, commonLogic.SubscriptionTraceFlowEmailBind, "trial_grant_skipped",
|
||||
"[SubscriptionFlow] trial already exists for owner",
|
||||
logger.Field("owner_user_id", ownerUserId),
|
||||
logger.Field("email", email),
|
||||
logger.Field("skip_reason", "trial_already_exists"),
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -49,16 +68,24 @@ func tryGrantTrialOnEmailBind(ctx context.Context, svcCtx *svc.ServiceContext, l
|
||||
// Cross-user check: prevent the same real inbox (via dot trick / + alias) from
|
||||
// getting multiple trials across different accounts.
|
||||
if auth.NormalizedEmailHasTrial(ctx, svcCtx.DB, email, rc.TrialSubscribe) {
|
||||
log.Infow("normalized email already has trial via another account, skip",
|
||||
commonLogic.SubscriptionTraceInfo(log, commonLogic.SubscriptionTraceFlowEmailBind, "trial_grant_skipped",
|
||||
"[SubscriptionFlow] normalized email already received a trial elsewhere",
|
||||
logger.Field("email", email),
|
||||
logger.Field("owner_user_id", ownerUserId),
|
||||
logger.Field("skip_reason", "normalized_email_has_trial"),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
sub, err := svcCtx.SubscribeModel.FindOne(ctx, rc.TrialSubscribe)
|
||||
if err != nil {
|
||||
log.Errorw("failed to find trial subscribe template", logger.Field("error", err.Error()))
|
||||
commonLogic.SubscriptionTraceError(log, commonLogic.SubscriptionTraceFlowEmailBind, "trial_grant_error",
|
||||
"[SubscriptionFlow] failed to load trial subscription template",
|
||||
logger.Field("owner_user_id", ownerUserId),
|
||||
logger.Field("email", email),
|
||||
logger.Field("trial_subscribe_id", rc.TrialSubscribe),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -76,9 +103,13 @@ func tryGrantTrialOnEmailBind(ctx context.Context, svcCtx *svc.ServiceContext, l
|
||||
Status: 1,
|
||||
}
|
||||
if err = svcCtx.UserModel.InsertSubscribe(ctx, userSub); err != nil {
|
||||
log.Errorw("failed to insert trial subscribe",
|
||||
logger.Field("error", err.Error()),
|
||||
logger.Field("owner_user_id", ownerUserId),
|
||||
commonLogic.SubscriptionTraceError(log, commonLogic.SubscriptionTraceFlowEmailBind, "trial_grant_error",
|
||||
"[SubscriptionFlow] failed to create trial subscription for email bind",
|
||||
append(commonLogic.UserSubscribeTraceFields(userSub),
|
||||
logger.Field("error", err.Error()),
|
||||
logger.Field("owner_user_id", ownerUserId),
|
||||
logger.Field("email", email),
|
||||
)...,
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -89,9 +120,12 @@ func tryGrantTrialOnEmailBind(ctx context.Context, svcCtx *svc.ServiceContext, l
|
||||
}
|
||||
}
|
||||
|
||||
log.Infow("trial granted on email bind",
|
||||
logger.Field("owner_user_id", ownerUserId),
|
||||
logger.Field("email", email),
|
||||
logger.Field("subscribe_id", sub.Id),
|
||||
commonLogic.SubscriptionTraceInfo(log, commonLogic.SubscriptionTraceFlowEmailBind, "trial_grant_succeeded",
|
||||
"[SubscriptionFlow] trial subscription granted after email bind",
|
||||
append(commonLogic.UserSubscribeTraceFields(userSub),
|
||||
logger.Field("owner_user_id", ownerUserId),
|
||||
logger.Field("email", email),
|
||||
logger.Field("trial_subscribe_id", sub.Id),
|
||||
)...,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
commonLogic "github.com/perfect-panel/server/internal/logic/common"
|
||||
"github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
@@ -39,6 +40,10 @@ type CacheKeyPayload struct {
|
||||
|
||||
func (l *VerifyEmailLogic) VerifyEmail(req *types.VerifyEmailRequest) error {
|
||||
req.Email = strings.ToLower(strings.TrimSpace(req.Email))
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowEmailBind, "verify_email_start",
|
||||
"[SubscriptionFlow] email verification started",
|
||||
logger.Field("email", req.Email),
|
||||
)
|
||||
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, constant.Security, req.Email)
|
||||
value, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result()
|
||||
if err != nil {
|
||||
@@ -59,6 +64,10 @@ func (l *VerifyEmailLogic) VerifyEmail(req *types.VerifyEmailRequest) error {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code expired")
|
||||
}
|
||||
l.svcCtx.Redis.Del(l.ctx, cacheKey)
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowEmailBind, "verify_email_code_verified",
|
||||
"[SubscriptionFlow] email verification code accepted",
|
||||
logger.Field("email", req.Email),
|
||||
)
|
||||
|
||||
u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
|
||||
if !ok {
|
||||
@@ -77,6 +86,12 @@ func (l *VerifyEmailLogic) VerifyEmail(req *types.VerifyEmailRequest) error {
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "UpdateUserAuthMethods error")
|
||||
}
|
||||
commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowEmailBind, "verify_email_completed",
|
||||
"[SubscriptionFlow] email verification completed and trial evaluation will run",
|
||||
logger.Field("user_id", u.Id),
|
||||
logger.Field("owner_user_id", method.UserId),
|
||||
logger.Field("email", req.Email),
|
||||
)
|
||||
tryGrantTrialOnEmailBind(l.ctx, l.svcCtx, l.Logger, method.UserId, req.Email)
|
||||
return nil
|
||||
}
|
||||
|
||||
+5
-1
@@ -36,9 +36,13 @@ func NewService(svc *svc.ServiceContext) *Service {
|
||||
}
|
||||
|
||||
func initServer(svc *svc.ServiceContext) *gin.Engine {
|
||||
|
||||
// start init system config
|
||||
initStart := time.Now()
|
||||
logger.Info("system initialization start")
|
||||
initialize.StartInitSystemConfig(svc)
|
||||
logger.Infow("system initialization complete",
|
||||
logger.Field("duration", time.Since(initStart).String()),
|
||||
)
|
||||
// init gin server
|
||||
r := gin.Default()
|
||||
r.RemoteIPHeaders = []string{"X-Original-Forwarded-For", "X-Forwarded-For", "X-Real-IP"}
|
||||
|
||||
Reference in New Issue
Block a user