emre-tarhan/kuveytturk-vpos-laravel-sdk

Kuveyt Türk Sanal Pos (Virtual POS) Laravel SDK with 3D Secure 2.0 support

Maintainers

Package info

github.com/emre-tarhan/kuveytturk-vpos-laravel-sdk

pkg:composer/emre-tarhan/kuveytturk-vpos-laravel-sdk

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-06-12 12:04 UTC

This package is auto-updated.

Last update: 2026-06-19 17:03:13 UTC


README

Latest Version PHP License

Kuveyt Turk Sanal POS entegrasyonu icin kapsamli Laravel SDK. Tam 3D Secure 2.0 destegi.

Ozellikler

  • 3D Secure 2.0 Tam Destek — Iki asamali odeme akisi
  • Iki Asamali Odeme: Kart Dogrulama + Provizyon (Odeme Alma)
  • Taksit Destegi — 24 aya kadar taksit imkani
  • Ertelemeli Odeme (Harcama Oteleme) — Esnek odeme planlama
  • Hash Veri Dogrulama — SHA1 tabanli istek dogrulama
  • Test & Produksiyon Ortamlari — Ayri endpoint'ler
  • Satis / Iptal / Iade / Kismi Iade Islemleri — Tam islem yasam dongusu
  • WCF SOAP Servis Entegrasyonu — Iptal/iade islemleri icin
  • Kapsamli Hata Yonetimi — Tum Kuveyt Turk hata kodlari
  • Laravel 12.x & 13.x Uyumlu
  • PHP 8.1, 8.2, 8.3, 8.4 Destegi

Gereksinimler

  • PHP: 8.1, 8.2, 8.3 veya 8.4
  • Laravel: 12.x veya 13.x
  • PHP Eklentileri: curl, libxml, openssl

Kurulum

composer require emre-tarhan/kuveytturk-vpos-laravel-sdk

Ayarlari Yayinlama

php artisan vendor:publish --tag=kuveytturk-vpos-config

Veya kurulum komutunu kullanin:

php artisan kuveytturk:install

Bu islem ayar dosyasini config/kuveytturk-vpos.php konumuna yayinlayacaktir.

Yapilandirma

Kuveyt Turk kimlik bilgilerinizi .env dosyasina ekleyin:

KUVEYTTURK_MERCHANT_ID=uye_isyeri_numaraniz
KUVEYTTURK_CUSTOMER_ID=musteri_numaraniz
KUVEYTTURK_USERNAME=api_kullanici_adiniz
KUVEYTTURK_PASSWORD=api_sifreniz
KUVEYTTURK_ENVIRONMENT=test # veya 'production'

Veya config/kuveytturk-vpos.php dosyasindan yapilandirin:

return [
    'merchant_id' => env('KUVEYTTURK_MERCHANT_ID', ''),
    'customer_id' => env('KUVEYTTURK_CUSTOMER_ID', ''),
    'username' => env('KUVEYTTURK_USERNAME', ''),
    'password' => env('KUVEYTTURK_PASSWORD', ''),
    'environment' => env('KUVEYTTURK_ENVIRONMENT', 'test'),
    'timeout' => env('KUVEYTTURK_TIMEOUT', 60),
    'connect_timeout' => env('KUVEYTTURK_CONNECT_TIMEOUT', 30),
];

Kullanim

Temel Satis (Tam 3D Akis)

use EmreTarhan\KuveytTurkVPos\Services\KuveytTurkVPos;
use EmreTarhan\KuveytTurkVPos\Dto\SaleRequest;
use EmreTarhan\KuveytTurkVPos\Enums\CurrencyCode;

// Servis konteynerinden enjekte et veya app'ten cozumle
$vpos = app(KuveytTurkVPos::class);

