Champagne

Plans

List and manage club plans synchronized with a connected Stripe account.

Stripe provider failures return a masked INTERNAL_SERVER_ERROR with a server-generated extensions.requestId. Use that ID to correlate the sanitized server diagnostic. Expected business validation errors keep their public codes; provider messages and payloads are never returned to clients.

The plans query returns active plans. Pass clubId to list only plans mapped to the selected Stripe merchant for that club. Omitting it retains the global active catalog used by the public application flow. Plans replaced by a split are omitted unless includeReplaced: true; the back-office Plans page requests them.

query Plans($clubId: ID) {
  plans(clubId: $clubId) {
    id
    name
    description
    amount
    currency
    interval
    type
    stripeId
    subscriberPrices {
      amount
      subscribers
    }
  }
}

The response is a non-null list with non-null entries:

{
  "data": {
    "plans": [
      {
        "id": "60000000-0000-4000-8000-000000000001",
        "name": "Adhésion 51 IÉNA (2026) - Abonnement Membre 2026",
        "description": "Adhésion annuelle donnant accès aux services et événements réservés aux membres.",
        "amount": 180000,
        "currency": "eur",
        "interval": "year",
        "type": "subscription",
        "stripeId": "champagne_iena_membre_2026",
        "subscriberPrices": [
          { "amount": 150000, "subscribers": 4 },
          { "amount": 180000, "subscribers": 12 }
        ]
      }
    ]
  }
}

Return fields

FieldTypeMeaning
idID!Internal UUID for the plan and membership relationship.
nameString!Display name.
descriptionStringOptional plan description.
amountInt!Nonnegative price: currency minor units for memberships, credit hundredths for add-ons, euro cents for credit packs.
currencyString!Lowercase three-letter currency code.
intervalString!Billing interval: month or year.
creditAmountIntFixed pack quantity in hundredths of a credit; required for credit packs, null for other plans.
typePlanType!subscription, addon, or credit_pack.
stripeIdStringOptional catalogue reference; not a universal connected Product/Price ID.
subscriberPrices[PlanSubscriberPrice!]Memberships with a Stripe subscription and a running paid period on this plan, grouped by the amount they currently pay and ordered by amount. Populated by plans for staff only; null without a staff session, on plans returned by mutations or nested in memberships.
replacedByPlanReplacement{ id, name } of the plan that replaced this one in a split; null otherwise. Populated by plans.

Stripe price references belong to memberships rather than plans. See the person query for membership fields. The back office converts amount from minor units for display. The back office always supplies its selected club ID. The editor offers searchable, localized currency names from Stripe's card presentment currency list; availability can still depend on the connected account and payment method. It preserves the entered major-unit price when changing the currency and rejects unsupported fractional amounts rather than rounding them. For example, 1,200 JPY is sent as amount: 1200, while 12 EUR is sent as amount: 1200. Stripe's ISK and UGX compatibility rules still use a factor of 100 but only permit whole major-unit amounts.

The back-office table can sort its returned plans by name, type, amount, or billing interval in either direction. This sorts the loaded list (up to 100 plans); the GraphQL plans query still returns its default name/ID order.

Create and update

mutation CreatePlan($input: PlanInput!) {
  createPlan(input: $input) {
    id
    name
    amount
    currency
    interval
    type
  }
}

PlanInput contains clubId, a retry-stable UUID requestId, name, optional description, nonnegative minor-unit amount, lowercase three-letter currency, interval (month or year) and type (subscription, addon, or credit_pack). Names are trimmed and limited to 200 characters; descriptions are limited to 1,000 characters; amounts cannot exceed 100000000 minor units.

createPlan creates a Product and recurring Price on the club's selected connected Stripe account before saving their local mapping. Retrying the same request uses the same Stripe idempotency keys and local plan ID.

mutation UpdatePlan($id: ID!, $input: PlanInput!) {
  updatePlan(id: $id, input: $input) {
    id
    name
    amount
    currency
    interval
  }
}

updatePlan updates the plan's name and description and its Stripe Product. A plan's currency, interval and type are fixed once it exists, and so is a subscription's amount: an update that changes any of them returns a BAD_USER_INPUT error before any Stripe call, and the back-office editor only shows those fields when creating a plan. To give a subscription a new price, split it.

Add-ons and credit packs keep an editable amount. Stripe Prices are immutable, so a new amount creates a replacement Price, updates the local mapping and archives the previous Price. Subscriptions keep renewing on the Price they were created with: every Price created for a plan is recorded, and renewals are validated against the Price the member is subscribed to.

Fields that cannot be updated

Configuration tools that compare a declared plan with the stored one cannot call updatePlan when one of these fields changes:

FieldSubscriptionAdd-on, credit pack
amountSplit the planUpdated in place, with a new requestId
typeDelete, then createDelete, then create
currencyDelete, then createDelete, then create
intervalDelete, then createDelete, then create

name, description and a credit pack's creditAmount are always updated in place. An in-place amount change creates a Stripe Price under the update's requestId, so that id must differ from the ones already used for the plan.

A subscription changes price with splitPlan, passing its current amount as subscriberAmount: its subscribers keep the plan and the price they pay, and the new plan takes its place for everyone else. A change of type, currency or interval needs the plan deleted and created again, which is refused while members hold it.

