Polar Billing logo

Polar Billing

CommunityPopular
fcakyon
polar-billing

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

Overview

Publisherfcakyon
Repositoryclaude-codex-settings
Skill namepolar-billing
Stars
1.1K
Forks
109
Bundled files
Instructions only
LicenseApache-2.0
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by fcakyon on GitHub. Read the source before you install it.

Installation

Install the Polar Billing AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/fcakyon/claude-codex-settings.git /tmp/claude-codex-settings
mkdir -p .claude/skills
cp -r /tmp/claude-codex-settings/plugins/polar-skills/skills/polar-billing .claude/skills/polar-billing
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Polar Billing in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Polar Billing on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Polar Billing is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

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
  2. Entity Relationships
  3. Main Services
  4. Dramatiq Background Tasks
  5. Stripe Integration
  6. Subscription Lifecycle
  7. Proration System
  8. Benefits & Credits
  9. Dunning & Payment Retry
  10. Transaction Ledger
  11. Key File Locations

1. Core Entities

Checkout

File: server/polar/models/checkout.py

Shopping cart/payment session before order confirmation.

FieldTypeDescription
statusCheckoutStatusopen, expired, confirmed, succeeded, failed
payment_processorPaymentProcessorstripe, manual
client_secretstrUnique identifier for frontend
amount, currencyint, strPrice in cents
tax_amount, discount_amountintCalculated amounts
allow_trial, trial_endbool, datetimeTrial configuration
seatsintFor seat-based products

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


CheckoutLink

File: server/polar/models/checkout_link.py

Persistent URL that creates Checkout Sessions on visit.

FieldTypeDescription
client_secretstrUnique identifier for the URL
seatsint | NonePreconfigured seat count for seat-based pricing
discount_idUUID | NonePreset discount to apply
trial_interval, trial_interval_countTrial configOverride product trial settings

Relationships: organization, products, discount


Order

File: server/polar/models/order.py

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

FieldTypeDescription
statusOrderStatuspending, paid, refunded, partially_refunded
billing_reasonOrderBillingReasonpurchase, subscription_create, subscription_cycle, subscription_update
subtotal_amountintAmount before discount/tax
discount_amountintDiscount applied
tax_amountintTax collected
applied_balance_amountintAccount balance applied
platform_fee_amountintPolar's fee
refunded_amountintAlready refunded
next_payment_attempt_atdatetimeDunning 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.

FieldTypeDescription
statusSubscriptionStatusincomplete, trialing, active, past_due, canceled, unpaid
amount, currencyint, strSubscription gross price
net_amountintNet amount (gross minus inclusive tax, equal to gross if tax-exclusive)
tax_behaviorTaxBehavior | NoneInclusive, exclusive, or null (set at creation)
recurring_intervalIntervalmonth, year
current_period_start/enddatetimeBilling period
trial_start/enddatetimeTrial period
cancel_at_period_endboolScheduled cancellation
canceled_at, ended_atdatetimeLifecycle timestamps
past_due_atdatetimeWhen payment failed
seatsintFor seat-based pricing

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


Transaction

File: server/polar/models/transaction.py

All money flows in the system.

FieldTypeDescription
typeTransactionTypepayment, processor_fee, refund, dispute, balance, payout
processorProcessorstripe, manual
amount, currencyint, strTransaction amount
tax_amountintTax portion

Self-referential relationships: payment_transaction, balance_transactions, incurred_transactions


Payment

File: server/polar/models/payment.py

Individual payment transaction.

FieldTypeDescription
statusPaymentStatuspending, succeeded, failed
processor_idstrStripe charge ID
methodstrcard, bank_transfer, etc.
triggerPaymentTrigger | NoneWhat initiated payment: purchase, subscription_cycle, retry_dunning, retry_customer, retry_payment_method_update, retry_admin
decline_reasonstrWhy payment failed
risk_level, risk_scorestr, intFraud assessment

Refund

File: server/polar/models/refund.py

FieldTypeDescription
statusRefundStatuspending, succeeded, failed, canceled
reasonRefundReasonduplicate, fraudulent, customer_request, etc.
amount, tax_amountintRefund amounts
revoke_benefitsboolWhether to revoke customer benefits

Customer

File: server/polar/models/customer.py

FieldTypeDescription
email, namestrContact info
billing_namestrName for invoices (falls back to name)
stripe_customer_idstrStripe link
billing_addressAddressStored address
tax_idstrFor tax compliance

Product & ProductPrice

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

ProductPrice TypesDescription
ProductPriceFixedFixed amount (set price_amount=0 for free)
ProductPriceCustomMerchant sets at checkout
ProductPriceMeteredUnitPay-per-unit
ProductPriceSeatUnitPer-seat with tiers