$request = new SaleRequest(
    merchantOrderId: 'ORDER-' . time(),
    amount: 100.00, // 100 TL - dahili olarak 10000'e donusturulur
    okUrl: 'https://siteniz.com/odeme/basarili',
    failUrl: 'https://siteniz.com/odeme/basarisiz',
    cardHolderName: 'Ahmet Yilmaz',
    cardNumber: '5188961939192544',
    cardExpireDateYear: '25',
    cardExpireDateMonth: '06',
    cardCVV2: '929',
    email: 'musteri@ornek.com',
    clientIp: request()->ip(),
    billAddrCity: 'Istanbul',
    billAddrCountry: '792', // Turkiye ulke kodu
    billAddrLine1: 'Ornek Sokak No:123',
    billAddrPostCode: '34000',
    billAddrState: '34',
    Cc: 'Ahmet Yilmaz',
    subscriber: 'musteri@ornek.com',
    currencyCode: CurrencyCode::TRY,
    installmentCount: 0, // 0 = taksitsiz, 1-24 arasi taksit
);

$response = $vpos->sale($request);

if ($response->isSuccessful()) {
    // Odeme basarili
    echo "Islem onaylandi! Siparis No: " . $response->orderId;
} else {
    // Odeme basarisiz
    echo "Odeme basarisiz: " . $response->responseMessage;
}

Iki Asamali Odeme (Manuel Kontrol)

Odeme akisi uzerinde daha fazla kontrole ihtiyaciniz varsa:

Asama 1: Kart Dogrulama

use EmreTarhan\KuveytTurkVPos\Dto\VerifyCardRequest;

$verifyRequest = new VerifyCardRequest(
    merchantOrderId: 'ORDER-' . time(),
    amount: 150.00,
    okUrl: 'https://siteniz.com/odeme/basarili',
    failUrl: 'https://siteniz.com/odeme/basarisiz',
    cardHolderName: 'Ahmet Yilmaz',
    cardNumber: '5188961939192544',
    cardExpireDateYear: '25',
    cardExpireDateMonth: '06',
    cardCVV2: '929',
    email: 'musteri@ornek.com',
    clientIp: request()->ip(),
    billAddrCity: 'Istanbul',
    billAddrCountry: '792',
    billAddrLine1: 'Ornek Sokak No:123',
    billAddrPostCode: '34000',
    billAddrState: '34',
    Cc: 'Ahmet Yilmaz',
    subscriber: 'musteri@ornek.com',
    currencyCode: CurrencyCode::TRY,
    installmentCount: 3, // 3 ay taksit
);

$verifyResponse = $vpos->verifyCard($verifyRequest);

// Asama 2 icin MD degerini saklayin
$mdValue = $verifyResponse->md; // Provizyon icin gerekli
$orderId = $verifyResponse->orderId;

Asama 2: Provizyon (Odeme Tamamlama)

use EmreTarhan\KuveytTurkVPos\Dto\ProvisionRequest;

$provisionRequest = new ProvisionRequest(
    merchantOrderId: 'ORDER-' . time(),
    amount: 150.00,
    md: $asama1denGelenMdDegeri,
    okUrl: 'https://siteniz.com/odeme/basarili',
    failUrl: 'https://siteniz.com/odeme/basarisiz',
    currencyCode: CurrencyCode::TRY,
    installmentCount: 3,
);

$provisionResponse = $vpos->provision($provisionRequest);

if ($provisionResponse->isSuccessful()) {
    // Odeme tamamlandi
    echo "Provizyon basarili! RRN: " . $provisionResponse->rrn;
}

Islem Iptali (Ayni Gun)

use EmreTarhan\KuveytTurkVPos\Dto\CancelRequest;
use EmreTarhan\KuveytTurkVPos\Enums\CurrencyCode;

$cancelRequest = new CancelRequest(
    merchantOrderId: 'ORDER-12345',
    amount: 100.00,
    provisionNumber: '123456', // Orijinal satis isleminden
    rrn: '789012345',          // Referans numarasi
    stan: '654321',            // Sistem iz numarasi
    orderId: 'orijinal-siparis-no',
    currencyCode: CurrencyCode::TRY,
);

$cancelResponse = $vpos->cancel($cancelRequest);

if ($cancelResponse->isSuccessful()) {
    echo "Islem basariyla iptal edildi!";
}

