FlexiLay works with any website that can make a server-side HTTP call — including sites built with Lovable. Your site creates a plan through the FlexiLay API and sends the customer to a FlexiLay-hosted payment page. Cards, deposits, instalments, retries and receipts are all handled by FlexiLay; your site never touches card data.
One-time setup, no code:
Register at my.flexilay.com/auth/register using your store's domain. Once your account is approved, connect Stripe and complete onboarding from the dashboard — payments settle directly into your own Stripe account.
Your API key (starts with fl_) is in the dashboard under
Settings → API Key. Treat it like a password.
Keep the API key server-side. In Lovable, store it as a backend secret and call FlexiLay from an edge function (Lovable Cloud / Supabase). Calls from the browser will fail (cross-origin blocked) and would expose your key.
When the customer chooses FlexiLay at your checkout, make one server-side request. Amounts are integer cents.
POST https://my.flexilay.com/api/plans/draft
X-Flexilay-Shop: store.example.com
X-Flexilay-Api-Key: fl_xxxxxxxx...
Content-Type: application/json
{
"amountCents": 25000, // $250.00
"currency": "AUD",
"customerName": "Alex Smith",
"customerEmail": "alex@example.com",
"customerMobile": "+61400000000",
"externalOrderId": "1001", // your order ID — echoed back in webhooks
"externalOrderName": "#1001",
"completeBy": "2026-12-01", // optional payment deadline — see below
"checkoutUrl": "https://store.example.com/checkout", // optional — enables "Cancel & return"
"lineItems": [
{ "title": "Product name", "quantity": 1, "priceCents": 25000 }
]
}
The response includes the hosted payment page URL:
{
"ok": true,
"planId": "ckw…",
"publicToken": "6f0c…",
"planUrl": "https://my.flexilay.com/storefront/plan/setup/6f0c…",
"completeBy": "2026-12-01" // null when no deadline applied
}
completeBy)
For date-sensitive orders — event tickets, bookings, hire — pass
completeBy as a YYYY-MM-DD date and FlexiLay limits the
customer's schedule options so every instalment falls on or before that
date. Frequencies that can't fit are hidden (e.g. monthly disappears on a
three-week deadline), the instalment slider is capped per frequency, and the
setup page shows the customer "This plan must be fully paid by …". If the date is
too close for any instalment plan, the customer is simply asked to
pay in full instead — the sale is never rejected.
null) for no deadline — the customer gets your normal schedule options exactly as before."completeBy": null. Your checkout can't fail over a bad date.// supabase/functions/create-flexilay-plan/index.ts
// Secrets: FLEXILAY_SHOP, FLEXILAY_API_KEY
Deno.serve(async (req) => {
const order = await req.json(); // validate & price the order server-side
const r = await fetch("https://my.flexilay.com/api/plans/draft", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Flexilay-Shop": Deno.env.get("FLEXILAY_SHOP")!,
"X-Flexilay-Api-Key": Deno.env.get("FLEXILAY_API_KEY")!,
},
body: JSON.stringify({
amountCents: order.totalCents,
currency: "AUD",
customerName: order.name,
customerEmail: order.email,
externalOrderId: order.id,
lineItems: order.items,
}),
});
const data = await r.json();
if (!data.ok) return new Response(JSON.stringify(data), { status: 502 });
// Send the browser to data.planUrl
return Response.json({ planUrl: data.planUrl, planId: data.planId });
});
Redirect the browser to planUrl. The customer picks their deposit,
payment frequency and number of instalments, then pays the deposit securely
(Stripe Elements, Apple Pay / Google Pay supported). After the deposit,
the plan activates and FlexiLay auto-charges the remaining instalments —
including retries, overdue reminders and receipts.
checkoutUrl)
Pass checkoutUrl when creating the plan and the payment page shows a
"Cancel & Return to your store" link at the top.
A customer who picked the wrong payment option isn't stranded: clicking it
confirms, cancels the unfunded draft, and sends them back to the URL you gave —
ideally your checkout with their cart intact. Without it, no link is shown and
the customer's only way back is the browser. Strongly recommended for every
checkout integration; must be an http(s) URL (anything else is
ignored).
There's also a merchant-level fallback: set a Checkout Return URL
in the dashboard under Settings and it's used whenever a plan
doesn't carry its own checkoutUrl. Handy as a safety net while your
integration doesn't send one yet — but the per-order URL is better, because it
can point at the customer's actual checkout session.
Prefer email instead of a redirect? Call
POST /api/plans/{planId}/send-setup-email with the same headers and
FlexiLay emails the customer their setup link.
If the customer doesn't finish setup, the draft expires quietly after
~24 hours — no reminder or cancellation emails are sent for
API-created plans, since an abandoned checkout usually means the customer moved
on or paid another way. Treat the order as unpaid until you see the deposit
confirmed (webhook status active).
In the FlexiLay dashboard under Settings → Status Webhook, enter a public
https:// URL on your site. Saving generates a signing secret
(shown next to the field). FlexiLay then POSTs to that URL on every plan status change:
POST https://your-site.com/flexilay-webhook
X-Flexilay-Event: plan.status
X-Flexilay-Shop: store.example.com
X-Flexilay-Signature: 3f2a… // HMAC-SHA256(raw body, signing secret), hex
{
"event": "plan.status",
"planId": "ckw…",
"externalOrderId": "1001",
"status": "active",
"currency": "AUD",
"totalAmountCents": 25000,
"remainingAmountCents": 20000,
"sentAt": "2026-08-05T09:30:00.000Z"
}
Always verify the signature before trusting the payload:
// supabase/functions/flexilay-webhook/index.ts — Secret: FLEXILAY_WEBHOOK_SECRET
Deno.serve(async (req) => {
const raw = await req.text();
const key = await crypto.subtle.importKey(
"raw", new TextEncoder().encode(Deno.env.get("FLEXILAY_WEBHOOK_SECRET")!),
{ name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
const mac = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(raw));
const expected = [...new Uint8Array(mac)]
.map((b) => b.toString(16).padStart(2, "0")).join("");
if (req.headers.get("x-flexilay-signature") !== expected)
return new Response("bad signature", { status: 401 });
const { externalOrderId, status, remainingAmountCents } = JSON.parse(raw);
// e.g. mark order 'paid' when a plan goes active (deposit collected),
// 'refunded/void' on cancelled, celebrate on completed 🎉
return new Response("ok");
});
Poll any time with the same auth headers — useful before fulfilling an order:
GET https://my.flexilay.com/api/plans/{planId}
// → { ok: true, plan: { status, remainingAmount, charges: [...] } }
GET https://my.flexilay.com/api/merchant/plans // list all your plans
| Plan status | Meaning |
|---|---|
draft | Created; customer hasn't completed setup yet |
scheduled / active | Deposit paid; instalments running — safe to treat the order as secured |
completed | Paid in full — release / ship the goods (lay-by) if you haven't already |
cancelled | Cancelled by merchant, customer, or after repeated failed payments |
abandoned | Setup never completed; the draft expired |
| Action | Request |
|---|---|
| Create a plan | POST /api/plans/draft |
| Email setup link | POST /api/plans/{id}/send-setup-email |
| Plan status + charges | GET /api/plans/{id} |
| List plans | GET /api/merchant/plans |
| Cancel a plan | POST /api/plans/{id}/cancel |
| Refund a charge | POST /api/plans/{id}/refund |
Base URL https://my.flexilay.com · every request carries
X-Flexilay-Shop + X-Flexilay-Api-Key · amounts in integer cents ·
currency defaults to AUD if omitted ·
completeBy (YYYY-MM-DD, optional) caps the schedule at a payment deadline ·
checkoutUrl (optional) adds a "Cancel & return" link to the payment page.
Testing: turn on demo mode in the FlexiLay dashboard (Settings → Test Mode) — you can run the full checkout flow end-to-end without real charges.