Skip to main content
SolvaPay automates billing through a combination of purchase lifecycle management and scheduled jobs. Recurring and usage-based plans are billed automatically, with usage costs calculated from the Usage timeseries.

Billing Cycles

Billing cycles determine how often customers are charged and when usage counters reset:
CycleDurationUse Case
weekly7 daysHigh-frequency, short-commitment services
monthly30 daysStandard SaaS subscriptions
quarterly90 daysDiscounted longer-term plans
yearly365 daysAnnual subscriptions with savings
custom30 days (default)Custom billing arrangements
The billing cycle is set on the plan and determines:
  • When the next payment is due (nextBillingDate on the purchase)
  • The time window for usage aggregation (periodStart to periodEnd)
  • When usage counters reset

Purchase States

A purchase moves through these states during its lifecycle:
StateDescription
trialingCustomer is in a free trial period (set by trialDays on the plan)
activePurchase is active and the customer has access
active (pending cancellation)Active with cancelledAt set — access continues until period end, can be reactivated
cancelledCancel confirmed at period end
expiredPurchase has ended (trial expired without payment, cancelled and period ended, or replaced by plan switch)
past_duePayment failed; purchase may be suspended

Reactivation

When a customer cancels a recurring purchase, the purchase enters a “pending cancellation” state — it remains active with cancelledAt set, and the customer keeps access until the current billing period ends. Before the period ends, the cancellation can be undone:
import { reactivateRenewal } from '@solvapay/next'

await reactivateRenewal(request, { purchaseRef: 'pur_...' })
This clears cancelledAt and restores autoRenew, so the purchase continues renewing as normal. A purchase.updated webhook fires. Preconditions: the purchase must be active, have cancelledAt set, and endDate must not have passed.

Plan Switching

When a customer wants to change plans on a product, call activatePlan with the new plan reference. If the customer already has an active purchase on a different plan for that product, the system automatically:
  1. Expires the existing purchase
  2. Creates a new purchase on the requested plan
import { activatePlan } from '@solvapay/next'

const result = await activatePlan(request, {
  productRef: 'prd_myapi',
  planRef: 'pln_pro', // new plan
})
This produces two webhook events: purchase.expired for the old purchase and purchase.created for the new one.

Usage Tracking on Purchases

For usage-based and hybrid plans, each purchase maintains a usage subdocument that tracks the current billing period:
{
  "usage": {
    "used": 0,
    "periodStart": "2025-01-01T00:00:00Z",
    "periodEnd": "2025-02-01T00:00:00Z",
    "resetDate": "2025-02-01T00:00:00Z",
    "overageUnits": 0,
    "overageCost": 0,
    "carriedOverUnits": 0
  }
}
The actual usage count comes from the Usage timeseries, not from the usage.used field. The timeseries is the source of truth — limit checks query UsageService.sumForMeter() from periodStart to the current time.

Limit Checking Flow

When a customer makes a request to a protected endpoint, access is determined by:
  1. Find the active purchase for the customer and product
  2. Look up the plan to get the limit
  3. If limit is 0unlimited access, allow immediately
  4. Resolve the plan’s auto-assigned requests meter
  5. Query the Usage timeseriesUsageService.sumForMeter(providerId, meterName, customerRef, periodStart, now)
  6. Compare against the hard cap — if used >= limit, deny with a paywall response
The response includes:
{
  "hasAccess": true,
  "used": 750,
  "remaining": 250,
  "limit": 1000,
  "freeUnits": 100,
  "isExceeded": false,
  "meterName": "requests"
}
When access is denied, the response includes a checkout URL so the customer can upgrade.

End-of-Period Billing

A daily cron job (11:00 AM UTC) processes end-of-period billing for usage-based and hybrid plans. For each active recurring purchase whose billing period has ended:

Usage-Based Plans

  1. Query the Usage timeseries for total usage in the period
  2. Subtract freeUnits from the total
  3. Apply the creditsPerUnit to the billable amount
  4. Respect the limit as a hard cap (usage beyond the cap is not billed unless overage is allowed)
  5. Create a payment intent for the calculated cost
Example:
Plan: 100 free units, 100 credits/unit, 10,000 limit
Usage: 5,250 records in the period

Billable = max(0, min(5250, 10000) - 100) = 5,150 units
Cost = 5,150 × 100 = 515,000 credits

Hybrid Plans

Hybrid plans combine a recurring base fee with usage-based charges:
  1. The base price is billed as part of the regular renewal
  2. Usage beyond the included amount is calculated separately
  3. Tiered pricing is applied if usageTiers are defined — each tier has its own creditsPerUnit for a range of usage
  4. Overage beyond the plan’s limit is charged at the overagePolicy.overageRate (or creditsPerUnit as fallback)
  5. The overagePolicy.maxOverage caps the maximum overage units
Example with tiered pricing:
Base: $49/month, 1,000 included units
Tiers:
  0–500:     50 credits/unit
  501–2,000: 30 credits/unit
  2,001+:    10 credits/unit
Overage: allowed, rate 80 credits/unit, max 5,000

Usage: 3,500 records

Included: 1,000 (covered by base price)
Billable: 2,500 units
  Tier 1: 500 × 50 = 25,000 credits
  Tier 2: 1,500 × 30 = 45,000 credits
  Tier 3: 500 × 10 = 5,000 credits
Usage cost: 75,000 credits
Total: $49.00 (base) + 75,000 credits (usage)

Renewal Processing

A daily cron job (10:00 AM UTC) handles recurring plan renewals:
  1. Find purchases where nextBillingDate ≤ now and autoRenew is enabled
  2. For paid plans, create a payment intent for the plan price
  3. For free plans, process the renewal directly
  4. Advance nextBillingDate to the next billing cycle

Trial Expiration

A daily cron job (8:00 AM UTC) handles trial expirations:
  • If requiresPayment is true: the purchase is suspended until payment is received
  • If requiresPayment is false: the purchase transitions to active

Usage Reset

A periodic job (every 30 minutes) resets usage counters on purchases whose reset date has passed:
  1. Set usage.used to 0 (or carry-over amount if rolloverUnusedUnits is enabled)
  2. Advance periodStart and periodEnd to the next period
  3. Calculate the next resetDate
Since the actual usage is derived from the Usage timeseries using the periodStart window, advancing periodStart effectively “resets” visible usage without deleting any usage records.

Billing Strategies (Hybrid Plans)

Hybrid plans support different billing strategies:
StrategyDescription
recurring_firstBill the base fee first, then calculate usage charges
usage_firstCalculate usage first, then add the base fee
combinedBill both components together

Proration (Recurring Plans)

When a customer upgrades or downgrades mid-cycle, proration policies determine how the change is handled:
MethodDescription
proportionalCharge/credit proportional to remaining days in the cycle
fullCharge the full new price immediately
noneWait until the next billing cycle to apply the change

Next Steps