| title | Subscriptions |
|---|---|
| description | The subscription lifecycle on Vowena - creation, state machine, status transitions, allowance calculations, and the one-signature authorization model. |
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.
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() |
When a subscriber calls subscribe(), several things happen in a single atomic transaction:
```
allowance = price_ceiling * effective_periods
```
Where `effective_periods` is:
- `max_periods` if the plan has a limit, or
- `120` if the plan is unlimited (`max_periods == 0`)
The allowance expiry is set to the **maximum Soroban allows**: approximately 6,312,000 ledgers (~347 days at 5-second block times).
**Plan:** 10 USDC/month, ceiling 15 USDC, max 12 periods
```
allowance = 15 USDC * 12 = 180 USDC
expiry = current_ledger + 6,312,000 (~347 days)
```
The subscriber's wallet will show: "Approve 180 USDC for Vowena contract".
```
allowance = 8 USDC * 120 = 960 USDC
expiry = current_ledger + 6,312,000 (~347 days)
```
After the allowance expires (~347 days), the subscriber would need to re-approve. The contract handles this gracefully - a charge will fail, the subscriber can re-approve and reactivate.
```
allowance = 25 USDC * 12 = 300 USDC
```
The allowance covers all 12 periods including trials. During the 2 trial periods, `charge()` advances the counter but does not transfer tokens. The allowance is not consumed during trials.
Cancellation is always available to the subscriber and is immediate:
const tx = await client.cancel({
subscriber: subscriberKeypair.publicKey(),
sub_id: 42n,
});If a subscription is Paused (charge failed, grace expired), the subscriber can reactivate it:
The subscriber ensures they have sufficient token balance and that the contract's allowance has not expired. The subscriber calls `reactivate(sub_id)`. The contract verifies the subscription is in `Paused` status and transitions it back to `Active`. The `next_billing_time` is reset so that the next `charge()` call can process immediately. If the original token allowance has expired while the subscription was paused, the subscriber will need to create a new subscription instead. Reactivation only works if the allowance is still valid.The most important page - understand exactly what happens during a charge. How merchants move subscribers to new plans with explicit consent.