| title | Plans |
|---|---|
| description | How merchants create, configure, and manage subscription plans on Vowena - including pricing, trials, grace periods, and the price ceiling mechanism. |
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.
| 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) |
The merchant invokes `create_plan` with all plan parameters. The contract calls `merchant.require_auth()` to verify the caller owns the merchant address. The contract validates the input: - `amount` must be greater than `0` - `period` must be greater than `0` - `price_ceiling` must be greater than or equal to `amount`
If any check fails, the transaction reverts with a descriptive error.
const client = new VowenaClient({ contractId, networkPassphrase, rpcUrl });
const tx = await client.createPlan({ merchant: merchantKeypair.publicKey(), token: USDC_CONTRACT_ID, amount: 10_0000000n, // 10 USDC (7 decimals) period: 2592000n, // ~30 days trial_periods: 1, // 1 free period max_periods: 12, // 12 months max grace_period: 259200n, // 3 days grace price_ceiling: 15_0000000n, // Can raise up to 15 USDC });
```bash Stellar CLI
stellar contract invoke \
--id $CONTRACT_ID \
--network testnet \
-- create_plan \
--merchant $MERCHANT_ADDRESS \
--token $USDC_ADDRESS \
--amount 100000000 \
--period 2592000 \
--trial_periods 1 \
--max_periods 12 \
--grace_period 259200 \
--price_ceiling 150000000
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.
The price ceiling is **immutable after plan creation**. A merchant cannot raise the ceiling - ever. This is by design: it guarantees that subscribers know the absolute maximum they could ever be charged per period. 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. 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. Set your price ceiling thoughtfully. Too low and you cannot adjust prices. Too high and subscribers may hesitate to approve a large allowance. A ceiling of 1.5x to 2x your initial price is a reasonable default.| 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 |
Plans are intentionally immutable once created. The only mutable field is amount (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
Merchants can deactivate a plan to stop new subscriptions. Existing subscriptions continue to bill normally - deactivation only prevents new subscribe() calls.
const tx = await client.deactivatePlan({
merchant: merchantKeypair.publicKey(),
plan_id: 1n,
});See how subscribers activate plans and the full subscription lifecycle. Understand what happens when a charge is triggered.