Skip to content

Powered by Grav CMS + Helios Theme

Subscription plans (add-on)

The plans that turn one variant into something sold on a calendar.

Provided by the KahunaCart Subscriptions plugin. A plan is catalogue configuration: it turns one existing variant into something sold on a calendar. What each field means commercially is on Subscriptions.

List subscription plans

GET /api/v1/kahunacart/subscription-plans Permission kahunacart.subscriptions.view

Every subscription plan in the store, each with the product and variant it sells, its billing interval, its free trial, its billing mode, its renewal variant, its grace period, its own price and label if it has them, what happens to its place when a subscription ends, the customer group it puts live subscribers in, its position among the variant's billing options, its per-provider price references and how many live subscriptions are running on it. A plan is a product variant plus a calendar: the name, the description, the images and any sale live on the variant in the base plugin's product editor, and so does the price unless the plan names its own in price_minor. One variant can carry several plans, which are the billing options its buy button offers, all selling from the variant's one stock count. On a store that sells in more than one currency each plan also carries currency_amounts, the plan's own price in the other currencies as decimal strings; a currency with no entry is one the plan cannot be bought in. The response also carries the base currency, the registered payment providers and the defaults a new plan opens with. The live count is what makes a delete decision honest, because a plan with subscriptions on it cannot be removed.

Get a subscription plan

GET /api/v1/kahunacart/subscription-plans/{id} Permission kahunacart.subscriptions.view

One plan with the variant it sells, the variant a renewal carries, its interval, trial, billing mode, grace period, price, label, stock setting, customer group, sort position, per-provider references and its live subscription count, plus the list of registered payment providers. On a store that sells in more than one currency it also has currency_amounts, the plan's own price in the other currencies. Read this before updating a plan so you know which fields are already set and whether anything is running on it.

Parameters

Name Type Description
id required integer Subscription plan id

List variants a plan can sell

GET /api/v1/kahunacart/subscription-plans/variants Permission kahunacart.subscriptions.view

Every purchasable variant in the catalog with its price in minor units, its product status, plan_ids listing every plan it already carries with the default first, and plan_id, the default plan, or null when it carries none. A variant can carry several plans, so an already-planned variant is still a valid choice for a second billing option; the ids are there so a Monthly is not created twice. Each row is named the way the storefront names it, product title then option combination then SKU, which is the only way to tell three sizes of one product apart on a catalog whose variant titles are empty. Capped at 500 rows, so narrow with q on a large catalog rather than paging.

Parameters

Name Type Description
q optional string Substring match on the product title, the variant title or the SKU

Create a subscription plan

POST /api/v1/kahunacart/subscription-plans Permission kahunacart.subscriptions.manage

Start selling an existing product variant on a calendar. Only variant_id is required and the product is resolved from it rather than trusted; everything else defaults to billing monthly, no trial, automatic billing mode, the store's grace period, the variant's price and enabled. A variant that does not exist is refused with 422. A variant that already carries a plan takes another as a second billing option, placed after the ones it has. Whatever the price, each subscription freezes what its customer actually paid, sale and coupon included, so it renews at that amount for the life of the agreement.

Parameters

Name Type Description
variant_id required integer The variant this plan sells, from list_plan_variants. A variant may carry several plans; which one a plan sells can never be changed afterwards
interval_unit optional string Months and years are calendar arithmetic with end-of-month clamping, so a monthly period starting on 31 January ends on 28 February; days and weeks are exact multiples of 86,400 seconds
One of: day, week, month, year
Default: month
interval_count optional integer How many units make one billing period; capped at 52, because 53 weeks is a typo for a year
Default: 1
trial_days optional integer Free days before the first invoice is due. The origin order is completed at a total of zero carrying a "Free trial" discount, and the subscription still renews at its full frozen amount, so a trial gives away the first period rather than repricing the agreement
Default: 0
billing_mode optional string auto uses provider mode when the payment method the customer picks can run a billing calendar and invoice mode when it cannot; provider always uses the platform's calendar and removes payment methods that cannot run one from checkout; invoice always has the store keep the calendar itself and email a pay link each period. Whichever it resolves to is frozen onto each subscription at creation, so changing this moves nobody who has already subscribed
One of: auto, provider, invoice
Default: auto
renewal_variant_id optional integer or null The variant a renewal order carries when it should not be the plan's own, which is how an introductory first period renews at the full price. Null means renewals carry the same variant as the purchase
grace_days optional integer or null Invoice mode only: how long an unpaid renewal keeps the subscription alive past the end of its period. Null uses the store setting, seven days by default. Nothing lapses during grace
price_minor optional integer or null What this billing option costs, in minor units. Null sells at the variant's own price, which is also where sales and price lists apply as usual. New subscribers only: each subscription renews at the amount it was sold at
currency_amounts optional object The plan's own price in the store's other currencies, only on a store that sells in more than one: currency code to {price} as a decimal string in that currency's own decimal places (unlike `price_minor`, which is minor units), for example {"EUR": {"price": "25.50"}, "JPY": {"price": "3900"}}. A plan with a price of its own can be bought only in the currencies that have an amount; an empty string or null removes one, and a key left out is left alone. A currency the store does not sell in, the base currency, too many decimals, or an amount on a plan with no `price_minor` is refused with a 422 and nothing is written. Clearing `price_minor` removes the plan's other amounts. Refused on a store that sells in one currency
label optional string or null The words on this option's choice on the buy button, such as "Quarterly, save $10 a month". Empty or null names it by how often it renews
stock_on_end optional string What happens to a subscription's place when it ends. keep leaves the unit used, so twenty places is twenty subscriptions ever; return puts it back on the variant's stock once the subscription has ended, never at a cancellation that is only scheduled
One of: keep, return
Default: keep
sort optional integer Position among the variant's billing options, lowest first. The first one on sale is what the buy button opens on
customer_group optional string or null The code of a customer group, such as members, that a customer is in for as long as their subscription on this plan is trialing, active or past due. It counts wherever a stored membership does: price lists, coupon conditions, member-only products and the site.customer_group.<code> permission a page's access header can ask for. Nothing is written to the group's member list, so the grant ends when the subscription stops being live. A code that is not an existing group is refused with 422; null or an empty string clears it
provider_refs optional object Per-provider price and product ids, as provider slug to a flat map, for example {"stripe": {"price": "price_123"}}. Accepted here because there is no plan to write them onto yet; afterwards use set_plan_provider_refs, which merges one provider at a time. Leaving them empty is usually right, since a driver that can create its own price does so on the first checkout and writes the id back
enabled optional boolean Disabled means "stop selling new subscriptions on this plan". Subscriptions already running on it keep renewing, because a renewal reads its interval off the plan whether or not it is still for sale
Default: true

