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: 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:

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:
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
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:
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:
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:

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:

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:

Proration (Recurring Plans)

When a customer upgrades or downgrades mid-cycle, proration policies determine how the change is handled:

Next Steps