# FINA API Documentation — Full > FINA is a B2B Buy Now, Pay Later (BNPL) service for Saudi Arabia by Silq. Merchants integrate FINA into their checkout so business buyers can split payments into repayment plans; FINA finances the order and pays the merchant out net of commission. The API is REST/JSON over HTTPS, authenticated with a merchant bearer token. Two integration types are available: Direct Merchant (you are the seller) and Marketplace (seller-aware calls for platforms hosting many sellers); ERP is coming soon. This file contains the complete FINA integration documentation — Direct Merchant and Marketplace (19 pages) — flattened into a single document. Marketplace pages whose content is identical to a Direct Merchant page (the shared order-scoped endpoints, the webhook, and error handling) appear once, under Direct Merchant; the Marketplace guide states the equivalence. The per-page index is at https://fina.sa/llms.txt. --- # FINA API Documentation FINA lets your business buyers split payments into flexible repayment plans — Buy Now, Pay Later built for B2B in Saudi Arabia. ## Integration types - **Direct Merchant** (available) — Offer FINA BNPL directly in your own checkout: eligibility, repayment plan selection, OTP-secured orders, and reconciliation. - **Marketplace** (available) — Offer FINA to buyers purchasing from sellers on your marketplace platform. Same endpoints as Direct Merchant, but every call is seller-aware. - **ERP** (coming soon) — Embed FINA credit into ERP-driven ordering and invoicing workflows. ## Key capabilities - **Plug & play** — minimal changes to your existing checkout flow. - **Real-time eligibility** — instant check of onboarding status and available credit. - **Repayment plan selection** — cost breakdown, installment schedule, and credit eligibility per plan. - **Secure authorisation** — OTP-based payment confirmation over SMS. - **Post-order operations** — invoice attachment, delivery confirmation, and invoice retrieval for reconciliation. Interactive demos of the full buyer journey (mock storefront + live API console) are at https://fina.sa/docs/direct-merchant/demo and https://fina.sa/docs/marketplace/demo. --- # Authentication Every FINA API request is authenticated with a merchant-scoped bearer token issued by the FINA team during onboarding — one token per environment. Pass it in the `Authorization` header of every request: ```bash curl -X POST "https://apibe.silqfi.xyz/fina/v1/users/check_eligibility" \ -H "Authorization: bearer FINA_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "buyer_phone_number": "966500000001" }' ``` > **Warning — Keep it server-side:** The bearer token identifies your merchant account. Call FINA APIs from your backend only — never embed the token in web or mobile clients. ## Environments | Environment | Base URL | Notes | |---|---|---| | Sandbox | `https://apibe.silqfi.xyz` | Test environment with test buyers and OTPs — no real money moves. | | Production | Contact the FINA team | Live environment. Base URL and a production bearer token are provided during go-live. | Bearer tokens are environment-specific: a Sandbox token will not work against Production and vice versa. ## HTTP status vs error_code Checkout and order endpoints return **HTTP 200 even for business failures** — the outcome is carried in the response body's `error_code` field (`"0"` means success). Always branch on `error_code`, not the HTTP status. See Error Handling for the full code catalogue. --- # Direct Merchant Integration Add FINA as a payment option in your own checkout. Buyers pick a repayment plan, confirm with an OTP, and FINA finances the order — you get paid out net of commission. ## Buyer journey The buyer walks through five steps in your checkout, each backed by one API call: 1. **Eligibility check** (API: Check Eligibility — `POST /fina/v1/users/check_eligibility`) — When the buyer reaches the payment page, call Check Eligibility. Active buyers see FINA as a payment option; new buyers are onboarded via the signup_url iframe. 2. **Repayment plan selection** (API: Repayment Plan Options — `GET /fina/v1/checkout/repayment_plans`) — Fetch the repayment plans available for the order amount and let the buyer pick one. Disable plans where has_enough_credits is false. 3. **Order placement** (API: Initiate Checkout — `POST /fina/v1/checkout/initiate`) — The buyer selects FINA and a repayment plan, then places the order. Initiate Checkout resolves the plan, creates the order, and sends an OTP to the buyer via SMS. 4. **OTP entry** (API: Resend OTP — `POST /fina/v1/orders/resend_otp`) — Prompt the buyer for the OTP. If it never arrived, resend it — up to 3 times per order. 5. **Authorisation** (API: Authorise Checkout — `POST /fina/v1/checkout/authorise`) — Submit the OTP with the order_id. On success the order is confirmed and the buyer's journey ends; on failure the buyer can retry (3 attempts). ## Post-order operations — your side The buyer's journey ends at authorisation. These calls happen later, from your backend, as you fulfil the order — the buyer never sees them: 1. **Add FINA Invoice** (API: Add FINA Invoice — `POST /fina/v1/invoices/add`) — Attach your invoice PDF to the order for downstream reconciliation. 2. **Confirm Delivery** (API: Confirm Delivery — `POST /fina/v1/orders/confirm_delivery`) — When the goods reach the buyer, mark the order DELIVERED with a proof of delivery. 3. **Get Customer Invoices** (API: Get Customer Invoices — `GET /fina/v1/invoices`) — Fetch the customer-visible invoices for an order, with signed download URLs. ## Order lifecycle Orders move through these statuses; each order operation is only valid in specific states (otherwise you get error 811): | Status | Meaning | |---|---| | `CREATED` | Initiate Checkout succeeded; OTP sent to the buyer via SMS. | | `AUTHORIZATION_INITIATED` | Down-payment plans only — OTP verified; the FINA-hosted down payment is pending at payment_url. | | `AUTHORISED` | Buyer's OTP verified via Authorise Checkout; payment confirmed (and any FINA-collected down payment settled). | | `OUT_FOR_DELIVERY` | Order handed to delivery (when applicable). | | `DELIVERED` | Merchant confirmed delivery with proof of delivery. | Exception statuses reachable from the happy path: | Status | Meaning | |---|---| | `EXPIRED` | Order was not authorised within ~30 minutes of creation. | | `CANCELLED` | Order was cancelled before completion. | | `REFUNDED` | Order was refunded after authorisation. | ## Rules to remember - OTP: buyers get 3 verification attempts and 3 resends per order; unauthorised orders expire after ~30 minutes. - Idempotency: order_reference_id is the idempotency anchor: sending the same reference twice returns ORDER_ALREADY_EXISTS (801) instead of creating a duplicate order. --- # Embedded Onboarding & Signing When Check Eligibility returns user_status NotFound or Pending, render its signup_url inside an iframe in your app, listen for the completion event, then close the iframe and resume checkout. ## Embedding the iframe > **Warning — Your domain must be whitelisted first:** The onboarding page only loads inside origins FINA has whitelisted (a Content-Security-Policy frame-ancestors restriction). Before integrating, share the domains that will embed the iframe — sandbox and production — with the FINA team. An un-whitelisted origin gets a blank iframe blocked by the browser. The same whitelisted domain also has to host the down-payment redirect targets you send in prepaid_payment.success_redirection_url and failure_redirection_url. ```html
``` ## Completion events (FINA → host) Communication uses the browser-native `window.postMessage` API. FINA emits `fina:signCompleted` only after the signing provider confirms success *and* FINA's backend has persisted the result — you never see a premature event that could later be reverted. | Event type | Meaning | Host action | |---|---|---| | `fina:signCompleted` | Signing confirmed and persisted by FINA. This is the primary signal. | Close the iframe and proceed with checkout. | | `fina:signRejected` | Buyer explicitly rejected or cancelled the signing (optional). | Close the iframe or show a specific message. | | `fina:signError` | Unrecoverable error in the signing pipeline (optional). | Show an error notification; decide whether to keep the iframe open. | ```typescript type FinaSignMessage = { type: 'fina:signCompleted' | 'fina:signRejected' | 'fina:signError'; payload: { userHashId?: string; signatureRequestId?: string; timestamp?: string; }; }; ``` ## Listening for completion Validate `event.origin` against the FINA origin for your environment: Sandbox is `https://apibe.silqfi.xyz`; contact the FINA team for the production origin. ```javascript // In the host (parent) window window.addEventListener('message', function (event) { // 1. SECURITY: only accept messages from the expected FINA origin const allowedOrigins = [ 'https://apibe.silqfi.xyz', // sandbox — add your production origin here (contact the FINA team) ]; if (!allowedOrigins.includes(event.origin)) { console.warn('Ignoring message from unknown origin:', event.origin); return; } const data = event.data; // 2. Handle successful completion if (data?.type === 'fina:signCompleted') { document.getElementById('fina-iframe-container').style.display = 'none'; // Resume the checkout flow here } // 3. Optional: rejection and errors if (data?.type === 'fina:signRejected') { // Buyer cancelled — close the iframe or show a specific state } if (data?.type === 'fina:signError') { // Unrecoverable error — show a notification and decide next steps } }); ``` ## Language control The iframe opens in Arabic by default. Any one of these forces a language: 1. Pass `language_code: "en"` (or `"ar"`) in the Check Eligibility request — the returned signup_url has `&lang=` baked in. **Recommended.** 2. Append `&lang=ar|en` to signup_url manually before setting iframe.src (overrides #1). 3. Send a runtime message after the iframe loads: ```javascript // Optional: switch the iframe language at runtime document.getElementById('fina-iframe').contentWindow.postMessage( { type: 'CHANGE_LANGUAGE', language: 'ar' }, 'https://apibe.silqfi.xyz' // your FINA origin; '*' also works — no sensitive data is sent ); // FINA responds with a LANGUAGE_CHANGED event ``` ## Security considerations - **Embedding is restricted to whitelisted origins.** The onboarding page's `frame-ancestors` CSP only permits origins registered with FINA. If the iframe renders blank, an unregistered origin is the most likely cause; check the browser console for a CSP violation and contact the FINA team to whitelist the domain. - **Origin validation is mandatory.** Validate `event.origin` against an allowlist of FINA origins before acting on any message — otherwise any embedded page could spoof a "signing complete" event. - In production FINA sends postMessage to your configured host origin, never `'*'`. In the other direction, using `'*'` as the target origin for your `CHANGE_LANGUAGE` message is acceptable — it carries no sensitive data. - Messages are plain JSON with a well-known `type` and `payload` — no executable code or sensitive data. - Cross-origin isolation applies: the host cannot reach into the FINA iframe's DOM; all coordination flows through postMessage. --- # Down Payment Webhook When a plan requires a down payment, FINA collects it on a hosted checkout page. Once that payment settles and the order authorises, FINA POSTs a signed confirmation to your webhook_url — the server-to-server source of truth that the down payment was received. > **Warning — Fulfil on the webhook, not the browser redirect:** A buyer can close the tab before your success_redirection_url ever loads. Treat the redirect as browser UX only; confirm the payment via this webhook, or pull the state on demand with Reconcile Prepaid Payment if a confirmation is delayed or missed. ## When it fires Once per order, when the FINA-collected down payment is paid and the order moves to `AUTHORISED`. Delivery is at-least-once — you may receive the same confirmation more than once, so make your handler idempotent. Configure the destination via `webhook_url` and `webhook_secret` in the Initiate Checkout request body. ## Handling the webhook Four things your handler must do. Each is expanded in the sections below. | To determine | Use | |---|---| | Payment completion | Check `payment_status == "COMPLETED"`. Only then is the down payment actually collected — do not treat receipt of the webhook alone as completion. | | Authenticity | Verify `X-Fina-Signature` against the raw HTTP body exactly as received. Never re-serialize the parsed JSON before hashing. | | Idempotency & dedup | Key off `event_id`. It is deterministic and identical across retries, so process each one once. | | Acknowledgement | Return a `2xx` as soon as you have durably stored the event. Anything else is treated as a failed delivery and retried. | ## Request & headers - `POST` with `Content-Type: application/json`, ~30s timeout per attempt. - Redirects are not followed — your endpoint must return a terminal `2xx` itself. Any non-2xx (including 3xx) or a timeout counts as a failed delivery and is retried. - Return `2xx` as soon as you have durably accepted the event; do fulfilment work asynchronously. Signature headers are present only when a `webhook_secret` was configured: | Header | Description | |---|---| | `X-Fina-Signature` | Lowercase-hex HMAC-SHA256 over "." keyed with your webhook_secret. | | `X-Fina-Timestamp` | Unix time (seconds) when the request was signed; also part of the signed string, so it cannot be tampered with. | > **Warning — No secret means an unsigned webhook:** If you did not set a webhook_secret, no signature headers are sent and you cannot verify authenticity — rely on TLS and treat the payload cautiously. Setting a secret is strongly recommended. ## Body | Field | Type | Description | |---|---|---| | `event_id` | string | Deterministic id for this event — use as your idempotency key; retries carry the same event_id. | | `order_reference_id` | string | Your order_reference_id from Initiate Checkout. | | `order_number` | string | FINA's order id (the order_id returned by Initiate Checkout). | | `order_status` | string | The order's lifecycle status at confirmation — AUTHORISED once the down payment is confirmed. | | `payment_status` | string | Same value space as Reconcile Prepaid Payment's payment_status — COMPLETED for a confirmed down payment. | | `amount` | number | Down-payment amount confirmed. | | `payment_method_code` | string | Payment method used (omitted if unavailable). | | `event_timestamp` | string | RFC 3339 UTC timestamp, e.g. 2026-07-15T04:30:58Z. | Example delivery: ```json { "event_id": "e3bb4ee574e355513ac2025d4fef13781ee119cb4e33a70d80ae865bf7a31e6a", "order_reference_id": "ORD-2026-0001", "order_number": "F26070910AB3XKQ70", "order_status": "AUTHORISED", "payment_status": "COMPLETED", "amount": 26.25, "payment_method_code": "E-PAYMENT mada", "event_timestamp": "2026-07-15T04:30:58Z" } ``` ## Verifying the signature Recompute the HMAC over the **exact raw request bytes** — do not re-serialize the parsed JSON, since key order and whitespace would differ and break the check. ```text signed_string = X-Fina-Timestamp + "." + expected = hex( HMAC_SHA256( webhook_secret, signed_string ) ) valid = constant_time_equals( expected, X-Fina-Signature ) ``` Node.js: ```javascript const crypto = require("crypto"); // Give your handler the RAW body (e.g. express.raw()), not a re-parsed object. function verifyFinaWebhook(rawBody, headers, secret) { const ts = headers["x-fina-timestamp"]; const sig = headers["x-fina-signature"]; if (!ts || !sig) return false; const expected = crypto.createHmac("sha256", secret) .update(ts + "." + rawBody) // rawBody = exact bytes received .digest("hex"); const ok = sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)); const fresh = Math.abs(Date.now() / 1000 - Number(ts)) <= 300; // replay window return ok && fresh; } ``` Python: ```python import hmac, hashlib, time def verify_fina_webhook(raw_body: bytes, headers, secret: str) -> bool: ts = headers.get("X-Fina-Timestamp") sig = headers.get("X-Fina-Signature") if not ts or not sig: return False expected = hmac.new( secret.encode(), f"{ts}.".encode() + raw_body, # raw_body = exact bytes received hashlib.sha256, ).hexdigest() if not hmac.compare_digest(expected, sig): return False return abs(time.time() - int(ts)) <= 300 # freshness window ``` PHP: ```php = -300 && d <= 300 // replay window } ``` C#: ```csharp using System.Security.Cryptography; using System.Text; // Give your handler the RAW body bytes, not a re-serialized object. static bool VerifyFinaWebhook(byte[] rawBody, string? ts, string? sig, string secret) { if (string.IsNullOrEmpty(ts) || string.IsNullOrEmpty(sig)) return false; using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)); byte[] prefix = Encoding.UTF8.GetBytes(ts + "."); byte[] signed = new byte[prefix.Length + rawBody.Length]; prefix.CopyTo(signed, 0); rawBody.CopyTo(signed, prefix.Length); // rawBody = exact bytes received string expected = Convert.ToHexString(hmac.ComputeHash(signed)).ToLowerInvariant(); bool ok = CryptographicOperations.FixedTimeEquals( Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(sig)); bool fresh = Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - long.Parse(ts)) <= 300; // replay window return ok && fresh; } ``` ## Reliability & idempotency - **At-least-once delivery.** FINA retries failed deliveries (non-2xx, timeout, network error) up to 5 attempts with backoff — the same confirmation can arrive more than once. - **Idempotency.** Dedupe on `event_id` — it is deterministic and identical across retries. Process each event once; ack duplicates with 2xx. - **Replay protection.** The signature covers `X-Fina-Timestamp`; reject requests whose timestamp falls outside a freshness window (e.g. ±5 minutes). - **Ordering.** Don't assume ordering — key all logic off `event_id` / `order_reference_id`. - If a confirmation is delayed, dropped, or never received, pull the authoritative state on demand via Reconcile Prepaid Payment. --- # Error Handling Checkout and order endpoints signal business failures inside the response body, not through HTTP status codes. ## Error envelope > **Warning — Branch on error_code, not HTTP status:** Business failures return HTTP 200 with a non-zero error_code — a numeric code serialized as a JSON string ("0" is success). HTTP 4xx/5xx appears only for malformed requests or infrastructure problems. ```json { "error_code": "801", "errors": [ "an order with this order_reference_id already exists" ], "message": "Order already exists", "data": null } ``` Two endpoints use different envelopes: **Check Eligibility** returns HTTP 400 with `{ is_error: true, message }` for business failures, and **Get Customer Invoices** signals failures with `success: false` and a message, with no numeric code. Each endpoint page documents its own envelope. ## Error code reference | Code | Name | Description | Resolution | |---|---|---|---| | `"400"` | INVALID_PARAMS | A required field is missing or a value failed validation (e.g. totals don't reconcile, invalid phone format, unknown field value). | Check errors[] for the failing field. Ensure order.total_amount equals the sum of items[].total_amount plus shipping_amount minus discount_amount. | | `"500"` | INTERNAL_ERROR | An unexpected failure occurred on FINA's side. | Retry with backoff; contact FINA support if it persists. | | `"801"` | ORDER_ALREADY_EXISTS | An order with the same order_reference_id already exists. order_reference_id is the idempotency anchor. | Use a fresh order_reference_id for new orders; reuse the existing order for retries. | | `"802"` | SELLER_NOT_VALID | The merchant resolved from the bearer token is not recognised for this operation. | Verify the bearer token belongs to the correct merchant account. | | `"803"` | SELLER_INACTIVE | The merchant account exists but is not active. | Contact the FINA team to activate the merchant account. | | `"804"` | ANOTHER_CHECKOUT_IN_PROGRESS | A concurrent checkout for the same buyer is already in flight. | Wait for the in-flight checkout to finish, then retry. | | `"805"` | CUSTOMER_NOT_ONBOARDED | The buyer has not completed FINA onboarding. | Run Check Eligibility and take the buyer through the signup_url onboarding flow. | | `"806"` | CUSTOMER_CREDIT_LIMIT_EXCEEDED | The buyer's available credit does not cover this order. | Offer a plan with a down payment, reduce the order amount, or hide FINA for this purchase. | | `"808"` | INVALID_ORDER | No order was found for the given order_id / order_number. | Use the order_id returned by Initiate Checkout. | | `"809"` | OTP_VERIFICATION_FAILED | The OTP is wrong or has expired. Buyers get 3 verification attempts. | Ask the buyer to re-enter the code, or call Resend OTP and retry Authorise Checkout. | | `"810"` | ANOTHER_ACTION_IN_PROGRESS | A concurrent action on the same order is already in flight. | Wait for the in-flight action to finish, then retry. | | `"811"` | INVALID_ORDER_STATE | The order is not in a state that allows this operation (e.g. authorising an expired order, confirming delivery on an unauthorised order). | Check the order lifecycle; only perform operations valid for the order's current status. | | `"812"` | NOTIFICATION_FAILED | The OTP SMS could not be sent to the buyer. | Retry Resend OTP; verify the buyer's phone number. | | `"813"` | INVALID_FILE | The invoice file could not be downloaded or is not a valid PDF. | Ensure invoice_url is publicly reachable and points to a valid PDF. | | `"876"` | MISSING_PLAN_SELECTION | No repayment plan could be resolved for the order. | Pass a repayment_config_id returned by Repayment Plan Options. | | `"877"` | INVALID_PLAN_SELECTION | The repayment_config_id is invalid, or commission_config.tenure_in_days / merchant_share do not match the selected plan. | Send a repayment_config_id from Repayment Plan Options and keep commission_config consistent with that plan. | | `"878"` | PLAN_NOT_CONFIGURED | No repayment plan is configured for the requested tenure_in_days / channel. | Only offer plans returned by Repayment Plan Options; contact FINA to configure more. | | `"881"` | BG_DOC_EXPIRED | The buyer's credit agreement document has expired. | Send the buyer through onboarding again via Check Eligibility's signup_url. | --- # Check Eligibility `POST /fina/v1/users/check_eligibility` Check whether a buyer is registered with FINA and has available credit, and whether the buyer may transact with your merchant account. For new buyers it returns a signup_url to start onboarding. Base URL (Sandbox): `https://apibe.silqfi.xyz` — the production base URL and bearer token are issued privately by the FINA team during go-live. Auth: send `Authorization: bearer FINA_BEARER_TOKEN` on every request (token issued per environment by the FINA team). > **Note — Different envelope:** This endpoint uses the { is_error, message, data } envelope. Business failures return HTTP 400 with is_error: true — unlike the checkout endpoints, which return HTTP 200 with a numeric error_code. ## Request | Field | Type | Required | Description | |---|---|---|---| | `buyer_phone_number` | string | yes | Buyer's phone number in international format without the plus sign. Normalised server-side. Example: 966500000001. | | `language_code` | enum: en \\| ar | no | Language baked into the returned signup_url. Defaults to Arabic when omitted. | | `buyer_name` | string | no | Buyer's full name — helps pre-fill onboarding for new buyers. Defaults to the buyer's phone number when omitted. Example: Mohammed Al-Rashid. | | `vat_number` | string | no | Buyer's VAT registration number (new buyers). Example: 300908432978431. | | `buyer_cr_number` | string | no | Buyer's Commercial Registration number (new buyers). Example: 7094220000. | | `buyer_nid` | string | no | Buyer's national ID (new buyers). Example: 2247862341. | | `buyer_email` | string | no | Buyer's email address (new buyers). Example: buyer@example.com. | | `buyer_last_6_month_avg_sales` | string | no | Buyer's average monthly sales over the last 6 months, if you hold it — used during credit assessment. | ## Response | Field | Type | Required | Description | |---|---|---|---| | `is_error` | boolean | no | false on success. | | `message` | string | no | Human-readable status of the lookup. | | `data` | object | no | Buyer summary payload. | | `data.eligibility` | enum: ELIGIBLE \\| INELIGIBLE | no | The decision to branch on. ELIGIBLE — show FINA as a payment option. INELIGIBLE — see reason. | | `data.reason` | enum: BUYER_NOT_ONBOARDED \\| SELLER_NOT_ONBOARDED | no | Why the buyer is ineligible. Empty when eligibility is ELIGIBLE. SELLER_NOT_ONBOARDED means the buyer is fine but is not onboarded with the seller you passed. | | `data.user_status` | enum: Active \\| Pending \\| NotFound | no | Buyer's onboarding state. Pending / NotFound — onboard the buyer via signup_url. | | `data.signup_url` | string | no | Onboarding URL for Pending / NotFound buyers. Embed it in an iframe — see Embedded Onboarding. Empty for Active buyers. | | `data.credit_amount` | number | no | Buyer's currently available credit, in SAR. | | `data.external_user_id` | string | no | FINA's identifier for the buyer. Can be passed to Repayment Plan Options as customer_id. | | `data.onboarding_completed` | boolean | no | Whether the buyer has finished onboarding. | | `data.customer_name` | string | no | Buyer's registered name. | | `data.sellers` | array | no | Per-seller status for the merchant account the request was authorised as. | | `data.sellers[].phone` | string | no | The seller's phone number. | | `data.sellers[].status` | enum: ONBOARDED \\| NOT_ONBOARDED \\| NOT_FOUND | no | Whether the buyer is onboarded with this seller. | ## Example request (Sandbox) ```bash curl -X POST "https://apibe.silqfi.xyz/fina/v1/users/check_eligibility" \ -H "Authorization: bearer FINA_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "buyer_phone_number": "966500000001", "language_code": "en" }' ``` ## Example responses **200 · Eligible buyer** ```json { "is_error": false, "message": "User is onboarded successfully, credit summary fetched.", "data": { "signup_url": "", "user_status": "Active", "credit_amount": 50000, "external_user_id": "12955", "onboarding_completed": true, "customer_name": "Mohammed Al-Rashid", "eligibility": "ELIGIBLE", "reason": "", "sellers": [ { "phone": "966567445399", "status": "ONBOARDED" } ] } } ``` **200 · New buyer** ```json { "is_error": false, "message": "User not found, please start the onboarding process with the provided signup URL.", "data": { "signup_url": "https://apibe.silqfi.xyz/self/onboarding?onboarding_type=self&token=2e233637-003e-481b-8367-bf57241296d6&buyer_phone_number=966500000002&vat_number=300908432978431&cr_number=7094220000&nid=2247862341", "user_status": "NotFound", "credit_amount": 0, "external_user_id": "0", "onboarding_completed": false, "customer_name": "", "eligibility": "INELIGIBLE", "reason": "BUYER_NOT_ONBOARDED", "sellers": [] } } ``` **200 · Not onboarded with you** ```json { "is_error": false, "message": "Buyer is not onboarded with the requested seller.", "data": { "signup_url": "", "user_status": "Active", "credit_amount": 50000, "external_user_id": "12955", "onboarding_completed": true, "customer_name": "Mohammed Al-Rashid", "eligibility": "INELIGIBLE", "reason": "SELLER_NOT_ONBOARDED", "sellers": [ { "phone": "966567445399", "status": "NOT_ONBOARDED" } ] } } ``` **400 · Validation error** ```json { "is_error": true, "message": "Invalid request: buyer_phone_number is required", "data": null } ``` --- # Repayment Plan Options `GET /fina/v1/checkout/repayment_plans` Returns the repayment plans available to a buyer for a given order amount — cost breakdown, installment schedule, down-payment requirement, and credit eligibility per plan. Base URL (Sandbox): `https://apibe.silqfi.xyz` — the production base URL and bearer token are issued privately by the FINA team during go-live. Auth: send `Authorization: bearer FINA_BEARER_TOKEN` on every request (token issued per environment by the FINA team). > **Note — Query parameters:** All parameters are sent in the query string — this GET takes no request body. > **Note — Carry repayment_config_id forward:** The buyer's chosen plan's repayment_config_id is what you pass to Initiate Checkout as payment.repayment_config_id. By default only plans the buyer's credit covers are returned; pass show_credit_insufficient_plans=true to see the rest and disable the ones where has_enough_credits is false. ## Request | Field | Type | Required | Description | |---|---|---|---| | `order_amount` | number | yes | Order total to price the plans against. Constraints: query param; must be > 0. Example: 100. | | `customer_phone_number` | string | no | Buyer's phone number. Pass exactly one of customer_phone_number or customer_id. Constraints: query param. Example: 966500000001. | | `customer_id` | number | no | FINA's external_user_id from Check Eligibility. Pass exactly one of customer_phone_number or customer_id. Constraints: query param. | | `merchant_share` | number | no | Merchant's share of the BNPL commission, in percent. If you send it here, send the same value in Initiate Checkout's commission_config. Constraints: query param; 0–100. | | `tenure_in_days` | number | no | Return only the plan matching this tenure_in_days value. Errors if that plan is not available to the buyer. Constraints: query param. | | `show_credit_insufficient_plans` | boolean | no | Set true to also return plans the buyer's credit does not cover (has_enough_credits: false). Constraints: query param; defaults to false. | | `platform` | enum: MERCHANT_DIRECT | no | Filters plans to those enabled for the channel. Use the same value as Initiate Checkout. Constraints: query param. | ## Response | Field | Type | Required | Description | |---|---|---|---| | `success` | boolean | no | true when plans were computed. | | `message` | string | no | Human-readable status. | | `error_code` | string | no | Numeric code as a string; "200" on success for this endpoint. | | `errors` | array | no | Validation / failure details, empty on success. | | `merchant_share` | number | no | Merchant commission share applied when pricing the plans, in percent. | | `plans` | array | no | One entry per available repayment plan. | | `plans[].repayment_config_id` | string | no | The repayment plan id. Pass the chosen plan's value to Initiate Checkout as payment.repayment_config_id. | | `plans[].name` | string | no | Display label for the plan. | | `plans[].tenure_in_days` | string | no | Plan length in days. | | `plans[].is_default` | boolean | no | The plan applied if none is explicitly selected. | | `plans[].commission_treatment` | enum: PROPORTIONAL_ON_FULL \\| PROPORTIONAL_EXCLUDING_PREPAID | no | How commission is applied across installments. | | `plans[].bnpl_cost_percentage` | number | no | BNPL cost for this plan, in percent. | | `plans[].order_amount` | number | no | Echo of the priced order amount. | | `plans[].customer_payable_amount` | number | no | Total the buyer repays across all installments. | | `plans[].prepaid_amount` | number | no | Day-zero down payment the buyer pays upfront (0 if none). | | `plans[].requires_prepaid_collection` | boolean | no | Whether a day-zero down payment must be collected at checkout. When true, include payment.prepaid_payment in Initiate Checkout — see the down-payment callout there. | | `plans[].has_enough_credits` | boolean | no | Whether the buyer's credit limit covers this plan. | | `plans[].financed_amount` | number | no | Amount financed by FINA (order amount minus down payment). | | `plans[].down_payment_amount` | number | no | Same as prepaid_amount — the day-zero amount. | | `plans[].total_commission_amount` | number | no | Total commission for the plan, in SAR. | | `plans[].merchant_commission_amount` | number | no | Commission borne by the merchant. | | `plans[].customer_commission_amount` | number | no | Commission borne by the buyer. | | `plans[].checkout_commission_amount` | number | no | Commission collected at checkout, if any. | | `plans[].net_payout_to_merchant` | number | no | What the merchant receives after commission. | | `plans[].commission_base` | number | no | Amount the commission percentage is applied to. | | `plans[].schedules` | array | no | Installment schedule for the plan. | | `plans[].schedules[].payment_installment_no` | string | no | 1-based installment number. | | `plans[].schedules[].payment_date` | string | no | Due date (YYYY-MM-DD). | | `plans[].schedules[].offset_days` | number | no | Days after order creation this installment falls due. 0 = due at checkout. | | `plans[].schedules[].principal_amount` | number | no | Principal due, in SAR. | | `plans[].schedules[].commission_amount` | number | no | Commission due, in SAR. | | `plans[].schedules[].total_due` | number | no | Total due for the installment. | ## Errors Business failures are returned as HTTP 200 with a non-zero `error_code` (a numeric code serialized as a JSON string; `"0"` means success). See the Error Handling page for the envelope. | Code | Name | Description | Resolution | |---|---|---|---| | `"400"` | INVALID_PARAMS | A required field is missing or a value failed validation (e.g. totals don't reconcile, invalid phone format, unknown field value). | Check errors[] for the failing field. Ensure order.total_amount equals the sum of items[].total_amount plus shipping_amount minus discount_amount. | | `"802"` | SELLER_NOT_VALID | The merchant resolved from the bearer token is not recognised for this operation. | Verify the bearer token belongs to the correct merchant account. | | `"878"` | PLAN_NOT_CONFIGURED | No repayment plan is configured for the requested tenure_in_days / channel. | Only offer plans returned by Repayment Plan Options; contact FINA to configure more. | | `"500"` | INTERNAL_ERROR | An unexpected failure occurred on FINA's side. | Retry with backoff; contact FINA support if it persists. | ## Example request (Sandbox) ```bash curl -X GET "https://apibe.silqfi.xyz/fina/v1/checkout/repayment_plans?customer_phone_number=966500000001&order_amount=100&merchant_share=50" \ -H "Authorization: bearer FINA_BEARER_TOKEN" ``` ## Example responses **200 · Success** ```json { "success": true, "message": "success", "error_code": "200", "errors": [], "merchant_share": 50, "plans": [ { "repayment_config_id": "4", "name": "Default", "tenure_in_days": "30", "is_default": true, "commission_treatment": "PROPORTIONAL_ON_FULL", "bnpl_cost_percentage": 10, "order_amount": 100, "total_commission_amount": 10, "merchant_commission_amount": 5, "customer_commission_amount": 5, "customer_payable_amount": 105, "net_payout_to_merchant": 95, "commission_base": 100, "down_payment_amount": 25, "checkout_commission_amount": 1.25, "prepaid_amount": 26.25, "financed_amount": 78.75, "schedules": [ { "payment_installment_no": "1", "payment_date": "2026-07-12", "offset_days": 0, "principal_amount": 25, "commission_amount": 1.25, "total_due": 26.25 }, { "payment_installment_no": "2", "payment_date": "2026-08-11", "offset_days": 30, "principal_amount": 75, "commission_amount": 3.75, "total_due": 78.75 } ], "requires_prepaid_collection": true, "has_enough_credits": true }, { "repayment_config_id": "6", "name": "Default", "tenure_in_days": "60", "is_default": false, "commission_treatment": "PROPORTIONAL_ON_FULL", "bnpl_cost_percentage": 10, "order_amount": 100, "total_commission_amount": 10, "merchant_commission_amount": 5, "customer_commission_amount": 5, "customer_payable_amount": 105, "net_payout_to_merchant": 95, "commission_base": 100, "down_payment_amount": 25, "checkout_commission_amount": 1.25, "prepaid_amount": 26.25, "financed_amount": 78.75, "schedules": [ { "payment_installment_no": "1", "payment_date": "2026-07-12", "offset_days": 0, "principal_amount": 25, "commission_amount": 1.25, "total_due": 26.25 }, { "payment_installment_no": "2", "payment_date": "2026-09-10", "offset_days": 60, "principal_amount": 75, "commission_amount": 3.75, "total_due": 78.75 } ], "requires_prepaid_collection": true, "has_enough_credits": true }, { "repayment_config_id": "7", "name": "Default", "tenure_in_days": "90", "is_default": false, "commission_treatment": "PROPORTIONAL_ON_FULL", "bnpl_cost_percentage": 10, "order_amount": 100, "total_commission_amount": 10, "merchant_commission_amount": 5, "customer_commission_amount": 5, "customer_payable_amount": 105, "net_payout_to_merchant": 95, "commission_base": 100, "down_payment_amount": 25, "checkout_commission_amount": 1.25, "prepaid_amount": 26.25, "financed_amount": 78.75, "schedules": [ { "payment_installment_no": "1", "payment_date": "2026-07-12", "offset_days": 0, "principal_amount": 25, "commission_amount": 1.25, "total_due": 26.25 }, { "payment_installment_no": "2", "payment_date": "2026-08-11", "offset_days": 30, "principal_amount": 25, "commission_amount": 1.25, "total_due": 26.25 }, { "payment_installment_no": "3", "payment_date": "2026-09-10", "offset_days": 60, "principal_amount": 25, "commission_amount": 1.25, "total_due": 26.25 }, { "payment_installment_no": "4", "payment_date": "2026-10-10", "offset_days": 90, "principal_amount": 25, "commission_amount": 1.25, "total_due": 26.25 } ], "requires_prepaid_collection": true, "has_enough_credits": true } ] } ``` **400 · Invalid params** ```json { "success": false, "message": "provide exactly one of customer_id or customer_phone_number", "error_code": "400", "errors": [ "provide exactly one of customer_id or customer_phone_number" ], "plans": [] } ``` **Plan not configured** ```json { "success": false, "message": "no active repayment plans configured", "error_code": "878", "errors": [ "no active repayment plans configured for this channel" ], "plans": [] } ``` --- # Initiate Checkout `POST /fina/v1/checkout/initiate` Initiates a BNPL checkout session for the buyer's chosen repayment plan, creates the order, and triggers an OTP SMS to the buyer. Base URL (Sandbox): `https://apibe.silqfi.xyz` — the production base URL and bearer token are issued privately by the FINA team during go-live. Auth: send `Authorization: bearer FINA_BEARER_TOKEN` on every request (token issued per environment by the FINA team). > **Note — Plan selection & consistency:** payment.repayment_config_id drives plan selection. When commission_config.use_config is true, commission_config.tenure_in_days must match the selected plan's length and merchant_share must match the value sent to Repayment Plan Options — otherwise the order is rejected with error 877. If repayment_config_id is omitted, FINA falls back to the default plan for commission_config.tenure_in_days. > **Warning — Down-payment plans:** If the chosen plan returned requires_prepaid_collection: true, the buyer owes the day-zero prepaid_amount upfront. Indicate how it is collected via payment.prepaid_payment.collection_method: "FINA" (FINA collects it from the buyer on a hosted payment page) or "MERCHANT" (you collect it and pass the reference in payment.prepaid_utr). For FINA collection, also send success_redirection_url and failure_redirection_url (always as a pair, both on the domain whitelisted with FINA for embedded onboarding) — the buyer lands on one of them after the hosted payment — and optionally webhook_url + webhook_secret for a signed server-to-server confirmation. The amount is echoed back in the response. ## Request | Field | Type | Required | Description | |---|---|---|---| | `payment` | object | yes | The checkout payload. | | `payment.platform` | enum: MERCHANT_DIRECT | yes | Integration channel. Always "MERCHANT_DIRECT" for this guide. | | `payment.currency_code` | string | no | Order currency. Optional — SAR is the only supported currency. Constraints: locked to "SAR". | | `payment.country_code` | string | no | Order country. Optional — SA is the only supported country. Constraints: locked to "SA". | | `payment.repayment_config_id` | number | no | The buyer's chosen plan from Repayment Plan Options. Falls back to the default plan when omitted. | | `payment.description` | string | no | Free-text order description. | | `payment.buyer` | object | yes | The buyer placing the order. | | `payment.buyer.phone_number` | string | yes | Buyer's phone number. The buyer must already be onboarded — see Check Eligibility. | | `payment.seller` | object | no | Your merchant details. Optional — FINA resolves the seller from your bearer token and overwrites anything you send here. | | `payment.seller.unique_refernce_code` | string | no | Your seller reference code. Note the typo in the field name. Constraints: spelling is intentional. | | `payment.seller.phone_number` | string | no | Merchant phone number. | | `payment.seller.business_name` | string | no | Registered business name. | | `payment.seller.email` | string | no | Merchant email. | | `payment.seller.vat_number` | string | no | Merchant VAT number. | | `payment.seller.cr_number` | string | no | Merchant CR number. | | `payment.order` | object | yes | Order contents and totals. | | `payment.order.order_reference_id` | string | yes | Your own order reference. Serves as the idempotency anchor — duplicates are rejected with error 801. | | `payment.order.source_order_number` | string | no | Order number in your own system, stored alongside the order for reconciliation. | | `payment.order.total_amount` | number | yes | Grand total the buyer pays. Constraints: = Σ items[].total_amount + shipping_amount − discount_amount. | | `payment.order.sub_total` | number | no | Total before tax and shipping. | | `payment.order.tax_amount` | number | no | Total tax, in SAR. | | `payment.order.shipping_amount` | number | no | Shipping cost, in SAR. | | `payment.order.discount_amount` | number | no | Discount applied, in SAR. | | `payment.order.items` | array | yes | Line items. Constraints: min 1 item; SKUs must be unique. | | `payment.order.items[].title` | string | yes | Item name. | | `payment.order.items[].reference_id` | string | yes | Your item identifier. | | `payment.order.items[].sku` | string | yes | Stock keeping unit. | | `payment.order.items[].quantity` | number | yes | Units ordered. Constraints: > 0. | | `payment.order.items[].unit_price` | number | no | Price per unit, ex-tax. | | `payment.order.items[].tax_amount` | number | no | Tax for the line, in SAR. | | `payment.order.items[].tax_percentage` | number | no | Tax rate, in percent. | | `payment.order.items[].total_amount` | number | no | Line total including tax. | | `payment.order.items[].size` | string | no | Variant size, if any. | | `payment.billing_address` | object | yes | Billing address. | | `payment.billing_address.first_name` | string | yes | Recipient's first name. | | `payment.billing_address.last_name` | string | no | Recipient's last name. | | `payment.billing_address.phone_number` | string | yes | Recipient's phone number. | | `payment.billing_address.address` | string | no | Street address. | | `payment.billing_address.city` | string | no | City. | | `payment.billing_address.region` | string | no | Region / province. | | `payment.billing_address.zip` | string | no | Postal code. | | `payment.billing_address.country_code` | string | no | ISO country code — "SA". | | `payment.billing_address.country_name` | string | no | Country display name. | | `payment.billing_address.latitude` | string | no | Latitude, as a string. | | `payment.billing_address.longitude` | string | no | Longitude, as a string. | | `payment.shipping_address` | object | yes | Shipping address. | | `payment.shipping_address.first_name` | string | yes | Recipient's first name. | | `payment.shipping_address.last_name` | string | no | Recipient's last name. | | `payment.shipping_address.phone_number` | string | yes | Recipient's phone number. | | `payment.shipping_address.address` | string | no | Street address. | | `payment.shipping_address.city` | string | no | City. | | `payment.shipping_address.region` | string | no | Region / province. | | `payment.shipping_address.zip` | string | no | Postal code. | | `payment.shipping_address.country_code` | string | no | ISO country code — "SA". | | `payment.shipping_address.country_name` | string | no | Country display name. | | `payment.shipping_address.latitude` | string | no | Latitude, as a string. | | `payment.shipping_address.longitude` | string | no | Longitude, as a string. | | `payment.commission_config` | object | no | Commission arrangement for the order. | | `payment.commission_config.use_config` | boolean | no | Set true to apply tenure_in_days / merchant_share below. | | `payment.commission_config.tenure_in_days` | number | no | Must match the selected plan's length when use_config is true. | | `payment.commission_config.merchant_share` | number | no | Merchant's share of the commission. Must match the value sent to Repayment Plan Options. Constraints: 0–100. | | `payment.prepaid_payment` | object | no | Required when the selected plan has requires_prepaid_collection: true. | | `payment.prepaid_payment.collection_method` | enum: FINA \\| MERCHANT | no | Who collects the day-zero down payment: FINA (from the buyer) or MERCHANT (you collect it yourself). | | `payment.prepaid_payment.success_redirection_url` | string | no | Where FINA returns the buyer's browser after a successful hosted down-payment (collection_method FINA). Must sit on the same domain you had whitelisted for embedded onboarding. Browser UX only — confirm the payment via the webhook or Reconcile Prepaid Payment, never the redirect alone. Constraints: set as a pair with failure_redirection_url; whitelisted domain. | | `payment.prepaid_payment.failure_redirection_url` | string | no | Where FINA returns the buyer's browser after a failed hosted down-payment. Must sit on the same whitelisted domain as success_redirection_url. Constraints: set as a pair with success_redirection_url; whitelisted domain. | | `payment.prepaid_payment.webhook_url` | string | no | Optional endpoint FINA POSTs a signed confirmation to once the FINA-collected down payment settles — see the Down Payment Webhook page. No embedded credentials. Constraints: HTTPS; ≤ 2048 chars; publicly reachable; origin allowlisted with FINA. | | `payment.prepaid_payment.webhook_secret` | string | no | Optional secret FINA uses to HMAC-SHA256-sign the webhook (X-Fina-Signature header). If omitted the webhook is sent unsigned — strongly recommended to set it. Constraints: stored exactly as sent. | | `payment.prepaid_utr` | string | no | Your payment reference for the collected down payment — required when collection_method is MERCHANT. | ## Response | Field | Type | Required | Description | |---|---|---|---| | `error_code` | string | no | Numeric code as a string; "0" on success. | | `errors` | array | no | Failure details, empty on success. | | `message` | string | no | Human-readable status. | | `data` | object | no | The created order. | | `data.order_id` | string | no | FINA's order identifier — used by all subsequent order operations. | | `data.order_reference_id` | string | no | Echo of your reference. | | `data.order_status` | string | no | Always "CREATED" on success. OTP has been sent to the buyer. | | `data.payment_url` | string | no | Payment link for FINA-collected down payments; empty otherwise. | | `data.prepaid_amount` | number | no | Day-zero down payment owed by the buyer (0 if none). | | `data.transaction_id` | string | no | Down-payment transaction reference, when applicable. | | `data.repayment_config_id` | string | no | The plan actually applied to the order — echoes your selection, or the default plan when you omitted one. | ## Errors Business failures are returned as HTTP 200 with a non-zero `error_code` (a numeric code serialized as a JSON string; `"0"` means success). See the Error Handling page for the envelope. | Code | Name | Description | Resolution | |---|---|---|---| | `"400"` | INVALID_PARAMS | A required field is missing or a value failed validation (e.g. totals don't reconcile, invalid phone format, unknown field value). | Check errors[] for the failing field. Ensure order.total_amount equals the sum of items[].total_amount plus shipping_amount minus discount_amount. | | `"801"` | ORDER_ALREADY_EXISTS | An order with the same order_reference_id already exists. order_reference_id is the idempotency anchor. | Use a fresh order_reference_id for new orders; reuse the existing order for retries. | | `"802"` | SELLER_NOT_VALID | The merchant resolved from the bearer token is not recognised for this operation. | Verify the bearer token belongs to the correct merchant account. | | `"803"` | SELLER_INACTIVE | The merchant account exists but is not active. | Contact the FINA team to activate the merchant account. | | `"804"` | ANOTHER_CHECKOUT_IN_PROGRESS | A concurrent checkout for the same buyer is already in flight. | Wait for the in-flight checkout to finish, then retry. | | `"805"` | CUSTOMER_NOT_ONBOARDED | The buyer has not completed FINA onboarding. | Run Check Eligibility and take the buyer through the signup_url onboarding flow. | | `"806"` | CUSTOMER_CREDIT_LIMIT_EXCEEDED | The buyer's available credit does not cover this order. | Offer a plan with a down payment, reduce the order amount, or hide FINA for this purchase. | | `"876"` | MISSING_PLAN_SELECTION | No repayment plan could be resolved for the order. | Pass a repayment_config_id returned by Repayment Plan Options. | | `"877"` | INVALID_PLAN_SELECTION | The repayment_config_id is invalid, or commission_config.tenure_in_days / merchant_share do not match the selected plan. | Send a repayment_config_id from Repayment Plan Options and keep commission_config consistent with that plan. | | `"878"` | PLAN_NOT_CONFIGURED | No repayment plan is configured for the requested tenure_in_days / channel. | Only offer plans returned by Repayment Plan Options; contact FINA to configure more. | | `"881"` | BG_DOC_EXPIRED | The buyer's credit agreement document has expired. | Send the buyer through onboarding again via Check Eligibility's signup_url. | | `"500"` | INTERNAL_ERROR | An unexpected failure occurred on FINA's side. | Retry with backoff; contact FINA support if it persists. | ## Example request (Sandbox) ```bash curl -X POST "https://apibe.silqfi.xyz/fina/v1/checkout/initiate" \ -H "Authorization: bearer FINA_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "payment": { "currency_code": "SAR", "country_code": "SA", "description": "Wholesale coffee order", "platform": "MERCHANT_DIRECT", "repayment_config_id": 4, "buyer": { "phone_number": "966500000001" }, "order": { "order_reference_id": "ORD-2026-0001", "source_order_number": "SRC-ORD-2026-0001", "total_amount": 100, "sub_total": 80, "tax_amount": 12, "shipping_amount": 8, "discount_amount": 0, "items": [ { "title": "Arabica Coffee Beans 1kg", "reference_id": "SKU-CFE-1KG", "sku": "SKU-CFE-1KG", "quantity": 2, "unit_price": 40, "tax_amount": 12, "tax_percentage": 15, "total_amount": 92, "size": "1kg" } ] }, "billing_address": { "first_name": "Mohammed", "last_name": "Al-Rashid", "phone_number": "966500000001", "address": "King Fahd Road, Al Olaya", "city": "Riyadh", "region": "Riyadh", "zip": "12211", "country_code": "SA", "country_name": "Saudi Arabia" }, "shipping_address": { "first_name": "Mohammed", "last_name": "Al-Rashid", "phone_number": "966500000001", "address": "King Fahd Road, Al Olaya", "city": "Riyadh", "region": "Riyadh", "zip": "12211", "country_code": "SA", "country_name": "Saudi Arabia" }, "commission_config": { "use_config": true, "tenure_in_days": 30, "merchant_share": 50 }, "prepaid_payment": { "collection_method": "FINA", "success_redirection_url": "https://merchant.example/checkout/fina/success", "failure_redirection_url": "https://merchant.example/checkout/fina/failure", "webhook_url": "https://api.merchant.example/webhooks/fina/prepaid", "webhook_secret": "whsec_9f2c1b4e" } } }' ``` ## Example responses **200 · Success** ```json { "error_code": "0", "errors": [], "message": "Order created successfully", "data": { "order_id": "F26070910AB3XKQ70", "order_reference_id": "ORD-2026-0001", "order_status": "CREATED", "payment_url": "", "prepaid_amount": 26.25, "transaction_id": "", "repayment_config_id": "4" } } ``` **Duplicate order** ```json { "error_code": "801", "errors": [ "an order with order_reference_id ORD-2026-0001 already exists" ], "message": "Order already exists", "data": null } ``` **Invalid plan** ```json { "error_code": "877", "errors": [ "commission_config.tenure_in_days does not match the selected repayment plan" ], "message": "Invalid plan selection", "data": null } ``` **Totals mismatch** ```json { "error_code": "400", "errors": [ "order.total_amount must equal sum(items.total_amount) + shipping_amount - discount_amount" ], "message": "Validation failed", "data": null } ``` --- # Authorise Checkout `POST /fina/v1/checkout/authorise` Validates the OTP entered by the buyer and finalises the payment. On success the order moves to AUTHORISED. Base URL (Sandbox): `https://apibe.silqfi.xyz` — the production base URL and bearer token are issued privately by the FINA team during go-live. Auth: send `Authorization: bearer FINA_BEARER_TOKEN` on every request (token issued per environment by the FINA team). > **Note — Retries & idempotency:** Buyers get 3 OTP verification attempts (error 809 on each failure) — use Resend OTP if the code never arrived. Authorising an already-authorised order returns success again rather than an error. Allow a few seconds after Initiate Checkout for the OTP SMS to be dispatched before calling. > **Warning — Down-payment plans do not go straight to AUTHORISED:** When the plan requires a day-zero down payment collected by FINA (prepaid_payment.collection_method "FINA"), the order comes back as AUTHORIZATION_INITIATED with a payment_url. Redirect the buyer to that URL — they pay the down payment on the hosted page and are then redirected to your success or failure redirection URL. Once the payment succeeds FINA POSTs the signed Down Payment Webhook to your webhook_url and the order becomes AUTHORISED; use Reconcile Prepaid Payment if the confirmation is delayed. ## Request | Field | Type | Required | Description | |---|---|---|---| | `order_id` | string | yes | The order_id returned by Initiate Checkout. Example: F26070910AB3XKQ70. | | `otp_code` | string | yes | The OTP the buyer received via SMS. Example: 1234. | ## Response | Field | Type | Required | Description | |---|---|---|---| | `error_code` | string | no | Numeric code as a string; "0" on success. | | `errors` | array | no | Failure details, empty on success. | | `message` | string | no | Human-readable status. | | `data` | object | no | The authorised order. | | `data.order_id` | string | no | FINA's order identifier. | | `data.order_reference_id` | string | no | Your order reference. | | `data.order_status` | enum: AUTHORISED \\| AUTHORIZATION_INITIATED | no | AUTHORISED on success. AUTHORIZATION_INITIATED when a FINA-collected down payment is still outstanding — see payment_url. | | `data.payment_url` | string | no | Hosted checkout link for the day-zero down payment; empty when nothing is owed upfront. | | `data.prepaid_amount` | number | no | Day-zero down payment owed by the buyer (0 if none). | | `data.transaction_id` | string | no | Down-payment transaction reference, when applicable. | | `data.repayment_config_id` | string | no | The repayment plan applied to the order. | ## Errors Business failures are returned as HTTP 200 with a non-zero `error_code` (a numeric code serialized as a JSON string; `"0"` means success). See the Error Handling page for the envelope. | Code | Name | Description | Resolution | |---|---|---|---| | `"400"` | INVALID_PARAMS | A required field is missing or a value failed validation (e.g. totals don't reconcile, invalid phone format, unknown field value). | Check errors[] for the failing field. Ensure order.total_amount equals the sum of items[].total_amount plus shipping_amount minus discount_amount. | | `"808"` | INVALID_ORDER | No order was found for the given order_id / order_number. | Use the order_id returned by Initiate Checkout. | | `"809"` | OTP_VERIFICATION_FAILED | The OTP is wrong or has expired. Buyers get 3 verification attempts. | Ask the buyer to re-enter the code, or call Resend OTP and retry Authorise Checkout. | | `"810"` | ANOTHER_ACTION_IN_PROGRESS | A concurrent action on the same order is already in flight. | Wait for the in-flight action to finish, then retry. | | `"811"` | INVALID_ORDER_STATE | The order is not in a state that allows this operation (e.g. authorising an expired order, confirming delivery on an unauthorised order). | Check the order lifecycle; only perform operations valid for the order's current status. | | `"500"` | INTERNAL_ERROR | An unexpected failure occurred on FINA's side. | Retry with backoff; contact FINA support if it persists. | ## Example request (Sandbox) ```bash curl -X POST "https://apibe.silqfi.xyz/fina/v1/checkout/authorise" \ -H "Authorization: bearer FINA_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "order_id": "F26070910AB3XKQ70", "otp_code": "1234" }' ``` ## Example responses **200 · Success** ```json { "error_code": "0", "errors": [], "message": "Order authorised successfully", "data": { "order_id": "F26070910AB3XKQ70", "order_reference_id": "ORD-2026-0001", "order_status": "AUTHORISED", "payment_url": "", "prepaid_amount": 0, "transaction_id": "", "repayment_config_id": "4" } } ``` **200 · Down payment pending** ```json { "error_code": "0", "errors": [], "message": "Down payment pending", "data": { "order_id": "F26070910AB3XKQ70", "order_reference_id": "ORD-2026-0001", "order_status": "AUTHORIZATION_INITIATED", "payment_url": "https://checkout.example/prepaid/9f2c1b4e", "prepaid_amount": 26.25, "transaction_id": "TXN-2026-88213", "repayment_config_id": "7" } } ``` **Invalid OTP** ```json { "error_code": "809", "errors": [ "otp verification failed" ], "message": "Invalid OTP", "data": null } ``` **Invalid order state** ```json { "error_code": "811", "errors": [ "order is not in an authorisable state" ], "message": "Invalid order state", "data": null } ``` --- # Reconcile Prepaid Payment `POST /fina/v1/checkout/reconcile_prepaid_payment` Pulls the authoritative state of a FINA-collected down payment and lets FINA reconcile it — the fallback when the down payment webhook is delayed, dropped, or never received. Base URL (Sandbox): `https://apibe.silqfi.xyz` — the production base URL and bearer token are issued privately by the FINA team during go-live. Auth: send `Authorization: bearer FINA_BEARER_TOKEN` on every request (token issued per environment by the FINA team). > **Note — Webhook first, reconcile as backstop:** The Down Payment Webhook is the primary, real-time confirmation. Call this endpoint on demand instead — when the buyer lands on your redirection URL, when a confirmation hasn't arrived within your expected window (poll with backoff, not a tight loop), or when a buyer returns to an awaiting-payment order. Fulfilment decisions key off payment_status here exactly as they would off the webhook. Only applicable to orders using collection_method FINA. > **Warning — Flat response envelope:** Unlike the other endpoints, this response has no data object and error_code is a number, not a string. It is also idempotent with side effects: a paid session is finalized (order authorised), a voided session is re-issued with a fresh payment_url, and an already-authorised order returns its current state unchanged. ## Request | Field | Type | Required | Description | |---|---|---|---| | `order_number` | string | yes | The FINA order id — the order_id returned by Initiate Checkout. Example: F26070910AB3XKQ70. | ## Response | Field | Type | Required | Description | |---|---|---|---| | `error_code` | number | no | 0 on success (numeric, not a string). | | `errors` | array | no | Failure details, empty on success. | | `message` | string | no | Human-readable status. | | `in_progress` | boolean | no | true — a finalize is already running concurrently. Treat as still processing and retry after a short delay. | | `payment_status` | enum: PENDING \\| INITIATED \\| VOIDED \\| PAID \\| COMPLETED | no | Current state of the prepaid session. Treat PAID / COMPLETED as confirmed — the order is authorised and safe to fulfil. | | `payment_url` | string | no | Hosted checkout URL for the current session. If the session is unpaid or was re-issued, present this to the buyer to complete or retry the payment. Empty once paid. | | `transaction_id` | string | no | Current payment transaction id. | ## Errors Business failures are returned as HTTP 200 with a non-zero `error_code` (a numeric code serialized as a JSON string; `"0"` means success). See the Error Handling page for the envelope. | Code | Name | Description | Resolution | |---|---|---|---| | `"400"` | INVALID_PARAMS | A required field is missing or a value failed validation (e.g. totals don't reconcile, invalid phone format, unknown field value). | Check errors[] for the failing field. Ensure order.total_amount equals the sum of items[].total_amount plus shipping_amount minus discount_amount. | | `"808"` | INVALID_ORDER | No order was found for the given order_id / order_number. | Use the order_id returned by Initiate Checkout. | | `"500"` | INTERNAL_ERROR | An unexpected failure occurred on FINA's side. | Retry with backoff; contact FINA support if it persists. | ## Example request (Sandbox) ```bash curl -X POST "https://apibe.silqfi.xyz/fina/v1/checkout/reconcile_prepaid_payment" \ -H "Authorization: bearer FINA_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "order_number": "F26070910AB3XKQ70" }' ``` ## Example responses **200 · Still unpaid** ```json { "error_code": 0, "errors": [], "message": "success", "in_progress": false, "payment_status": "INITIATED", "payment_url": "https://checkout.example/prepaid/9f2c1b4e", "transaction_id": "" } ``` **200 · Paid** ```json { "error_code": 0, "errors": [], "message": "success", "in_progress": false, "payment_status": "PAID", "payment_url": "", "transaction_id": "TXN-2026-88213" } ``` **Unknown order** ```json { "error_code": 808, "errors": [ "order not found" ], "message": "Invalid order", "in_progress": false, "payment_status": "", "payment_url": "", "transaction_id": "" } ``` --- # Resend OTP `POST /fina/v1/orders/resend_otp` Resends the OTP SMS for an existing checkout session when the buyer did not receive the first one. Base URL (Sandbox): `https://apibe.silqfi.xyz` — the production base URL and bearer token are issued privately by the FINA team during go-live. Auth: send `Authorization: bearer FINA_BEARER_TOKEN` on every request (token issued per environment by the FINA team). > **Note:** A maximum of 3 resends are allowed per order, and unauthorised orders expire ~30 minutes after creation — after that, create a new order. ## Request | Field | Type | Required | Description | |---|---|---|---| | `order_id` | string | yes | The order_id returned by Initiate Checkout. Example: F26070910AB3XKQ70. | ## Response | Field | Type | Required | Description | |---|---|---|---| | `error_code` | string | no | Numeric code as a string; "0" on success. | | `errors` | array | no | Failure details, empty on success. | | `message` | string | no | Human-readable status. | | `success` | boolean | no | true when the OTP was resent. | | `data` | object | no | The order the OTP was resent for. | | `data.order_id` | string | no | FINA's order identifier. | | `data.order_reference_id` | string | no | Your order reference. | | `data.order_status` | string | no | Current order status. | ## Errors Business failures are returned as HTTP 200 with a non-zero `error_code` (a numeric code serialized as a JSON string; `"0"` means success). See the Error Handling page for the envelope. | Code | Name | Description | Resolution | |---|---|---|---| | `"400"` | INVALID_PARAMS | A required field is missing or a value failed validation (e.g. totals don't reconcile, invalid phone format, unknown field value). | Check errors[] for the failing field. Ensure order.total_amount equals the sum of items[].total_amount plus shipping_amount minus discount_amount. | | `"808"` | INVALID_ORDER | No order was found for the given order_id / order_number. | Use the order_id returned by Initiate Checkout. | | `"811"` | INVALID_ORDER_STATE | The order is not in a state that allows this operation (e.g. authorising an expired order, confirming delivery on an unauthorised order). | Check the order lifecycle; only perform operations valid for the order's current status. | | `"812"` | NOTIFICATION_FAILED | The OTP SMS could not be sent to the buyer. | Retry Resend OTP; verify the buyer's phone number. | ## Example request (Sandbox) ```bash curl -X POST "https://apibe.silqfi.xyz/fina/v1/orders/resend_otp" \ -H "Authorization: bearer FINA_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "order_id": "F26070910AB3XKQ70" }' ``` ## Example responses **200 · Success** ```json { "error_code": "0", "errors": [], "message": "OTP resent successfully", "data": { "order_id": "F26070910AB3XKQ70", "order_reference_id": "ORD-2026-0001", "order_status": "CREATED" }, "success": true } ``` **Order not found** ```json { "error_code": "808", "errors": [ "order not found" ], "message": "Invalid order", "data": null, "success": false } ``` **SMS failed** ```json { "error_code": "812", "errors": [ "failed to send otp notification" ], "message": "Notification failed", "data": null, "success": false } ``` --- # Confirm Delivery `POST /fina/v1/orders/confirm_delivery` Marks an order as delivered by submitting a proof of delivery (POD), such as an image or document URL. Required for downstream reconciliation. Base URL (Sandbox): `https://apibe.silqfi.xyz` — the production base URL and bearer token are issued privately by the FINA team during go-live. Auth: send `Authorization: bearer FINA_BEARER_TOKEN` on every request (token issued per environment by the FINA team). > **Warning — delivery_proof_url is fetched server-side:** FINA downloads the file at delivery_proof_url and re-hosts it, so the URL must point at a real, reachable image or document. A placeholder or auth-protected link fails. An expiring pre-signed link (e.g. S3/GCS) works fine as long as it is still valid when you call this endpoint. ## Request | Field | Type | Required | Description | |---|---|---|---| | `order_id` | string | yes | The order_id returned by Initiate Checkout. Example: F26070910AB3XKQ70. | | `delivery_proof_url` | string | yes | URL of the proof of delivery (image or document). FINA fetches it server-side, so it must resolve without authentication — an expiring pre-signed link (e.g. S3/GCS) works fine as long as it is still valid when you call this endpoint. Constraints: must be fetchable without authentication. Example: https://example.com/pod/ORD-2026-0001.jpg. | | `confirmation_type` | enum: POD \\| OTP | no | How delivery is being confirmed. Defaults to proof-of-delivery — leave it unset unless FINA has enabled OTP delivery confirmation for you. | ## Response | Field | Type | Required | Description | |---|---|---|---| | `error_code` | string | no | Numeric code as a string; "0" on success. | | `errors` | array | no | Failure details, empty on success. | | `message` | string | no | Human-readable status. | | `success` | boolean | no | true when delivery was confirmed. | | `data` | object | no | The delivered order. | | `data.order_id` | string | no | FINA's order identifier. | | `data.order_reference_id` | string | no | Your order reference. | | `data.order_status` | string | no | "DELIVERED" on success. | ## Errors Business failures are returned as HTTP 200 with a non-zero `error_code` (a numeric code serialized as a JSON string; `"0"` means success). See the Error Handling page for the envelope. | Code | Name | Description | Resolution | |---|---|---|---| | `"400"` | INVALID_PARAMS | A required field is missing or a value failed validation (e.g. totals don't reconcile, invalid phone format, unknown field value). | Check errors[] for the failing field. Ensure order.total_amount equals the sum of items[].total_amount plus shipping_amount minus discount_amount. | | `"808"` | INVALID_ORDER | No order was found for the given order_id / order_number. | Use the order_id returned by Initiate Checkout. | | `"811"` | INVALID_ORDER_STATE | The order is not in a state that allows this operation (e.g. authorising an expired order, confirming delivery on an unauthorised order). | Check the order lifecycle; only perform operations valid for the order's current status. | | `"500"` | INTERNAL_ERROR | An unexpected failure occurred on FINA's side. | Retry with backoff; contact FINA support if it persists. | ## Example request (Sandbox) ```bash curl -X POST "https://apibe.silqfi.xyz/fina/v1/orders/confirm_delivery" \ -H "Authorization: bearer FINA_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "order_id": "F26070910AB3XKQ70", "delivery_proof_url": "https://example.com/pod/ORD-2026-0001.jpg" }' ``` ## Example responses **200 · Success** ```json { "error_code": "0", "errors": [], "message": "Delivery confirmed successfully", "data": { "order_id": "F26070910AB3XKQ70", "order_reference_id": "ORD-2026-0001", "order_status": "DELIVERED" }, "success": true } ``` **Invalid order state** ```json { "error_code": "811", "errors": [ "order must be authorised before confirming delivery" ], "message": "Invalid order state", "data": null, "success": false } ``` --- # Add FINA Invoice `POST /fina/v1/invoices/add` Attaches your invoice PDF to an existing order for downstream reconciliation. Base URL (Sandbox): `https://apibe.silqfi.xyz` — the production base URL and bearer token are issued privately by the FINA team during go-live. Auth: send `Authorization: bearer FINA_BEARER_TOKEN` on every request (token issued per environment by the FINA team). > **Note — Prefer invoice_pdf_data — send the PDF inline as base64:** Every request must carry the invoice file in exactly one of invoice_pdf_data or invoice_url. Sending neither is rejected. invoice_pdf_data is the recommended option: the bytes travel with the request, so nothing depends on a link staying public or unexpired. Fall back to invoice_url only when you cannot inline the file. > **Warning — invoice_url is fetched server-side:** If you do send invoice_url, FINA downloads the file and re-hosts it, so the URL must point at a real, reachable PDF. A placeholder or auth-protected link fails with error 813. An expiring pre-signed link (e.g. S3/GCS) works only while it is still valid when you call this endpoint — which is exactly the failure mode invoice_pdf_data avoids. ## Request | Field | Type | Required | Description | |---|---|---|---| | `channel` | enum: FINA | no | Always "FINA" for this integration. | | `order_number` | string | yes | The order_id returned by Initiate Checkout. Example: F26070910AB3XKQ70. | | `invoice_number` | string | yes | Your invoice number. Example: INV-2026-0001. | | `invoice_pdf_data` | string | no | The invoice PDF as a base64 string. Preferred over invoice_url — the file travels with the request, so nothing depends on a link being publicly reachable or unexpired. Constraints: preferred; required unless invoice_url is sent. Example: JVBERi0xLjcKCjEgMCBvYmoKPDwvVHlwZS9DYXRhbG9n…. | | `invoice_url` | string | no | URL of the invoice PDF, for when you cannot inline the file. FINA fetches it server-side, so it must resolve without authentication — an expiring pre-signed link (e.g. S3/GCS) works only while it is still valid when you call this endpoint. Constraints: fallback; required unless invoice_pdf_data is sent. Example: https://example.com/invoices/INV-2026-0001.pdf. | ## Response | Field | Type | Required | Description | |---|---|---|---| | `error_code` | string | no | Numeric code as a string; "0" on success. | | `errors` | array | no | Failure details, empty on success. | | `message` | string | no | Human-readable status. | | `success` | boolean | no | true when the invoice was attached. | | `data` | object | no | The order the invoice was attached to. | | `data.order_id` | string | no | FINA's order identifier. | | `data.order_reference_id` | string | no | Your order reference. | | `data.order_status` | string | no | Current order status. | ## Errors Business failures are returned as HTTP 200 with a non-zero `error_code` (a numeric code serialized as a JSON string; `"0"` means success). See the Error Handling page for the envelope. | Code | Name | Description | Resolution | |---|---|---|---| | `"400"` | INVALID_PARAMS | A required field is missing or a value failed validation (e.g. totals don't reconcile, invalid phone format, unknown field value). | Check errors[] for the failing field. Ensure order.total_amount equals the sum of items[].total_amount plus shipping_amount minus discount_amount. | | `"808"` | INVALID_ORDER | No order was found for the given order_id / order_number. | Use the order_id returned by Initiate Checkout. | | `"811"` | INVALID_ORDER_STATE | The order is not in a state that allows this operation (e.g. authorising an expired order, confirming delivery on an unauthorised order). | Check the order lifecycle; only perform operations valid for the order's current status. | | `"813"` | INVALID_FILE | The invoice file could not be downloaded or is not a valid PDF. | Ensure invoice_url is publicly reachable and points to a valid PDF. | | `"500"` | INTERNAL_ERROR | An unexpected failure occurred on FINA's side. | Retry with backoff; contact FINA support if it persists. | ## Example request (Sandbox) ```bash curl -X POST "https://apibe.silqfi.xyz/fina/v1/invoices/add" \ -H "Authorization: bearer FINA_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "channel": "FINA", "order_number": "F26070910AB3XKQ70", "invoice_number": "INV-2026-0001", "invoice_pdf_data": "JVBERi0xLjcKCjEgMCBvYmoKPDwvVHlwZS9DYXRhbG9n…" }' ``` ## Example responses **200 · Success** ```json { "error_code": "0", "errors": [], "message": "Invoice added successfully", "data": { "order_id": "F26070910AB3XKQ70", "order_reference_id": "ORD-2026-0001", "order_status": "DELIVERED" }, "success": true } ``` **Invalid file** ```json { "error_code": "813", "errors": [ "failed to download invoice file" ], "message": "Invalid file", "data": null, "success": false } ``` --- # Get Customer Invoices `GET /fina/v1/invoices` Retrieves the customer-visible invoices associated with an order, including short-lived signed download URLs. Base URL (Sandbox): `https://apibe.silqfi.xyz` — the production base URL and bearer token are issued privately by the FINA team during go-live. Auth: send `Authorization: bearer FINA_BEARER_TOKEN` on every request (token issued per environment by the FINA team). > **Note — Different envelope:** Failures on this endpoint are signalled by success: false with a message — there is no numeric error_code field. > **Note — Filtered server-side:** Only CREDIT_LIABILITY invoices that already have a stored document are returned, so the list is empty until FINA has generated one — an invoice you attached via Add FINA Invoice does not appear here. ## Request | Field | Type | Required | Description | |---|---|---|---| | `order_number` | string | yes | The order_id returned by Initiate Checkout. Constraints: query param. Example: F26070910AB3XKQ70. | ## Response | Field | Type | Required | Description | |---|---|---|---| | `success` | boolean | no | true when the lookup succeeded. | | `message` | string | no | Failure reason when success is false. | | `order_invoices` | array | no | Customer-visible invoices for the order. | | `order_invoices[].id` | string | no | FINA's invoice record id. | | `order_invoices[].invoice_number` | string | no | Invoice number. | | `order_invoices[].invoice_type_name` | string | no | Invoice category, e.g. "CREDIT_LIABILITY". | | `order_invoices[].status_name` | enum: PENDING \\| PROCESSING \\| COMPLETED \\| FAILED | no | Generation status of the invoice document. | | `order_invoices[].url` | string | no | Storage path of the invoice (not directly downloadable). | | `order_invoices[].expiring_url` | string | no | Signed download URL, valid for ~10 minutes. Fetch again when it expires. | | `order_invoices[].invoice_type` | number | no | Numeric invoice-type code. | | `order_invoices[].status` | number | no | Numeric status code. | ## Example request (Sandbox) ```bash curl -X GET "https://apibe.silqfi.xyz/fina/v1/invoices?order_number=F26070910AB3XKQ70" \ -H "Authorization: bearer FINA_BEARER_TOKEN" ``` ## Example responses **200 · Success** ```json { "success": true, "message": "", "order_invoices": [ { "id": "8234", "invoice_number": "10-01-1-2604922", "invoice_type": 3, "status": 4, "url": "invoices/04-2026/CREDIT_LIABILITY/invoice_11439_1776618063.pdf", "expiring_url": "https://storage.googleapis.com/.../invoice_11439_1776618063.pdf?X-Goog-Expires=599&X-Goog-Signature=...", "invoice_type_name": "CREDIT_LIABILITY", "status_name": "COMPLETED" } ] } ``` **Order not found** ```json { "success": false, "message": "order not found", "order_invoices": [] } ``` **Missing order_number** ```json { "success": false, "message": "order_number is required", "order_invoices": [] } ``` --- # Marketplace Integration Offer FINA as a payment option to buyers purchasing from the sellers on your platform. The flow mirrors the Direct Merchant integration — the same /fina/v1 endpoints — but every call is seller-aware: you identify which of your sellers the buyer is transacting with. ## Seller-aware calls A direct merchant *is* the seller, so the seller is resolved from your bearer token. A marketplace hosts many sellers, so three calls take an explicit seller identity: - **Check Eligibility** — pass `seller_phones` (a single-seller array) to get the buyer's onboarding status with that seller. - **Repayment Plans** — pass `seller_phone_number` and `platform: "MARKETPLACE"`. - **Create Order** — include a `payment.seller` block plus `checkout_flow` — your marketplace's own identifier, e.g. `"AROMA_WHOLESALE"`. > **Warning — Your checkout_flow must be registered first:** payment.checkout_flow carries your marketplace's own identifier — there is no shared default value. Before integrating, share the identifier you want to use with the FINA team; FINA adds it to the system and validates every Create Order request against it. An unregistered checkout_flow gets the order rejected. Buyer onboarding is buyer-level, not seller-level: the buyer onboards with FINA once, from your marketplace (see Embedded Onboarding). A seller who is not onboarded (eligibility reason SELLER_NOT_ONBOARDED) cannot be fixed through that iframe — FINA can't be offered on the order until the seller is onboarded. Everything else is identical to Direct Merchant — the order-scoped calls (Authorise, Resend OTP, Confirm Delivery, Add Invoice, Get Customer Invoices) act on an `order_id`, and the Embedded Onboarding, Down Payment Webhook, and Error Handling pages match the Direct Merchant versions. ## Buyer journey The buyer walks through five steps at your seller's checkout, each backed by one API call: 1. **Eligibility check** (API: Check Eligibility — `POST /fina/v1/users/check_eligibility`) — When the buyer reaches the payment page, call Check Eligibility. Active buyers see FINA as a payment option; new buyers are onboarded via the signup_url iframe. 2. **Repayment plan selection** (API: Repayment Plan Options — `GET /fina/v1/checkout/repayment_plans`) — Fetch the repayment plans available for the order amount and let the buyer pick one. Disable plans where has_enough_credits is false. 3. **Order placement** (API: Create Order — `POST /fina/v1/checkout/initiate`) — The buyer selects FINA and a repayment plan, then places the order. Initiate Checkout resolves the plan, creates the order, and sends an OTP to the buyer via SMS. 4. **OTP entry** (API: Resend OTP — `POST /fina/v1/orders/resend_otp`) — Prompt the buyer for the OTP. If it never arrived, resend it — up to 3 times per order. 5. **Authorisation** (API: Authorise Checkout — `POST /fina/v1/checkout/authorise`) — Submit the OTP with the order_id. On success the order is confirmed and the buyer's journey ends; on failure the buyer can retry (3 attempts). ## Post-order operations — your side The buyer's journey ends at authorisation. These calls happen later, from your backend, as the seller fulfils the order — the buyer never sees them: 1. **Add FINA Invoice** (API: Add FINA Invoice — `POST /fina/v1/invoices/add`) — Attach your invoice PDF to the order for downstream reconciliation. 2. **Confirm Delivery** (API: Confirm Delivery — `POST /fina/v1/orders/confirm_delivery`) — When the goods reach the buyer, mark the order DELIVERED with a proof of delivery. 3. **Get Customer Invoices** (API: Get Customer Invoices — `GET /fina/v1/invoices`) — Fetch the customer-visible invoices for an order, with signed download URLs. ## Order lifecycle Orders move through these statuses; each order operation is only valid in specific states (otherwise you get error 811): | Status | Meaning | |---|---| | `CREATED` | Initiate Checkout succeeded; OTP sent to the buyer via SMS. | | `AUTHORIZATION_INITIATED` | Down-payment plans only — OTP verified; the FINA-hosted down payment is pending at payment_url. | | `AUTHORISED` | Buyer's OTP verified via Authorise Checkout; payment confirmed (and any FINA-collected down payment settled). | | `OUT_FOR_DELIVERY` | Order handed to delivery (when applicable). | | `DELIVERED` | Merchant confirmed delivery with proof of delivery. | Exception statuses reachable from the happy path: | Status | Meaning | |---|---| | `EXPIRED` | Order was not authorised within ~30 minutes of creation. | | `CANCELLED` | Order was cancelled before completion. | | `REFUNDED` | Order was refunded after authorisation. | ## Rules to remember - OTP: buyers get 3 verification attempts and 3 resends per order; unauthorised orders expire after ~30 minutes. - Idempotency: order_reference_id is the idempotency anchor: sending the same reference twice returns ORDER_ALREADY_EXISTS (801) instead of creating a duplicate order. --- # Check Eligibility `POST /fina/v1/users/check_eligibility` Check whether a buyer is registered with FINA and has available credit, and whether the buyer may transact with the seller they are checking out with on your marketplace platform. For new buyers it returns a signup_url to start onboarding. Base URL (Sandbox): `https://apibe.silqfi.xyz` — the production base URL and bearer token are issued privately by the FINA team during go-live. Auth: send `Authorization: bearer FINA_BEARER_TOKEN` on every request (token issued per environment by the FINA team). > **Warning — One seller per request:** seller_phones must contain exactly one seller. Passing more than one is rejected with an HTTP 400 validation error — check eligibility once per seller the buyer is checking out with. > **Note — Different envelope:** This endpoint uses the { is_error, message, data } envelope. Business failures return HTTP 400 with is_error: true — unlike the checkout endpoints, which return HTTP 200 with a numeric error_code. ## Request | Field | Type | Required | Description | |---|---|---|---| | `buyer_phone_number` | string | yes | Buyer's phone number in international format without the plus sign. Normalised server-side. Example: 966567896789. | | `seller_phones` | array | yes | The seller the buyer is checking out with, as a single-element array holding one phone number in international format without the plus sign. Because a marketplace hosts many sellers, this identifies which seller the buyer is transacting with. Exactly one seller is supported per request; passing more than one is rejected with a validation error. Constraints: exactly one seller — passing more than one returns HTTP 400. | | `seller_phones[].seller_phone` | string | no | The seller's phone number in international format without the plus sign. Example: 966543215678. | | `language_code` | enum: en \\| ar | no | Language baked into the returned signup_url. Defaults to Arabic when omitted. | | `buyer_name` | string | no | Buyer's full name — helps pre-fill onboarding for new buyers. Defaults to the buyer's phone number when omitted. Example: Mohammed Al-Rashid. | | `vat_number` | string | no | Buyer's VAT registration number (new buyers). Example: 300908432978431. | | `buyer_cr_number` | string | no | Buyer's Commercial Registration number (new buyers). Example: 7094220000. | | `buyer_nid` | string | no | Buyer's national ID (new buyers). Example: 2247862341. | | `buyer_email` | string | no | Buyer's email address (new buyers). Example: buyer@example.com. | | `buyer_last_6_month_avg_sales` | string | no | Buyer's average monthly sales over the last 6 months, if you hold it — used during credit assessment. | ## Response | Field | Type | Required | Description | |---|---|---|---| | `is_error` | boolean | no | false on success. | | `message` | string | no | Human-readable status of the lookup. | | `data` | object | no | Buyer summary payload. | | `data.eligibility` | enum: ELIGIBLE \\| INELIGIBLE | no | The decision to branch on. ELIGIBLE — show FINA as a payment option. INELIGIBLE — see reason. | | `data.reason` | enum: BUYER_NOT_ONBOARDED \\| SELLER_NOT_ONBOARDED | no | Why the buyer is ineligible. Empty when eligibility is ELIGIBLE. SELLER_NOT_ONBOARDED means the buyer is fine but is not onboarded with the seller you passed in seller_phones. | | `data.user_status` | enum: Active \\| Pending \\| NotFound | no | Buyer's onboarding state. Pending / NotFound — onboard the buyer via signup_url. | | `data.signup_url` | string | no | Onboarding URL for Pending / NotFound buyers. Embed it in an iframe — see Embedded Onboarding. Empty for Active buyers. | | `data.credit_amount` | number | no | Buyer's currently available credit, in SAR. | | `data.external_user_id` | string | no | FINA's identifier for the buyer. Can be passed to Repayment Plan Options as customer_id. | | `data.onboarding_completed` | boolean | no | Whether the buyer has finished onboarding. | | `data.customer_name` | string | no | Buyer's registered name. | | `data.sellers` | array | no | Status for the seller passed in seller_phones — a single-element array. | | `data.sellers[].phone` | string | no | The seller's phone number. | | `data.sellers[].status` | enum: ONBOARDED \\| NOT_ONBOARDED \\| NOT_FOUND | no | Whether the buyer is onboarded with this seller. | ## Example request (Sandbox) ```bash curl -X POST "https://apibe.silqfi.xyz/fina/v1/users/check_eligibility" \ -H "Authorization: bearer FINA_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "buyer_phone_number": "966567896789", "seller_phones": ["966543215678"], "language_code": "en" }' ``` ## Example responses **200 · Eligible buyer** ```json { "is_error": false, "message": "User is onboarded successfully, credit summary fetched.", "data": { "signup_url": "", "user_status": "Active", "credit_amount": 9180.76, "external_user_id": "12955", "onboarding_completed": true, "customer_name": "Mohammed Al-Rashid", "eligibility": "ELIGIBLE", "reason": "", "sellers": [ { "phone": "966543215678", "status": "ONBOARDED" } ] } } ``` **200 · New buyer** ```json { "is_error": false, "message": "User not found, please start the onboarding process with the provided signup URL.", "data": { "signup_url": "https://apibe.silqfi.xyz/self/onboarding?onboarding_type=self&token=2e233637-003e-481b-8367-bf57241296d6&buyer_phone_number=966567896789&vat_number=300908432978431&cr_number=7094220000&nid=2247862341", "user_status": "NotFound", "credit_amount": 0, "external_user_id": "0", "onboarding_completed": false, "customer_name": "", "eligibility": "INELIGIBLE", "reason": "BUYER_NOT_ONBOARDED", "sellers": [] } } ``` **200 · Not onboarded with seller** ```json { "is_error": false, "message": "Buyer is not onboarded with the requested seller.", "data": { "signup_url": "", "user_status": "Active", "credit_amount": 9180.76, "external_user_id": "12955", "onboarding_completed": true, "customer_name": "Mohammed Al-Rashid", "eligibility": "INELIGIBLE", "reason": "SELLER_NOT_ONBOARDED", "sellers": [ { "phone": "966543215678", "status": "NOT_ONBOARDED" } ] } } ``` **400 · Multiple sellers** ```json { "is_error": true, "message": "Invalid request: only one seller is supported per eligibility check", "data": null } ``` **400 · Validation error** ```json { "is_error": true, "message": "Invalid request: buyer_phone_number is required", "data": null } ``` --- # Repayment Plan Options `GET /fina/v1/checkout/repayment_plans` Returns the repayment plans (tenures) available to a buyer for a given order amount on your marketplace — cost breakdown, installment schedule, down-payment requirement, and credit eligibility per plan. Price the plans against the sub-merchant (seller) the buyer is purchasing from by passing platform and seller_phone_number. Base URL (Sandbox): `https://apibe.silqfi.xyz` — the production base URL and bearer token are issued privately by the FINA team during go-live. Auth: send `Authorization: bearer FINA_BEARER_TOKEN` on every request (token issued per environment by the FINA team). > **Note — Query parameters:** All parameters are sent in the query string — this GET takes no request body. > **Note — Identify the seller:** On a marketplace, send platform=MARKETPLACE and the seller_phone_number of the sub-merchant the buyer is checking out with so plans are priced against that seller's configuration. Pass the same platform value to Initiate Checkout. > **Note — Carry repayment_config_id forward:** The buyer's chosen plan's repayment_config_id is what you pass to Initiate Checkout as payment.repayment_config_id. By default only plans the buyer's credit covers are returned; pass show_credit_insufficient_plans=true to see the rest and disable the ones where has_enough_credits is false. ## Request | Field | Type | Required | Description | |---|---|---|---| | `order_amount` | number | yes | Order total to price the plans against. Constraints: query param; must be > 0. Example: 515. | | `customer_phone_number` | string | no | Buyer's phone number. Pass exactly one of customer_phone_number or customer_id. Constraints: query param. Example: 966567896789. | | `customer_id` | number | no | FINA's external_user_id from Check Eligibility. Pass exactly one of customer_phone_number or customer_id. Constraints: query param. | | `platform` | enum: MARKETPLACE | yes | Integration platform identifier. Required for marketplace — send MARKETPLACE. Filters plans to those enabled for the channel; use the same value as Initiate Checkout. Constraints: query param. Example: MARKETPLACE. | | `seller_phone_number` | string | yes | Phone number of the seller (sub-merchant) on your marketplace the buyer is purchasing from. Plans are priced against this seller's configuration. Constraints: query param; international format, no leading +. Example: 966543215678. | | `merchant_share` | number | no | Merchant's share of the BNPL commission, in percent. If you send it here, send the same value in Initiate Checkout's commission_config. Constraints: query param; 0–100. | | `tenure_in_days` | number | no | Return only the plan for this tenure. Errors if the tenure is not available to the buyer. Constraints: query param. | | `show_credit_insufficient_plans` | boolean | no | Set true to also return plans the buyer's credit does not cover (has_enough_credits: false). Constraints: query param; defaults to false. | ## Response | Field | Type | Required | Description | |---|---|---|---| | `success` | boolean | no | true when plans were computed. | | `message` | string | no | Human-readable status. | | `error_code` | string | no | Numeric code as a string; "200" on success for this endpoint. | | `errors` | array | no | Validation / failure details, empty on success. | | `merchant_share` | number | no | Merchant commission share applied when pricing the plans, in percent. | | `plans` | array | no | One entry per available repayment plan. | | `plans[].repayment_config_id` | string | no | The tenure id. Pass the chosen plan's value to Initiate Checkout as payment.repayment_config_id. | | `plans[].name` | string | no | Display label for the plan. | | `plans[].tenure_in_days` | string | no | Tenure length in days. | | `plans[].is_default` | boolean | no | The plan applied if none is explicitly selected. | | `plans[].commission_treatment` | enum: PROPORTIONAL_ON_FULL \\| PROPORTIONAL_EXCLUDING_PREPAID | no | How commission is applied across installments. | | `plans[].bnpl_cost_percentage` | number | no | BNPL cost for this tenure, in percent. | | `plans[].order_amount` | number | no | Echo of the priced order amount. | | `plans[].customer_payable_amount` | number | no | Total the buyer repays across all installments. | | `plans[].prepaid_amount` | number | no | Day-zero down payment the buyer pays upfront (0 if none). | | `plans[].requires_prepaid_collection` | boolean | no | true — a day-zero down payment must be collected at checkout (see Initiate Checkout's prepaid_payment). | | `plans[].has_enough_credits` | boolean | no | Whether the buyer's credit limit covers this plan. | | `plans[].financed_amount` | number | no | Amount financed by FINA (order amount minus down payment). | | `plans[].down_payment_amount` | number | no | Same as prepaid_amount — the day-zero amount. | | `plans[].total_commission_amount` | number | no | Total commission for the plan, in SAR. | | `plans[].merchant_commission_amount` | number | no | Commission borne by the merchant. | | `plans[].customer_commission_amount` | number | no | Commission borne by the buyer. | | `plans[].checkout_commission_amount` | number | no | Commission collected at checkout, if any. | | `plans[].net_payout_to_merchant` | number | no | What the merchant receives after commission. | | `plans[].commission_base` | number | no | Amount the commission percentage is applied to. | | `plans[].schedules` | array | no | Installment schedule for the plan. | | `plans[].schedules[].payment_installment_no` | string | no | 1-based installment number. | | `plans[].schedules[].payment_date` | string | no | Due date (YYYY-MM-DD). | | `plans[].schedules[].offset_days` | number | no | Days after order creation this installment falls due. 0 = due at checkout. | | `plans[].schedules[].principal_amount` | number | no | Principal due, in SAR. | | `plans[].schedules[].commission_amount` | number | no | Commission due, in SAR. | | `plans[].schedules[].total_due` | number | no | Total due for the installment. | ## Errors Business failures are returned as HTTP 200 with a non-zero `error_code` (a numeric code serialized as a JSON string; `"0"` means success). See the Error Handling page for the envelope. | Code | Name | Description | Resolution | |---|---|---|---| | `"400"` | INVALID_PARAMS | A required field is missing or a value failed validation (e.g. totals don't reconcile, invalid phone format, unknown field value). | Check errors[] for the failing field. Ensure order.total_amount equals the sum of items[].total_amount plus shipping_amount minus discount_amount. | | `"802"` | SELLER_NOT_VALID | The merchant resolved from the bearer token is not recognised for this operation. | Verify the bearer token belongs to the correct merchant account. | | `"878"` | PLAN_NOT_CONFIGURED | No repayment plan is configured for the requested tenure_in_days / channel. | Only offer plans returned by Repayment Plan Options; contact FINA to configure more. | | `"500"` | INTERNAL_ERROR | An unexpected failure occurred on FINA's side. | Retry with backoff; contact FINA support if it persists. | ## Example request (Sandbox) ```bash curl -X GET "https://apibe.silqfi.xyz/fina/v1/checkout/repayment_plans?customer_phone_number=966567896789&order_amount=515&platform=MARKETPLACE&seller_phone_number=966543215678" \ -H "Authorization: bearer FINA_BEARER_TOKEN" ``` ## Example responses **200 · Success** ```json { "success": true, "message": "success", "error_code": "200", "errors": [], "merchant_share": 100, "plans": [ { "repayment_config_id": "4", "name": "Default", "tenure_in_days": "30", "is_default": true, "commission_treatment": "PROPORTIONAL_ON_FULL", "bnpl_cost_percentage": 3, "order_amount": 515, "total_commission_amount": 15.45, "merchant_commission_amount": 15.45, "customer_commission_amount": 0, "customer_payable_amount": 515, "net_payout_to_merchant": 499.55, "down_payment_amount": 0, "checkout_commission_amount": 0, "prepaid_amount": 0, "financed_amount": 515, "commission_base": 515, "schedules": [ { "payment_installment_no": "1", "payment_date": "2026-07-21", "offset_days": 30, "principal_amount": 515, "commission_amount": 0, "total_due": 515 } ], "requires_prepaid_collection": false, "has_enough_credits": true }, { "repayment_config_id": "7", "name": "Default", "tenure_in_days": "90", "is_default": true, "commission_treatment": "PROPORTIONAL_EXCLUDING_PREPAID", "bnpl_cost_percentage": 9, "order_amount": 515, "total_commission_amount": 40, "merchant_commission_amount": 40, "customer_commission_amount": 0, "customer_payable_amount": 515, "net_payout_to_merchant": 475, "down_payment_amount": 128.75, "checkout_commission_amount": 0, "prepaid_amount": 128.75, "financed_amount": 386.25, "commission_base": 386.25, "schedules": [ { "payment_installment_no": "1", "payment_date": "2026-06-21", "offset_days": 0, "principal_amount": 128.75, "commission_amount": 0, "total_due": 128.75 }, { "payment_installment_no": "2", "payment_date": "2026-07-21", "offset_days": 30, "principal_amount": 128.75, "commission_amount": 0, "total_due": 128.75 }, { "payment_installment_no": "3", "payment_date": "2026-08-20", "offset_days": 60, "principal_amount": 128.75, "commission_amount": 0, "total_due": 128.75 }, { "payment_installment_no": "4", "payment_date": "2026-09-20", "offset_days": 90, "principal_amount": 128.75, "commission_amount": 0, "total_due": 128.75 } ], "requires_prepaid_collection": true, "has_enough_credits": true } ] } ``` **400 · Invalid params** ```json { "success": false, "message": "provide exactly one of customer_id or customer_phone_number", "error_code": "400", "errors": [ "provide exactly one of customer_id or customer_phone_number" ], "plans": [] } ``` **Plan not configured** ```json { "success": false, "message": "no active repayment plans configured", "error_code": "878", "errors": [ "no active repayment plans configured for this channel" ], "plans": [] } ``` --- # Create Order `POST /fina/v1/checkout/initiate` Initiates a BNPL checkout session on behalf of one of your marketplace sellers, creates the order, and triggers an OTP SMS to the buyer. As a marketplace platform you identify both the buyer and the seller (sub-merchant) fulfilling the order. Base URL (Sandbox): `https://apibe.silqfi.xyz` — the production base URL and bearer token are issued privately by the FINA team during go-live. Auth: send `Authorization: bearer FINA_BEARER_TOKEN` on every request (token issued per environment by the FINA team). > **Note — Buyer & seller:** Marketplace checkout involves three parties: your platform (authenticated by the bearer token), the buyer (payment.buyer.phone_number), and the seller — the sub-merchant on your marketplace fulfilling the order — identified by payment.seller.phone_number. The seller must be onboarded to FINA before you can transact on their behalf. > **Note — Registering your checkout_flow:** payment.checkout_flow carries your marketplace's own identifier — not a fixed value. Share the identifier you want to use with the FINA team while integrating; FINA adds it to the system and validates every Create Order request against it. The examples on this page use "AROMA_WHOLESALE", the marketplace from the interactive demo. > **Warning — Plan selection & consistency:** payment.repayment_config_id drives plan selection. When commission_config.use_config is true, commission_config.tenure_in_days must match the selected plan's tenure and merchant_share must match the value sent to Repayment Plan Options — otherwise the order is rejected with error 877. If repayment_config_id is omitted, FINA falls back to the default plan for commission_config.tenure_in_days. ## Request | Field | Type | Required | Description | |---|---|---|---| | `payment` | object | yes | The checkout payload. | | `payment.platform` | enum: MARKETPLACE | yes | Integration channel. Always "MARKETPLACE" for this guide. | | `payment.checkout_flow` | string | yes | Your marketplace's identifier — e.g. "AROMA_WHOLESALE". Share the value you want to use with the FINA team while integrating: FINA adds it to the system and validates every Create Order request against it. Requests carrying an unregistered checkout_flow are rejected. Constraints: assigned by FINA at onboarding. | | `payment.currency_code` | string | no | Order currency. Optional — SAR is the only supported currency. Constraints: locked to "SAR". | | `payment.country_code` | string | no | Order country. Optional — SA is the only supported country. Constraints: locked to "SA". | | `payment.repayment_config_id` | number | no | The buyer's chosen plan from Repayment Plan Options. Falls back to the default plan when omitted. | | `payment.description` | string | no | Free-text order description. | | `payment.buyer` | object | yes | The buyer placing the order. | | `payment.buyer.phone_number` | string | yes | Buyer's phone number. The buyer must already be onboarded — see Check Eligibility. | | `payment.seller` | object | yes | The seller (sub-merchant) on your marketplace fulfilling this order. Required for marketplace checkout — this is what distinguishes it from direct-merchant checkout, where the merchant is always the seller. | | `payment.seller.phone_number` | string | yes | Seller's phone number. The seller must already be onboarded to FINA on your marketplace. | | `payment.order` | object | yes | Order contents and totals. | | `payment.order.order_reference_id` | string | yes | Your own order reference. Serves as the idempotency anchor — duplicates are rejected with error 801. | | `payment.order.source_order_number` | string | no | Order number in your own system, stored alongside the order for reconciliation. | | `payment.order.total_amount` | number | yes | Grand total the buyer pays. Constraints: = Σ items[].total_amount + shipping_amount − discount_amount. | | `payment.order.sub_total` | number | no | Total before tax and shipping. | | `payment.order.tax_amount` | number | no | Total tax, in SAR. | | `payment.order.shipping_amount` | number | no | Shipping cost, in SAR. | | `payment.order.discount_amount` | number | no | Discount applied, in SAR. | | `payment.order.items` | array | yes | Line items. Constraints: min 1 item; SKUs must be unique. | | `payment.order.items[].title` | string | yes | Item name. | | `payment.order.items[].reference_id` | string | yes | Your item identifier. | | `payment.order.items[].sku` | string | yes | Stock keeping unit. | | `payment.order.items[].quantity` | number | yes | Units ordered. Constraints: > 0. | | `payment.order.items[].unit_price` | number | no | Price per unit, ex-tax. | | `payment.order.items[].tax_amount` | number | no | Tax for the line, in SAR. | | `payment.order.items[].discount_amount` | number | no | Discount for the line, in SAR. | | `payment.order.items[].total_amount` | number | no | Line total including tax. | | `payment.order.items[].size` | string | no | Variant size, if any. | | `payment.order.items[].size_type` | string | no | Sizing convention, e.g. "standard". | | `payment.order.items[].color` | string | no | Variant colour, if any. | | `payment.order.items[].category` | string | no | Product category. | | `payment.order.items[].product_material` | string | no | Primary material of the product. | | `payment.order.items[].product_url` | string | no | Link to the product page. | | `payment.order.items[].image_url` | string | no | Link to the product image. | | `payment.billing_address` | object | yes | Billing address. | | `payment.billing_address.first_name` | string | yes | Recipient's first name. | | `payment.billing_address.last_name` | string | no | Recipient's last name. | | `payment.billing_address.phone_number` | string | yes | Recipient's phone number. | | `payment.billing_address.address` | string | no | Street address. | | `payment.billing_address.city` | string | no | City. | | `payment.billing_address.region` | string | no | Region / province. | | `payment.billing_address.zip` | string | no | Postal code. | | `payment.billing_address.country_code` | string | no | ISO country code — "SA". | | `payment.billing_address.country_name` | string | no | Country display name. | | `payment.billing_address.latitude` | string | no | Latitude, as a string. | | `payment.billing_address.longitude` | string | no | Longitude, as a string. | | `payment.shipping_address` | object | yes | Shipping address. | | `payment.shipping_address.first_name` | string | yes | Recipient's first name. | | `payment.shipping_address.last_name` | string | no | Recipient's last name. | | `payment.shipping_address.phone_number` | string | yes | Recipient's phone number. | | `payment.shipping_address.address` | string | no | Street address. | | `payment.shipping_address.city` | string | no | City. | | `payment.shipping_address.region` | string | no | Region / province. | | `payment.shipping_address.zip` | string | no | Postal code. | | `payment.shipping_address.country_code` | string | no | ISO country code — "SA". | | `payment.shipping_address.country_name` | string | no | Country display name. | | `payment.shipping_address.latitude` | string | no | Latitude, as a string. | | `payment.shipping_address.longitude` | string | no | Longitude, as a string. | | `payment.commission_config` | object | no | Commission arrangement for the order. | | `payment.commission_config.use_config` | boolean | no | Set true to apply tenure_in_days / merchant_share below. | | `payment.commission_config.tenure_in_days` | number | no | Must match the selected plan's tenure when use_config is true. | | `payment.commission_config.merchant_share` | number | no | Merchant's share of the commission. Must match the value sent to Repayment Plan Options. Constraints: 0–100. | | `payment.prepaid_payment` | object | no | Required when the selected plan has requires_prepaid_collection: true. | | `payment.prepaid_payment.collection_method` | enum: FINA \\| MERCHANT | no | Who collects the day-zero down payment: FINA (from the buyer) or MERCHANT (you collect it yourself). | | `payment.prepaid_payment.success_redirection_url` | string | no | Where FINA returns the buyer after a successful hosted down-payment (collection_method FINA). Must sit on the same domain you had whitelisted for embedded onboarding. Constraints: whitelisted domain. | | `payment.prepaid_payment.failure_redirection_url` | string | no | Where FINA returns the buyer after a failed hosted down-payment. Must sit on the same whitelisted domain as success_redirection_url. Constraints: whitelisted domain. | | `payment.prepaid_utr` | string | no | Your payment reference for the collected down payment — required when collection_method is MERCHANT. | ## Response | Field | Type | Required | Description | |---|---|---|---| | `error_code` | string | no | Numeric code as a string; "0" on success. | | `errors` | array | no | Failure details, empty on success. | | `message` | string | no | Human-readable status. | | `data` | object | no | The created order. | | `data.order_id` | string | no | FINA's order identifier — used by all subsequent order operations. | | `data.order_reference_id` | string | no | Echo of your reference. | | `data.order_status` | string | no | Always "CREATED" on success. OTP has been sent to the buyer. | | `data.payment_url` | string | no | Payment link for FINA-collected down payments; empty otherwise. | | `data.prepaid_amount` | number | no | Day-zero down payment owed by the buyer (0 if none). | | `data.transaction_id` | string | no | Down-payment transaction reference, when applicable. | | `data.repayment_config_id` | string | no | The plan actually applied to the order — echoes your selection, or the default plan when you omitted one. | ## Errors Business failures are returned as HTTP 200 with a non-zero `error_code` (a numeric code serialized as a JSON string; `"0"` means success). See the Error Handling page for the envelope. | Code | Name | Description | Resolution | |---|---|---|---| | `"400"` | INVALID_PARAMS | A required field is missing or a value failed validation (e.g. totals don't reconcile, invalid phone format, unknown field value). | Check errors[] for the failing field. Ensure order.total_amount equals the sum of items[].total_amount plus shipping_amount minus discount_amount. | | `"801"` | ORDER_ALREADY_EXISTS | An order with the same order_reference_id already exists. order_reference_id is the idempotency anchor. | Use a fresh order_reference_id for new orders; reuse the existing order for retries. | | `"802"` | SELLER_NOT_VALID | The merchant resolved from the bearer token is not recognised for this operation. | Verify the bearer token belongs to the correct merchant account. | | `"803"` | SELLER_INACTIVE | The merchant account exists but is not active. | Contact the FINA team to activate the merchant account. | | `"804"` | ANOTHER_CHECKOUT_IN_PROGRESS | A concurrent checkout for the same buyer is already in flight. | Wait for the in-flight checkout to finish, then retry. | | `"805"` | CUSTOMER_NOT_ONBOARDED | The buyer has not completed FINA onboarding. | Run Check Eligibility and take the buyer through the signup_url onboarding flow. | | `"806"` | CUSTOMER_CREDIT_LIMIT_EXCEEDED | The buyer's available credit does not cover this order. | Offer a plan with a down payment, reduce the order amount, or hide FINA for this purchase. | | `"876"` | MISSING_PLAN_SELECTION | No repayment plan could be resolved for the order. | Pass a repayment_config_id returned by Repayment Plan Options. | | `"877"` | INVALID_PLAN_SELECTION | The repayment_config_id is invalid, or commission_config.tenure_in_days / merchant_share do not match the selected plan. | Send a repayment_config_id from Repayment Plan Options and keep commission_config consistent with that plan. | | `"878"` | PLAN_NOT_CONFIGURED | No repayment plan is configured for the requested tenure_in_days / channel. | Only offer plans returned by Repayment Plan Options; contact FINA to configure more. | | `"881"` | BG_DOC_EXPIRED | The buyer's credit agreement document has expired. | Send the buyer through onboarding again via Check Eligibility's signup_url. | | `"500"` | INTERNAL_ERROR | An unexpected failure occurred on FINA's side. | Retry with backoff; contact FINA support if it persists. | ## Example request (Sandbox) ```bash curl -X POST "https://apibe.silqfi.xyz/fina/v1/checkout/initiate" \ -H "Authorization: bearer FINA_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "payment": { "buyer": { "phone_number": "966567896789" }, "seller": { "phone_number": "966543215678" }, "order": { "order_reference_id": "ORD-REF-1013", "total_amount": 515.00, "discount_amount": 0, "tax_amount": 65.22, "shipping_amount": 15.00, "items": [ { "title": "Test Product", "sku": "SKU-001", "quantity": 1, "unit_price": 434.78, "total_amount": 500.00, "tax_amount": 65.22, "discount_amount": 0, "reference_id": "ITEM-001", "size": "M", "size_type": "standard", "color": "black", "category": "apparel", "product_material": "cotton", "product_url": "https://example.com/p/sku-001", "image_url": "https://example.com/img/sku-001.jpg" } ] }, "billing_address": { "first_name": "Test", "address": "King Fahd Rd", "city": "Riyadh", "zip": "12211", "phone_number": "966594298432", "country_code": "SA", "country_name": "Saudi Arabia" }, "shipping_address": { "first_name": "Test", "address": "King Fahd Rd", "city": "Riyadh", "zip": "12211", "phone_number": "966594298432", "country_code": "SA", "country_name": "Saudi Arabia" }, "platform": "MARKETPLACE", "currency_code": "SAR", "country_code": "SA", "description": "Test marketplace checkout", "checkout_flow": "AROMA_WHOLESALE" } }' ``` ## Example responses **200 · Success** ```json { "error_code": "0", "errors": [], "message": "Order created successfully", "data": { "order_id": "F26070910AB3XKQ70", "order_reference_id": "ORD-REF-1013", "order_status": "CREATED", "payment_url": "", "prepaid_amount": 0, "transaction_id": "", "repayment_config_id": "4" } } ``` **Duplicate order** ```json { "error_code": "801", "errors": [ "an order with order_reference_id ORD-REF-1013 already exists" ], "message": "Order already exists", "data": null } ``` **Invalid plan** ```json { "error_code": "877", "errors": [ "commission_config.tenure_in_days does not match the selected repayment plan" ], "message": "Invalid plan selection", "data": null } ``` **Totals mismatch** ```json { "error_code": "400", "errors": [ "order.total_amount must equal sum(items.total_amount) + shipping_amount - discount_amount" ], "message": "Validation failed", "data": null } ```