Integration of Karta Pokupok Installment on Website
"Karta Pokupok" is a Belarusian installment card from Karta Pokupok LLC (formerly affiliated with Meridian Bank). Works like Halva: the holder gets the goods now, pays in equal installments interest-free, the store receives the full amount immediately minus commission. Significant reach among Belarusian shoppers — the card is issued in major retail chains.
Integration Architecture
Integration is built via REST API of the partner cabinet. Sequence:
- Store forms a request via API → receives a link to the form
- Buyer fills out the form and confirms installment (SMS code)
- Webhook notifies store of request status
- On status
APPROVED— ship the order
Creating a Request
class KartaPokupokService
{
private const BASE_URL = 'https://api.kartapokupok.by/v1';
public function createApplication(Order $order, int $months): array
{
$response = Http::withHeaders([
'X-Partner-Id' => env('KP_PARTNER_ID'),
'X-Partner-Token' => env('KP_TOKEN'),
'Content-Type' => 'application/json',
])->post(self::BASE_URL . '/applications', [
'order' => [
'id' => $order->id,
'amount' => $order->total, // in BYN
'term' => $months, // 3, 6, 12, 18, 24
'purpose' => 'Order #' . $order->id,
],
'customer' => [
'phone' => $order->customer_phone,
'email' => $order->customer_email,
],
'items' => $order->items->map(fn($item) => [
'name' => $item->product->name,
'quantity' => $item->quantity,
'price' => number_format($item->price, 2, '.', ''),
'total' => number_format($item->price * $item->quantity, 2, '.', ''),
])->toArray(),
'callback_url' => 'https://example.com/webhook/karta-pokupok',
'success_url' => 'https://example.com/payment/success',
'fail_url' => 'https://example.com/payment/fail',
]);
// Returns application_id and redirect_url
return $response->json();
}
}
Webhook
public function webhook(Request $request): Response
{
// HMAC signature verification
$body = $request->getContent();
$receivedSign = $request->header('X-Signature');
$expectedSign = hash_hmac('sha256', $body, env('KP_WEBHOOK_SECRET'));
if (!hash_equals($expectedSign, $receivedSign)) {
return response('Bad signature', 403);
}
$payload = $request->json()->all();
// Statuses: APPROVED, REJECTED, CANCELLED, EXPIRED
match ($payload['status']) {
'APPROVED' => $this->onApproved($payload),
'REJECTED' => $this->onRejected($payload),
default => null,
};
return response('OK');
}
private function onApproved(array $payload): void
{
Order::where('id', $payload['order_id'])->update([
'status' => 'paid',
'payment_type' => 'karta_pokupok',
'kp_application' => $payload['application_id'],
'paid_at' => now(),
]);
}
Installment Calculator on Website
Displaying monthly payment next to price is standard practice. Calculation is simple: amount divided by number of months:
interface InstallmentOption {
months: number;
monthlyPayment: number;
}
function calculateInstallments(price: number, availableTerms: number[]): InstallmentOption[] {
return availableTerms.map(months => ({
months,
monthlyPayment: Math.ceil(price / months * 100) / 100,
}));
}
// Example usage
const options = calculateInstallments(299.90, [3, 6, 12]);
// [{ months: 3, monthlyPayment: 99.97 }, { months: 6, monthlyPayment: 49.99 }, ...]
function InstallmentBadge({ price }: { price: number }) {
const minMonthly = Math.ceil(price / 24 * 100) / 100; // maximum term
return (
<div className="installment-badge">
from <strong>{minMonthly.toFixed(2)} BYN/month</strong>{' '}
in installments via "Karta Pokupok"
</div>
);
}
Getting Available Terms
Installment terms depend on product category and amount. Current conditions are requested via API:
$terms = Http::withHeaders([
'X-Partner-Id' => env('KP_PARTNER_ID'),
'X-Partner-Token' => env('KP_TOKEN'),
])->get(self::BASE_URL . '/terms', [
'amount' => $order->total,
'category' => $product->kp_category_code,
])->json('available_terms');
If API returns empty array — the product or amount doesn't meet installment conditions. The "Karta Pokupok" payment option should be hidden for that item.
Request Status
Besides webhook, request status can be queried manually — useful for the return page:
$status = Http::withHeaders([...])
->get(self::BASE_URL . '/applications/' . $applicationId)
->json('status');
Program connection timeframe is about 5 business days. Will need to provide data about legal entity, assortment, and turnover.







