‹ 首页

polar-billing

@fcakyon · 收录于 1 周前 · 上游提交 6 天前

This skill should be used when working on Polar billing system, Stripe integration, subscription lifecycle, checkout flows, or benefit provisioning.

适合你,如果你正在开发或维护 Polar 计费系统,需要处理 Stripe 支付和订阅生命周期。

/ 下载安装
polar-billing.skill双击,或拖进 Claude 桌面版 / Cowork,即完成安装↓ .skill↓ .zip
用别的 agent?下载 .zip 解压,把文件夹放进它的技能目录
Claude Code~/.claude/skills/(项目级 .claude/skills/)
Codex CLI~/.codex/skills/
Cursor自动读取上面两处目录
其他工具见其文档的「skills」目录;两个下载是同一份文件,只是名字不同
/ 通过 npx 安装 校验哈希
npx oh-my-skill add fcakyon/claude-codex-settings/polar-billing
/ 通过 bash 安装
curl -fsSL https://oh-my-skill.com/install.sh | bash -s -- fcakyon/claude-codex-settings/polar-billing
/ 已经装过?验证本机副本,不用重装
npx oh-my-skill verify fcakyon/claude-codex-settings/polar-billing
安装目标可用 --agent / --scope 或 --to 明确指定;省略时只会在唯一已存在的 agent 目录上自动选择,零命中或多命中会停止并提示。content_hash 缺失或不一致均拒装。
773GitHub stars
~3.8K上下文体积 · 单文件
镜像托管

怎么用

技能原文 SKILL.md作者撰写 · Apache-2.0 · 44b7c00

Polar Billing System

Comprehensive guide to Polar's billing infrastructure, covering entities, flows, Stripe integration, and benefit provisioning.

Quick Reference
Checkout → Payment → Order → Transaction → Benefits
                         ↓
                   Subscription (if recurring)
                         ↓
                   Subscription Cycle → Order → ...
