Billing Products
Product catalog model and admin management workflow.
Catalog Model
Billing products are stored in billing_products with non-secret metadata:
- product key
- type (
subscriptionorone_time) - plan slug
- interval (
monthly/yearlywhen applicable) - optional entitlement slug
- Stripe price ID, managed internally after sync
- mirrored Stripe Product/Price metadata
- active flag
- sort order
Admins manage product prices in Yayaw. When a product amount is saved, Yayaw creates or updates the matching Stripe Product, creates a new Stripe Price when the amount, currency, or interval changes, archives the previous Price for new checkouts, and stores the resulting Stripe price ID plus mirrored Product/Price metadata. Stripe secrets stay in environment variables.
Products & Services is the source of truth for billing product names, prices,
types, intervals, Stripe sync status, and checkout availability. CMS data models
must not duplicate those commercial fields.
Stripe coupons and promotion codes are mirrored in:
billing_stripe_couponsbilling_stripe_promotion_codes
Stripe remains the source of truth for discounts; Yayaw mirrors them for CMS variables and MCP inspection.
Admin Workflow
Use the dashboard admin page:
/dashboard/admin/billing-products/dashboard/admin/billing-settings
Current V1 capabilities:
- toggle product active state
- edit product amount and currency
- create and mirror the Stripe Product/Price through the Stripe API
- edit sort order
- keep product keys stable
Billing settings currently cover:
- grace period days
- GitHub code-access repository settings
Plan feature limits live in billing_plans and are read by runtime billing
services. Media quotas and seat limits should be changed through the runtime
billing plan model, not hard-coded into product cards.
CMS Product Content
The seed creates one global billing-product-content data model with one entry
per billing catalog product. Each entry is linked by the locked
catalog_product_key field and can hold only editorial additions such as badge,
summary, feature bullets, CTA label, or highlight state.
Use this model to complement catalog variables in page or section content. Use
{billing.product.<productKey>.*} and {billing.price.<productKey>.*} for
product names, prices, Stripe price IDs, and checkout availability.
Organization Workflow
Use the organization billing page:
/dashboard/organization/billing/dashboard/organization/plans
Current V1 capabilities:
/billingfocuses on active plan summary and internal activity/plansfocuses on plan selection and checkout actions- start subscription checkout
- start one-time checkout (
pro_lifetime) - open Stripe billing portal
- view disabled checkout reasons before clicking actions
The billing portal action is disabled until the organization has a recorded Stripe customer. This prevents a confusing portal failure before the first successful checkout creates the customer in Stripe.
Operational Notes
- If a product has no synced Stripe price ID, inactive mirrored Stripe Price/Product, or a sync error, checkout is disabled for that product.
- Subscription and one-time Stripe Checkout sessions allow Stripe promotion codes.
- Seed only creates missing product rows and does not overwrite operator-managed catalog changes.
- Product keys are stable contracts. Add a new product key for a new commercial offer rather than repurposing an existing key with different semantics.
- Stripe Price IDs are immutable for amount/currency/interval. Amount changes create a new active Price and archive the old Price for new checkouts.
- Discount data is mirrored from Stripe for read-side usage only. Create and govern coupons/promotion codes in Stripe.
- One-time lifetime checkout grants
pro_lifetimeand creates a durable code-access grant keyed by Stripe checkout session. - Subscription checkout creates a durable code-access grant keyed by Stripe subscription ID, then refreshes or revokes that grant on subscription events.
MCP Workflow
Control-plane tools expose the catalog and Stripe discount mirror:
yayaw_billing_products_listyayaw_billing_product_updateyayaw_stripe_discounts_listyayaw_stripe_discounts_sync
Writes require control-plane:admin, Yayaw billing-product:update authorization, and a reason.
Validation
Useful checks after billing catalog changes:
bun test src/lib/server/services/billing/billing-products.test.ts
bun test src/lib/server/services/billing/stripe-catalog-sync.test.ts
bun test src/lib/server/services/billing/billing-organization-view.test.ts
bun run test