composer require kvell/kvell-php<?php
require __DIR__ . '/vendor/autoload.php';
$kvell = new \Kvell\KvellClient([
'apiKey' => getenv('KVELL_API_KEY'),
'secretKey' => getenv('KVELL_SECRET_KEY'),
'environment' => 'sandbox', // switch to 'production' when you go live
]);
<?php
require __DIR__ . '/vendor/autoload.php';
$kvell = new \Kvell\KvellClient([
'apiKey' => getenv('KVELL_API_KEY'),
'secretKey' => getenv('KVELL_SECRET_KEY'),
'environment' => 'sandbox',
]);
// Amounts are always integers in kuruş (1/100 TRY) — 15000 = 150.00 TRY
$session = $kvell->paymentSessions->create([
'transactionId' => 'ORDER-2026-001',
'amount' => 15000,
'currency' => 'TRY',
'description' => 'Order #1234',
'returnUrl' => 'https://merchant.example.com/return',
]);
echo 'Redirect the buyer to: ' . $session->checkoutUrl . PHP_EOL;
<?php
use Kvell\Exception\KvellError;
require __DIR__ . '/vendor/autoload.php';
$kvell = new \Kvell\KvellClient([
'apiKey' => getenv('KVELL_API_KEY'),
'secretKey' => getenv('KVELL_SECRET_KEY'),
'environment' => 'sandbox',
]);
try {
$payment = $kvell->payments->charge([
'transactionId' => 'ORDER-2026-001',
'amount' => 15000, // kuruş
'currency' => 'TRY',
'cardToken' => 'tok_9f8e7d6c5b4a',
]);
echo 'Captured payment ' . $payment->paymentId . PHP_EOL;
} catch (KvellError $err) {
switch ($err->code) {
case 'KVELL-PAY-4001':
error_log('Card declined (requestId=' . $err->requestId . ')');
break;
case 'KVELL-GW-1002':
error_log('Signature invalid, check your secret key (requestId=' . $err->requestId . ')');
break;
default:
error_log('Kvell error ' . $err->code . ' (requestId=' . $err->requestId . ')');
}
throw $err;
}
<?php
require __DIR__ . '/vendor/autoload.php';
$signatureHeader = $_SERVER['HTTP_X_KVELL_SIGNATURE'] ?? '';
$eventId = $_SERVER['HTTP_X_KVELL_EVENT_ID'] ?? '';
$rawBody = file_get_contents('php://input'); // raw bytes, never json_decode first
$secret = getenv('KVELL_WEBHOOK_SECRET');
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($expected, $signatureHeader)) {
http_response_code(401);
exit('invalid signature');
}
// isEventProcessed()/markEventProcessed() hit your own dedup store (Redis, DB, etc.)
if (isEventProcessed($eventId)) {
http_response_code(200);
exit('duplicate');
}
markEventProcessed($eventId);
$event = json_decode($rawBody, true);
switch ($event['eventType']) {
case 'payment.captured':
// Fulfil the order for $event['payload']['paymentId'] asynchronously
break;
}
http_response_code(200);
echo 'ok';