FlexPrice 订阅暂停(Subscription Pause)功能设计解析:从 PRD 到源码落地

原创2026-10-09 16:36:571,333 阅读

FlexPrice 订阅暂停(Subscription Pause)功能设计解析:从 PRD 到源码落地

FlexPrice 是面向开发者的用量计费(Usage-based billing)平台。本文将围绕仓库中的产品需求文档 docs/prds/subscription_pause.md 及其配套的 Phase 0 实施文档 docs/prds/subscription_pause_phase0.md,完整讲解"订阅暂停/恢复"功能的需求背景、数据模型、API 契约、计费影响计算、实现阶段划分与测试方案,并结合 internal/domain/subscription/pause.go、internal/api/dto/subscription_pause.go、internal/api/v1/subscription_pause.go 等源码,验证该功能在 FlexPrice 中的真实落地形态。读完本文,你将掌握暂停功能的状态机设计、PauseMode/ResumeMode 语义、billing_impact 的计算逻辑,以及如何在 FlexPrice 中调用暂停与恢复 API。

一、需求背景:为什么订阅需要"暂停"而不是只有"取消"

1.1 问题陈述

在传统的订阅计费系统中,客户面对暂时不需要服务的情况只有两个选择:

  1. 继续为未使用的服务付费;
  2. 直接取消订阅,未来需要时再重新订阅。

这两种选择都会带来负面影响:前者降低客户满意度,后者直接导致客户流失(churn)与客户终身价值(LTV)下降。暂停功能正是为了填补"继续付费"与"彻底取消"之间的空档,让客户在财务受限、季节性业务波动、长期出差或暂时无需求等场景下,能够保留订阅记录但停止计费。

1.2 现状与目标

PRD 指出,在引入暂停功能前,FlexPrice 的订阅状态机只支持三种状态:

  • active:订阅激活且正常计费;
  • cancelled:订阅已取消;
  • trialing:订阅处于试用期。

虽然在类型定义中已经存在 SubscriptionStatusPaused("paused")常量,但没有任何功能实现去支撑它。这一点在 internal/types/subscription.go 中可以得到印证:该文件里订阅状态枚举实际包含了 active、paused、cancelled、incomplete、trialing、draft 六种状态,paused 早已就位,只是缺少配套的业务逻辑。

暂停功能的目标是:

  • 提高客户留存率(retention);
  • 提升客户满意度;
  • 提供更灵活的计费方式;
  • 减少因财务约束导致的非自愿流失(involuntary churn)。

二、核心功能与状态机设计

2.1 四大核心能力

PRD 定义了暂停功能的四项核心能力:

  1. Pause Subscription(暂停):临时暂停订阅,停止计费但保留订阅记录;
  2. Resume Subscription(恢复):将已暂停的订阅恢复为激活状态,重新开始计费;
  3. Scheduled Pause/Resume(定时暂停/恢复):支持在未来日期定时执行暂停与恢复;
  4. Pause History(暂停历史):记录每一次暂停/恢复操作,用于报告与分析。

2.2 PauseStatus:订阅的暂停状态

PRD 提出在 Subscription 模型上新增 PauseStatus 字段,取值范围为 none、active、scheduled。源码 internal/types/subscription.go 中该类型已实际落地,并且比 PRD 设计得更完整,额外增加了两个终态:

  • PauseStatusNone("none"):未暂停;
  • PauseStatusActive("active"):当前处于暂停中;
  • PauseStatusScheduled("scheduled"):已安排在未来暂停;
  • PauseStatusCompleted("completed"):暂停已结束(订阅已恢复);
  • PauseStatusCancelled("cancelled"):暂停请求被取消。

PauseStatus 类型带有 String() 与 Validate() 方法,Validate() 会在非法值传入时通过 internal/errors 返回带 ErrValidation 标记的错误,这保证了状态字段在 API 层与服务层都能被一致校验。

2.3 PauseMode 与 ResumeMode:暂停/恢复的触发时机

PRD 定义了 PauseMode(immediate / period_end / scheduled)与 ResumeMode(immediate / scheduled / auto)两种模式。这两类枚举在仓库中有独立的定义文件 internal/types/pause_mode.go,其含义如下:

枚举 取值 语义
PauseMode immediate 立即暂停,从当前时刻停止计费
PauseMode scheduled 在指定时间(pause_start)开始暂停
PauseMode period_end 在当前计费周期结束时暂停,周期内继续正常计费
ResumeMode immediate 立即恢复订阅
ResumeMode scheduled 在指定日期恢复
ResumeMode auto 依据 pause_end 自动恢复

两种枚举均实现了 Validate(),只接受上表列出的合法值。

三、数据模型:Subscription 扩展与 SubscriptionPause 表

3.1 Subscription 模型新增字段

PRD 要求在 Subscription 结构体上增加两个字段。这两个字段在 internal/domain/subscription/model.go 中均已存在:

PauseStatus types.PauseStatus `db:"pause_status" json:"pause_status"`

// ActivePauseID references the current active pause configuration
// This will be null if no pause is active or scheduled
ActivePauseID *string `db:"active_pause_id" json:"active_pause_id,omitempty"`
  • PauseStatus:跟踪订阅当前的暂停状态;
  • ActivePauseID:指向当前生效的暂停配置记录(subscription_pauses 表的主键),无暂停或仅排定暂停时为空指针。

同时 Subscription 模型还新增了关联字段 Pauses <a href="https://link.gitcode.com/i/1f99ff0d3b43f956594fe1d378bebff7" target="_blank">]*SubscriptionPause(见 [internal/domain/subscription/model.go),用于在查询时一次性加载该订阅的全部暂停记录;GetSubscriptionFromEnt 中通过 sub.Edges.Pauses 完成这一关联映射(internal/domain/subscription/model.go)。

3.2 SubscriptionPause 表结构

PRD 给出了 SubscriptionPause 结构体草案。仓库中实际实现的版本位于 internal/domain/subscription/pause.go,与 PRD 相比做了以下调整:

  • 字段精简:移除了 Status string、PauseDays *int 字段——暂停时长改由 PauseStart 与 PauseEnd 两个时间点推导;
  • 字段升级为类型安全枚举:PauseStatus、PauseMode、ResumeMode 分别使用 types.PauseStatus、types.PauseMode、types.ResumeMode 而非裸字符串;
  • 新增多租户字段:EnvironmentID(环境隔离)以及继承自 types.BaseModel 的 TenantID、Status、CreatedAt、CreatedBy、UpdatedAt、UpdatedBy——这与 FlexPrice 的多租户架构一致。

最终字段清单:

字段 类型 说明
ID string 暂停记录唯一标识
SubscriptionID string 所属订阅 ID
PauseStatus types.PauseStatus none / active / scheduled / completed / cancelled
PauseMode types.PauseMode immediate / scheduled / period_end
ResumeMode types.ResumeMode immediate / scheduled / auto
PauseStart time.Time 暂停实际开始时间
PauseEnd *time.Time 暂停结束时间(无限期暂停时为 nil)
ResumedAt *time.Time 实际恢复时间(手动恢复时写入)
OriginalPeriodStart time.Time 暂停发生时所在计费周期的起始时间
OriginalPeriodEnd time.Time 暂停发生时所在计费周期的结束时间
Reason string 暂停原因
Metadata types.Metadata 附加元数据
EnvironmentID string 环境标识
BaseModel types.BaseModel 租户/状态/审计时间字段

其中 OriginalPeriodStart 与 OriginalPeriodEnd 是"恢复时还原计费周期"的关键:恢复操作会以这两个原始周期为基准,叠加暂停时长,重新推算调整后的计费周期。

3.3 ORM 与迁移

SubscriptionPause 领域模型与 ORM 层(ent 框架)之间有 SubscriptionPauseFromEnt / SubscriptionPauseListFromEnt 两个转换函数(internal/domain/subscription/pause.go),说明该表已经纳入 ent 生成代码体系(ent/subscriptionpause 与 ent/schema/subscription_pause.go),配套的数据库迁移可直接通过 ent 迁移机制生成。

四、API 契约:暂停、恢复与历史查询

4.1 Pause Subscription Request

PRD 给出的暂停请求体包含 pause_mode、pause_start、pause_end、pause_days、reason、dry_run、metadata。实际 DTO 实现在 internal/api/dto/subscription_pause.go,字段语义与校验规则如下:

{
  "pause_mode": "immediate",
  "pause_start": "2023-10-15T00:00:00Z",
  "pause_end": "2023-12-31T00:00:00Z",
  "pause_days": 30,
  "reason": "Customer traveling",
  "dry_run": false,
  "metadata": {
    "requested_by": "customer"
  }
}

字段约束(DTO 内 Validate() 方法实现,见 internal/api/dto/subscription_pause.go):

  • pause_mode 必填,且必须是 types.PauseMode 枚举中的合法值;
  • 当 pause_mode = scheduled 时,pause_start 必填(PRD 中原先的 pause_start 字段在实现版中为可选指针,仅在 scheduled 模式下强制要求);
  • pause_end 与 pause_days 二选一,同时指定会返回校验错误;
  • pause_days 必须为正整数(validate:"omitempty,gt=0");
  • pause_end 必须在未来(实现中还额外要求 pause_end 晚于 pause_start,见 validate:"omitempty,gtfield=PauseStart");
  • reason 最长 255 字符;
  • dry_run 默认为 false。

4.2 Pause Subscription Response

暂停响应由三部分组成:更新后的 subscription、新建的 pause 记录、以及 billing_impact 计费影响明细(internal/api/dto/subscription_pause.go):

{
  "subscription": {
    "id": "sub_123",
    "status": "paused",
    "pause_status": "active"
  },
  "pause": {
    "id": "pause_456",
    "subscription_id": "sub_123",
    "pause_mode": "immediate",
    "pause_start": "2023-10-15T14:30:00Z",
    "pause_end": "2023-12-31T00:00:00Z",
    "original_period_start": "2023-10-01T00:00:00Z",
    "original_period_end": "2023-10-31T23:59:59Z",
    "reason": "Customer traveling"
  },
  "billing_impact": {
    "current_period_adjustment": -50.00,
    "next_billing_date": "2023-12-31T00:00:00Z",
    "next_billing_amount": 100.00,
    "pause_duration_days": 77
  },
  "dry_run": false
}

注意 PRD 草案中的 pause_days 字段在响应里已不直接返回(时长由 pause_start/pause_end 推导并反映在 billing_impact.pause_duration_days 中)。

4.3 Resume Subscription Request / Response

恢复请求体(internal/api/dto/subscription_pause.go):

{
  "resume_mode": "immediate",
  "dry_run": false,
  "metadata": {
    "requested_by": "customer"
  }
}

实现版 DTO 的校验相对轻量:resume_mode 必填且必须是合法枚举;PRD 中提及的 resume_date(scheduled 恢复所需)在当前实现版 DTO 中尚未出现,属于后续阶段能力。恢复响应结构与暂停响应对称,区别在于:

  • subscription.status 回到 active,pause_status 回到 none;
  • pause.status 变为 completed,并写入 resumed_at;
  • billing_impact 中的 adjusted_period_start / adjusted_period_end 展示按暂停时长平移后的新计费周期。

4.4 暂停历史查询

PRD 规划的 GetPauseHistory 在实现中演变为 ListPauses,对应响应类型 ListSubscriptionPausesResponse(items + total,见 internal/api/dto/subscription_pause.go)。同时 PRD 提出的"每次暂停/恢复写入订阅历史事件"(subscription.paused / subscription.resumed)仍然成立,作为审计与分析的数据来源。

4.5 HTTP 层实现

HTTP 处理器位于 internal/api/v1/subscription_pause.go,基于 Gin 框架实现了三个接口:

  • POST /subscriptions/{id}/pause → PauseSubscription;
  • POST /subscriptions/{id}/resume → ResumeSubscription;
  • GET /subscriptions/{id}/pauses → ListPauses。

关键实现细节:当请求携带 dry_run = true 时,处理器不会调用 PauseSubscription / ResumeSubscription,而是改调 CalculatePauseImpact / CalculateResumeImpact,返回形如 {"impact": {...}} 的纯计算结果,不产生任何副作用(internal/api/v1/subscription_pause.go)。这与 PRD 对 dry run 模式的要求完全一致:不修改订阅、返回计费影响、响应标记 dry_run: true。

五、服务层:暂停/恢复的业务流程

5.1 服务接口

PRD 要求为 SubscriptionService 增加 PauseSubscription、ResumeSubscription、GetPauseHistory 三个方法。仓库实现位于 internal/interfaces/service.go 与 internal/ee/service/subscription.go(企业版服务实现),实际方法集为:

  • PauseSubscription(ctx, id, req)
  • ResumeSubscription(ctx, id, req)
  • CalculatePauseImpact(ctx, id, req)
  • CalculateResumeImpact(ctx, id, req)
  • ListPauses(ctx, id)

5.2 暂停流程(Immediate Pause)

PRD 与 Phase 0 文档共同描述的暂停处理流程如下:

  1. 校验请求:pause_mode 必须合法,pause_end 与 pause_days 不能同时指定;
  2. 校验订阅状态:订阅当前必须是 active(Phase 0 实现中,状态不是 active 或已处于暂停状态时直接报错);
  3. 计算暂停结束时间:若指定 pause_days,则按 pauseStart + pauseDays 推导 pauseEnd;
  4. 计算计费影响:调用 calculateBillingImpact,得到 billing_impact;
  5. dry run 提前返回:若是预览请求,直接返回影响结果,不写库;
  6. 创建暂停记录:生成 pause_xxx 前缀 ID(types.GenerateUUIDWithPrefix("pause")),写入 PauseStart = now、OriginalPeriodStart/End = 订阅当前计费周期;
  7. 更新订阅:PauseStatus = active、ActivePauseID = pauseID、SubscriptionStatus = paused;
  8. 事务提交:暂停记录创建与订阅更新在同一个事务(WithTx)中完成,保证原子性。

5.3 恢复流程(Immediate Resume)

恢复流程同样由 Phase 0 文档给出伪代码,逻辑对称:

  1. 校验状态:订阅必须是 paused,且 PauseStatus = active、ActivePauseID 非空;
  2. 读取活跃暂停记录:通过 ActivePauseID 获取 pause;
  3. 计算暂停时长:now - PauseStart 换算为天数;
  4. 计算恢复计费影响:以 OriginalPeriodStart/End 与订阅当前周期为基准构造 billing_impact;
  5. 更新暂停记录:PauseStatus = completed、写入 ResumedAt;
  6. 更新订阅:PauseStatus = none、ActivePauseID = nil、SubscriptionStatus = active;
  7. 平移计费周期:CurrentPeriodStart/End 各加上暂停时长(subscription.CurrentPeriodStart.Add(pauseDuration)),实现"账单顺延";
  8. 事务提交:暂停记录更新与订阅更新在同一事务中完成。

这套"按暂停时长平移计费周期"的策略正是 PRD 中"Resume Billing 示例"的实现方式:原周期 Oct 1–Oct 31,暂停 Oct 15–Nov 15(31 天),恢复后调整周期为 Nov 15–Dec 15,下次账单日为 Dec 15。

5.4 与计费周期 Cron 的集成

PRD 明确指出,处理订阅计费周期的定时任务需要更新三件事:跳过 paused 状态的订阅、处理定时暂停/恢复、处理基于 pause_end 的自动恢复。Phase 0 文档给出的 UpdateBillingPeriods 示例即是在批处理循环开头增加:

// Skip paused subscriptions
if sub.SubscriptionStatus == types.SubscriptionStatusPaused {
    s.Logger.Infow("skipping paused subscription", "subscription_id", sub.ID)
    continue
}

这一改动保证暂停期间不会生成账单、不会推进计费周期。定时(scheduled)暂停/恢复与自动恢复属于后续阶段能力,但架构上都是对这一批处理逻辑的扩展。

六、计费与开票影响:billing_impact 详解

6.1 BillingImpactDetails 结构

PRD 定义了 BillingImpactDetails 结构体,用于向用户透明展示暂停/恢复的财务影响。实现中该结构体位于 internal/types(types.BillingImpactDetails),核心字段:

字段 类型 语义
CurrentPeriodAdjustment float64 当期调整额;负值表示退款/贷记给客户(预付费模式中未使用部分)
NextBillingDate *time.Time 下一张发票生成日期;暂停期间为空
NextBillingAmount float64 下一计费日应扣金额,可能因中途恢复而按比例折算
OriginalPeriodStart / OriginalPeriodEnd *time.Time 暂停前的原始计费周期
AdjustedPeriodStart / AdjustedPeriodEnd *time.Time 暂停后调整的计费周期
PauseDurationDays int 总暂停天数

6.2 计费影响的计算因素

billing_impact 的计算结果取决于四个因素:

  1. 计费模型:预付费(advance)还是后付费(arrears);
  2. 暂停模式:immediate 还是 period_end;
  3. 订阅的计费周期(月/周/年等);
  4. 暂停时长。

6.3 预付费(Advance Billing)下的立即暂停

预付费模式下,客户已为整个周期付费,立即暂停时应退还未使用部分:

  • 月度订阅:$100/月;
  • 计费周期:10 月 1 日—10 月 31 日;
  • 暂停日期:10 月 15 日;
  • 未使用天数:16 天(10 月 16 日—31 日);
  • 贷记金额:$100 × (16/31) = $51.61;
  • 下次计费日期:暂停结束后。

6.4 后付费(Arrears Billing)下的立即暂停

后付费模式下,暂停只影响"本期已使用部分"的结算:

  • 月度订阅:$100/月;
  • 计费周期:10 月 1 日—10 月 31 日;
  • 暂停日期:10 月 15 日;
  • 已用天数:15 天(10 月 1 日—15 日);
  • 应结金额:$100 × (15/31) = $48.39;
  • 暂停期间不再计费。

6.5 Period-End 暂停与恢复

  • Period-End 暂停:无需按比例折算——本周期正常结账,暂停期间不产生账单;
  • 恢复计费:将计费周期按暂停时长整体顺延后恢复正常计费(示例见 5.3)。

6.6 Phase 0 的简化计算

Phase 0 文档提供了 calculateBillingImpact 参考实现(docs/prds/subscription_pause_phase0.md),其计算逻辑为:

  1. 用 CurrentPeriodEnd - CurrentPeriodStart 求周期总天数;
  2. 用 CurrentPeriodEnd - pauseStart 求剩余天数;
  3. 剩余天数 / 总天数 × 月度金额得出 CurrentPeriodAdjustment(预付费场景为负值即贷记);
  4. 根据 pauseEnd / pauseDays 推导 NextBillingDate 与 PauseDurationDays;
  5. 将 CurrentPeriodStart/End 平移 pauseDurationDays 天得到 AdjustedPeriodStart/End。

同时文档明确标注了该阶段的两处简化:无限期暂停用 30 天占位值、金额计算使用占位常量 100.0,并注明"未来阶段将基于订阅真实金额、区分固定价格与用量价格进行计算"。生产实现中这些能力由服务层结合订阅的实际行项目金额计算。

七、Dry Run 模式:先算账、再动手

PRD 将 dry run 定义为一个关键的用户体验能力:客户在确认暂停/恢复前,可以先预览财务影响。规则如下:

  1. 不修改订阅与暂停记录;
  2. 响应中返回完整的 billing_impact;
  3. 响应中不包含更新后的 subscription 与 pause 对象;
  4. 响应中 dry_run 字段为 true。

实际 HTTP 实现严格遵循这一契约:处理器检测到 dry_run: true 后立即转入 CalculatePauseImpact / CalculateResumeImpact 分支,只返回 {"impact": ...}(internal/api/v1/subscription_pause.go)。由于 dry run 完全跳过持久化路径,其计算结果与真实执行时的 billing_impact 一致性由测试用例保障(见第八节)。

八、边界情况、租户配置与测试策略

8.1 边界情况清单

PRD 列出的边界情况对实现提出了明确的约束,其中"暂停期间禁止计划变更"这一点已在服务端体现——subscription_plan_change.go 的 OpenAPI 注释中明确将 paused 列为计划变更接口不可操作的状态(internal/api/v1/subscription_plan_change.go)。其余边界规则汇总如下:

边界场景 规则
试用期暂停 允许暂停;恢复时试用结束日期按暂停时长顺延
多次暂停 同一时刻只允许一个活跃暂停;累计统计暂停次数与时长
暂停期间的用量 继续记录 usage 事件,但不计入账单;报告中标记"paused"
暂停期间的订阅变更 不允许计划变更;变更请求排队,恢复后应用
暂停期间的取消 允许取消,状态直接置为 cancelled
暂停前的欠费 要求先结清未付发票;或允许暂停但仅在付款后恢复

8.2 租户级配置项

PRD 规划了 5 项租户配置,属于后期阶段(Phase 2)能力:

  1. Allow Pause:是否启用暂停功能;
  2. Maximum Pause Duration:最大允许暂停时长;
  3. Minimum Time Between Pauses:两次暂停之间的最小活跃间隔;
  4. Proration Settings:贷记/扣费的计算策略;
  5. Pause Limits:每个订阅允许的暂停次数上限。

8.3 测试策略与仓库内测试佐证

PRD 规划的测试场景包括:暂停激活订阅、恢复暂停订阅、dry run 一致性、定时暂停/恢复、自动恢复、以及各类边界(试用期暂停、暂停中取消、多种计费模型)。

仓库中已有对应的服务层测试 internal/ee/service/subscription_pause_test.go,其中 TestSubscriptionPauseService 覆盖了多种 pause_mode 组合(PauseModeImmediate 等),并断言暂停响应的 Pause.PauseMode 与请求一致(internal/ee/service/subscription_pause_test.go),同时验证了暂停后订阅状态、billing_impact 等输出。API 测试 SDK 中也有对应的 pause/resume 调用示例(api/tests/go/test_sdk.go、api/tests/ts/test_sdk.ts),可作为客户端接入的参考。

九、实施路线图:从 Phase 0 到 Phase 2

PRD 将暂停功能拆分为三个阶段:

Phase 0(最小可行实现)

  • 数据库字段与 subscription_pauses 表;
  • 基本立即暂停/恢复;
  • 计费周期 Cron 跳过 paused 订阅;
  • dry run 预览支持;
  • 简单按比例(proration)计算。

Phase 1(核心功能)

  • period_end 暂停;
  • 定时暂停与恢复;
  • 基于 pause_end 的自动恢复;
  • 暂停历史 API。

Phase 2(高级特性)

  • 租户配置项;
  • 暂停期间的用量处理;
  • 报告与分析;
  • 部分暂停(仅暂停特定行项目)。

迁移与上线策略:

  1. 数据库迁移:为 subscription 表加字段、新建 subscription_pauses 表、存量订阅初始化 pause_status = none;
  2. API 版本:在现有 API 版本内新增端点并补充文档;
  3. 灰度发布:先上线数据库变更 → 以功能开关(feature flag)发布代码 → 面向 beta 客户 → 逐步全量。

九·补 监控指标

PRD 建议上线后追踪以下业务指标,用于评估暂停功能对留存与收入的影响:

指标 定义
Pause Rate 暂停订阅占比
Pause Duration 暂停时长的均值与分布
Resume Rate 已暂停订阅中恢复的比例
Churn After Pause 恢复后 X 天内取消的比例
Revenue Impact 因暂停累计发放的贷记总额

十、未来方向

PRD 展望了 5 个演进方向,可作为后续产品迭代的输入:

  1. Partial Pauses:按行项目暂停而非整订阅暂停;
  2. Pause Tiers:不同档位的暂停选项(如带广告免费暂停 vs. 无广告付费暂停);
  3. Pause Suggestions:基于用量模式主动建议暂停;
  4. Pause Incentives:以激励促使客户提前恢复;
  5. Seasonal Pause Programs:为季节性业务提供预定义的暂停套餐。

十一、小结:PRD 到实现的对照

从 docs/prds/subscription_pause.md 到当前仓库源码,暂停功能的核心设计几乎完整落地,同时实现也对 PRD 做了若干务实修正:

  • 状态枚举更完整:PauseStatus 增加了 completed / cancelled 两个终态(internal/types/subscription.go);
  • 字段类型更安全:暂停记录的 PauseMode / ResumeMode / PauseStatus 均使用强类型枚举(internal/domain/subscription/pause.go);
  • 字段有取舍:实现版去掉了 Status、PauseDays 裸字段,暂停时长由时间点推导;
  • 历史查询更直接:GetPauseHistory 演化为 ListPauses,通过 GET /subscriptions/{id}/pauses 暴露(internal/api/v1/subscription_pause.go);
  • dry run 独立成方法:CalculatePauseImpact / CalculateResumeImpact 将预览与执行彻底解耦。

对于想深入研究的读者,建议按以下路径阅读源码:先看 PRD 了解需求全貌 → 阅读 internal/domain/subscription/pause.go 与 internal/domain/subscription/model.go 掌握数据模型 → 阅读 internal/api/dto/subscription_pause.go 理解 API 契约 → 阅读 internal/api/v1/subscription_pause.go 与 internal/ee/service/subscription.go 追踪请求处理链路 → 最后用 internal/ee/service/subscription_pause_test.go 验证行为。

登录后查看全文
flexprice