Skip to content
Gaurav TiwariDocs

Pay What You Want, Free Orders, Bundles and Order Bumps

Version 0.2.0 Updated November 15, 2026

Four ways to sell beyond a fixed price: plans where the buyer picks the price, free products with abuse controls, bundles of several products sold as one, and order bumps on the checkout page. This page covers how each works, what the buyer sees, and what is refused and why. None of them needs a module.

Pay what you want

A plan can use pricing: "pwyw" instead of a fixed price. You set three amounts:

  • Minimum (minPriceMinor): the lowest amount accepted. It can be 0.
  • Suggested (suggestedPriceMinor): prefilled at checkout. It is also the plan’s list price (priceMinor is kept equal to it), so the storefront, bundle shares and upgrade credit all read it.
  • Maximum (maxPriceMinor): optional. Leave it empty for no maximum.

Set these on the product’s Pricing tab in the admin, or with commerce/plans/save.

At checkout

  • The checkout page shows an amount field, prefilled with the suggested price, with the minimum and maximum spelled out. In a cart, each pay-what-you-want line has its own field.
  • The amount goes to the server as the cart line’s customAmountMinor (or top-level customAmountMinor for the one-product shorthand). It is in minor units, like every amount in the API.
  • An amount below the minimum is refused with PWYW_BELOW_MINIMUM, and one above the maximum with PWYW_ABOVE_MAXIMUM. Vendwick never clamps an amount. The buyer must see and accept the price they pay.
  • The amount is frozen on the quote line (unitAmount, pricing: "pwyw") and on the checkout’s terms (unitMinor). Changing the plan later does not change a checkout that already started.
  • A pay-what-you-want line always sells one unit.
  • A fixed-price plan ignores customAmountMinor and sells at its price.

Coupons and tax

  • Coupons never discount a pay-what-you-want line, so a 100% coupon cannot take the price below the minimum. A coupon on a cart still discounts its other lines.
  • Exclusive tax is added on top of the chosen amount. With tax-inclusive prices, the tax sits inside the chosen amount, as it does for every price in such a store.
  • In plan-price terms (the line amount minus its discount), a line is never below the minimum, whatever the tax mode.

What is refused

RefusedWhy
Automatic renewal (recurring) on a pay-what-you-want plan, at plan saveA provider subscription needs one fixed price to charge every term. Not in 0.2.0.
A suggested price below the minimum, or a maximum below the suggested priceThe checkout could not prefill a valid amount.
Upgrading a license to a pay-what-you-want plan (to_pwyw)Proration needs a price to compare against.
A pay-what-you-want plan as an order bumpThe bump price is already the offer.

Upgrading from a pay-what-you-want plan works. The credit is based on min(suggested price, amount paid).

A plan with a term (a yearly pay-what-you-want plan, for example) renews by hand at the amount the buyer last paid for that license: its newest paid renewal, else the purchase. The amount stays within the plan’s current bounds, so it is never below today’s minimum (or above today’s maximum). The renewal quote and checkout show that amount. A license with no known payment renews at the suggested price.

Storefront

Pay-what-you-want plans in storefront/products and storefront/product carry pricing: "pwyw", min_price, suggested_price and max_price (major units, like price; max_price is null without a maximum), plus the same three in minor units (min_price_minor and so on). price_minor is the suggested price. Fixed-price plans are unchanged.

<Price> and <Product> show "From $5.00" for a plan with a minimum, or "Pay what you want" for a minimum of 0, followed by the suggested price.

Free checkouts

A checkout whose total is 0 skips the payment provider. Free plans, a pay-what-you-want minimum of 0, and a 100% coupon all lead here. Because nothing is charged, free purchases get their own limits:

  • Rate limits: 3 free checkouts per hour per IP address, and 5 per day per email address. Over the limit, the start answers RATE_LIMITED (429).
  • One free grant per email and plan: the second free checkout of the same plan by the same email is refused with FREE_ALREADY_CLAIMED (409). A free checkout that fails or expires gives its grant back. A free plan you issue from the admin (Issue license, commerce/orders/issue-license) uses up that email’s grant for the plan too, so the customer cannot then take it again from the storefront. Tick Does not count as the customer’s free claim (countsAsFreeClaim: false) to leave it unused. Paid plans you issue never touch the grant.
  • One unit of each item: a free checkout sells one of each item, and refuses the same plan on several lines ("A free checkout includes one of each item.").
  • Aliases share one grant: the grant and the per-email limit use the mailbox behind the address: a +tag is ignored, and for Gmail the dots and googlemail.com are too. So ada+free@gmail.com and a.da@googlemail.com share ada@gmail.com‘s grant. The order still goes to the address the buyer typed.
  • Limited coupons: an unconfirmed free checkout (see below) holds no use of a coupon with a limit. The use is taken when the buyer confirms the email; if the coupon ran out meanwhile, the confirmation is refused with the coupon’s message and "Start the checkout again."
  • Turnstile: when Turnstile keys are configured, every purchase needs a passed check, free ones included.