Iade (Gun Sonu Sonrasi)

use EmreTarhan\KuveytTurkVPos\Dto\RefundRequest;

$refundRequest = new RefundRequest(
    merchantOrderId: 'ORDER-12345',
    amount: 100.00,
    provisionNumber: '123456',
    rrn: '789012345',
    stan: '654321',
    orderId: 'orijinal-siparis-no',
    currencyCode: CurrencyCode::TRY,
);

$refundResponse = $vpos->refund($refundRequest);

if ($refundResponse->isSuccessful()) {
    echo "Iade basariyla gerceklestirildi!";
}

Kismi Iade

use EmreTarhan\KuveytTurkVPos\Dto\PartialRefundRequest;

$partialRefundRequest = new PartialRefundRequest(
    merchantOrderId: 'ORDER-12345',
    amount: 50.00, // Kismi tutar
    provisionNumber: '123456',
    rrn: '789012345',
    stan: '654321',
    orderId: 'orijinal-siparis-no',
    currencyCode: CurrencyCode::TRY,
);

$partialRefundResponse = $vpos->partialRefund($partialRefundRequest);

if ($partialRefundResponse->isSuccessful()) {
    echo "Kismi iade basariyla gerceklestirildi!";
}

Gercek Entegrasyon Ornegi (3D Secure Akisi)

Asagida produksiyon ortaminda kullanilan, tam 3D Secure odeme akisi ornegi bulunmaktadir. Bu akis 5 asamadan olusur: odeme baslatma, bankaya yonlendirme, callback karsilama, provizyon tamamlama ve siparisi sonuclandirma.

Asama 1: Servis Konteynerine Kayit

// App\Providers\AppServiceProvider::register()
use EmreTarhan\KuveytTurkVPos\Services\KuveytTurkVPos;

$this->app->singleton(KuveytTurkVPos::class, function () {
    return KuveytTurkVPos::fromConfig(config('kuveytturk-vpos'));
});

Asama 2: Odeme Baslatma (Controller)

Kart dogrulama islemi bankadan HTML dondurur. Bu HTML'i tarayicida render etmeniz gerekir — otomatik olarak bankanin 3D Secure sayfasina yonlendiren bir form icerir.

use EmreTarhan\KuveytTurkVPos\Services\KuveytTurkVPos;
use EmreTarhan\KuveytTurkVPos\Dto\VerifyCardRequest;
use EmreTarhan\KuveytTurkVPos\Enums\CurrencyCode;

public function odeme(Request $request, KuveytTurkVPos $vpos)
{
    $verifyRequest = new VerifyCardRequest(
        merchantOrderId: 'ORDER-' . time(),
        amount: 100.00,
        okUrl: route('odeme.callback.basarili'),
        failUrl: route('odeme.callback.basarisiz'),
        cardHolderName: $request->input('kart_sahibi'),
        cardNumber: $request->input('kart_numarasi'),
        cardExpireDateYear: $request->input('kart_yil'),   // 2 haneli: '28'
        cardExpireDateMonth: $request->input('kart_ay'),    // 2 haneli: '12'
        cardCVV2: $request->input('kart_cvv'),
        email: $request->input('email'),
        clientIp: $request->ip(),
        billAddrCity: 'Istanbul',
        billAddrCountry: '792',
        billAddrLine1: 'Ornek Sokak',
        billAddrPostCode: '34000',
        billAddrState: '34',
        Cc: $request->input('kart_sahibi'),
        subscriber: $request->input('email'),
        currencyCode: CurrencyCode::TRY,
        installmentCount: 0,
    );

    $hashCalculator = $vpos->getHashCalculator();
    $hashData = $verifyRequest->generateHash($hashCalculator);
    $xml = $verifyRequest->toXml($hashData);

    // Bankanin kart dogrulama endpoint'ine gonder
    $response = Http::withHeaders([
        'Content-Type' => 'application/xml',
    ])->withBody($xml, 'application/xml')
      ->post($vpos->getCardVerificationUrl());

    $body = $response->body();

    // Banka 3D Secure yonlendirme HTML'i dondurur — session'a kaydet ve tarayicida render et
    if (str_contains($body, '<html') || str_contains($body, '<form')) {
        session(['secure3d_html' => $body]);
        return redirect()->route('odeme.secure3d');
    }

    // Dogrudan basarisizlik (3D Secure yonlendirmesi yok)
    return back()->withError('Odeme baslatma basarisiz.');
}