Table of Contents
  1. [Core Entities](#1-core-entities)
  2. [Entity Relationships](#2-entity-relationships)
  3. [Main Services](#3-main-services)
  4. [Dramatiq Background Tasks](#4-dramatiq-background-tasks)
  5. [Stripe Integration](#5-stripe-integration)
  6. [Subscription Lifecycle](#6-subscription-lifecycle)
  7. [Proration System](#7-proration-system)
  8. [Benefits & Credits](#8-benefits--credits)
  9. [Dunning & Payment Retry](#9-dunning--payment-retry)
  10. [Transaction Ledger](#10-transaction-ledger)
  11. [Key File Locations](#11-key-file-locations)

1. Core Entities
Checkout

File: server/polar/models/checkout.py

Shopping cart/payment session before order confirmation.

| Field | Type | Description | |-------|------|-------------| | status | CheckoutStatus | open, expired, confirmed, succeeded, failed | | payment_processor | PaymentProcessor | stripe, manual | | client_secret | str | Unique identifier for frontend | | amount, currency | int, str | Price in cents | | tax_amount, discount_amount | int | Calculated amounts | | allow_trial, trial_end | bool, datetime | Trial configuration | | seats | int | For seat-based products |

Relationships: organization, customer, product, product_price, discount, subscription (for upgrades)


Order

File: server/polar/models/order.py

Represents a billing event (one-time purchase or subscription cycle).

| Field | Type | Description | |-------|------|-------------| | status | OrderStatus | pending, paid, refunded, partially_refunded | | billing_reason | OrderBillingReason | purchase, subscription_create, subscription_cycle, subscription_update | | subtotal_amount | int | Amount before discount/tax | | discount_amount | int | Discount applied | | tax_amount | int | Tax collected | | applied_balance_amount | int | Account balance applied | | platform_fee_amount | int | Polar's fee | | refunded_amount | int | Already refunded | | next_payment_attempt_at | datetime | Dunning retry time |

Computed Properties:

  • net_amount = subtotal - discount
  • total_amount = net + tax
  • due_amount = max(0, total + applied_balance)
  • payout_amount = net - platform_fee - refunded

Subscription

File: server/polar/models/subscription.py

Recurring billing relationship.

| Field | Type | Description | |-------|------|-------------| | status | SubscriptionStatus | incomplete, trialing, active, past_due, canceled, unpaid | | amount, currency | int, str | Subscription price | | recurring_interval | Interval | month, year | | current_period_start/end | datetime | Billing period | | trial_start/end | datetime | Trial period | | cancel_at_period_end | bool | Scheduled cancellation | | canceled_at, ended_at | datetime | Lifecycle timestamps | | past_due_at | datetime | When payment failed | | seats | int | For seat-based pricing |

Relationships: customer, product, payment_method, discount, meters, grants (benefits)


Transaction

File: server/polar/models/transaction.py

All money flows in the system.

| Field | Type | Description | |-------|------|-------------| | type | TransactionType | payment, processor_fee, refund, dispute, balance, payout | | processor | Processor | stripe, manual | | amount, currency | int, str | Transaction amount | | tax_amount | int | Tax portion |

Self-referential relationships: payment_transaction, balance_transactions, incurred_transactions


Payment

File: server/polar/models/payment.py

Individual payment transaction.

| Field | Type | Description | |-------|------|-------------| | status | PaymentStatus | pending, succeeded, failed | | processor_id | str | Stripe charge ID | | method | str | card, bank_transfer, etc. | | decline_reason | str | Why payment failed | | risk_level, risk_score | str, int | Fraud assessment |


Refund

File: server/polar/models/refund.py

| Field | Type | Description | |-------|------|-------------| | status | RefundStatus | pending, succeeded, failed, canceled | | reason | RefundReason | duplicate, fraudulent, customer_request, etc. | | amount, tax_amount | int | Refund amounts | | revoke_benefits | bool | Whether to revoke customer benefits |


Customer

File: server/polar/models/customer.py

| Field | Type | Description | |-------|------|-------------| | email, name | str | Contact info | | stripe_customer_id | str | Stripe link | | billing_address | Address | Stored address | | tax_id | str | For tax compliance |


Product & ProductPrice

Files: server/polar/models/product.py, server/polar/models/product_price.py

| ProductPrice Types | Description | |-------------------|-------------| | ProductPriceFixed | Fixed amount | | ProductPriceCustom | Merchant sets at checkout | | ProductPriceFree | Zero cost | | ProductPriceMeteredUnit | Pay-per-unit | | ProductPriceSeatUnit | Per-seat with tiers |


BillingEntry

File: server/polar/models/billing_entry.py

Audit log for billing calculations.

| Field | Type | Description | |-------|------|-------------| | type | BillingEntryType | cycle, proration, metered, seats_increase, seats_decrease | | direction | Direction | debit, credit | | amount | int | Entry amount |


2. Entity Relationships
Organization
├── Product
│   ├── ProductPrice (multiple per product)
│   └── ProductBenefit → Benefit
├── Customer
│   ├── Subscription → 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. Main Services
SubscriptionService

File: server/polar/subscription/service.py

Core subscription operations:

# Creation
create_or_update_from_checkout(checkout, payment_method) → (Subscription, created)

# Updates
update_product(subscription, product_id, proration_behavior)
update_seats(subscription, seats, proration_behavior)
update_discount(subscription, discount_id)
update_trial(subscription, trial_end)

# 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

File: 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

File: 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

File: server/polar/payment/service.py

upsert_from_stripe_charge(charge, checkout, order)
handle_success(payment)  # Complete order
handle_failure(payment)  # Update order status
RefundService

File: server/polar/refund/service.py

create(order, amount, reason, revoke_benefits)
upsert_from_stripe(stripe_refund)
BenefitGrantService

File: 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 Background Tasks
Subscription Tasks

File: server/polar/subscription/tasks.py

| Task | Trigger | Action | |------|---------|--------| | subscription.cycle | Scheduler at period end | Renew subscription, create order | | subscription.update_product_benefits_grants | Product benefits changed | Update all grants | | subscription.cancel_customer | Customer deleted | Cancel all subscriptions |

Order Tasks

File: server/polar/order/tasks.py

| Task | Trigger | Action | |------|---------|--------| | order.create_subscription_order | Subscription cycle | Create billing order | | order.trigger_payment | Order ready | Charge payment method | | order.balance | Payment success | Create ledger entries | | order.invoice | Order created | Generate PDF invoice | | order.process_dunning | Hourly cron | Find orders for retry | | order.process_dunning_order | Individual retry | Retry single payment |

Stripe Webhook Tasks

File: server/polar/integrations/stripe/tasks.py

| Task | Stripe Event | Action | |------|--------------|--------| | charge.succeeded | Payment complete | Create order, provision benefits | | charge.failed | Payment failed | Mark order failed | | charge.updated | Charge settled | Create ledger transaction | | refund.created/updated | Refund processed | Update refund record | | charge.dispute.created | Chargeback | Create dispute, revoke benefits | | payout.paid | Payout complete | Update payout status |

Benefit Tasks

File: server/polar/benefit/tasks.py

| Task | Trigger | Action | |------|---------|--------| | benefit.enqueue_benefits_grants | Order/subscription | Queue individual grants | | benefit.grant | Individual benefit | Provision access (GitHub, Discord, etc.) | | benefit.revoke | Cancellation/refund | Remove access | | benefit.cycle | Subscription renewal | Reset credits with rollover |

Checkout Tasks

File: server/polar/checkout/tasks.py

| Task | Trigger | Action | |------|---------|--------| | checkout.handle_free_success | Free product | Complete without payment | | checkout.expire_open_checkouts | Every 15 min | Mark expired checkouts |

Payout Tasks

File: server/polar/payout/tasks.py

| Task | Trigger | Action | |------|---------|--------| | payout.trigger_stripe_payouts | Daily 00:15 UTC | Initiate pending payouts |


5. Stripe Integration
Webhook Endpoints

File: server/polar/integrations/stripe/endpoints.py

  • /v1/integrations/stripe/webhook - Direct webhooks
  • /v1/integrations/stripe/webhook-connect - Connect account webhooks
Implemented Webhooks

Payment Flow:

  • payment_intent.succeeded - Payment complete
  • payment_intent.payment_failed - Payment failed
  • setup_intent.succeeded - Card saved
  • charge.pending/failed/succeeded/updated - Charge lifecycle

Refunds:

  • refund.created/updated/failed

Disputes:

  • charge.dispute.created/updated/closed

Connect:

  • account.updated - Account info changed
  • payout.updated/paid - Payout lifecycle
Webhook Processing Flow
Stripe POST → Verify signature → ExternalEvent.enqueue()
                                        ↓
                               Store in external_events table
                                        ↓
                               Enqueue Dramatiq task
                                        ↓
                               Worker processes async
                                        ↓
                               Mark handled_at on success
StripeService

File: server/polar/integrations/stripe/service.py

Key methods:

  • create_payment_intent(), create_setup_intent()
  • create_refund(), get_refund()
  • create_tax_calculation(), create_tax_transaction()
  • transfer(), create_payout()

6. Subscription Lifecycle
Creation Flow
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
Cycle Flow (Renewal)
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
Cancellation Flow

At Period End:

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

Immediately:

subscription_service.revoke(subscription)
# Sets status=canceled, ended_at=now
# Benefits revoked immediately
# Seats canceled if seat-based
Trial Flow
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. Proration System
When Prorations Occur
  1. Product change - Upgrade/downgrade to different tier
  2. Seat change - Add/remove seats
  3. Interval change - Monthly to yearly
Proration Calculation
# 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
Proration Behaviors

| Behavior | Action | |----------|--------| | prorate | Add to next invoice | | invoice | Create order immediately |

BillingEntry for Prorations
# 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
)
Seat Proration
# 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. Benefits & Credits
Benefit Types

| Type | Description | Grant Action | |------|-------------|--------------| | meter_credit | Usage allowances | Create meter_credited event | | github_repository | Repo access | Add to GitHub team | | discord | Server role | Assign Discord role | | license_keys | License distribution | Generate key | | downloadables | File access | Grant download permission | | custom | Webhook-based | Call external URL |

Benefit Grant Flow
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
Benefit Revocation Flow
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
Meter Credits

Grant:

# Create event with units
Event(type="meter_credited", units=100)
# Update CustomerMeter

Cycle (renewal):

# 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)

Revoke:

# Negative credit event
Event(type="meter_credited", units=-remaining_units)
Grace Period

Organizations can configure benefit_revocation_grace_period (days) to delay benefit revocation for past_due subscriptions.


9. Dunning & Payment Retry
Dunning Process
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
Retry Schedule

Configured in organization settings. Typical pattern:

  • Day 1: First failure
  • Day 3: Retry 1
  • Day 5: Retry 2
  • Day 7: Final retry, then mark unpaid
Subscription Status During Dunning
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. Transaction Ledger
Transaction Types

| Type | Description | |------|-------------| | payment | Customer payment received | | processor_fee | Stripe fees | | refund | Money returned to customer | | refund_reversal | Refund failed/reversed | | dispute | Chargeback loss | | dispute_reversal | Won dispute | | balance | Internal balance transfer | | payout | Money sent to creator |

Creating Payment Transactions
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)
Payout Flow
1. Creator has balance from transactions
2. payout.trigger_stripe_payouts (daily)
3. Calculate available balance
4. Create Payout record
5. stripe_service.transfer() to Connect account
6. stripe_service.create_payout() to bank
7. payout.paid webhook → update status

11. Key File Locations
Models
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
Services
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
Background Tasks
server/polar/
├── subscription/tasks.py
├── order/tasks.py
├── checkout/tasks.py
├── benefit/tasks.py
├── payout/tasks.py
└── integrations/stripe/tasks.py
Stripe Integration
server/polar/integrations/stripe/
├── endpoints.py    # Webhook handlers
├── service.py      # Stripe API wrapper
├── tasks.py        # Webhook processing tasks
└── payment.py      # Payment resolution helpers

Common Debugging Scenarios
Payment Failed
  1. Check Payment record for decline_reason
  2. Check Order.status and next_payment_attempt_at
  3. Look at external_events for Stripe webhook
Benefits Not Granted
  1. Check BenefitGrant record for errors
  2. Look at benefit.grant task in Dramatiq logs
  3. Verify product has benefits attached
Proration Issues
  1. Check BillingEntry records for subscription
  2. Verify billing_reason on Order
  3. Check subscription's current_period dates
Subscription Not Cycling
  1. Check scheduler_locked_at on subscription
  2. Verify APScheduler is running
  3. Check subscription.cycle task logs
按 Apache-2.0 许可原样转载,未经改动 · 在 GitHub 查看 →

评论

登录即可评论;带「已验证安装」的,是发布者名下有本店的安装或持有记录。