Pricing & Coupons
This page covers the commercial model that sits above payment providers: how plans are shaped, how recurring and one-time purchases behave, how proration works, where users manage billing, and how coupons and receipts fit into the flow.
Plans and pricing model
A plan is the product definition your users buy. Providers fulfill the payment, but the app decides what a plan means: duration, token grant, renewal behavior, and whether it supports organizations.
| Field or concept | What it controls |
|---|---|
| Plan | Name, description, price, duration, token grant, recurring behavior, organization support |
| PlanPrice | Provider-specific localized price for a plan |
| autoRenew | Whether the plan is recurring or one-time |
| supportsOrganizations | Whether the plan can power an organization workspace |
| tokenLimit | How many paid tokens or credits are granted |
One-time vs recurring plans
One-time plans
A one-time plan has autoRenew=false. The user pays once and is not charged again automatically. The access window can either follow the configured duration or be marked as lifetime access.
- Useful for short access windows like 24-hour or 7-day offers
- Can also be modeled as a lifetime purchase when
isLifetime=true - Often works as a credit or token top-up
- Finite plans end when
expiresAtis reached; lifetime plans stay active without renewal
In practice, a finite plan becomes fully inactive when the expiry lifecycle catches up with that timestamp. The built-in expiry job and grace-window settings control exactly when access is removed, so a just-expired plan may still be inside its configured grace period until the next expiry check runs.
Recurring plans
A recurring plan has autoRenew=true. It renews automatically according torecurringInterval and recurringIntervalCount.
- Supports monthly, yearly, weekly, and other interval combinations
- Can be cancelled at period end rather than immediately
- Can participate in proration when switching between recurring plans
Personal vs workspace scope
Plans can also be personal or workspace-oriented. When supportsOrganizations=true, the plan is eligible to power an organization and team workflows.
Checkout modes and user flows
The app uses two checkout shapes depending on what the user is buying:
| Mode | Used for | Effect |
|---|---|---|
| payment | One-time purchases | Creates a one-time payment rather than a renewing subscription |
| subscription | Recurring plans | Creates or updates a recurring subscription record |
Users encounter these flows primarily through /pricing, /dashboard/plan, and /dashboard/billing.
What one-time purchases actually do
One-time purchases are more nuanced than “create a temporary plan.” The payment service resolves different behaviors depending on the user's current active subscription.
| Situation | Behavior |
|---|---|
| No active subscription | Create a new one-time subscription |
| Active one-time subscription in same plan family | Extend the existing one-time subscription |
| Active one-time subscription in different family (personal vs org-supporting mismatch) | Replace the existing one-time subscription |
| Active recurring subscription | Treat the purchase as a token or credit top-up instead of replacing the recurring plan |
Lifetime one-time plans use the same flow shape with an explicit lifetime flag, so the branching stays minimal: the subscription is still created or extended through the normal one-time handlers, but the resulting access is treated as lifetime instead of finite-duration.
Note
Localized provider pricing
SaaSyBase supports per-provider, per-currency pricing through the PlanPrice model. A single plan can therefore have multiple localized prices without becoming multiple products in your app.
PlanPrice {
planId
provider
currency
amountCents
externalPriceId
}Example: the same plan can be $10 USD on Stripe and ₦15,000 NGN on Paystack without splitting your business logic into separate plans.
Recurring intervals
Recurring plans use both a unit and a multiplier:
| Field | Example | Meaning |
|---|---|---|
| recurringInterval | month | The billing unit |
| recurringIntervalCount | 2 | Every 2 units, e.g. every 2 months |
Note
recurringIntervalCount >= 7. Shorter daily cadences are skipped there while other providers can still work.Proration and plan changes
Proration only applies when moving between recurring plans. It is not used for one-time purchases.
When it applies
- The current subscription must be active and recurring
- The target plan must also be recurring
- The target plan must be different from the current plan
- Recurring proration must be enabled in settings (
ENABLE_RECURRING_PRORATION)
Proration flow
| Step | Route or state | What happens |
|---|---|---|
| Preview | GET /api/subscription/proration?planId=... | Returns the estimated charge or credit, line items, and downgrade or upgrade context |
| Apply | POST /api/subscription/proration | Begins the plan change and can place the subscription into a pending state |
| Pending state | prorationPendingSince + PENDING | The subscription is waiting for provider-side confirmation |
| Finalize | Webhook confirmation | Subscription returns to ACTIVE once the provider confirms the change |
Downgrades and scheduling
Depending on provider and change type, some downgrades are scheduled for the end of the current cycle instead of applied immediately. The subscription can carry scheduledPlanId and scheduledPlanDate metadata for this.
Catalog sync and price auto-creation
When PAYMENT_AUTO_CREATE="true", the app can create missing provider-side catalog objects for supported providers during seed or plan save flows.
PAYMENT_AUTO_CREATE="true"In the admin plan UI, manual provider price ID fields are advanced overrides. The normal workflow is to leave them blank and let sync populate them.
Cancellation and billing management
Cancellation is generally handled as cancel-at-period-end rather than immediate loss of access.
| Flow | Route | Notes |
|---|---|---|
| User cancel | POST /api/billing/cancel | Requests cancel-at-period-end behavior |
| Billing portal / management link | POST /api/billing/customer-portal | Creates a provider-specific billing management session or redirect |
| Undo cancel | POST /api/billing/undo-cancel | Restores auto-renew when supported |
Provider behavior differs. Stripe supports a native customer portal directly. Paystack relies on an app-level workaround. Some providers rely more on hosted management URLs than a full Stripe-style portal.
Coupon system
Coupons are provider-aware but app-controlled. SaaSyBase supports both native provider coupons and in-app discount calculation.
| Capability | Details |
|---|---|
| Discount types | Percent-off and amount-off |
| Duration | once, repeating for N months, or forever |
| Plan restrictions | CouponPlan join table limits redemption to specific plans |
| Currency awareness | Amount-off coupons can be currency-specific |
| Availability windows | startsAt and endsAt |
| Usage caps | Max redemptions and activation toggles |
Provider behavior
| Provider type | Behavior |
|---|---|
| Stripe / Paddle | Can use provider-native coupon or promotion objects |
| Paystack / Razorpay | Discount is calculated in-app before checkout |
| Razorpay offers | Optional offer linking can be driven through coupon metadata conventions |
Do not apply the same discount twice. If your flow is already reducing the amount in-app for Paystack or Razorpay, make sure you are not also layering an equivalent provider-side discount on top of that checkout.
Where coupons appear
/admin/couponsfor create/edit/activation/dashboard/couponsfor redeemed and pending user coupons- Pricing and checkout UI can surface coupon application during purchase flows
/api/admin/couponsand/api/dashboard/couponsfor management and visibility
What the UI actually exposes
| Route | Audience | What it does |
|---|---|---|
| /pricing | Public or signed-in user | Browse plans and start checkout |
| /dashboard/plan | Signed-in user | Review current subscription, pending changes, and compare plans |
| /dashboard/billing | Signed-in user | Manage billing, invoices, renewal timing, and payment methods or links |
| /dashboard/coupons | Signed-in user | View redeemed and pending coupons |
| /admin/plans | Admin | Create, edit, price, activate, and sync plans |
| /admin/coupons | Admin | Manage coupon rules and activation |
Admin plan and subscription workflows
Admins manage the catalog from /admin/plans. The UI exposes both recurring and non-recurring offers, active subscriber counts, and sync helpers for provider price IDs.
Subscription lifecycle tools also exist in admin APIs, including schedule-cancel, expire, edit, and force-cancel flows. Those are documented in the API reference but matter here because they shape what billing management means operationally.
Invoices and refund receipts
SaaSyBase generates provider-agnostic PDF invoices and refund receipts server-side using pdf-lib. That gives every provider a consistent document experience.
| Document | What it includes | Route |
|---|---|---|
| Invoice PDF | Branding, bill-to details, plan details, discounts, totals, provider reference | /api/billing/invoice/[paymentId] |
| Refund receipt PDF | Refund ID, amount refunded, original transaction details, coupon context | /api/billing/refund-receipt/[paymentId] |
Tip

