What is a Subscription?
A subscription is the on-chain record linking a subscriber to a plan. It tracks billing state, lifecycle status, and timing. Each subscription is its own persistent ledger entry, enabling parallel processing across the network.State Machine
Every subscription moves through a defined set of states. Transitions are enforced by the contract - there is no way to skip a state or force an invalid transition.| From | To | Trigger |
|---|---|---|
| - | Active | subscribe() called |
| Active | Paused | Charge fails and grace period expires |
| Active | Expired | max_periods reached during charge |
| Active | Cancelled | Subscriber calls cancel() |
| Paused | Active | Subscriber calls reactivate() (funds now available) |
| Paused | Cancelled | One more billing period passes while paused, or subscriber calls cancel() |
Paused is a recoverable state. If a subscriber tops up their wallet and calls
reactivate(), the subscription returns to Active and billing resumes. But if another full billing period passes while paused, the next charge() call will transition it to Cancelled permanently.There is no transition from Cancelled or Expired back to any other state. These are terminal. The subscriber must create a new subscription if they want to resume.
Creating a Subscription
When a subscriber callssubscribe(), several things happen in a single atomic transaction:
Subscriber authorization
The subscriber signs the transaction. Soroban’s auth tree means the subscriber signs once - this single signature covers both the
subscribe() contract call and the token.approve() call nested inside it. No separate approval transaction needed.Plan validation
The contract loads the plan and verifies:
- The plan exists
- The plan is
active == true - The subscriber is not the merchant (cannot subscribe to your own plan)
Allowance calculation
The contract calculates how much token allowance to request:Where
effective_periods is:max_periodsif the plan has a limit, or120if the plan is unlimited (max_periods == 0)
Token approval
The contract calls
token.approve(subscriber, contract_address, allowance, expiry_ledger). This grants the Vowena contract permission to pull up to allowance tokens from the subscriber over the approval period.Subscription stored
The contract increments
NextSubId, creates the Subscription struct with status: Active, and writes it to persistent storage. The subscription ID is appended to both SubscriberSubs(subscriber) and PlanSubs(plan_id) indexes.Allowance Calculation Examples
- 12-month plan
- Unlimited plan
- Plan with trial
Plan: 10 USDC/month, ceiling 15 USDC, max 12 periodsThe subscriber’s wallet will show: “Approve 180 USDC for Vowena contract”.
Cancellation
Cancellation is always available to the subscriber and is immediate:Who can cancel?
Who can cancel?
Only the subscriber can cancel their own subscription. The contract calls
subscriber.require_auth(). Merchants cannot cancel on behalf of subscribers.What happens to the allowance?
What happens to the allowance?
The remaining token allowance stays until it expires naturally on the ledger. The contract will never use it after cancellation because the subscription status is
Cancelled and charge() checks status before attempting any transfer.Is it refundable?
Is it refundable?
Cancellation does not trigger an automatic refund. The current period is already paid. Merchants can issue voluntary refunds separately.
Reactivation
If a subscription is Paused (charge failed, grace expired), the subscriber can reactivate it:Top up wallet
The subscriber ensures they have sufficient token balance and that the contract’s allowance has not expired.
Call reactivate
The subscriber calls
reactivate(sub_id). The contract verifies the subscription is in Paused status and transitions it back to Active.What’s Next
Billing
The most important page - understand exactly what happens during a charge.
Migrations
How merchants move subscribers to new plans with explicit consent.