Pricing Plans
Configure billing products, Stripe prices, and mobile-facing pricing behavior.
Billing model used by this API
This app uses @kit/billing config for pricing metadata and resolves checkout through provider integrations (Stripe by default).
billing.config.ts shape is products -> plans -> lineItems:
product.id: internal product key for feature grouping (for examplestarter).plan.id: internal plan key (for examplestarter-monthly).plan.lineItems[].id: provider variant ID (for example Stripe price IDprice_xxx) used by the provider layer.
Stripe setup workflow (required for mobile pricing)
- In Stripe Dashboard, create one product per internal product (for example
Zovi Daily). - Create one recurring price per available interval under that product.
- Add
plan_idas metadata on each price for easy operations:
metadata: { billing_plan_id: "starter-monthly" }
- Copy each Stripe price ID into the matching
lineItems[].idinweb/apps/web/config/billing.config.ts.
Supported intervals for mobile plans
- Monthly plans:
interval: "month",interval_count: 1 - Quarterly plans: use Stripe interval + count to preserve provider compatibility:
interval=month interval_count=3
If annual pricing exists, use interval_count: 12 and expose it through the same API contract as an available plan variant.
Example: starter + monthly + quarterly
export default createBillingSchema({
provider: 'stripe',
products: [
{
id: 'starter',
name: 'Zovi Daily',
description: 'Essential protocol support',
currency: 'USD',
features: ['Feature 1', 'Feature 2'],
plans: [
{
id: 'starter-monthly',
name: 'Starter Monthly',
paymentType: 'recurring',
interval: 'month',
lineItems: [
{
id: 'price_stripe_monthly',
name: 'Starter',
cost: 9.99,
type: 'flat',
},
],
},
{
id: 'starter-quarterly',
name: 'Starter Quarterly',
paymentType: 'recurring',
interval: 'month',
lineItems: [
{
id: 'price_stripe_quarterly',
name: 'Starter',
cost: 24.99,
type: 'flat',
},
],
},
],
},
],
});
price_stripe_quarterly above should be configured in Stripe as:
interval=month interval_count=3
Mobile API contract target for /api/v1/plans
Mobile expects a compact pricing payload:
{
"ok": true,
"data": {
"products": [
{
"id": "starter",
"name": "Zovi Daily",
"currency": "USD",
"features": ["Feature 1", "Feature 2"],
"plans": [
{
"id": "starter-monthly",
"name": "Monthly",
"interval": "month",
"intervalCount": 1,
"price": 9.99,
"priceVariantId": "price_stripe_monthly",
"provider": "stripe"
}
]
}
]
}
}
Keep this structure stable so mobile can render selection deterministically.
Coupons and business rules (future-ready)
Today this project is configuration-first and provider-driven. Future phases can add coupon and business-rule logic without changing required fields by keeping pricing fields stable and adding optional policy fields.
Planned extension points
businessRulesis now supported as a pass-through extension on a plan or product. If set inbilling.config.ts, it is exposed in/api/v1/plansasbusinessRules. Start with no-ops today (e.g.,{}), and later add eligibility predicates there.- Optional plan metadata in response:
coupons: display text/labels for user awarenesscouponIds: provider coupon IDsbusinessRules: structured eligibility metadata
- Optional checkout resolver that:
- checks user/context eligibility,
- applies a qualifying coupon set,
- returns resolved plan availability for the request.
Safe rollout pattern
- Configure Stripe coupons and customer promotions in Stripe first (minimum risk path).
- Add internal mapping to plan IDs in
billing.config.tsor admin-owned config. - Add resolver logic in API stage after auth + authorization checks.
- Keep plan IDs and Stripe price IDs explicit and unchanged.
Related files
web/apps/web/config/billing.config.tsweb/apps/web/config/billing.sample.config.ts