PAYATHON 2026

订阅制 web service 适合使用什么 framework?

支付小周

结论

不要试图寻找一个同时包办注册、计量、订阅、支付、权限控制和后台运维的”万能 framework”。更可靠的做法是把系统分成三个部分:

  1. 用成熟的 Web framework 构建账户系统、管理后台和自助服务门户;
  2. 用支付或订阅计费平台处理支付凭据、发票、税务和 recurring billing;
  3. 单独实现 API key、用量计量、配额和 entitlement(服务权益)控制。

如果团队没有明确的语言偏好,在 Linux 环境下可以优先考虑:

  • Django + Django REST Framework:自带认证、ORM 和完善的管理后台,适合快速搭建运营管理功能。
  • Ruby on Rails:适合订阅制 SaaS,开发账户、邮件、后台和业务流程的效率较高。
  • Laravel:PHP 团队容易上手,生态中已有较成熟的订阅计费集成。
  • Spring Boot:适合已有 Java 基础设施、权限模型复杂或合规要求较高的团队。

对多数中小团队来说,Django + PostgreSQL + Redis + Celery + 支付平台 是一个稳妥的起点。Web framework 不应直接保存银行卡信息,计费平台也不应承担实时访问控制。

为什么不建议依赖单一的订阅系统

这些需求看上去都属于”订阅管理”,实际涉及多个生命周期不同的领域:

领域主要职责
Identity注册、登录、邮箱验证、密码重置
Billing支付方式、订阅、发票、退款、优惠码
Metering记录 API 调用次数、存储量或处理量
Entitlement判断账户是否有权使用某项功能
API ManagementAPI key、限流、配额、密钥轮换
Operations服务状态、审计日志、告警和人工处理
Communication邮件模板、账单提醒、用量预警

支付平台擅长处理账单,但通常不适合在每次 API 请求时同步判断权限。API gateway 擅长鉴权和限流,却不了解退款、欠费重试等账单状态。如果把所有职责强行塞进一个系统,日后修改套餐或计费方式会很困难。

推荐的系统结构

CMS Plugin / SDK / API Client
              |
         API Gateway
              |
      Authentication + Rate Limit
              |
         Web Service
              |
        Usage Event Queue
              |
       Metering Service
              |
     PostgreSQL / Analytics Store

Self-service Portal
      |          |
Account DB    Billing Provider
                   |
                Webhook
                   |
          Subscription Sync
                   |
          Entitlement Store

设计时应遵循以下原则:

  • 客户端只持有可撤销的 API key,不接触内部账户 ID 或支付信息。
  • 每次请求先完成鉴权和限流,再记录用量。
  • 本地数据库保存订阅状态的业务投影,以便快速完成授权判断。
  • 支付状态通过 webhook 异步同步,同时设置定期对账任务。
  • 每条用量事件都应有唯一 ID,防止重复计费。

实施步骤

1. 先定义套餐和计费单位

选择 framework 之前,先确定实际售卖的内容。例如:

  • 免费套餐:每月 1,000 次请求;
  • 基础套餐:每月 100,000 次请求;
  • 超出部分按每 1,000 次请求收费;
  • 高级功能仅向特定套餐开放。

还要明确以下规则:

  • 配额按自然月还是订阅周期重置;
  • 请求失败是否计费;
  • 重试请求是否重复计费;
  • 套餐升级立即生效还是在下个周期生效;
  • 降级后如何处理已经超出的用量;
  • 欠费后立即停用,还是提供 grace period;
  • 账户暂停后是否保留数据。

这些规则会直接影响数据模型和计量方式,比选择 framework 更值得优先确定。

2. 建立本地账户和权益模型

系统至少需要以下实体:

User
Organization
Membership
ApiKey
Plan
Subscription
Entitlement
UsageEvent
UsageAggregate
InvoiceReference
WebhookEvent
AuditLog

订阅最好归属于 Organization,不要直接归属于 User。以后增加团队成员、企业账户或多个 API key 时,就不必重新设计计费模型。

订阅记录需要保存外部计费系统的标识,但不要把计费系统返回的完整对象直接作为本地业务模型:

class Subscription(models.Model):
    organization = models.OneToOneField(
        "Organization",
        on_delete=models.CASCADE,
    )
    provider_customer_id = models.CharField(max_length=255, unique=True)
    provider_subscription_id = models.CharField(
        max_length=255,
        unique=True,
        null=True,
    )
    plan_code = models.CharField(max_length=100)
    status = models.CharField(max_length=32)
    current_period_end = models.DateTimeField(null=True)
    cancel_at_period_end = models.BooleanField(default=False)
    updated_at = models.DateTimeField(auto_now=True)

