emre-tarhan / kuveytturk-vpos-laravel-sdk
Kuveyt Türk Sanal Pos (Virtual POS) Laravel SDK with 3D Secure 2.0 support
Package info
github.com/emre-tarhan/kuveytturk-vpos-laravel-sdk
pkg:composer/emre-tarhan/kuveytturk-vpos-laravel-sdk
Requires
- php: ^8.1|^8.2|^8.3|^8.4
- illuminate/support: ^12.0|^13.0
Requires (Dev)
- mockery/mockery: ^1.6
- orchestra/testbench: ^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
README
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