Email confirmation

The setting Confirm the email before delivering free orders (free_checkout_verify_email) is on by default. With it on:

  1. The buyer completes a free checkout. The checkout stays pending, and the page says "Check your email".
  2. The buyer gets the "Confirm your email" message (template free_confirm) with a signed link. The link works for 48 hours.
  3. Opening the link shows a confirmation page with a button. Opening the link alone confirms nothing, so mail scanners that follow links cannot trigger it.
  4. Pressing the button settles the checkout and delivers the order: receipt, license keys and account link, as usual.

The link works once. Using it again shows "Already confirmed". A link that was changed or has expired is refused, and an unconfirmed checkout expires and gives its free grant back.

A buyer who is signed in to the portal with the same email gets the free order at once. Orders you issue from the admin (Issue license) and orders from the Core Forms bridge are always delivered at once.

Changed from 0.1.0: free plans were delivered as soon as the buyer submitted the checkout. In 0.2.0 they wait for the email confirmation unless you turn the setting off (the Free orders section of Store settings, General).

Bundles

A bundle is a product type whose plans list other products and plans (grants: [{ productId, planId }]). One purchase of the bundle plan delivers every one of them.

Contents

Set a bundle’s contents on its Bundle contents tab: pick a product and plan, then reorder or remove items. The same tab sets the bundle’s term ("Licenses run for") and automatic renewal.

The rules, checked when the plan is saved and again whenever the bundle is priced:

  • At most 20 items, each plan once.
  • Each item must be an active licensed product (plugin, theme, EmDash plugin, digital product) or a service, with an active plan of that product.
  • No bundle inside a bundle, no physical products, no events.

If an item is archived after the bundle was saved, the bundle cannot be sold (bundle_child_archived, 409) until you edit its contents. A checkout that already started keeps the items frozen on its terms. An empty bundle cannot be sold (bundle_empty).

Price, tax and invoices

A bundle is one line: it is priced, discounted, taxed and invoiced as one row. The receipt, the invoice and the customer portal list its contents beneath it ("Includes: My Plugin Pro").

What the buyer gets

The order has one item for the bundle and one child item for each product in it (parentItemId points at the bundle’s item). Child items have totalAmountMinor: 0, and their netMinor is their share of the bundle’s net amount, weighted by each item’s list price. The shares always add up to the bundle’s net exactly.

Each licensed item gets its own license:

  • with its own productId and planId, so downloads, npm access, page access and the license API work exactly as for a license bought on its own;
  • with orderItemId set to its child item, and bundleItemId set to the bundle’s item;
  • with the site limit of its own plan, and the term (period, trial or license length) of the bundle plan.

Renewals

A bundle plan can have a term and can renew automatically. One subscription renews the whole bundle: its row sits on the first licensed item’s license, with the bundle’s product and plan. A renewal, whether the buyer renews by hand from any of the bundle’s licenses, you renew it from the admin, or the provider charges the subscription, extends every license of the bundle once, at the bundle’s price. The renewal order item lists the licenses it extended (groupLicenseIds).

  • Automatic renewals on a bundle plan (the Automatic renewals box on its contents tab) sells the bundle as a provider subscription, like a single license: the provider renews it, a renewal refund rewinds the whole bundle, and past due, cancel and resume leave the licenses as they are. The bundle must grant at least one licensed product (it is that license’s subscription).
  • Quantity above 1: a buyer can take several copies of a bundle with a term. Each copy is its own group (its licenses carry bundleUnit 0, 1 and so on) with its own subscription row, and renews on its own: renewing one copy charges one bundle price and extends only that copy’s licenses. A subscription renews one unit, so several copies are bought as a one-time purchase and each copy renews by hand, even when the plan has Automatic renewals on.