Read one plan

query Plan($clubId: ID!, $id: ID!) {
  plan(clubId: $clubId, id: $id) {
    id
    name
    description
    amount
    creditAmount
    currency
    interval
    type
  }
}

Returns one plan offered by the club's selected merchant, including a plan replaced by a split, or null. Like plans, it is public. Both ids must be UUIDs. subscriberPrices is null unless the caller manages the catalog of that club.

Split a plan

Splitting gives new and current subscribers different prices. The split plan keeps its name, ID and memberships and is reserved to its current subscribers; a new plan with a name chosen by staff takes its place for everyone else.

mutation SplitPlan($input: SplitPlanInput!) {
  splitPlan(input: $input) {
    plan {
      id
      name
      amount
    }
    previous {
      id
      name
      amount
    }
    scheduled
  }
}
{
  "input": {
    "clubId": "51000000-0000-4000-8000-000000000051",
    "planId": "60000000-0000-4000-8000-000000000001",
    "requestId": "7f7a2b3c-1d2e-4f50-8a6b-9c0d1e2f3a4b",
    "name": "Adhésion 51 IÉNA (2027) - Abonnement Membre",
    "amount": 200000,
    "subscriberAmount": 190000
  }
}

SplitPlanInput contains clubId, the subscription planId to split, a retry-stable UUID requestId (also the new plan's ID), the new plan's trimmed name (1–200 characters) and optional description, and two minor-unit prices (0–100000000): amount for new subscribers and subscriberAmount for current subscribers. Currency and interval are copied from the split plan. Only subscription plans can be split, and a plan can be split once; retrying the same requestId returns the same result.

splitPlan returns the new plan, the retained previous plan and the number of current subscribers whose price change was scheduled. It:

  • creates a Stripe Product and Price for the new plan and maps it to the club's merchant;
  • keeps the split plan's name, sets its amount to subscriberAmount and links it to the new plan through replacedBy. When subscriberAmount differs from its current amount, a new Price is created on the split plan's Product;
  • replaces the split plan with the new plan in every PLAN_SELECTION block of the club's onboardings and increments their revision. Blocks offering all plans need no change;
  • hides the split plan from plans (unless includeReplaced: true), from membershipOffers, from Checkout and from application plan selection;
  • schedules the price change for current subscribers, as described below, and records plan.split in staff activity with replacedBy, newSubscriberAmount, subscriberAmount, subscribersRepriced and subscriberPriceFrom.

Current subscribers are memberships of the split plan with a Stripe subscription, a paid period that has not ended, and a Price in the same currency and interval. Members already paying subscriberAmount are left unchanged. The others get the new Price scheduled one calendar month later and receive a renewal notification (in-app and email, per their preferences) with the old and new amounts and the renewal date from which the new price is charged.

The new price applies from the member's next payment, or one month after the split if that payment is sooner. The notification states that date. A background worker switches each subscription item to the scheduled Price once the notice month has passed:

  • if the subscription has not renewed since the split, the switch uses proration_behavior: "none" and the next renewal charges the new price;
  • if it renewed during the notice month (at the old price), the switch uses proration_behavior: "always_invoice", so Stripe immediately invoices the prorated difference for the rest of the current period (a credit when the price goes down). Proration invoices do not change the membership's paid period.

Subscriptions that have ended in Stripe are dropped from the schedule.

Applications that already selected the split plan but have not paid can no longer check out with it.

Delete

mutation DeletePlan($id: ID!, $clubId: ID!) {
  deletePlan(id: $id, clubId: $clubId)
}

A plan members still hold cannot be deleted: the mutation answers BAD_USER_INPUT with Members still hold this plan. End or move their memberships before deleting it. and archives nothing. A member holds a plan while their membership has no end date, has not expired, or is inside a paid period; ended memberships do not count. This applies to a plan replaced by a split too, for as long as its subscribers stay on it.

Otherwise deletion archives the connected Price and Product, removes the merchant mapping and hides the local plan. Historical plans referenced by memberships or checkout attempts remain stored. If several clubs share the same merchant profile, they share its plan catalog too.

Ordering and limits

Plans are ordered by name and then ID. The query returns at most 100 records and does not accept sorting or pagination arguments. An empty catalog returns [].

The plan query is public for application flows. Plan mutations (createPlan, updatePlan, deletePlan and splitPlan) require a staff session that administers the selected club or holds its catalog_manage permission; see staff permissions. Mutations require configured sandbox Stripe billing and a selected merchant. Checkout resolves active subscription Product/Price mappings by club → selected merchant → local plan. Use membershipOffers(clubId) for purchasing readiness and offers.

Club credits

See Club credits for credit packs, balances, refill history, credit-priced add-ons and reservations, and cancellation refunds. Membership subscriptions stay billed in money.

Credit packs require a positive EUR amount and create a one-time Stripe price. Their stored interval is ignored for billing. Rate settings determine the credits granted, snapshotted when Checkout starts. Add-ons are also purchased once with credits; their interval is not a recurring charge.

Credit packs require creditAmount in PlanInput (1–100,000,000 hundredths). For example, amount: 10000, creditAmount: 125000 sells 1,250 credits for €100. Changing the club rate does not change this quantity.

On this page