Asama 3: 3D Secure Sayfasini Render Etme (Frontend)

Bankanin HTML'i otomatik gonderilen bir form icerir. Gizli bir div'de gosterip otomatik gonderin:

<!-- resources/views/odeme/secure3d.blade.php -->
<div id="secure3d-container" style="display:none;">
    {!! session('secure3d_html') !!}
</div>
<script>
    setTimeout(function() {
        document.getElementById('secure3d-container').querySelector('form').submit();
    }, 500);
</script>
<p>3D Secure dogrulamasina yonlendiriliyorsunuz...</p>

Asama 4: Banka Callback'inin Karsilanmasi (CSRF/Session Middleware Haric)

Onemli: Banka callback URL'lerinize cross-site POST gonderir. Bu route'lar middleware gruplarinin disinda olmalidir (CSRF ve session yok):

// bootstrap/app.php veya routes/web.php — middleware gruplarinin DISINDA
Route::post('/odeme/callback/basarili', [OdemeCallbackController::class, 'basarili'])
    ->name('odeme.callback.basarili');
Route::post('/odeme/callback/basarisiz', [OdemeCallbackController::class, 'basarisiz'])
    ->name('odeme.callback.basarisiz');

Asama 5: Provizyon (Odeme Tamamlama)

use EmreTarhan\KuveytTurkVPos\Dto\ProvisionRequest;
use EmreTarhan\KuveytTurkVPos\Enums\CurrencyCode;

public function basarili(Request $request, KuveytTurkVPos $vpos)
{
    // Banka URL-encoded XML olarak AuthenticationResponse gonderir
    $rawXml = urldecode($request->getContent());

    // XML'i guvenli sekilde cozumle
    libxml_use_internal_errors(true);
    $dom = new \DOMDocument();
    $dom->loadXML($rawXml, LIBXML_NONET | LIBXML_NOCDATA);
    libxml_clear_errors();
    $xpath = new \DOMXPath($dom);

    $getir = fn (string $tag): ?string =>
        ($nodes = $xpath->query("//*[local-name()='{$tag}']")) && $nodes->length > 0
            ? $nodes->item(0)->textContent : null;

    $md = $getir('MD');
    $orderId = $getir('OrderId');
    $responseCode = $getir('ResponseCode');
    $merchantOrderId = $getir('MerchantOrderId');

    // Hash dogrulama: SHA1_base64(MerchantOrderId + ResponseCode + OrderId + HashPassword)
    $hashCalculator = $vpos->getHashCalculator();
    $hashPassword = $hashCalculator->getHashPassword();
    $beklenenHash = base64_encode(sha1($merchantOrderId . $responseCode . $orderId . $hashPassword, true));
    $gelenHash = $getir('HashData');

    if (!hash_equals($beklenenHash, $gelenHash)) {
        // Hash uyumsuzlugu — callback'i reddet
        return response('Gecersiz hash', 400);
    }

    // Asama 2: Provizyon
    $provisionRequest = new ProvisionRequest(
        merchantOrderId: $merchantOrderId,
        amount: 100.00,
        md: $md,
        okUrl: route('odeme.callback.basarili'),
        failUrl: route('odeme.callback.basarisiz'),
        currencyCode: CurrencyCode::TRY,
        installmentCount: 0,
    );

    $hashData = $provisionRequest->generateHash($hashCalculator);
    $provisionXml = $provisionRequest->toXml($hashData);

    $response = Http::withHeaders([
        'Content-Type' => 'application/xml',
    ])->withBody($provisionXml, 'application/xml')
      ->post($vpos->getProvisionUrl());

    // Provizyon yanitini cozumle ve siparisi sonuclandir...
    if ($responseCode === '00') {
        // Odeme basarili — siparis durumunu guncelle
    }
}

