Plans
Subscriptions
Token Allowance
Billing Cycle
Price Ceiling
Grace Periods
Plans
A plan is an immutable billing template created by a merchant on-chain. It defines everything about how a subscription will be billed.Plan Fields
Plan Fields
| Field | Type | Description |
|---|---|---|
token | Address | The SEP-41 token used for billing (e.g., USDC) |
amount | i128 | Amount charged per billing period (in stroops, 7 decimals) |
period | u64 | Billing period length in seconds (e.g., 2,592,000 for 30 days) |
trial_periods | u32 | Number of billing periods before the first charge (0 = no trial) |
max_periods | u32 | Maximum number of chargeable periods (0 = unlimited) |
grace_period | u64 | Time in seconds after a failed charge before the subscription pauses |
price_ceiling | i128 | Maximum amount the plan can be adjusted to without subscriber re-auth |
merchant | Address | The merchant’s Stellar address that receives payments |
Immutability
Immutability
Plans are immutable once created. This is a deliberate design choice:
- Subscribers know exactly what they signed up for - the terms can never change silently.
- Price changes require creating a new plan and using the migration flow.
- Subscribers must explicitly accept any migration, preserving full consent.
update_plan_amount() to adjust the billing amount, but only up to the price_ceiling. This allows minor price adjustments (e.g., rounding changes) without requiring a full migration.Creating a Plan
Creating a Plan
planId is a sequential integer assigned by the contract. Use it to reference this plan in all subsequent operations.Subscriptions
A subscription is an on-chain record that links a subscriber to a plan. It tracks the current state of the billing relationship.Subscription Statuses
Subscription Statuses
A subscription moves through a defined set of states:
- Active - The subscription is live. Charges can be triggered each period.
- Paused - A charge failed, the grace period expired, and the subscription is on hold. It can be reactivated.
- Cancelled - Permanently ended. Either the subscriber/merchant cancelled, or a paused subscription was not reactivated in time.
- Expired - The
max_periodslimit was reached. The subscription completed naturally.
Subscription Data
Subscription Data
Each subscription stores:
| Field | Description |
|---|---|
subscriber | The subscriber’s Stellar address |
plan_id | The plan this subscription is linked to |
status | Current status (Active, Paused, Cancelled, Expired) |
created_at | Timestamp when the subscription was created |
last_charged_at | Timestamp of the most recent successful charge |
periods_charged | Number of billing periods successfully charged |
failed_at | Timestamp of the most recent failed charge attempt (if any) |
Who Can Cancel?
Who Can Cancel?
Both the subscriber and the merchant can cancel a subscription at any time by calling
cancel(). The subscriber does not need the merchant’s permission, and the merchant does not need the subscriber’s permission.Cancellation is an on-chain transaction - no frontend, API, or dashboard is required. Any tool that can invoke a Soroban contract can cancel a subscription.Token Allowance (Pull Billing)
This is the core mechanism that makes Vowena possible. Understanding token allowances is essential to understanding the protocol.
approve / transfer_from pattern - the same concept as ERC-20 allowances on Ethereum.
How It Works
How It Works
- When a subscriber calls
subscribe(), the transaction includes atoken.approve()call that grants the Vowena contract permission to transfer USDC from the subscriber’s account. - The approval specifies:
- Spender: The Vowena contract address
- Amount: The plan’s
price_ceilingmultiplied by remaining periods (or a large value for unlimited plans) - Expiry: A ledger-based expiration
- Each billing period, when
charge()is called, the contract callstransfer_from()to move USDC from the subscriber to the merchant. - No new signature is needed for each charge - the initial allowance covers all future payments.
What the Subscriber Sees
What the Subscriber Sees
When the subscriber’s wallet prompts for the signature, it displays the full authorization tree:
- Contract call:
vowena.subscribe(subscriber, plan_id) - Token approval:
usdc.approve(subscriber, vowena_contract, amount, expiry_ledger)
One Signature, Two Authorizations
One Signature, Two Authorizations
Security Properties
Security Properties
- The allowance is granted only to the Vowena contract, not to the merchant.
- The contract enforces billing rules (period timing, amount limits, status checks) before calling
transfer_from(). - The subscriber can revoke the allowance at any time by calling
cancel()or by directly callingtoken.approve()with a zero amount. - Allowances have a ledger-based expiration on Soroban - they don’t last forever.
Billing Cycle
The billing cycle is the heartbeat of every subscription. Here’s what happens each period.The charge() Flow
The charge() Flow
When
charge() is called, the contract performs these checks in order:- Status check - Is the subscription Active?
- Time check - Has at least one full
periodelapsed sincelast_charged_at? - Trial check - Has the trial period ended? (If not, the period counter increments but no transfer occurs.)
- Allowance check - Does the subscriber have sufficient token allowance for the Vowena contract?
- Balance check - Does the subscriber have enough USDC balance?
- Max periods check - Has
max_periodsbeen reached? (If so, mark as Expired.)
transfer_from() to move amount USDC from the subscriber to the merchant.Permissionless Charging
Permissionless Charging
The
charge() function is permissionless - literally anyone can call it. The caller address parameter is simply used for attribution; it does not need to be the merchant or the subscriber.This design enables:- Merchant self-service: Merchants run their own keeper bots
- Dashboard keepers: The Vowena dashboard offers a hosted keeper service
- Third-party keepers: Independent services that charge subscriptions for a fee
- Subscriber-initiated: Subscribers can even trigger their own charges
charge(). There is no trust assumption on the caller.Timing
Timing
The contract uses Soroban’s For the first charge after subscribing (and after any trial periods),
ledger.timestamp() for timing. A charge is eligible when:last_charged_at is set to the subscription creation time plus trial periods.Price Ceiling
The price ceiling is a subscriber protection mechanism built directly into the protocol.What It Does
What It Does
When a merchant creates a plan, they set a
price_ceiling - the maximum amount the plan’s billing amount can ever be adjusted to.- A merchant can call
update_plan_amount()to change the billing amount, but only up to the price_ceiling. - If the merchant needs to charge more than the ceiling, they must create a new plan and use the migration flow.
- The subscriber must explicitly accept any migration.
Example
Example
A merchant creates a plan:
amount: 9.99 USDCprice_ceiling: 14.99 USDC
- Create a new plan with the new pricing
- Request a migration for each subscriber
- Each subscriber individually accepts (or rejects) the migration
Why It Matters
Why It Matters
In traditional SaaS billing, companies can silently raise prices and charge your card the new amount. Vowena makes this impossible:
- The price ceiling is set at plan creation and is immutable.
- The subscriber’s token allowance is based on the price ceiling, not the current amount.
- Any change above the ceiling requires explicit subscriber consent via the migration flow.
Grace Periods
Grace periods provide a safety net for failed charges, preventing immediate subscription loss due to temporary balance issues.The Grace Flow
The Grace Flow
- A
charge()attempt fails (insufficient balance or allowance). - The contract records
failed_atwith the current timestamp. - The subscription remains Active during the grace window.
- During the grace period,
charge()can be retried. - If a retry succeeds,
failed_atis cleared and billing continues normally. - If the grace period expires without a successful charge, the subscription moves to Paused.
- A Paused subscription gets one more billing period for reactivation.
- If not reactivated, the subscription moves to Cancelled.
Configuring Grace Periods
Configuring Grace Periods
The
Choose a grace period that balances subscriber convenience with cash flow needs. 3 days is a common default.
grace_period is set per plan, in seconds:| Value | Duration |
|---|---|
86_400 | 1 day |
259_200 | 3 days |
604_800 | 7 days |
0 | No grace period (fail immediately pauses) |
Best Practices
Best Practices
- Merchants: Set a reasonable grace period (1-7 days). Too short and you lose subscribers to temporary balance dips. Too long and you delay revenue.
- Subscribers: If you receive a “charge failed” notification, top up your USDC balance before the grace period expires.
- Keepers: Implement retry logic during the grace window. A charge that fails on day 1 may succeed on day 2 if the subscriber tops up.
Keepers
A keeper is any service or bot that calls
charge() on subscriptions when they become due. The term comes from the DeFi concept of “keeper bots” that maintain protocol health.Why Keepers Exist
Why Keepers Exist
Smart contracts on Soroban (and most blockchains) cannot execute automatically. Someone must submit a transaction to trigger the
charge() function each billing period.Keepers solve this problem. They monitor subscriptions and call charge() at the right time.Keeper Options
Keeper Options
Merchant Self-Hosted
Run the SDK’s keeper module on your own infrastructure. Full control, no dependencies.
Dashboard Keeper
The Vowena Dashboard includes a hosted keeper service. Toggle it on per plan - no code required.
Third-Party Keepers
Independent keeper services that monitor and charge subscriptions across the network. Permissionless and competitive.
Running Your Own Keeper
Running Your Own Keeper
Next Steps
How It Works
See the full subscription flow in a visual step-by-step walkthrough.
Quickstart
Build your first subscription in 5 minutes with the SDK.
Protocol Deep Dive
Explore the contract architecture, storage model, and advanced features.
FAQ
Get answers to common questions about Vowena.