Refunds and upgrades

  • A refund line targets the bundle’s item, not a child item. Revoking it disables every license of the bundle. A full refund disables them all, as with any order.
  • A partial refund of another item in the same order leaves the bundle’s licenses alone.
  • Refunding a bundle renewal in full rewinds every license that renewal extended, not only the one that holds the subscription. Each goes back to the expiry it had before the renewal (the renewal order item stores each license’s previous and new expiry in group). A license whose expiry moved in step with the holder since (a later renewal) takes the holder’s rebuilt term; any other change is left alone and logged as bundle_member_not_rewound.
  • A license that came with a bundle cannot be upgraded on its own (bundle_member).

Order bumps

An order bump is an optional extra offered on the checkout page at its own price, such as "Add priority support for a year". Manage bumps in the Order bumps section of Store settings, General (or with the save-order-bump MCP tool): the product and plan it sells, its price (blank for the plan price), a headline and description, the products it is shown for, and its status.

When a bump is offered

  • The cart holds a product it is shown for (showFor), or any product when that list is empty.
  • The bump and its product and plan are active.
  • Its product is not in the cart already.
  • The cart is priced in the bump plan’s currency, and adding the bump keeps the cart valid. A bump never adds a second subscription.

The page shows up to three bumps as checkboxes. Ticking one adds it to the order. Without JavaScript, press "Update total" first.

How the server checks a bump

A ticked bump is sent as a cart line with source: "bump". The server checks again that an active bump for that product and plan applies to the other lines, then freezes the bump price on the line (unitAmount, and unitMinor, source and bumpId on the terms). A request with a bump line whose trigger is not in the cart, for an archived bump, or that would break the cart rules is refused with BUMP_UNAVAILABLE (409). A hand-made request cannot get a bump price.

If a bump stops being offered while the buyer is on the page, the page says so and asks them to continue without it. A ticked bump is never dropped silently.

Coupons

A coupon discounts a bump line only when its product filter names the bump’s product. A coupon for every product leaves bump lines at their bump price.

API reference

Requests

  • checkout/quote, internal/checkout/page, internal/checkout/start: each line takes customAmountMinor (pay what you want) and source: "bump" (order bumps). The shorthand takes a top-level customAmountMinor.
  • internal/checkout/page answers bumps[] (id, headline, description, product, plan, priceMinor, listPriceMinor, display) and problems[] (lineIndex, code, message) for an amount or a bump the page could not price as asked. Each item in items[] can carry pwyw (min, suggested, max, display), source: "bump", and includes[] for a bundle.
  • internal/checkout/start can answer { kind: "free", fulfilled: false, next: "confirm-email" }.
  • internal/checkout/confirm-free (checkoutId, issued, token): answers { status: "confirmed", fulfilled, orderNumber } or { status: "used", orderNumber }. An invalid or expired link answers 403.
  • commerce/plans/save takes pricing, minPriceMinor, suggestedPriceMinor, maxPriceMinor (null clears it) and, for bundle plans, grants.
  • commerce/bumps/list, commerce/bumps/save, commerce/bumps/delete.

Error codes

PWYW_BELOW_MINIMUM, PWYW_ABOVE_MAXIMUM, PWYW_INVALID_AMOUNT (400), BUMP_UNAVAILABLE (409), FREE_ALREADY_CLAIMED (409), bundle_empty, bundle_child_archived (409), bundle_nested, bundle_child_type, bundle_child_missing, bundle_child_plan, bundle_too_many, invalid_pwyw, invalid_bundle, invalid_bump (400).

Records

  • Plans: pricing, minPriceMinor, suggestedPriceMinor, maxPriceMinor, grants.
  • Quote lines: pricing: "pwyw", source: "bump".
  • Purchase terms lines: unitMinor, grants (frozen products and plans), source: "bump", bumpId. Renewal terms: groupLicenseIds.
  • Order items: bundle children carry parentItemId, totalAmountMinor: 0 and their netMinor share. The bundle item’s fulfillmentData.contents names them. A child’s fulfillmentData.bundle names the bundle.
  • Licenses: bundleItemId on bundle children.
  • Claims: free-grant:<emailKey>:<planId>.
Share

Was this helpful?

Still stuck? Open a support ticket