Fondy Payment System Integration
Fondy is Ukrainian payment gateway, actively used in Ukraine, with EU licenses. Supports Visa, Mastercard, Google Pay, Apple Pay, local Ukrainian methods (Privat24, MONO, Ukrbank). Documentation in Russian and English, REST API stable.
Connection Parameters
From merchant.fondy.eu personal account get:
-
merchant_id— store identifier -
secret_key— for request signature
Test transactions processed via same URL, difference only in test card data.
Hosted Payment Page — Basic Integration
Most common variant — redirect to Fondy page:
function buildFondyPayment(int $orderId, int $amountInKopecks, string $currency = 'UAH'): array
{
$merchantId = env('FONDY_MERCHANT_ID');
$secretKey = env('FONDY_SECRET_KEY');
$params = [
'merchant_id' => $merchantId,
'order_id' => $orderId . '_' . time(), // uniqueness mandatory
'order_desc' => "Order #{$orderId}",
'amount' => $amountInKopecks,
'currency' => $currency,
'response_url' => 'https://example.com/payment/return',
'server_callback_url' => 'https://example.com/webhook/fondy',
'lang' => 'ru',
];
// Signature: SHA1 from value concatenation, sorted by key, via |
ksort($params);
$signString = $secretKey . '|' . implode('|', array_values($params));
$params['signature'] = sha1($signString);
return $params;
}
// Send POST to https://pay.fondy.eu/api/checkout/redirect/
Form can be submitted directly as HTML POST, or via API with checkout_url getting.
API Method with URL Getting
$response = Http::post('https://pay.fondy.eu/api/checkout/url/', [
'request' => buildFondyPayment(12345, 150000),
]);
$checkoutUrl = $response->json('response.checkout_url');
return redirect($checkoutUrl);
Signature Check in Webhook
public function handleCallback(Request $request): Response
{
$data = $request->all();
// Remove signature from data for check
$received = $data['signature'];
unset($data['signature']);
// Remove empty fields
$data = array_filter($data, fn($v) => $v !== '' && $v !== null);
ksort($data);
$expected = sha1(env('FONDY_SECRET_KEY') . '|' . implode('|', array_values($data)));
if (!hash_equals($expected, $received)) {
return response('Bad signature', 403);
}
if ($data['order_status'] === 'approved') {
// order_id contains time suffix, separate original ID
$orderId = (int) explode('_', $data['order_id'])[0];
Order::where('id', $orderId)->update([
'status' => 'paid',
'transaction_id' => $data['payment_id'],
]);
}
// Fondy expects HTTP 200 with body {"response":"accept"}
return response()->json(['response' => 'accept']);
}
Statuses order_status: approved, declined, expired, processing, reversed (refund).
Embedded Form (Checkout JS)
For embedding form directly on page without Fondy transition:
<script src="https://pay.fondy.eu/static_common/v1/checkout/ipsp.js"></script>
$ipsp.get('checkout').config({
merchantId: FONDY_MERCHANT_ID,
amount: 1500,
currency: 'UAH',
orderId: 'order-12345',
orderDesc: 'Test order',
signature: serverGeneratedSignature,
lang: 'ru',
fields: false, // use native Fondy fields
fee: false,
theme: {
preset: 'silver',
},
});
Signature for JS-widget generated on server and passed to client. Never generate on browser side — secret key can't be passed to frontend.
Refunds
$params = [
'merchant_id' => env('FONDY_MERCHANT_ID'),
'order_id' => $originalOrderId,
'amount' => 75000, // partial refund
'currency' => 'UAH',
'comment' => 'Customer request',
];
ksort($params);
$params['signature'] = sha1(env('FONDY_SECRET_KEY') . '|' . implode('|', array_values($params)));
Http::post('https://pay.fondy.eu/api/reverse/order/', ['request' => $params]);
Nuances
order_id uniqueness in Fondy mandatory requirement. If same order_id used repeatedly, transaction rejected. Common practice — add timestamp or UUID to internal order ID. Save mapping fondy_order_id → local_order_id before transaction creating.
Account confirmation period in production mode — 2–5 business days.







