Installments & BIN
As soon as the buyer types the first digits of their card, you can identify the card (brand, card family, issuing bank) and show a real installment table — including any installment surcharge — before the payment is submitted. The centerpiece is quote binding: when you request a specific installment option for a known BIN, the response carries a quoteToken, and the payment charges exactly the amounts that quote displayed. No drift between what the buyer saw and what their card was charged.
All amounts are minor-unit (kuruş) values: request amount is an integer, and quoted prices come back as minor-unit strings — "totalPrice": "10500" means ₺105,00. Never send decimals or floats.
How it works
Identify the card
When 6–8 digits are available, call BIN info to learn brand, family and bank — and whether the card is installment-eligible.
Fetch the installment table
Call BIN installments with the amount (and the BIN, once known). You get the available options (1×, 3×, 6×…), each with its per-month and total amounts.
Display the table
Render the options exactly as returned. The totals already include any installment surcharge — never recompute them client-side.
Bind the chosen option
When the buyer picks an option greater than 1×, call BIN installments again with binOrPan and the chosen installment — the response now has binding: true and a signed quoteToken. Pass it as installmentQuoteToken in the payment request (Card — 2D or Card — 3D Secure). The platform charges the quoted amounts — displayed == charged.
Server-side endpoints (Bearer)
These run on your backend with your at_live_… access token.
POST /bin/info — card lookup
Send the first 6 or 8 digits. If you accidentally send a longer PAN, only the prefix is used and the number is not stored.
curl https://api-gateway.lunixi.com/api/v1/bin/info \
-H "Authorization: Bearer at_live_5f3e2b1a..." \
-H "Content-Type: application/json" \
-d '{ "binOrPan": "54000000" }'{
"status": "success",
"code": "BIN_FOUND",
"data": {
"found": true,
"binNumber": "54000000",
"bankName": "Example Bank",
"bankCode": "0001",
"cardBrand": "MASTERCARD",
"cardFamily": "World",
"cardProgram": "",
"cardType": "CREDIT",
"cardLevel": "CLASSIC",
"force3d": false,
"isCommercial": false,
"supportedInstallment": true,
"enabledInstallments": [2, 3, 6, 9]
}
}An unknown BIN does not fail the call: you get code: "BIN_PATTERN_FALLBACK" with found: false and brand-pattern-derived fields, so the form never breaks.
GET /bin/card-taxonomy — the reference lists
Returns the live lists of brands, card families and banks the platform knows. Use it to build dropdowns, map BIN results to your own display names, or validate configuration — instead of hardcoding lists that go stale.
curl https://api-gateway.lunixi.com/api/v1/bin/card-taxonomy \
-H "Authorization: Bearer at_live_5f3e2b1a..."POST /bin/installments — installment table & binding quote
Send the amount (or an intentId); optionally the BIN. Two modes, one endpoint:
- Indicative table —
amount(+ optionalbinOrPan): returns the offered options withbinding: falseand noquoteToken. - Binding quote —
binOrPanand a singleinstallment > 1: returnsbinding: trueplus a signedquoteTokenandexpiresAt.
The format parameter controls the shape of the result:
format | Returns |
|---|---|
data (default) | Raw JSON options — you render your own UI |
html | Adds a ready-to-embed HTML installment table (html field) |
mobile | A payload shaped for native mobile rendering |
curl https://api-gateway.lunixi.com/api/v1/bin/installments \
-H "Authorization: Bearer at_live_5f3e2b1a..." \
-H "Content-Type: application/json" \
-d '{
"binOrPan": "54000000",
"amount": 10050,
"installment": 3,
"format": "data"
}'{
"status": "success",
"code": "INSTALLMENT_QUOTE_ISSUED",
"data": {
"binding": true,
"quoteToken": "iq_5b8d03aa…",
"expiresAt": "2026-07-06T12:20:00Z",
"methods": {
"CARD": {
"installmentDetails": [
{
"cardFamily": "World",
"bankName": "Example Bank",
"installmentPrices": [
{ "installment": 1, "totalPrice": "10050", "monthlyPrice": "10050", "offered": true, "markupPct": null },
{ "installment": 3, "totalPrice": "10500", "monthlyPrice": "3500", "offered": true, "markupPct": "4.48" },
{ "installment": 6, "totalPrice": null, "monthlyPrice": null, "offered": false, "markupPct": null }
]
}
]
}
}
}
}totalPrice / monthlyPrice are minor-unit amounts as strings; an option that is not offered comes back with offered: false and null prices (never a misleading computed price). markupPct is the surcharge label to display ("+%X vade farkı") — the prices already include it. Multi-installment totals can exceed the base amount — that difference is the installment surcharge, already priced into the quote.
In-form endpoints (checkout form token)
Inside the hosted checkout form session, the same two lookups exist under /form/…, authenticated with the checkout form token — sent in the request body as the token field, not as a header — so the buyer's browser can call them directly:
| Endpoint | Same as | Difference |
|---|---|---|
POST /form/bin/info | /bin/info | Body-token auth (token field) |
POST /form/bin/installments | /bin/installments | Body-token auth (token field); merchant, intent and amount are resolved from the token (the body carries only binOrPan, paymentMethod, installment); no html format |
// In the buyer's browser, inside the checkout session
const res = await fetch(
'https://api-gateway.lunixi.com/api/v1/form/bin/installments',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: checkoutFormToken, // auth — from POST /payments/intents
binOrPan: '54000000',
installment: 3, // omit for the indicative table
}),
},
);
const quote = await res.json();Quote binding — displayed == charged
Installment pricing is a promise to the buyer, so the platform makes it contractual:
- The quote you fetch is computed against the live provider credentials and surcharge configuration at that moment, and sealed under a
quoteTokenwith an expiry. - You display the options from that quote — never amounts you computed yourself.
- The payment request (
form/2d,form/3d,direct/payment) carries the token back asinstallmentQuoteToken. - At charge time the platform validates the token and charges exactly the quoted amounts. If the quote has expired, or it doesn't match the payment (different card BIN, different amount, unavailable option), the payment is rejected — it is never silently re-priced.
Never cache installment tables across sessions or recompute surcharges client-side. Rates can change at any time; only the quoteToken guarantees that the buyer is charged what they were shown. If a quote expires while the buyer hesitates, fetch a fresh one and re-render the table.
The amount in the payment request is the basket total (the base amount); you do not send the installment fee. The system computes the fee from the binding quote and adds it to the charged amount: charged amount = amount + installment fee. Amount fields in responses and webhooks show this charged amount.
Paying without installments? Simply omit installmentQuoteToken — the payment charges the base amount in a single installment.
Errors
| Situation | What you see | What to do |
|---|---|---|
| BIN too short / not 6–8 usable digits | 400 validation error | Wait until at least 6 digits are typed |
| Unknown BIN | BIN_PATTERN_FALLBACK with found: false | Fall back to a no-installment (single payment) flow |
| Card not installment-eligible | Options come back offered: false (or 1×-only) | Show single payment only |
Expired quoteToken at charge time | Payment rejected — quote no longer valid | Fetch a fresh quote, re-render, resubmit |
| Quote/payment mismatch (different BIN or amount) | Payment rejected — quote does not match | Fetch a new quote for the actual card and amount |
html format on /form/bin/installments | Ignored — HTML embed is not available in form scope | Render the data payload with the form's own UI |
No amount and no intentId on /bin/installments | AMOUNT_REQUIRED | Send the minor-unit amount (or an intentId) |
Webhooks
BIN and quote lookups are read-only and emit no webhooks. The events you receive come from the payment itself (payment.captured, payment.failed, …) — see Webhooks.
Next steps
- Card — 2D — passing
installmentQuoteTokenin a direct charge - Card — 3D Secure — the same binding through the challenge flow
- Checkout Form — where the in-form BIN endpoints live