BillingEntry

File: server/polar/models/billing_entry.py

Audit log for billing calculations.

FieldTypeDescription
typeBillingEntryTypecycle, proration, metered, seats_increase, seats_decrease
directionDirectiondebit, credit
amountintEntry amount

2. Entity Relationships

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. Main Services

SubscriptionService

File: server/polar/subscription/service.py

Core subscription operations:

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

File: server/polar/order/service.py

python
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

python
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

python
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

python
create(order, amount, reason, revoke_benefits)
upsert_from_stripe(stripe_refund)
# Also enqueues chargeback prevention notice for dispute_prevention refunds

BenefitGrantService

File: server/polar/benefit/grant/service.py

python
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

TaskTriggerAction
subscription.cycleScheduler at period endRenew subscription, create order
subscription.update_product_benefits_grantsProduct benefits changedUpdate all grants
subscription.cancel_customerCustomer deletedCancel all billable subscriptions (trialing, active, past_due)

Order Tasks

File: server/polar/order/tasks.py

TaskTriggerAction
order.create_subscription_orderSubscription cycleCreate billing order
order.trigger_paymentOrder readyCharge payment method
order.balancePayment successCreate ledger entries
order.invoiceOrder createdGenerate PDF invoice
order.process_dunningHourly cronFind orders for retry
order.process_dunning_orderIndividual retryRetry single payment

Stripe Webhook Tasks

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

TaskStripe EventAction
charge.succeededPayment completeCreate order, provision benefits
charge.failedPayment failedMark order failed
charge.updatedCharge settledCreate ledger transaction
refund.created/updatedRefund processedUpdate refund record
charge.dispute.createdChargebackCreate dispute, revoke benefits
payout.paidPayout completeUpdate payout status

Benefit Tasks

File: server/polar/benefit/tasks.py

TaskTriggerAction
benefit.enqueue_benefits_grantsOrder/subscriptionQueue individual grants
benefit.grantIndividual benefitProvision access (GitHub, Discord, etc.)
benefit.revokeCancellation/refundRemove access
benefit.cycleSubscription renewalReset credits with rollover

Checkout Tasks

File: server/polar/checkout/tasks.py

TaskTriggerAction
checkout.handle_free_successFree productComplete without payment
checkout.expire_open_checkoutsEvery 15 minMark expired checkouts

Payout Tasks

File: server/polar/payout/tasks.py

TaskTriggerAction
payout.trigger_stripe_payoutsDaily 00:15 UTCInitiate pending payouts
payout.createdPayout createdEvent hook (fires for held payouts too)
payout.transferAfter payout.createdStripe transfer (skipped for held)
payout.release_held_payoutsOrg approvedMove held → pending, enqueue transfers
payout.cancel_account_payoutsOrg denied/blocked/offboardingCancel held+pending payouts
payout.cancel_held_payoutsPayout account swapCancel only held payouts on old account

Refund Tasks

File: server/polar/refund/tasks.py

TaskTriggerAction
refund.send_chargeback_prevention_noticeDispute prevention refund createdEmail org owners/admins about refund

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:

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

python
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

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

BehaviorAction
prorateAdd to next invoice
invoiceCreate order immediately

BillingEntry for Prorations

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

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

TypeDescriptionGrant Action
meter_creditUsage allowancesCreate meter_credited event
github_repositoryRepo accessAdd to GitHub team
discordServer roleAssign Discord role
license_keysLicense distributionGenerate key
downloadablesFile accessGrant download permission
slack_shared_channelSlack Connect channelCreate/invite to shared channel
feature_flagFeature toggle (API-only)None — merchant reads via API
customCustomer-visible noteNone — displayed in customer portal

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:

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

Cycle (renewal):

python
# 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:

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

TypeDescription
paymentCustomer payment received
processor_feeStripe fees
refundMoney returned to customer
refund_reversalRefund failed/reversed
disputeChargeback loss
dispute_reversalWon dispute
balanceInternal balance transfer
payoutMoney 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
   - 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. 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
├── refund/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

Frequently asked questions

What does the Polar Billing AI skill do?

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

Why use Polar Billing on TypingMind?

Because you install it once and use it with any model. Polar Billing is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Polar Billing in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/fcakyon/claude-codex-settings/tree/main/plugins/polar-skills/skills/polar-billing. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Polar Billing?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Polar Billing?

As many as you like. As long as a model supports skills, you can use Polar Billing with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Polar Billing AI skill free?

Yes. It is published on GitHub by fcakyon under the Apache-2.0 license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