Integrate
Idempotency
Retry a create safely, without charging the customer twice.
merchant_reference is your idempotency key: the same reference returns the same intent.
How it works
Section titled “How it works”| Request | Answer |
|---|---|
| First create with a reference | 201, new intent |
| Later create, same reference | 200, the existing intent, any status (even succeeded, canceled) |
| Two identical creates at once | One intent |
- Unique per application. One per payment, such as your order number.
- A repeat ignores its other fields: amount, description and expiry stay as created.
- A repeat must still be valid (
422otherwise).expires_atlimits apply only on creation.
# Run it twice: the first call answers 201, the second 200 with the same uuid.curl -i https://pay.fanak.ly/api/v1/payment-intents \ -H "X-API-Key: $FANAK_API_KEY" \ -H "Authorization: Bearer $FANAK_ACCESS_TOKEN" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{"merchant_reference": "order-1042", "amount": 5000, "currency": "LYD", "description": "Order #1042"}'use Illuminate\Http\Client\ConnectionException;use Illuminate\Http\Client\RequestException;use Illuminate\Support\Facades\Http;
// Retry network errors and 5xx only: a 4xx will not succeed on retry.$retryable = fn (Throwable $e): bool => $e instanceof ConnectionException || ($e instanceof RequestException && $e->response->serverError());
$response = Http::withHeaders(['X-API-Key' => config('services.fanak.api_key')]) ->withToken(config('services.fanak.access_token')) ->acceptJson() ->retry(3, 1000, $retryable, throw: false) ->post('https://pay.fanak.ly/api/v1/payment-intents', [ 'merchant_reference' => "order-{$order->id}", 'amount' => $order->amount, 'currency' => 'LYD', 'description' => "Order #{$order->id}", ]) ->throw();
$intent = $response->json('data');
if ($intent['amount'] !== $order->amount) { throw new RuntimeException("merchant_reference order-{$order->id} was already used for another amount.");}async function createIntent(order, tries = 3) { for (let attempt = 1; ; attempt++) { try { const response = await fetch('https://pay.fanak.ly/api/v1/payment-intents', { method: 'POST', headers: { 'X-API-Key': process.env.FANAK_API_KEY, Authorization: `Bearer ${process.env.FANAK_ACCESS_TOKEN}`, Accept: 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ merchant_reference: `order-${order.id}`, amount: order.amount, currency: 'LYD', description: `Order #${order.id}`, }), });
if (response.status >= 500) { throw new Error(`Fanak answered ${response.status}`); }
if (!response.ok) { // A 4xx will not succeed on retry: fix the request. throw Object.assign(new Error(await response.text()), { final: true }); }
const { data: intent } = await response.json();
if (intent.amount !== order.amount) { throw Object.assign(new Error(`order-${order.id} was already used for another amount`), { final: true }); }
return intent; } catch (error) { if (error.final || attempt >= tries) throw error; await new Promise((resolve) => setTimeout(resolve, 1000 * attempt)); } }}Choosing references
Section titled “Choosing references”- Derive it from your order, so retries reuse it.
- A second payment for one order needs a new one:
order-1042-2. - At most 255 characters.