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 example starter).
  • plan.id: internal plan key (for example starter-monthly).
  • plan.lineItems[].id: provider variant ID (for example Stripe price ID price_xxx) used by the provider layer.

Stripe setup workflow (required for mobile pricing)

  1. In Stripe Dashboard, create one product per internal product (for example Zovi Daily).
  2. Create one recurring price per available interval under that product.
  3. Add plan_id as metadata on each price for easy operations:
metadata: { billing_plan_id: "starter-monthly" }
  1. Copy each Stripe price ID into the matching lineItems[].id in web/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

  • businessRules is now supported as a pass-through extension on a plan or product. If set in billing.config.ts, it is exposed in /api/v1/plans as businessRules. Start with no-ops today (e.g., {}), and later add eligibility predicates there.
  • Optional plan metadata in response:
    • coupons: display text/labels for user awareness
    • couponIds: provider coupon IDs
    • businessRules: 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

  1. Configure Stripe coupons and customer promotions in Stripe first (minimum risk path).
  2. Add internal mapping to plan IDs in billing.config.ts or admin-owned config.
  3. Add resolver logic in API stage after auth + authorization checks.
  4. Keep plan IDs and Stripe price IDs explicit and unchanged.
  • web/apps/web/config/billing.config.ts
  • web/apps/web/config/billing.sample.config.ts