| title | create_plan |
|---|---|
| description | Creates a new subscription billing plan on-chain with configurable pricing, billing period, trial length, grace period, and price ceiling protections. |
fn create_plan(
env: Env,
merchant: Address,
token: Address,
amount: i128,
period: u64,
trial_periods: u32,
max_periods: u32,
grace_period: u64,
price_ceiling: i128,
name: String,
project_id: u64,
) -> u64Creates a new billing plan under an existing Project. The plan defines the terms of recurring payments: token, amount, billing period, trial length, price protection ceiling, a human-readable name, and the project_id that owns it. Returns the chain-assigned plan_id.
| Name | Type | Description |
|---|---|---|
merchant |
Address |
The merchant's Stellar address. Must sign the transaction. |
token |
Address |
The SEP-41 token contract address (e.g., USDC). |
amount |
i128 |
Amount charged per billing period, in stroops (7 decimal places). |
period |
u64 |
Billing period duration in seconds (e.g., 2592000 for 30 days). |
trial_periods |
u32 |
Number of free trial periods before billing begins. Use 0 for no trial. |
max_periods |
u32 |
Maximum number of billing periods. Use 0 for unlimited. |
grace_period |
u64 |
Grace window in seconds after a failed charge before the subscription pauses. |
price_ceiling |
i128 |
Maximum amount the plan can ever charge per period, in stroops. Protects subscribers from price increases. |
name |
String |
Human-readable plan name shown on checkout pages and in the dashboard (e.g. "Pro"). |
project_id |
u64 |
The Project this plan belongs to. Must exist and be owned by merchant. |
merchant.require_auth();The merchant address must sign the transaction.
u64 - the newly created plan ID.
| Event | Topics | Data |
|---|---|---|
plan_created |
merchant, plan_id |
Plan struct |
| Code | Name | Description |
|---|---|---|
| 3 | InvalidAmount |
amount is zero or negative. |
| 4 | InvalidPeriod |
period is zero. |
| 5 | CeilingBelowAmount |
price_ceiling is less than amount. |
| 6 | PlanNotFound |
project_id does not exist on chain. |
| 2 | Unauthorized |
merchant does not own the referenced project. |
```typescript import { VowenaClient, NETWORKS, toStroops } from "@vowena/sdk";
const client = new VowenaClient({
contractId: NETWORKS.testnet.contractId,
rpcUrl: NETWORKS.testnet.rpcUrl,
networkPassphrase: NETWORKS.testnet.networkPassphrase,
});
const tx = await client.buildCreatePlan({
merchant: "GMERCHANT...ADDR",
token: NETWORKS.testnet.usdcAddress,
amount: toStroops("9.99"), // 99900000n stroops
period: 2_592_000, // 30 days
trialPeriods: 1, // 1 free period
maxPeriods: 0, // unlimited
gracePeriod: 259_200, // 3 days
priceCeiling: toStroops("14.99"), // max 14.99 USDC
name: "Pro", // display name
projectId: 1, // parent project
});
const signedXdr = await signTransaction(tx);
const result = await client.submitTransaction(signedXdr);
console.log("Plan ID:", result.planId); // e.g., 1
```
toStroops("9.99"); // → 99900000n
toStroops("14.99"); // → 149900000n
toStroops("1.00"); // → 10000000nWhen using the CLI, pass the raw stroop value directly.
Set `price_ceiling` thoughtfully. It protects subscribers from unexpected price hikes - the merchant can call `update_plan_amount` to raise the price, but never above the ceiling. If you need to exceed the ceiling, create a new plan and use the [migration flow](/api-reference/request-migration).