Integrations › iDEAL subscriptions on a headless store
iDEAL subscriptions on a headless store
For stores built on the Storefront API, for example with Lovable. Your store sends its cart to Progus and gets a ready checkout with iDEAL and the subscription price back.
How it works
When a buyer clicks "Pay with iDEAL", your store sends its cart to the Progus API. The API checks the products and subscription plans with Shopify and returns a ready checkout with iDEAL and the subscription price. After payment, Progus creates the subscription and bills the next deliveries through Mollie. You do not set up selling plans or discounts yourself.
What to do
- Enter your shop domain below, copy the text at the bottom of this page and add it to your AI builder (for example the Knowledge of your Lovable project) or hand it to your developer.
- Then ask, for example:
Following the Progus instructions, add a "Pay with iDEAL" button next to the existing checkout button and handle the status after the order as described.
- Place one real iDEAL order and let us know, so we can check that the subscription was created.
Good to know
- Always send the chosen plan (
sellingPlanId) with a subscription line. Without it, the line is an ordinary one-time purchase. - Some carts cannot be paid with iDEAL as a subscription, for example plans paid in advance for several deliveries or plans from another app. The API sends those to your normal checkout automatically.
- Your cart is emptied only after the order is really paid. If the buyer comes back without paying, everything stays.
- If the API cannot be reached, the buyer goes through your normal checkout, so no order is lost.
- A Storefront token of your own is only needed when your products are published only to your headless channel.
Your shop domain
Technical instructions. Paste them unchanged.
# iDEAL subscriptions on a headless Shopify store (Progus Subscriptions)
Your store sells subscriptions with Progus Subscriptions. Subscribers who pay with iDEAL,
Bancontact or Wero are billed by Progus through Mollie. Shopify's own checkout cannot start
these subscriptions by itself, so the iDEAL button must go through the Progus API below.
Card subscriptions keep working through your normal Shopify checkout.
## 1. Show subscription options (standard Shopify)
Query `sellingPlanGroups` on the product (Storefront API) and let the buyer pick a plan.
Keep the chosen `sellingPlanId` on the cart line, exactly as for any Shopify subscription.
If your Storefront token cannot read selling plans (Shopify answers ACCESS_DENIED for
`sellingPlanGroups` or `sellingPlanAllocations`; the token the Lovable app creates is one),
read them from Progus instead, with the product id or handle:
GET https://subscriptions-app.progus.com/storefront/v1/plans?shop=your-shop.myshopify.com&product=<product id>&country=NL
The answer lists `variants`, each with its `sellingPlans` (`id`, `name`, `price`).
Send that `id` as `sellingPlanId`. The plan ids in the Progus admin URL are a different id.
## 2. Add a "Pay with iDEAL" button next to your checkout button
On click, send the cart lines to Progus and redirect to the returned URL.
Do NOT clear your cart here: the buyer may come back from checkout.
const API = "https://subscriptions-app.progus.com/storefront/v1";
const SHOP = "your-shop.myshopify.com";
async function payWithIdeal(cart) {
const res = await fetch(`${API}/checkout?shop=${SHOP}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
paymentMethod: "ideal",
lines: cart.lines.map((l) => ({
lineId: l.id, // your own cart line id, echoed back
merchandiseId: l.variantId, // ProductVariant GID or numeric id
quantity: l.quantity,
sellingPlanId: l.sellingPlanId, // omit for one-time items
attributes: l.attributes, // e.g. engraving; optional
})),
buyerIdentity: { countryCode: "NL" },
}),
});
const data = await res.json();
if (res.status === 409) {
// Shopify changed the cart (e.g. not enough stock): show data.adjustments
// to the buyer and let them fix the cart. Do not redirect.
return showCartAdjustments(data.adjustments);
}
if (!res.ok) throw new Error(data.message);
// Remember exactly what went to checkout; the cart itself stays as it is.
localStorage.setItem(
"progusPendingCheckout",
JSON.stringify({ checkoutId: data.checkoutId, lines: data.lines }),
);
window.location.href = data.checkoutUrl;
}
`attributes` is a list of `{ key, value }` pairs, e.g. `[{ key: "Engraving", value: "Anna" }]`.
A 409 answer carries `adjustments`, a list of `{ line, requestedQuantity, quantity, code }`,
where `line` is the index into the `lines` you sent.
## 3. After a confirmed purchase, remove only what was bought
On every page load, if a pending checkout is stored, ask Progus for its status. Only
`completed` means an order exists. Then remove exactly the lines that went to that checkout:
match on `lineId`; if a line has none, match on the same variant AND the same selling plan AND
the same attributes (compare ids as Progus returned them: full GIDs). Subtract the quantity.
Anything the buyer added afterwards stays. `open` or `unknown`: change nothing.
Apply each checkout only once: mark it as applied BEFORE changing the cart, so a second tab
or a reload never subtracts the same lines twice.
const pending = JSON.parse(localStorage.getItem("progusPendingCheckout") || "null");
if (pending && !pending.applied) {
const res = await fetch(
`${API}/checkout/status?shop=${SHOP}&checkoutId=${encodeURIComponent(pending.checkoutId)}`,
);
const { status } = await res.json();
if (status === "completed") {
localStorage.setItem(
"progusPendingCheckout",
JSON.stringify({ ...pending, applied: true }),
);
removePurchasedLines(pending.lines); // by lineId, else variant + plan + attributes
localStorage.removeItem("progusPendingCheckout");
}
if (status === "unknown") localStorage.removeItem("progusPendingCheckout");
}
Do not build the iDEAL cart yourself and do not add `_progus_*` attributes: Progus decides per
cart whether iDEAL is possible (prepaid plans, other apps' plans and some currency setups go to
the normal checkout automatically; `route` and `reason` tell you which one was used).
## 4. Optional
- One-product "Subscribe now" link without a cart:
`https://subscriptions-app.progus.com/storefront/v1/subscribe?shop=your-shop.myshopify.com&variant=<id>&plan=<id>&quantity=1`
The link cannot carry a `storefrontAccessToken`: a store whose products are published only to
its headless channel must use the POST call above, with the token.
- If the API answers `variant_not_found`, your products are published only to your headless
channel: add `"storefrontAccessToken": "<your PUBLIC Storefront token>"` to the body.
`invalid_storefront_token` means the token is not a public Storefront API token.
- Errors: 429 (too many requests) or 503 (temporarily unavailable): send the buyer to your normal checkout instead.
## 5. Test before launch
Place one real iDEAL order. The checkout must show iDEAL and the subscription price, the
thank-you page shows "Manage your subscription", and the purchased items (only those) are gone
from your cart the next time you open the store. Also start a checkout, go back without
paying, and check that your cart is unchanged.
