关于 polar-billing
polar-billing 是面向 Polar 计费系统开发者的参考技能,梳理计费实体、服务及结账、订阅、支付、退款和权益发放流程。
主要功能
- 01计费实体与关系指南
- 02结账和订阅生命周期说明
- 03支付、退款及 Stripe 流程
- 04后台任务与服务索引
- 05权益和额度发放概览
适用场景
- 01追踪结账如何形成订单和支付
- 02了解订阅续费、按比例计费和重试
- 03查找计费改动对应的服务或后台任务
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ fcakyon/ claude-codex-settings --skill polar-billing安装后在 Claude Code 中输入/polar-billing确认已加载。
详细介绍
Polar 计费系统
Polar 计费基础设施综合指南,涵盖实体、流程、Stripe 集成和权益发放。
快速参考
Checkout → Payment → Order → Transaction → Benefits
↓
Subscription (if recurring)
↓
Subscription Cycle → Order → ...
目录
1. 核心实体
结账
文件: server/polar/models/checkout.py
确认订单前的购物车/付款会话。
| 字段 | 类型 | 说明 |
|---|---|---|
status | CheckoutStatus | open、expired、confirmed、succeeded、failed |
payment_processor | PaymentProcessor | stripe、manual |
client_secret | str | 前端使用的唯一标识符 |
amount、currency | int、str | 以分为单位的价格 |
tax_amount、discount_amount | int | 计算得出的金额 |
allow_trial、trial_end | bool、datetime | 试用配置 |
seats | int | 用于按席位计费的产品 |
关系: organization、customer、product、product_price、discount、subscription(用于升级)
结账链接
文件: server/polar/models/checkout_link.py
访问时会创建结账会话的持久 URL。
| 字段 | 类型 | 说明 |
|---|---|---|
client_secret | str | URL 的唯一标识符 |
seats | int | None | 按席位定价的预设席位数 |
discount_id | UUID | None | 要应用的预设折扣 |
trial_interval、trial_interval_count | Trial config | 覆盖产品的试用设置 |
关系: organization、products、discount
订单
文件: server/polar/models/order.py
表示一笔计费事件(一次性购买或订阅周期)。
| 字段 | 类型 | 说明 |
|---|---|---|
status | OrderStatus | pending、paid、refunded、partially_refunded |
billing_reason | OrderBillingReason | purchase、subscription_create、subscription_cycle、subscription_update |
subtotal_amount | int | 折扣/税费前的金额 |
discount_amount | int | 已应用的折扣 |
tax_amount | int | 已收取的税款 |
applied_balance_amount | int | 已使用的账户余额 |
platform_fee_amount | int | Polar 的费用 |
refunded_amount | int | 已退款金额 |
next_payment_attempt_at | datetime | 催款重试时间 |
计算属性:
net_amount= 小计 - 折扣total_amount= 净额 + 税费due_amount= max(0, 总额 + 已使用余额)payout_amount= 净额 - 平台费用 - 已退款金额
订阅
文件: server/polar/models/subscription.py
周期性计费关系。
| 字段 | 类型 | 说明 |
|---|---|---|
status | SubscriptionStatus | incomplete、trialing、active、past_due、canceled、unpaid |
amount、currency | int、str | 订阅总价 |
net_amount | int | 净额(总额减去含税税额;若税费不含在价格中,则等于总额) |
tax_behavior | TaxBehavior | None | 含税、不含税或 null(创建时设置) |
recurring_interval | Interval | month、year |
current_period_start/end | datetime | 计费周期 |
trial_start/end | datetime | 试用期 |
cancel_at_period_end | bool | 计划取消 |
canceled_at、ended_at | datetime | 生命周期时间戳 |
past_due_at | datetime | 付款失败时间 |
seats | int | 用于按席位定价 |
关系: organization、customer、product、payment_method、discount、meters、grants(权益)
交易
文件: server/polar/models/transaction.py
系统中的所有资金流转。
| 字段 | 类型 | 描述 |
|---|---|---|
type | TransactionType | payment、processor_fee、refund、dispute、balance、payout |
processor | Processor | stripe、manual |
amount、currency | int、str | 交易金额 |
tax_amount | int | 税额部分 |
自引用关系: payment_transaction、balance_transactions、incurred_transactions
付款
文件: server/polar/models/payment.py
单笔付款交易。
| 字段 | 类型 | 描述 |
|---|---|---|
status | PaymentStatus | pending、succeeded、failed |
processor_id | str | Stripe 交易 ID |
method | str | card、bank_transfer 等 |
trigger | PaymentTrigger | None | 付款触发原因:purchase、subscription_cycle、retry_dunning、retry_customer、retry_payment_method_update、retry_admin |
decline_reason | str | 付款失败原因 |
risk_level、risk_score | str、int | 欺诈评估 |
退款
文件: server/polar/models/refund.py
| 字段 | 类型 | 描述 |
|---|---|---|
status | RefundStatus | pending、succeeded、failed、canceled |
reason | RefundReason | duplicate、fraudulent、customer_request 等 |
amount、tax_amount | int | 退款金额 |
revoke_benefits | bool | 是否撤销客户权益 |
客户
文件: server/polar/models/customer.py
| 字段 | 类型 | 描述 |
|---|---|---|
email、name | str | 联系信息 |
billing_name | str | 发票使用的姓名(默认使用 name) |
stripe_customer_id | str | Stripe 关联 ID |
billing_address | Address | 保存的地址 |
tax_id | str | 用于税务合规 |
产品与产品价格
文件: server/polar/models/product.py、server/polar/models/product_price.py
| 产品价格类型 | 描述 |
|---|---|
ProductPriceFixed | 固定金额(免费时将 price_amount=0) |
ProductPriceCustom | 商家在结账时设置价格 |
ProductPriceMeteredUnit | 按用量付费 |
ProductPriceSeatUnit | 按席位计费,可设分级价格 |
账单条目
文件: server/polar/models/billing_entry.py
账单计算的审计日志。
| 字段 | 类型 | 描述 |
|---|---|---|
type | BillingEntryType | cycle、proration、metered、seats_increase、seats_decrease |
direction | Direction | debit、credit |
amount | int | 条目金额 |
2. 实体关系
Organization
├── Product
│ ├── ProductPrice (multiple per product)
│ └── ProductBenefit → Benefit
├── Customer
│ ├── Subscription → Organization, Product, Discount
│ │ ├── SubscriptionProductPrice
│ │ ├── SubscriptionMeter
│ │ └── BenefitGrant
│ ├── Order → Product, Subscription
│ │ └── OrderItem
│ ├── PaymentMethod
│ └── Wallet
├── Checkout → Customer, Product
├── Discount
│ └── DiscountRedemption
└── Account (for payouts)
└── Payout → Transaction
Transaction (ledger)
├── payment → Order, Customer
├── refund → Refund, Order
├── dispute → Dispute, Order
├── processor_fee → parent payment
└── payout → Account
3. 主要服务
SubscriptionService
文件: server/polar/subscription/service.py
订阅核心操作:
# Creation
create_or_update_from_checkout(checkout, payment_method) → (Subscription, created)
# Updates
update_product(subscription, product_id, proration_behavior, discount=None)
update_seats(subscription, seats, proration_behavior)
update_discount(subscription, discount) # discount: UUID | Literal["unset"]
update_trial(subscription, trial_end)
update_metadata(subscription, metadata)
# Lifecycle
cycle(subscription) # Period renewal
cancel(subscription) # At period end
revoke(subscription) # Immediately
uncancel(subscription)
# Benefits
enqueue_benefits_grants(task="grant"|"revoke", customer, product)
OrderService
文件: server/polar/order/service.py
create_from_checkout(checkout) # One-time purchase
create_subscription_order(subscription, billing_reason) # Recurring
trigger_payment(order) # Charge customer
create_order_balance(order) # Ledger entries
CheckoutService
文件: server/polar/checkout/service.py
create(product, customer_data, discount_code)
confirm(checkout) # Lock checkout for payment
handle_stripe_success(checkout, charge)
handle_free_success(checkout) # No payment needed
PaymentService
文件: server/polar/payment/service.py
upsert_from_stripe_charge(charge, checkout, order)
handle_success(payment) # Complete order
handle_failure(payment) # Update order status
RefundService
文件: server/polar/refund/service.py
create(order, amount, reason, revoke_benefits)
upsert_from_stripe(stripe_refund)
# Also enqueues chargeback prevention notice for dispute_prevention refunds
BenefitGrantService
文件: server/polar/benefit/grant/service.py
enqueue_benefits_grants(task, customer, product, order=None, subscription=None)
grant_benefit(customer, benefit)
revoke_benefit(customer, benefit)
4. Dramatiq 后台任务
订阅任务
文件: server/polar/subscription/tasks.py
| 任务 | 触发条件 | 操作 |
|---|---|---|
subscription.cycle | 调度器在周期结束时触发 | 续订并创建订单 |
subscription.update_product_benefits_grants | 产品权益变更 | 更新所有授权 |
subscription.cancel_customer | 客户删除 | 取消所有可计费订阅(trialing、active、past_due) |
订单任务
文件: server/polar/order/tasks.py
| 任务 | 触发条件 | 操作 |
|---|---|---|
order.create_subscription_order | 订阅周期 | 创建账单订单 |
order.trigger_payment | 订单就绪 | 扣款 |
order.balance | 付款成功 | 创建账簿条目 |
order.invoice | 订单创建 | 生成 PDF 发票 |
order.process_dunning | 每小时 cron | 查找需要重试的订单 |
order.process_dunning_order | 单笔重试 | 重试单笔付款 |
Stripe Webhook 任务
文件: server/polar/integrations/stripe/tasks.py
| 任务 | Stripe 事件 | 操作 |
|---|---|---|
charge.succeeded | 付款完成 | 创建订单、发放权益 |
charge.failed | 付款失败 | 将订单标记为失败 |
charge.updated | 扣款结算 | 创建账簿交易 |
refund.created/updated | 退款已处理 | 更新退款记录 |
charge.dispute.created | 拒付 | 创建争议、撤销权益 |
payout.paid | 付款完成 | 更新打款状态 |
权益任务
文件: server/polar/benefit/tasks.py
| 任务 | 触发条件 | 操作 |
|---|---|---|
benefit.enqueue_benefits_grants | 订单/订阅 | 将单项权益发放加入队列 |
benefit.grant | 单项权益 | 开通访问权限(GitHub、Discord 等) |
benefit.revoke | 取消/退款 | 移除访问权限 |
benefit.cycle | 订阅续订 | 重置积分并结转余额 |
结账任务
文件: server/polar/checkout/tasks.py
| 任务 | 触发条件 | 操作 |
|---|---|---|
checkout.handle_free_success | 免费产品 | 无需付款即可完成 |
checkout.expire_open_checkouts | 每 15 分钟 | 将已过期的结账标记为过期 |
打款任务
文件: server/polar/payout/tasks.py
| 任务 | 触发条件 | 操作 |
|---|---|---|
payout.trigger_stripe_payouts | 每日 00:15 UTC | 发起待处理的打款 |
payout.created | 打款已创建 | 事件钩子(暂缓的打款也会触发) |
payout.transfer | payout.created 之后 | Stripe 转账(暂缓时跳过) |
payout.release_held_payouts | 组织已获批准 | 将暂缓打款改为待处理,并将转账加入队列 |
payout.cancel_account_payouts | 组织被拒绝/封锁/停止合作 | 取消暂缓和待处理的打款 |
payout.cancel_held_payouts | 打款账户更换 | 仅取消旧账户上的暂缓打款 |
退款任务
文件: server/polar/refund/tasks.py
| 任务 | 触发条件 | 操作 |
|---|---|---|
refund.send_chargeback_prevention_notice | 创建争议预防退款 | 向组织所有者/管理员发送退款邮件 |
5. Stripe 集成
Webhook 端点
文件: server/polar/integrations/stripe/endpoints.py
/v1/integrations/stripe/webhook- 直接 Webhook/v1/integrations/stripe/webhook-connect- Connect 账户 Webhook
已实现的 Webhook
付款流程:
payment_intent.succeeded- 付款完成payment_intent.payment_failed- 付款失败setup_intent.succeeded- 已保存银行卡charge.pending/failed/succeeded/updated- 扣款生命周期
退款:
refund.created/updated/failed
争议:
charge.dispute.created/updated/closed
Connect:
account.updated- 账户信息已更改payout.updated/paid- 打款生命周期
Webhook 处理流程
Stripe POST → Verify signature → ExternalEvent.enqueue()
↓
Store in external_events table
↓
Enqueue Dramatiq task
↓
Worker processes async
↓
Mark handled_at on success
StripeService
文件: server/polar/integrations/stripe/service.py
主要方法:
create_payment_intent()、create_setup_intent()create_refund()、get_refund()create_tax_calculation()、create_tax_transaction()transfer()、create_payout()
6. 订阅生命周期
创建流程
1. Checkout created (status=open)
2. Customer completes payment
3. Stripe charge.succeeded webhook
4. payment.handle_success() called
5. checkout_service.handle_stripe_success()
6. subscription_service.create_or_update_from_checkout()
- Creates Subscription (status=active or trialing)
- Sets billing period
- Applies discount
- Resets meters
7. Enqueue benefit grants
8. Send confirmation email
周期流程(续订)
1. APScheduler triggers at period end
2. subscription.cycle task runs
3. subscription_service.cycle()
- Check cancel_at_period_end
- If true: set status=canceled, revoke benefits
- If false: advance period dates, check discount expiry
4. Create billing entry (type=cycle)
5. Enqueue order.create_subscription_order
6. Order created with billing_reason=subscription_cycle
7. Enqueue order.trigger_payment
8. Stripe charges payment method
9. charge.succeeded → ledger entries → benefits renewed
取消流程
在周期结束时:
subscription_service.cancel(subscription)
# Sets cancel_at_period_end=True, ends_at=current_period_end
# Benefits remain until period ends
# On next cycle: status=canceled, benefits revoked
立即取消:
subscription_service.revoke(subscription)
# Sets status=canceled, ended_at=now
# Benefits revoked immediately
# Seats canceled if seat-based
试用流程
1. Checkout with trial_end set
2. Subscription created with status=trialing
3. No payment during trial
4. At trial_end, cycle task runs
5. Status transitions to active
6. Order created with billing_reason=subscription_cycle_after_trial
7. First payment charged
7. 按比例计费系统
发生按比例计费的情况
- 产品变更 - 升级或降级到不同档位
- 席位变更 - 添加或移除席位
- 计费周期变更 - 从月付改为年付
按比例计费的计算
# Calculate time remaining in period
pct_remaining = (period_end - now) / (period_end - period_start)
# Old product credit (what they paid but won't use)
old_credit = old_price * old_pct_remaining
# New product debit (what they owe for remainder)
new_debit = new_price * new_pct_remaining
# Net proration
net = new_debit - old_credit
按比例计费的行为
| 行为 | 操作 |
|---|---|
prorate | 加入下一张发票 |
invoice | 立即创建订单 |
按比例计费的 BillingEntry
# Credit entry (old product)
BillingEntry(
type=BillingEntryType.proration,
direction=BillingEntryDirection.credit,
amount=prorated_old_amount
)
# Debit entry (new product)
BillingEntry(
type=BillingEntryType.proration,
direction=BillingEntryDirection.debit,
amount=prorated_new_amount
)
席位按比例计费
# Adding 2 seats at $10/seat with 50% time remaining
delta_amount = 2 * $10 * 0.5 = $10
BillingEntry(
type=BillingEntryType.subscription_seats_increase,
direction=BillingEntryDirection.debit,
amount=1000 # cents
)
8. 权益与额度
权益类型
| 类型 | 说明 | 授予操作 |
|---|---|---|
meter_credit | 用量额度 | 创建 meter_credited 事件 |
github_repository | 仓库访问权限 | 添加到 GitHub 团队 |
discord | 服务端角色 | 分配 Discord 角色 |
license_keys | 许可证分发 | 生成密钥 |
downloadables | 文件访问权限 | 授予下载权限 |
slack_shared_channel | Slack Connect 频道 | 创建共享频道或邀请加入 |
feature_flag | 功能开关(仅限 API) | 无 — 商家通过 API 读取 |
custom | 客户可见的备注 | 无 — 在客户门户中显示 |
权益授予流程
1. Order/Subscription created
2. enqueue_benefits_grants(task="grant")
3. For each benefit in product:
- Skip if already granted
- Enqueue benefit.grant task
4. benefit.grant task:
- Get/create BenefitGrant record
- Call strategy.grant() (type-specific)
- Set granted_at
- Store properties
- Send webhook
权益撤销流程
1. Subscription canceled or order refunded
2. enqueue_benefits_grants(task="revoke")
3. For each granted benefit:
- Enqueue benefit.revoke task
4. benefit.revoke task:
- Call strategy.revoke() (type-specific)
- Set revoked_at
- Send webhook
计量额度
授予:
# Create event with units
Event(type="meter_credited", units=100)
# Update CustomerMeter
周期处理(续订):
# Calculate rollover
rollover = min(remaining_units, rollover_limit)
# Reset meter
Event(type="meter_reset")
# Credit new period + rollover
Event(type="meter_credited", units=base_units + rollover)
撤销:
# Negative credit event
Event(type="meter_credited", units=-remaining_units)
宽限期
组织可以配置 benefit_revocation_grace_period(天数),以延迟撤销 past_due 订阅的权益。
9. 催款与支付重试
催款流程
1. order.process_dunning runs hourly
2. Finds orders where next_payment_attempt_at <= now
3. For each order:
- Enqueue order.process_dunning_order
4. process_dunning_order:
- Get customer's payment method
- Attempt payment via Stripe
- On success: mark order paid
- On failure: schedule next attempt
重试计划
在组织设置中配置。典型模式:
- 第 1 天:首次失败
- 第 3 天:第 1 次重试
- 第 5 天:第 2 次重试
- 第 7 天:最后一次重试,然后标记为未付款
催款期间的订阅状态
payment fails → status=past_due, past_due_at=now
↓
benefits may continue (grace period)
↓
retry succeeds → status=active
↓
retry fails → status=unpaid, benefits revoked
10. 交易账本
交易类型
| 类型 | 说明 |
|---|---|
payment | 收到客户付款 |
processor_fee | Stripe 费用 |
refund | 退还给客户的款项 |
refund_reversal | 退款失败或被撤销 |
dispute | 拒付损失 |
dispute_reversal | 争议胜诉 |
balance | 内部余额转账 |
payout | 向创作者支付款项 |
创建支付交易
1. charge.updated webhook (charge settled)
2. Get balance_transaction from Stripe
3. Extract settlement amount and fees
4. Create Transaction(type=payment)
5. Enqueue processor_fee.create_payment_fees
6. Create Transaction(type=processor_fee)
付款流程
1. Creator has balance from transactions
2. payout.trigger_stripe_payouts (daily)
3. Calculate available balance
4. Create Payout record
- ACTIVE/OFFBOARDED org: status=pending, enqueue payout.created + payout.transfer
- REVIEW/SNOOZED org: status=held, enqueue payout.created only
5. stripe_service.transfer() to Connect account (skipped for held)
6. stripe_service.create_payout() to bank
7. payout.paid webhook → update status
Held payout lifecycle:
- When org approved: payout.release_held_payouts → status=pending, enqueue transfer
- When org denied/blocked/offboarding: payout.cancel_account_payouts → cancel + refund
- When payout account swapped: payout.cancel_held_payouts (old account only)
11. 关键文件位置
模型
server/polar/models/
├── checkout.py
├── order.py
├── order_item.py
├── subscription.py
├── subscription_product_price.py
├── transaction.py
├── payment.py
├── refund.py
├── dispute.py
├── payout.py
├── customer.py
├── product.py
├── product_price.py
├── discount.py
├── benefit.py
├── benefit_grant.py
└── billing_entry.py
服务
server/polar/
├── subscription/service.py
├── order/service.py
├── checkout/service.py
├── payment/service.py
├── refund/service.py
├── dispute/service.py
├── payout/service.py
├── benefit/
│ ├── service.py
│ ├── grant/service.py
│ └── strategies/
│ ├── meter_credit/service.py
│ ├── github_repository/service.py
│ ├── discord/service.py
│ └── ...
└── transaction/service/
├── payment.py
├── refund.py
└── dispute.py
后台任务
server/polar/
├── subscription/tasks.py
├── order/tasks.py
├── checkout/tasks.py
├── benefit/tasks.py
├── payout/tasks.py
├── refund/tasks.py
└── integrations/stripe/tasks.py
Stripe 集成
server/polar/integrations/stripe/
├── endpoints.py # Webhook handlers
├── service.py # Stripe API wrapper
├── tasks.py # Webhook processing tasks
└── payment.py # Payment resolution helpers
常见调试场景
支付失败
- 检查
Payment记录中的decline_reason - 检查
Order.status和next_payment_attempt_at - 查看 external_events 中的 Stripe webhook
未授予权益
- 检查
BenefitGrant记录是否存在错误 - 查看 Dramatiq 日志中的 benefit.grant 任务
- 确认产品已关联权益
按比例计费问题
- 检查订阅的
BillingEntry记录 - 确认 Order 上的 billing_reason
- 检查订阅的 current_period 日期
订阅未按周期处理
- 检查订阅上的
scheduler_locked_at - 确认 APScheduler 正在运行
- 检查 subscription.cycle 任务日志