What is a Plan?
A plan is the billing template that a merchant publishes on-chain. It defines what subscribers are paying for, how much, how often, and under what terms. Subscribers choose a plan and create a subscription against it. Plans are shared objects - one plan can have thousands of active subscriptions. The plan itself stores no per-subscriber state.Plan Fields
| Field | Type | Description |
|---|---|---|
id | u64 | Auto-incremented unique identifier assigned at creation |
merchant | Address | The Stellar address that receives subscription payments |
token | Address | The SAC token contract used for billing (e.g., USDC) |
amount | i128 | Amount charged per billing period (in token’s smallest unit, e.g., stroops) |
period | u64 | Billing period length in seconds (e.g., 2592000 for ~30 days) |
trial_periods | u32 | Number of free periods before billing begins (0 = no trial) |
max_periods | u32 | Maximum number of billing periods before auto-expiry (0 = unlimited) |
grace_period | u64 | Time in seconds after a failed charge before the subscription pauses |
price_ceiling | i128 | Maximum amount the merchant can ever set via update_plan_amount |
created_at | u64 | Ledger timestamp when the plan was created |
active | bool | Whether the plan accepts new subscriptions (true by default) |
Creating a Plan
Merchant calls create_plan
The merchant invokes
create_plan with all plan parameters. The contract calls merchant.require_auth() to verify the caller owns the merchant address.Validation checks
The contract validates the input:
amountmust be greater than0periodmust be greater than0price_ceilingmust be greater than or equal toamount
Plan is stored
The contract increments
NextPlanId, constructs the Plan struct, and writes it to persistent storage under DataKey::Plan(id). The plan ID is also appended to the merchant’s index at DataKey::MerchantPlans(merchant).Price Ceiling
The price ceiling is one of Vowena’s core consumer protection features. When a subscriber signs up, they authorize a token allowance based on the ceiling, not the current amount.How it works
Merchant flexibility
The merchant can call
update_plan_amount to adjust the price up or down, as long as the new amount stays at or below the ceiling. This allows for small price adjustments, seasonal promotions, or inflation adjustments without requiring subscribers to re-authorize.Subscriber guarantee
The subscriber’s token allowance is calculated from
price_ceiling * periods, not amount * periods. Even if the merchant raises the price to the ceiling, the allowance already covers it. The subscriber never needs to sign again.Example
| Scenario | Amount | Ceiling | Allowed? |
|---|---|---|---|
| Initial plan | 10 USDC | 15 USDC | Yes |
| Small raise | 12 USDC | 15 USDC | Yes - within ceiling |
| Promotion | 8 USDC | 15 USDC | Yes - can lower freely |
| Big raise | 20 USDC | 15 USDC | No - exceeds ceiling, use migration |
Plan Immutability
Plans are intentionally immutable once created. The only mutable field isamount (within the ceiling). You cannot change:
- The billing period
- The token
- The merchant address
- Trial periods or max periods
- The grace period
- The price ceiling itself
Deactivating a Plan
Merchants can deactivate a plan to stop new subscriptions. Existing subscriptions continue to bill normally - deactivation only prevents newsubscribe() calls.
Deactivation is a soft state change. The plan data remains on-chain and existing subscribers are unaffected. There is no “delete plan” - on-chain data is permanent.
What’s Next
Subscriptions
See how subscribers activate plans and the full subscription lifecycle.
Billing
Understand what happens when a charge is triggered.