Update a subscription plan

PATCH /api/v1/kahunacart/subscription-plans/{id} Permission kahunacart.subscriptions.manage

Change a plan's calendar. Only the fields sent are written, so switching one off does not have to resend the interval. The variant is not editable and sending a different variant_id is refused with 422: every running subscription froze its amount from that variant, and repointing the plan would silently change what those customers believe they are paying for. An interval unit nobody bills in is also a 422. Everything here applies to new subscribers only, including the billing mode, since each subscription carries its own frozen copy.

Parameters

Name Type Description
id required integer Subscription plan id
interval_unit optional string
One of: day, week, month, year
interval_count optional integer At least 1, at most 52.
trial_days optional integer New subscribers only; a running trial is unaffected
billing_mode optional string Frozen onto each subscription at creation, so this moves nobody already subscribed
One of: auto, provider, invoice
renewal_variant_id optional integer or null Null means renewals carry the plan's own variant
grace_days optional integer or null Invoice mode only; null uses the store setting
price_minor optional integer or null What this billing option costs, in minor units. Null sells at the variant's own price, which is also where sales and price lists apply as usual. New subscribers only: each subscription renews at the amount it was sold at
currency_amounts optional object The plan's own price in the store's other currencies, only on a store that sells in more than one: currency code to {price} as a decimal string in that currency's own decimal places (unlike `price_minor`, which is minor units), for example {"EUR": {"price": "25.50"}, "JPY": {"price": "3900"}}. A plan with a price of its own can be bought only in the currencies that have an amount; an empty string or null removes one, and a key left out is left alone. A currency the store does not sell in, the base currency, too many decimals, or an amount on a plan with no `price_minor` is refused with a 422 and nothing is written. Clearing `price_minor` removes the plan's other amounts. Refused on a store that sells in one currency
label optional string or null The words on this option's choice on the buy button, such as "Quarterly, save $10 a month". Empty or null names it by how often it renews
stock_on_end optional string What happens to a subscription's place when it ends. keep leaves the unit used, so twenty places is twenty subscriptions ever; return puts it back on the variant's stock once the subscription has ended, never at a cancellation that is only scheduled
One of: keep, return
Default: keep
sort optional integer Position among the variant's billing options, lowest first. The first one on sale is what the buy button opens on
customer_group optional string or null The code of a customer group, such as members, that a customer is in for as long as their subscription on this plan is trialing, active or past due. It counts wherever a stored membership does: price lists, coupon conditions, member-only products and the site.customer_group.<code> permission a page's access header can ask for. Nothing is written to the group's member list, so the grant ends when the subscription stops being live. A code that is not an existing group is refused with 422; null or an empty string clears it
enabled optional boolean Untick to stop selling the plan without touching anything already running on it. This is almost always what is wanted instead of delete_plan

Delete a subscription plan

DELETE /api/v1/kahunacart/subscription-plans/{id} Permission kahunacart.subscriptions.manage

Remove a plan. Refused with 422 while any live subscription is running on it, naming the count, because a subscription reads its interval off the plan every time the calendar advances and losing the row would leave the store unable to say when the next charge is due. Cancel those subscriptions or let them run out first. To stop selling a plan without touching what is running on it, send enabled false to update_plan instead.

Parameters

Name Type Description
id required integer Subscription plan id

Set a plan's provider references

PUT /api/v1/kahunacart/subscription-plans/{id}/provider-refs/{provider} Permission kahunacart.subscriptions.manage

Write one payment platform's price or product ids onto a plan, so a provider-mode checkout opens the agreement against a price created by hand at the platform. Stripe reads price, Polar reads product, and each driver's own documentation names what it wants. Merged rather than replaced, and merged for that provider only, so a store running two platforms cannot have one call wipe the other's ids: an empty object is a no-op, and a key sent as null is what clears one. Values must be single scalars, never nested. Filling these in is optional, because a driver that can create its own price does so on the first checkout and writes the id back here itself.

Parameters

Name Type Description
id required integer Subscription plan id
provider required string Payment provider slug, for example stripe or polar, from the providers list on get_plan
refs required object Flat map of reference name to a single scalar value, for example {"price": "price_123"}. A null value clears that one reference