Plans & Billing
Per-app, per-bundle, per-seat, tiered, multi-currency. Stripe Connect under the hood.
Plans & Billing
Every app you register can sell subscriptions. WolfieAuth handles the entire pricing → checkout → recurring → invoicing pipeline. Money flows to your customer’s Stripe Connect account; the platform takes a 5% fee tracked locally and KSeF-invoiced monthly.
Plan scopes
A plan can be scoped two ways:
- Per-App (default) — every plan you create lives on one OidcClient. Most apps use this. Different apps in the same org can have wildly different pricing.
- Per-Bundle (opt-in) — a single plan grants access to every app in an
AppBundle. Use for “Premium tier unlocks CRM + Mail + Storage” combos.
Both pull money to the same connected Stripe account (the owning org’s). The choice is purely about catalog organisation.
Pricing models
| Model | When to use | Stripe shape |
|---|---|---|
| Flat per-org | One subscription = one org pays one fee | unit_amount |
| Per-seat | Charge by member count | usage_type=licensed, quantity = active members |
| Volume tiered | First 10 seats $99, next 50 $89, etc. | billing_scheme=tiered, tiers_mode=volume |
| Graduated tiered | First 3 seats free, then $5 each | billing_scheme=tiered, tiers_mode=graduated |
| Multi-currency | Different price for PLN vs USD vs EUR | Multiple MembershipPrice rows per plan |
| Per-country | Specific countries get different prices | countryCodes: ["PL"] etc.; pricing-page picks via cf-ipcountry |
Creating a plan
/admin/clients/<your-app-id>#plans → + Nowy plan:
- Name (e.g. “Pro”) — plan slug auto-derives from name with diacritic folding (“Plan Złoty” →
plan-zloty) - Description, color, feature flags (free-form strings your app interprets, e.g.
ksef_enabled,unlimited_storage) - Click “Add price” → opens modal with full options:
- Amount (in major units, e.g.
99.00for 99 PLN) - Currency (
PLN/USD/EUR/ etc.) - Interval (
month/year/one_time) - Per-seat toggle + seat label (
seats/developers/vendors) - Country codes (
["PL"]for Polish-only pricing, empty = global) - Volume tiers (table editor, monotonically-increasing brackets)
- Amount (in major units, e.g.
- Save → plan exists in DB. “Sync to Stripe” button → provisions the Stripe Product + Price on your Connect account.
How customers subscribe
The pricing page lives at https://auth.wolfieguard.com/billing/<your-app-clientId> — public, no login required. Customer picks a plan, enters seat count if per-seat, gets routed to Stripe Checkout, comes back on success.
The @wolfieauth/sdk-paywall package can also embed this picker IN your app’s signup flow:
// hooks.server.ts
const auth = createWolfieAuthHandle({
template: { id: 'guest-request', issuer, clientId, branding: {...} },
sessionSecret,
paywall: {
enabled: true,
stripePublishableKey: process.env.STRIPE_PUBLISHABLE_KEY,
plans: await fetch(`${issuer}/api/billing/${clientId}/plans`).then(r => r.json()),
allowSkip: false, // or true for "free trial → upgrade"
},
});
Per-seat with auto-bump
Per-seat plans have a quirk: when an org admin invites a new member, the seat count needs to bump up. Otherwise you’ve got 5 paid seats and 7 active members, with #6 and #7 effectively free.
WolfieAuth handles this automatically. On every successful invite:
1. New OrgMembership row inserted
2. checkSeatCapBeforeAdd() runs against the active subscription
3. If new member count > current quantity:
stripe.subscriptions.update(sub.id, {
quantity: newCount,
proration_behavior: "create_prorations",
})
4. Stripe charges the prorated difference on next invoice
Reverse on revoke — auto-decrement, credit on next invoice. Floor at quantity=1 (you can’t have a 0-seat sub).
Comp overrides (“free pass per user”)
Sometimes you want to comp specific accounts — founding customers, beta testers, employees with personal accounts, partner orgs. WolfieAuth has a per-(user, app) override:
/admin/clients/<app-id>#users → ∞ toggle next to the user
-- AppEntitlementOverride row created
INSERT INTO "AppEntitlementOverride" (
userId, clientId, planSlug, planName, features, expiresAt
) VALUES (
'<user-sub>', '<app-id>', 'comp', 'Complimentary',
ARRAY[]::text[], -- empty = wildcard, all features
NULL -- or a date for time-boxed comp
);
The OIDC claim emitter then synth-injects an ACTIVE plan entry into wolfieauth_plans[]:
{
"planSlug": "comp",
"planName": "Complimentary",
"status": "ACTIVE",
"appClientId": "wolfie-wolfiecrm",
"features": [],
"currentPeriodEnd": null
}
SDK helpers (hasActiveFeature, etc.) treat this exactly like a paid sub. The user can’t tell they’re comped — they get the experience, you don’t get the billing.
Money flow + platform fee
Customer pays $99/month for "Pro" plan on your CRM
↓
Stripe Connect routes $99 to your customer's connected acct (their org)
↓
WolfieAuth tracks $4.95 (5%) as a platform fee row
↓
Monthly aggregator job runs on day-1
↓
Generates a wolfiecrm KSeF invoice for that month's aggregate fee
↓
Customer org pays platform invoice via Stripe / bank transfer
The 5% rate is configurable per-org (override via admin); default is 5%. The KSeF integration uses the wolfiecrm API — see wolfieksef Perfex module if your customer uses Perfex CRM.
wolfieauth-platform itself
WolfieAuth charges customer orgs for using WolfieAuth. The “Pro” plan on the wolfieauth-platform self-client ships with this default shape:
- 3 seats free, $5/seat thereafter
billing_scheme: tiered,tiers_mode: graduated,usage_type: licensed- Money goes to the platform Stripe account directly (not Connect — different code path)
Members of the platform-owner org (the wolfie org) automatically get a platform-owner-grant synthetic plan via the OIDC claim emitter — they don’t need to subscribe to themselves.
Continue reading
- Stripe & test mode — how to test billing end-to-end with fake cards before going live with real money
- Admin — Plans panel, Comp toggle, Sync to Stripe button
- SDKs —
@wolfieauth/sdk-paywallfor embedded checkout
Last updated: