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 Billing System
Polar の請求インフラストラクチャに関する包括的なガイドです。エンティティ、フロー、Stripe との統合、特典の付与について説明します。
クイックリファレンス
Checkout → Payment → Order → Transaction → Benefits
↓
Subscription (if recurring)
↓
Subscription Cycle → Order → ...
目次
- コアエンティティ
- エンティティの関係
- 主なサービス
- Dramatiq バックグラウンドタスク
- Stripe との統合
- サブスクリプションのライフサイクル
- 日割り計算システム
- 特典とクレジット
- 督促と支払いの再試行
- 取引台帳
- 主なファイルの場所
1. コアエンティティ
Checkout
ファイル: 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(アップグレード用)
CheckoutLink
ファイル: server/polar/models/checkout_link.py
アクセスすると Checkout セッションを作成する、永続的な URL です。
| フィールド | 型 | 説明 |
|---|---|---|
client_secret | str | URL の一意の識別子 |
seats | int | None | シート制の価格設定に使用する、事前設定済みのシート数 |
discount_id | UUID | None | 適用する事前設定済みの割引 |
trial_interval, trial_interval_count | Trial config | 製品のトライアル設定を上書きします |
関連: organization、products、discount
Order
ファイル: server/polar/models/order.py
請求イベント(1回限りの購入またはサブスクリプションの請求サイクル)を表します。
| フィールド | 型 | 説明 |
|---|---|---|
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= 正味金額 - プラットフォーム手数料 - 返金額
Subscription
ファイル: 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
| ProductPrice の種類 | 説明 |
|---|---|
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 | 個別の再試行 | 1 件の支払いを再試行 |
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を確認- Stripeウェブフックのexternal_eventsを確認
特典が付与されない場合
BenefitGrantレコードのエラーを確認- Dramatiqログでbenefit.grantタスクを確認
- 商品に特典が紐付いていることを確認
日割り計算の問題
- サブスクリプションの
BillingEntryレコードを確認 - Orderのbilling_reasonを確認
- サブスクリプションのcurrent_periodの日付を確認
サブスクリプションがサイクルしない場合
- サブスクリプションの
scheduler_locked_atを確認 - APSchedulerが実行中であることを確認
- subscription.cycleタスクのログを確認