status 的可选值应按自己的业务规则定义,例如 trialing、active、past_due、suspended 和 canceled。不要让各处代码直接比较计费平台的原始状态字符串。

3. 把 API key 与账户权限分开

API key 负责识别调用方。是否允许调用,则取决于账户状态、套餐权益和当前用量。

数据库不要保存 API key 明文,只保存散列值以及用于定位的前缀:

import hashlib
import secrets


def issue_api_key():
    raw_key = f"svc_{secrets.token_urlsafe(32)}"
    key_hash = hashlib.sha256(raw_key.encode("utf-8")).hexdigest()

    return {
        "raw_key": raw_key,      # 只在创建时展示一次
        "prefix": raw_key[:12],
        "key_hash": key_hash,
    }


def hash_api_key(raw_key: str) -> str:
    return hashlib.sha256(raw_key.encode("utf-8")).hexdigest()

API key 应支持:

  • 创建和撤销;
  • 多 key 管理;
  • 记录最后使用时间;
  • 权限范围 scopes;
  • 到期时间;
  • 密钥轮换;
  • 异常使用告警。

4. 在请求路径中执行权益检查

不要在每次 API 请求中调用支付平台,否则支付平台超时可能导致整个服务不可用。

from dataclasses import dataclass


@dataclass(frozen=True)
class AccessDecision:
    allowed: bool
    reason: str
    remaining: int | None = None


def check_access(subscription, usage: int, monthly_limit: int) -> AccessDecision:
    if subscription.status not in {"trialing", "active"}:
        return AccessDecision(False, "subscription_inactive")

    if usage >= monthly_limit:
        return AccessDecision(False, "quota_exceeded", remaining=0)

    return AccessDecision(
        True,
        "allowed",
        remaining=monthly_limit - usage,
    )

免费账户同样应该有明确的 entitlement,不要在代码各处散落 if not paid。例如:

{
  "plan": "free",
  "features": {
    "api_access": true,
    "batch_processing": false
  },
  "limits": {
    "requests_per_month": 1000,
    "requests_per_minute": 10
  }
}

采用这种方式后,修改套餐时主要调整配置和数据,不需要改动所有业务接口。

5. 使用幂等的用量事件

只维护一个不断累加的全局计数器,很难在出现问题时对账。更稳妥的方式是先记录不可变的 usage event,再进行异步聚合。

{
  "event_id": "01JEXAMPLE8N7N3Q4QJ6YF4",
  "organization_id": "org_123",
  "api_key_id": "key_456",
  "meter": "api_request",
  "quantity": 1,
  "occurred_at": "2026-08-29T10:30:00Z",
  "idempotency_key": "request_789"
}

数据库应为 idempotency_key 或 event_id 添加唯一约束。这样即使消息队列重复投递,也不会重复计费。

在高流量服务中,请求不宜同步等待复杂的用量聚合。常见的处理方式是:

  1. 请求完成后写入队列或轻量事件表;
  2. 后台任务批量聚合;
  3. 接近限额时,使用 Redis 等快速计数器辅助判断;
  4. 周期结束时,根据原始事件重新核对聚合结果。

6. 正确处理支付 webhook

支付结果必须以服务端 webhook 为准。浏览器跳转到”支付成功”页面,不能直接作为开通权限的依据。

from django.db import transaction
from django.http import HttpResponse


def billing_webhook(request):
    event = verify_and_parse_webhook(
        body=request.body,
        signature=request.headers.get("Billing-Signature"),
    )

    with transaction.atomic():
        record, created = WebhookEvent.objects.get_or_create(
            provider_event_id=event["id"],
            defaults={"event_type": event["type"]},
        )

        if not created:
            return HttpResponse(status=200)

        apply_billing_event(event)
        record.processed = True
        record.save(update_fields=["processed"])

    return HttpResponse(status=200)

verify_and_parse_webhook 必须使用所选计费平台提供的官方 SDK 或签名验证方式。不同平台的 header 名称、签名算法和事件结构并不相同,示例中的占位名称不能直接照搬。

实现时还要处理以下情况:

  • webhook 可能重复到达;
  • 事件可能乱序;
  • webhook 处理可能失败;
  • 取消订阅后不一定立即失效;
  • 退款、争议和扣款失败可能晚于订单创建;
  • 本地状态可能与计费平台不一致。

因此,系统需要保存原始事件标识、处理结果和失败原因,并通过定期运行的 reconciliation job 完成对账。

构建、购买还是采用开源系统

使用托管计费平台

托管计费平台适合希望尽快上线,又不想自行处理支付凭据、账单和税务问题的团队。

这类平台通常提供:

  • checkout;
  • payment method 管理;
  • recurring billing;
  • invoice;
  • discount code;
  • customer portal;
  • webhook;
  • 部分用量计费能力。

