Purchases and refunds
Custom credit purchases, Stripe auto top-up, and how refunds and chargebacks reverse credits.
No customPurchase block ships, so custom purchases and auto top-up are off. Add pricingConfig.credits.customPurchase to let users buy credits beyond their plan:
| Key | Type | Description |
|---|---|---|
enabled | boolean | Allow custom credit purchases. |
autoTopUp | boolean | Allow users to opt into automatic refills. |
minimumCredits | number | Floor per custom purchase. |
presetCredits | number | Suggested amount in the purchase UI. |
stripeProductId / polarProductId | string? | Metered line item per provider. |
A top-up fires only when all hold: customPurchase.enabled and customPurchase.autoTopUp are on, the active provider is "stripe", the paying user has set autoTopUpEnabled/autoTopUpThreshold/autoTopUpAmount (threshold and amount must be > 0), and the fresh balance is below the threshold.
removeCredits(successful or insufficient) andsettleCreditswithcause: "usage"both run the check.- Refund and chargeback settlements never auto-top-up, so a reversal cannot charge the customer again.
Auto top-up is Stripe-only: under Polar users still buy credits manually, but no automatic refill runs. Asynchronous methods (SEPA/ACH) confirm days after the charge, so a pending-charge marker blocks further triggers and one low balance cannot mint multiple live charges. It clears on payment_intent.succeeded or payment_intent.payment_failed - enable both events - or after a 14-day backstop TTL.
Refunds and chargebacks
Prepaid credits are non-refundable, and a notice says so at the point of purchase. That is policy copy: it does not block a refund you issue from your provider dashboard, which settles the credit reversal below.
| Event | Effect |
|---|---|
charge.refunded / order.refunded | Settles the applicable credit reversal. Custom-credit, top-up, and subscription refunds reverse each newly refunded share; product and lifetime-plan benefits reverse once, on the first reversal event. |
charge.dispute.created (Stripe) | Settles the dispute's newly reversible share and suspends the buyer (Better Auth banned), ending their live sessions so a reversed payment stops spending immediately. An admin lifts the ban after review. |
- Already-spent credits become debt: a 100-credit reversal against a balance of
10leaves-90, repaid naturally by later additions. Credits are one pooled balance with no per-source accounting, so a reversal consumes whatever is in it - credits bought separately for cash included. - The durable
charge_reversalstable tracks cumulative refunded and disputed amounts per charge, so repeated, partial, or out-of-order events can never reverse more than the original grant.
Enable charge.dispute.created in your Stripe webhook - it is off by default and it is the chargeback defense, since a disputing customer cannot be declined the way a refund request can. Polar is a merchant of record and handles disputes itself (a lost dispute arrives as a normal refund), so this handling is Stripe-only.