CSP Basliklari (Gerekli)

3D Secure yonlendirme formunun engellenmemesi icin Kuveyt Turk domainlerini Content Security Policy'nize ekleyin:

// Middleware veya yapilandirma
'connect-src' => [..., 'https://*.kuveytturk.com.tr'],
'form-action' => [..., 'https://*.kuveytturk.com.tr'],

Yanit Nesneleri

SaleResponse

Ozellik Aciklama
isSuccessful() Boolean — islem onaylandiysa true
responseCode Kuveyt Turk yanit kodu ("00" = basarili)
responseMessage Okunabilir yanit mesaji
orderId Banka siparis numarasi
provisionNumber Provizyon numarasi
rrn Referans numarasi
stan Sistem iz numarasi
transactionTime Islem zaman damgasi
merchantOrderId Sizin siparis numaraniz
md 3D Secure MD degeri

Hata Yonetimi

use EmreTarhan\KuveytTurkVPos\Services\KuveytTurkVPos;
use EmreTarhan\KuveytTurkVPos\Exceptions\KuveytTurkVPosException;
use EmreTarhan\KuveytTurkVPos\Enums\ErrorCode;

try {
    $response = $vpos->sale($request);

    if ($response->isSuccessful()) {
        // Basarili islemi ele al
    } else {
        // Is hatasini ele al
        $errorCode = ErrorCode::fromString($response->responseCode);
        echo "Hata: " . $errorCode->label();
    }
} catch (KuveytTurkVPosException $e) {
    // Baglanti/dogrulama hatalarini ele al
    echo "Hata: " . $e->getMessage();
}

Hata Kodlari

Kod Aciklama
00 Otorizasyon verildi / Islem basarili
01 Gecersiz kart numarasi
05 Yetersiz bakiye
12 Gecersiz uye isyeri
14 Gecersiz islem / CVV
17 Kart limiti asildi
54 Kart suresi dolmus
59 Sahtekarlik suphesi
70 Mukerrer siparis
91 Zaman asimi
96 Sistem hatasi
99 Hashdata uyusmazligi

Tutar Formatı

Tum tutarlar gercek deger olarak belirtilmelidir (ornegin 100.50 = 100 TL 50 kurus).

SDK bankanin formatina otomatik donusturur (tutar x 100, ornegin 100.50 TL = 10050).

// Hepsi ayni sonucu verir:
$request = new SaleRequest(..., amount: 100.50, ...);
$request = new SaleRequest(..., amount: '100.50', ...);
$request = new SaleRequest(..., amount: 10050, ...); // Zaten banka formatinda

Test Ortami

Sandbox testleri icin asagidaki test kartini kullanin:

Alan Deger
Kart Numarasi 5188 9619 3919 2544
CVV 929
Son Kullanma 06/25
3D Sifre 123456

Test Endpoint'leri:

  • Kart Dogrulama: https://boatest.kuveytturk.com.tr/boa.virtualpos.services/Home/ThreeDModelPayGate
  • Provizyon: https://boatest.kuveytturk.com.tr/boa.virtualpos.services/Home/ThreeDModelProvisionGate

Produksiyon Endpoint'leri:

  • Kart Dogrulama: https://sanalpos.kuveytturk.com.tr/ServiceGateWay/Home/ThreeDModelPayGate
  • Provizyon: https://sanalpos.kuveytturk.com.tr/ServiceGateWay/Home/ThreeDModelProvisionGate

Test Calistirma

composer test

Katkida Bulunma

Katkilar memnuniyetle karsilanir! Lutfen bir Pull Request gonderin.

Lisans

MIT Lisansi. Detaylar icin LICENSE dosyasina bakin.

Destek

Sorular ve sorunlar icin lutfen GitHub'da issue acin: https://github.com/emre-tarhan/kuveytturk-vpos-laravel-sdk