以下功能通常仍需自行实现:

  • API key;
  • entitlement;
  • 实时限流;
  • 精确的用量采集;
  • 服务状态监控;
  • 与内部账号体系同步。

平台的具体功能、支持地区、支付方式和费用可能发生变化。选型时应以候选平台当前的官方文档和合同为准。

使用开源计费系统

如果系统必须私有化部署,可以考察专门的 billing 或 usage-based billing 项目,例如 Kill Bill、Lago 等。但”开源计费系统”通常不等于完整的 SaaS 应用,它们可能只覆盖账单、套餐、发票或用量计费中的一部分。

评估时至少要验证:

  • 项目维护活跃度;
  • 数据迁移和升级路径;
  • webhook 与支付渠道覆盖;
  • 税务和发票能力;
  • 用量计费模型;
  • 多币种支持;
  • 管理后台成熟度;
  • 审计和权限控制;
  • 失败重试与对账工具;
  • 商业支持和许可证。

不要只因为某个项目标注了 “subscription” 就直接采用。最好用真实的套餐升级、降级、欠费、退款和重复事件场景完成一次原型验证。

自行构建

自行开发账户门户、entitlement 和 metering 通常是合理的,但自行处理银行卡、税务和完整账单引擎的成本往往很高。

比较实用的边界是:

  • 自建产品规则、套餐权益和用量系统;
  • 购买支付、发票和税务能力;
  • 早期使用 framework 自带的管理后台处理运营工作;
  • 业务稳定后,再逐步替换需要深度定制的部分。

管理后台应该具备什么

第一阶段没有必要开发一个外观复杂的独立后台。Django Admin、Rails Active Admin 一类工具已经可以支持大量内部操作。建议优先提供以下能力:

  • 按邮箱、组织 ID、API key 前缀和外部 customer ID 搜索;
  • 查看订阅状态及其变更记录;
  • 查看当前周期的用量;
  • 撤销或轮换 API key;
  • 临时授予 entitlement,并设置到期时间;
  • 重新处理失败的 webhook;
  • 触发单个账户与计费平台的对账;
  • 查看扣款失败和超限账户;
  • 记录管理员执行操作的原因。

所有人工修改都应写入 AuditLog,记录操作者、修改前后的值、原因和时间。管理员不应绕过审计记录,直接在数据库中随意修改订阅状态。

需要特别注意的问题

不要保存支付凭据

应使用支付平台托管的 checkout 或 tokenization。自己的服务器只保存 customer_id、payment_method_id 等引用。是否需要满足 PCI DSS,以及具体适用范围,应由支付服务商和合规专业人员根据实际集成方式确认。

认证、限流和月度配额不是一回事

  • 认证回答”调用者是谁”;
  • entitlement 回答”允许使用什么”;
  • rate limit 回答”短时间内能调用多快”;
  • quota 回答”一个计费周期内能使用多少”。

这些机制应独立配置,否则套餐升级和异常流量处理容易互相干扰。

不要立即删除取消账户的数据

“取消订阅”、“暂停服务”、“关闭账户”和”删除个人数据”是不同的操作。系统需要分别定义数据保留期限、恢复策略和隐私删除流程。

为 CMS plugin 和 SDK 设计稳定接口

客户端可能很长时间不升级,因此 API 应提供:

  • 明确的版本策略,例如 /v1/...;
  • 机器可读的错误码;
  • 幂等请求支持;
  • 合理的 timeout 和 retry 指南;
  • 用量响应头;
  • API key 轮换机制。

例如:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Too many requests. Retry later."
  }
}

月度配额耗尽和每分钟限流应返回不同的错误码,客户端才能判断是稍后重试,还是提示用户升级套餐。

实用的起步方案

需要快速上线的团队可以采用以下组合:

Django + Django REST Framework
PostgreSQL
Redis
Celery
托管支付/订阅平台
Nginx 或 API Gateway
Prometheus + Grafana
结构化日志与错误追踪服务

第一阶段可以先完成注册、API key、固定套餐、月度配额、托管 checkout、webhook 同步和基础后台。复杂的按量阶梯计价、套餐组合器和自定义消息模板系统可以暂缓开发。

实际用户行为稳定后,再按真实需求增加:

  • 阶梯计价;
  • 预付额度;
  • 企业合同;
  • 多币种;
  • 税务处理;
  • 团队席位;
  • 更细粒度的 entitlement;
  • 独立的客户门户。

订阅制 Web Service 需要的并不是某一个特定 framework,而是一套职责边界清楚的架构。Web framework 负责账户和业务流程,计费平台负责资金与账单,内部的 entitlement 和 metering 系统负责判断谁能使用多少服务。这样的拆分既便于快速上线,也不会把核心访问控制绑定到某个支付供应商。

备注:内容仅供参考。