voxyfy / anadolupay
Türk banka sanal POS'ları ve ödeme kuruluşları için birleşik Laravel ödeme geçidi — NestPay, Garanti, PosNet, PayFlex, PayFor, InterPos, KuveytPos, PayTR, Param.
Requires
- php: ^8.2
- illuminate/http: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- psr/log: ^2.0|^3.0
Requires (Dev)
- larastan/larastan: ^3.10
- laravel/pint: ^1.30
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^3.0|^4.0|^5.0
- pestphp/pest-plugin-arch: ^3.0|^4.0|^5.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0|^5.0
README
Türk bankalarının sanal POS'ları için tek arayüz.
Türkiye'de banka entegrasyonu yazmak, aynı işi on yedi kez farklı şekilde
yapmaktır. Garanti tutarı kuruş cinsinden tam sayı ister, NestPay ondalıklı
dizgi. Garanti hash'i ayraçsız birleştirip büyük harfe çevirir, NestPay alanları
sıralayıp | ile birleştirir, PosNet ; kullanır. Kuveyt Türk size form
alanları değil hazır bir HTML sayfası döner. VakıfBank provizyon adımında kart
bilgisini bir daha ister. Bunların hiçbiri dokümantasyonda yan yana yazmaz;
her birini ayrı ayrı öğrenirsiniz.
Bu paket o farkları tek bir sözleşmenin arkasına alır:
$response = AnadoluPay::driver('garanti')->createPayment($data); return response($response->toHtmlForm());
Bankayı değiştirmek için 'garanti' yerine 'akbank' yazmak yeterlidir.
Çalışan bir örnek uygulama için: Voxyfy/anadolupay-laravel
Ne yapmaz: Arayüz üretmez, sipariş durumu tutmaz, stok düşmez, fatura kesmez. Ödeme akışını yürütür ve yanıtı normalleştirir; gerisi sizin uygulamanızın işi.
Canlıya çıkmadan önce okuyun
Bu paketteki protokoller bankaların public dokümantasyonuna göre yazıldı ve istek üretimi, imza ve yanıt eşlemesi birim testleriyle kilitlendi. Ancak hiçbir driver gerçek bir bankaya karşı çalıştırılmadı.
Testler benim yazdığım algoritmayı doğrular, bankanın beklediğini değil. Bir alanın sırası yanlışsa test yeşil kalır, banka işlemi reddeder. Kullanacağınız her banka için kendi test üye işyeri bilgilerinizle en az bir 3D Secure satış ve bir iade çalıştırın. Hash hesabında bankalar zaman zaman kuruluma özel farklılıklar tanımlıyor.
Bu uyarı banka driver'ları içindir.
iyzicodriver'ının imza şeması resmi dokümantasyondan doğrulanmış ve testle kilitlenmiştir.
İçindekiler Kurulum · Örnek proje · Desteklenen bankalar · Nasıl çalışır · Yapılandırma · Ödeme akışı · İade ve iptal · Ödeme modelleri · Yetenekler · Tutarlar · Bankaların tuhaflıkları · Hata yönetimi · Event'ler · Loglama · Test ortamı · Güvenlik · Yeni banka eklemek · Yol haritası
Kurulum
composer require voxyfy/anadolupay
php artisan vendor:publish --tag="anadolupay-config"
PHP 8.2+, Laravel 12 veya 13. Auto-discovery açıktır, ek adım yoktur.
Laravel 13 en az PHP 8.3 ister; PHP 8.2 kullanıyorsanız Laravel 12'de kalırsınız. CI her iki kombinasyonu da koşar.
Uçtan uca kurulmuş bir Laravel projesi görmek isterseniz anadolupay-laravel deposu ödeme başlatma, 3D dönüşü ve iade akışlarını örnekliyor.
Desteklenen bankalar
Türkiye'deki sanal POS'lar birkaç ortak altyapı ailesine iner. Aynı aileyi kullanan bankalar aynı driver'ı paylaşır; aralarındaki fark yalnızca uç nokta ve kimlik bilgisidir.
| Driver | Banka | Altyapı | 3D | 3D Pay | 3D Host | Non-secure | İade | İptal |
|---|---|---|---|---|---|---|---|---|
akbank |
Akbank | Asseco / Payten | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
isbank |
İş Bankası | Asseco / Payten | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
ziraat |
Ziraat Bankası | Asseco / Payten | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
halkbank |
Halkbank | Asseco / Payten | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
qnb |
QNB Finansbank | Asseco / Payten | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
teb |
TEB | Asseco / Payten | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
sekerbank |
Şekerbank | Asseco / Payten | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
garanti |
Garanti BBVA | GVPS | ✅ | ✅ | — | ✅ | ✅ | ✅ |
yapikredi |
Yapı Kredi | PosNet (XML) | ✅ | — | — | ✅ | ✅ | ✅ |
albaraka |
Albaraka Türk | PosNet V1 (JSON) | ✅ | — | ✅ | ✅ | ✅ | ✅ |
vakifbank |
VakıfBank | PayFlex V4 | ✅ | — | — | ✅ | ✅ | ✅ |
ziraat-payflex |
Ziraat Bankası | PayFlex V4 | ✅ | — | — | ✅ | ✅ | ✅ |
denizbank |
DenizBank | InterPos | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
qnb-payfor |
QNB / Enpara | PayFor | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
ziraat-katilim |
Ziraat Katılım | PayFor | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
kuveytturk |
Kuveyt Türk | BOA / TDV2.0 | ✅ | — | — | ✅ | — | — |
vakif-katilim |
Vakıf Katılım | BOA | ✅ | — | ✅ | ✅ | ✅ | ✅ |
Ödeme kuruluşları:
| Driver | Kuruluş | 3D | 3D Pay | 3D Host | Non-secure | İade | İptal |
|---|---|---|---|---|---|---|---|
akbank-pos |
Akbank (yeni JSON API) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
paytr |
PayTR | — | ✅ | ✅ | ✅ | ✅ | — |
param |
Param | ✅ | ✅ | — | ✅ | ✅ | — |
tosla |
Tosla (AkÖde) | — | ✅ | ✅ | ✅ | ✅ | ✅ |
iyzico |
iyzico | ✅ | — | — | — | — | — |
fake |
geliştirme için sahte driver | — | — | — | ✅ | ✅ | — |
Aynı bankanın iki driver'ı varsa ikisi de gerçektir; hangisinin tanımlandığını sanal POS sözleşmenizden teyit edin:
- Akbank →
akbank(eski NestPay) veyaakbank-pos(yeni JSON API) - Ziraat →
ziraat(NestPay) veyaziraat-payflex(PayFlex) - QNB Finansbank →
qnb(NestPay) veyaqnb-payfor(PayFor)
Nasıl çalışır
Bankalar arasındaki fark yüzeyde protokol (XML / JSON / SOAP / form), derinde imza algoritmasıdır. Paket bu iki katmanı ayırır:
CreatePaymentData ─┐
├─► AnadoluPay::driver('garanti')
CardData ──────────┘ │
▼
┌─────────────────────────────┐
│ AbstractBankGateway │ ortak akış:
│ createPayment / verify │ form → hash → provizyon
│ refund │
└──────────────┬──────────────┘
│ banka-özel eşleme
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
AssecoGateway GarantiGateway PosNetGateway … (13 driver)
sha512 + '|' sha512 UPPER sha256 + ';'
CC5Request XML GVPSRequest XML posnetRequest XML
│ │ │
└──────────────────────┼──────────────────────┘
▼
BankHttpClient
XML/JSON/form kodlama · maskeli loglama
Bir driver yalnızca yedi metodu doldurur: build3dFormFields(),
checkCallbackHash(), is3dAuthSuccess(), provision(),
mapCallbackResponse(), mapProvisionResponse(), extractOrderId().
Akış kontrolü, hata yönetimi, HTTP ve loglama temel sınıfta tek yerde durur. Bir bankada düzeltilen akış hatası hepsinde düzelir; bu, on yedi kopyanın ayrı ayrı bakımını yapmaktan farkı.
Dönen PaymentResponse ve VerificationResponse bankadan bağımsızdır: hangi
driver'ı kullanırsanız kullanın success, paymentId ve status aynı anlama
gelir. Bankanın ham yanıtı raw içinde korunur — normalleştirme bilgi kaybı
yaratmaz.
Yapılandırma
Yalnızca kullandığınız bankanın değişkenlerini doldurun. Diğer preset'ler boş kalabilir; yalnızca çağrıldıklarında hata verirler.
Aynı kavramın bankalarda farklı adları var:
| Config | Bankadaki karşılığı |
|---|---|
merchant_id |
ClientId · MerchantId · ShopCode · merchantSafeId |
terminal_id |
TerminalId · TerminalNo · terminalSafeId |
username |
Name · UserCode · ProvUserID |
password |
API şifresi |
secret_key |
store key · hash key · GUID (3D anahtarı) |
# Garanti BBVA GARANTI_MERCHANT_ID=xxxxxxx GARANTI_TERMINAL_ID=30690000 GARANTI_USERNAME=PROVAUT GARANTI_PASSWORD=xxxxxxx GARANTI_SECRET_KEY=xxxxxxx GARANTI_REFUND_USERNAME=PROVRFN # iade/iptal ayrı kullanıcı ister GARANTI_REFUND_PASSWORD=xxxxxxx # Akbank (NestPay) AKBANK_MERCHANT_ID=xxxxxxx AKBANK_USERNAME=xxxxxxx AKBANK_PASSWORD=xxxxxxx AKBANK_SECRET_KEY=xxxxxxx # Yapı Kredi PosNet — posnet_id ayrı bir alandır, merchant_id değildir YAPIKREDI_MERCHANT_ID=xxxxxxx YAPIKREDI_TERMINAL_ID=xxxxxxx YAPIKREDI_POSNET_ID=xxxxxxx YAPIKREDI_SECRET_KEY=xxxxxxx
Tüm anahtarlar için yayınladığınız config/anadolupay.php dosyasına bakın.
Ödeme akışı
Türk banka sanal POS'larında 3D Secure bir GET yönlendirmesi değil, bankanın 3D geçidine yapılan bir form POST'udur. Akış üç adımdır:
[1] createPayment() [2] tarayıcı [3] verify()
imzalı form üret ──► bankaya POST ──► hash doğrula
kullanıcı SMS/ + provizyon iste
app onayı
1 · Ödemeyi başlat
use Voxyfy\AnadoluPay\DTO\CardData; use Voxyfy\AnadoluPay\DTO\CreatePaymentData; use Voxyfy\AnadoluPay\Facades\AnadoluPay; $data = new CreatePaymentData( amount: 199.90, currency: 'TRY', orderId: 'SIPARIS-123', customer: [ 'name' => 'Ahmet Yılmaz', 'email' => 'ahmet@example.com', 'phone' => '5551112233', ], successUrl: route('odeme.donus'), failUrl: route('odeme.donus'), card: new CardData( number: '5528790000000008', expireMonth: '12', expireYear: '2030', cvv: '123', holderName: 'Ahmet Yılmaz', ), installment: 1, paymentModel: CreatePaymentData::MODEL_3D_SECURE, ip: $request->ip(), ); $response = AnadoluPay::driver('garanti')->createPayment($data); return response($response->toHtmlForm());
toHtmlForm() otomatik gönderilen bir sayfa üretir. Formu kendiniz render
etmek isterseniz:
$response->formAction; // bankanın 3D geçidi $response->formMethod; // 'POST' $response->formFields; // imzalı gizli alanlar
Kuveyt Türk, Vakıf Katılım ve Param form alanı yerine hazır bir HTML sayfası
döner; bu durumda formFields boştur ve içerik $response->htmlContent
içindedir. toHtmlForm() iki durumu da doğru ele alır — elle uğraşmak yerine
onu kullanın.
2 · Dönüşü doğrula
use Voxyfy\AnadoluPay\DTO\VerifyPaymentData; $result = AnadoluPay::driver('garanti')->verify(new VerifyPaymentData( payload: $request->all(), headers: $request->headers->all(), rawBody: $request->getContent(), )); if ($result->success) { // $result->paymentId bankanın işlem referansıdır. // Saklayın — iade ve iptal için gerekecek. }
verify() sırayla: dönüş hash'ini doğrular (eşleşmezse
InvalidSignatureException), 3D doğrulama durumunu kontrol eder, klasik 3D
Secure modelinde bankaya provizyon isteğini gönderir. 3D Pay ve 3D Host'ta
provizyon banka tarafında tamamlandığı için ikinci istek atılmaz.
PayFlex (VakıfBank / Ziraat) sipariş bağlamı ister. Banka provizyon adımında kart bilgisini ve tutarı yeniden sorar ama bunları dönüşte göndermez — siz sağlarsınız:
$result = AnadoluPay::driver('vakifbank')->verify(new VerifyPaymentData( payload: $request->all(), order: [ 'id' => 'SIPARIS-123', 'amount' => 199.90, 'currency' => 'TRY', 'ip' => $request->ip(), 'card' => ['number' => '...', 'expire_month' => '12', 'expire_year' => '30', 'cvv' => '123'], ], ));
İade ve iptal
use Voxyfy\AnadoluPay\DTO\RefundPaymentData; AnadoluPay::driver('akbank')->refund(new RefundPaymentData('SIPARIS-123')); // tam AnadoluPay::driver('akbank')->refund(new RefundPaymentData('SIPARIS-123', 49.90)); // kısmi
Gün sonu kapanmadan önce iade değil iptal kullanın — daha hızlı ve komisyonsuzdur:
AnadoluPay::driver('akbank')->cancel(new RefundPaymentData('SIPARIS-123'));
Bazı bankalar işlemi sipariş numarasıyla değil, kendi referanslarıyla eşler.
Bu referansı ödeme sırasında saklayıp metadata ile geçin:
| Banka | Gereken alan | Nereden gelir |
|---|---|---|
| Garanti | ref_ret_num |
provizyon yanıtı Transaction.RetrefNum |
| Yapı Kredi | host_ref_num |
provizyon yanıtı hostlogkey |
| PayFlex | transaction_id |
provizyon yanıtı TransactionId |
| Vakıf Katılım | remote_order_id |
provizyon yanıtı OrderId |
new RefundPaymentData('SIPARIS-123', 49.90, metadata: ['ref_ret_num' => '...']);
cancel()vestatus()şu anPaymentGatewayInterface'de değil, driver'lara özel metotlardır. Yani statik tip güvenliği yoktur; desteklemeyen bir driver'da çağırırsanız runtime'da patlar. Hangi driver'ın hangisini desteklediği tabloda yazıyor.
Ödeme modelleri
| Sabit | Ne yapar | Ne zaman |
|---|---|---|
MODEL_3D_SECURE |
Doğrulama sonrası ayrı provizyon isteği | Varsayılan; en yaygın |
MODEL_3D_PAY |
Doğrulama ve provizyon tek adımda bankada | Daha az round-trip isteyen kurulumlar |
MODEL_3D_HOST |
Kart formu da bankada toplanır | Kart verisi sunucunuza hiç uğramaz — PCI kapsamını daraltır |
MODEL_NON_SECURE |
3D yok, doğrudan provizyon | Mail order / abonelik |
3D Host modelinde card vermeniz gerekmez.
Yetenekler
Her banka her işlemi sunmaz. Bu bir eksiklik değil, sağlayıcı sınırıdır: PayTR iptal (void) API'si sunmaz, Akbank'ın yeni API'si tekil durum sorgusu sunmaz, Kuveyt Türk ön provizyon sunmaz.
Paket bunu tip düzeyinde bildirir — desteklenmeyen bir metodu çağırmadan
önce instanceof ile kontrol edin:
use Voxyfy\AnadoluPay\Contracts\SupportsStatusQuery; $gateway = AnadoluPay::driver('garanti'); if ($gateway instanceof SupportsStatusQuery) { $status = $gateway->status('SIPARIS-123'); }
| Driver | Durum | İptal | Ön prov. | Geçmiş | BIN | Taksit | Tekrar |
|---|---|---|---|---|---|---|---|
akbank, isbank, ziraat, halkbank, qnb, teb, sekerbank |
✅ | ✅ | ✅ | ✅ | — | — | ✅ |
garanti |
✅ | ✅ | ✅ | ✅ | ✅ | — | ✅ |
yapikredi, albaraka |
✅ | ✅ | ✅ | — | — | — | — |
vakifbank, ziraat-payflex |
✅ | ✅ | ✅ | — | — | — | ✅ |
denizbank |
✅ | ✅ | ✅ | — | — | — | — |
qnb-payfor, ziraat-katilim |
✅ | ✅ | ✅ | ✅ | — | — | — |
kuveytturk |
✅ | ✅ | — | — | — | — | — |
vakif-katilim |
✅ | ✅ | ✅ | ✅ | — | — | — |
akbank-pos |
— | ✅ | ✅ | ✅ | — | — | ✅ |
paytr |
✅ | — | — | — | ✅ | ✅ | — |
param |
✅ | ✅ | ✅ | — | — | — | — |
tosla |
✅ | ✅ | ✅ | ✅ | — | ✅ | — |
iyzico |
✅ | — | — | — | ✅ | ✅ | — |
Arayüzler: SupportsStatusQuery, SupportsCancellation,
SupportsPreAuthorization, SupportsOrderHistory, SupportsBinQuery,
SupportsInstallmentQuery, SupportsRecurringPayments.
Durum sorgusu
Zaman aşımı gibi belirsiz durumları kapatmanın tek yolu budur.
$status = AnadoluPay::driver('garanti')->status('SIPARIS-123'); $status->found; // banka bu siparişi tanıyor mu $status->isPaid(); // para tahsil edildi mi $status->isPending(); // 3D doğrulaması bekleniyor $status->amount; // Money $status->refundedAmount;
Bankaların birbirine benzemeyen durum kodları (A, 1, SUCCESS,
Başarılı) tek bir sözlüğe indirgenir. Tanınmayan bir kod unknown
döner — sessizce "başarılı" sayılmaz.
Ön provizyon isPaid() için false döndürür: tutar bloke edilmiştir ama
tahsil edilmemiştir.
Ön provizyon
$gateway = AnadoluPay::driver('garanti'); // Bloke et $response = $gateway->preAuthorize($data); // Nihai tutar belli olunca tahsil et $gateway->capture(new CapturePaymentData( orderId: 'SIPARIS-123', amount: 149.90, // blokeden küçük olabilir metadata: ['ref_ret_num' => '...'], // Garanti bu referansı ister ));
Bloke süresiz değildir; bankaya göre 1-30 gün içinde kapatılmazsa düşer.
Taksit ve BIN
$bin = AnadoluPay::driver('iyzico')->binLookup('415565'); $bin->bankName; // "Garanti BBVA" $bin->isCredit(); $options = AnadoluPay::driver('paytr')->installmentOptions( Money::fromMinorUnits(19990), ); foreach ($options as $option) { $option->count; // 3 $option->totalPrice; // Money — komisyon dâhil $option->monthlyPrice; }
BIN sorgusuna kart numarasının tamamını göndermeyin; ilk 6-8 hane yeter.
Tekrarlayan ödeme
Plan ilk ödemeyle birlikte bankaya bildirilir; sonraki çekimleri banka kendisi başlatır.
new CreatePaymentData( amount: 49.90, // … metadata: ['recurring' => new RecurringPlan( interval: 1, frequency: RecurringPlan::FREQUENCY_MONTH, paymentCount: 12, )], );
Desteklenen frekanslar bankaya göre değişir — Garanti yıllık, PayFlex
haftalık tekrar sunmaz. supportedRecurringFrequencies() ile sorgulayın;
desteklenmeyen bir frekans PaymentFailedException fırlatır.
Tutarlar
Tutarlar paket içinde her zaman kuruş cinsinden tam sayı olarak taşınır.
0.1 + 0.2 !== 0.3 olduğu için float ile hesaplanan bir tutar imzaya giren
dizgiyi bir kuruş kaydırabilir ve banka işlemi reddeder.
use Voxyfy\AnadoluPay\Support\Money; new CreatePaymentData(amount: Money::fromMinorUnits(19990), ...); // 199,90 TL new CreatePaymentData(amount: 199.90, ...); // eşdeğer
float vermek çalışmaya devam eder — iki ondalık haneye yuvarlanıp kuruşa
çevrilir. Tutarı zaten kuruş olarak tutuyorsanız (veritabanında int kolon
gibi) Money::fromMinorUnits() kesinlik kaybı olmayan tek yoldur.
Driver'lar tutara yalnızca $data->money() üzerinden erişir ve bankanın
istediği biçime orada çevirir:
| Örnek (199,90 TL) | Kullanan | |
|---|---|---|
toMinorUnitsString() |
"19990" |
Garanti, PosNet, Kuveyt Türk, Tosla |
toDecimalString() |
"199.90" |
Akbank POS, PayFlex, iyzico |
toNaturalString() |
"199.9" |
NestPay, PayFor, InterPos |
Bankaların tuhaflıkları
Driver'lar bunları sizin için hallediyor. Burada olmalarının sebebi, bir şey ters gittiğinde nereye bakacağınızı bilmeniz.
Tutar formatı üç farklı. Garanti, PosNet, Kuveyt Türk ve Tosla kuruş
cinsinden tam sayı ister (199.90 → 19990). NestPay ve PayFor PHP'nin doğal
float gösterimini ister (199.9 → "199.9", 100.0 → "100"). Akbank POS ve
PayFlex iki ondalıklı dizgi ister ("199.90"). Hash tam olarak gönderilen
dizgi üzerinden hesaplandığı için bu formatlar değiştirilemez.
Taksit alanı tek çekimde bile dört farklı. NestPay boş dizgi, PosNet '00',
PayFor ve Kuveyt Türk '0', Param '1' bekler. PayFlex alanı hiç göndermez.
Para birimi kodu her yerde ISO 4217 sayısal değil. Kuveyt Türk dört haneli
kullanır (0949), PosNet V1 harf kısaltması (TL, US, EU).
Garanti iade için ayrı kullanıcı ister. refund ve void işlemlerinde
securityData normal şifreyle değil iade şifresiyle hesaplanır. İki ayrı
kullanıcı tanımlamazsanız iadeler reddedilir.
PosNet üç sunucu isteği yapar. Önce oosRequestData ile veri paketleri
alınır, sonra 3D geçidine POST edilir, dönüşte oosResolveMerchantData ile
çözülüp oosTranData ile provizyon tamamlanır. Sipariş numarası 20 haneye
sıfırla doldurulur; iade/iptalde 24 hane olur ve 3D siparişler TDSC ön eki
alır.
Ziraat Katılım'ın dönüş hash'i banka tarafında tutarsız üretiliyor. Bu
yüzden o preset'te verify_hash varsayılan olarak kapalıdır. Bankanız
düzelttiyse ZIRAAT_KATILIM_VERIFY_HASH=true yapın.
PayTR bildirimi OK yanıtı bekler. Webhook'unuz gövdede düz metin OK
döndürmezse PayTR bildirimi tekrar tekrar gönderir.
NestPay hash'i alanları sıralar. Alanlar doğal sırada (harf duyarsız)
sıralanır, hash/encoding/nationalidno çıkarılır, sona secret key eklenir,
| ve \ karakterleri kaçırılır. Forma yeni bir alan eklerseniz hash'e de
girer — banka bunu bilmiyorsa işlem reddedilir.
Hata yönetimi ve yeniden deneme
Ödeme entegrasyonlarında en tehlikeli hata, belirsiz olandır. Banka "reddettim" derse ne yapacağınız bellidir; ama istek zaman aşımına uğradığında paranın çekilip çekilmediğini bilmezsiniz. Paket bu ikisini tip düzeyinde ayırır:
AnadoluPayException
├── PaymentFailedException kesin: banka isteği aldı ve reddetti
├── InvalidSignatureException imza tutmadı — sahte callback olabilir
├── DuplicatePaymentException aynı sipariş için ikinci deneme
├── UnsupportedOperationException driver bu işlemi desteklemiyor
├── DriverNotFoundException yapılandırma hatası
└── TransportException BELİRSİZ: istek ulaştı mı, işlendi mi?
├── GatewayUnreachableException bağlantı kurulamadı / zaman aşımı
└── GatewayHttpException 2xx dışı yanıt veya çözümlenemeyen gövde
TransportException yakaladığınızda ödemeyi başarısız saymayın — durumu
banka üzerinden sorgulayın veya müşteriye "işleminiz kontrol ediliyor" deyin.
use Voxyfy\AnadoluPay\Exceptions\PaymentFailedException; use Voxyfy\AnadoluPay\Exceptions\TransportException; try { $result = AnadoluPay::driver('garanti')->verify($data); } catch (PaymentFailedException $e) { // Kesin ret: siparişi iptal edebilirsiniz. } catch (TransportException $e) { // Belirsiz: siparişi "beklemede" bırakın, durum sorgusuyla teyit edin. $e->safeToRetry; // yalnızca isteğin bankaya ulaşmadığı kesinse true }
Yeniden deneme
ANADOLUPAY_RETRY_TIMES=2 ANADOLUPAY_RETRY_SLEEP_MS=250
Retry yalnızca bankaya ulaşılamayan durumlarda yapılır: bağlantı reddedildi, DNS çözülemedi, TLS kurulamadı. Bu hatalarda isteğin bankaya varmadığı bilinir.
Zaman aşımı ve HTTP hataları tekrar denenmez. Her ikisinde de istek bankaya ulaşmış ve işlenmiş olabilir; körlemesine ikinci bir ödeme isteği göndermek çift çekim demektir. Bu davranış testle kilitlidir.
Varsayılan 0dır — yani retry kapalıdır. Açmadan önce sipariş durumunu kendi
tarafınızda takip ettiğinizden emin olun.
Event'ler
Ödeme akışının dört noktasında event yayınlanır. Hiçbiri kart verisi taşımaz, çünkü dinleyicilerin çoğu bu veriyi loglar veya kuyruğa yazar.
| Event | Ne zaman | Taşıdığı |
|---|---|---|
PaymentInitiated |
müşteri bankaya yönlendirilmeden önce | driver, orderId, Money, model, taksit |
PaymentVerified |
dönüş doğrulanıp provizyon tamamlanınca | driver, orderId, paymentId, success, status |
PaymentFailed |
akış bir istisnayla kesilince | driver, orderId, reason, exception |
RefundIssued |
iade isteği gönderilince | driver, paymentId, Money, refundId, success |
Event::listen(PaymentVerified::class, function (PaymentVerified $event) { if ($event->success) { Order::where('code', $event->orderId)->update([ 'status' => 'paid', 'payment_reference' => $event->paymentId, ]); } });
PaymentVerified success: false ile de gelebilir — bu, doğrulama akışının
hatasız tamamlandığı ama ödemenin alınmadığı anlamına gelir. PaymentFailed
ise akışın kesildiği durumdur; istisna yutulmaz, event'ten sonra yukarı çıkar.
ANADOLUPAY_EVENTS=false ile kapatılabilir.
Mükerrer ödeme koruması
ANADOLUPAY_IDEMPOTENCY=true ANADOLUPAY_IDEMPOTENCY_TTL=30
Aynı sipariş numarası için pencere içinde ikinci bir createPayment()
çağrısı DuplicatePaymentException fırlatır. Asıl hedef kullanıcının "Öde"
düğmesine iki kez basmasıdır.
Pencere bilinçli olarak kısadır (varsayılan 30 sn): ödeme gerçekten başarısız olduğunda müşterinin aynı sipariş numarasıyla tekrar denemesi meşrudur. Başlatma isteği hata alırsa kilit hemen bırakılır.
Kilit Cache::add() ile alınır — atomiktir, yani iki eşzamanlı istekten
yalnızca biri geçer. Bunun çalışması için array dışında bir cache sürücüsü
(redis, memcached, database) gerekir.
Bu bir kolaylıktır, kesin garanti değildir. Mükerrer çekime karşı asıl savunma, siparişin durumunu kendi veritabanınızda tutmak ve ödemesi alınmış siparişler için akışı hiç başlatmamaktır.
Loglama
Banka bir işlemi reddettiğinde size yalnızca bir kod döner
(ProcReturnCode=99). Sorunun hangi alanda olduğunu ancak gönderdiğiniz
gövdeyi görerek anlarsınız. Entegrasyon geliştirirken açın:
ANADOLUPAY_LOGGING=true ANADOLUPAY_LOG_CHANNEL=anadolupay # boşsa uygulamanın varsayılan kanalı
[debug] AnadoluPay banka isteği {"bank":"garanti","url":"…","body":"<GVPSRequest>…
<Number>415565******6111</Number>
<CVV2>[gizlendi]</CVV2>…"}
[debug] AnadoluPay banka yanıtı {"bank":"garanti","status":200,"duration_ms":412,…}
Maskeleme iki katmanlıdır. Birincisi alan adına göre (cvv, password,
secret_key…). İkincisi değerin biçimine göre: Luhn kontrolünden geçen her
13–19 haneli sayı, alan adı ne olursa olsun maskelenir. İkinci katman
olmadan, on altı driver'ın farklı adlandırdığı kart alanlarından birini
gözden kaçırmak kart verisini loga düşürürdü.
Luhn kontrolü yanlış pozitifleri de eler — PosNet'in 20 haneye doldurulmuş
sipariş numaraları ve NestPay'in MD taşıyan Number alanı okunabilir kalır.
Başarısız HTTP yanıtları warning, gerisi debug seviyesindedir.
Loglama varsayılan olarak kapalıdır. Maskeleme uygulansa bile bu kayıtların nereye yazıldığı bilinçli bir tercih olmalıdır: kalıcı bir kanal seçiyorsanız erişimini kısıtlayın ve saklama süresi tanımlayın.
iyzico
iyzico bankalardan iki noktada ayrılır ve paket ikisini de sizin yerinize halleder:
- 3D adımında form alanı değil, base64 kodlanmış hazır bir HTML sayfası döner.
Paket bunu çözer;
toHtmlForm()doğrudan basılabilir HTML verir. - Her yanıt, callback ve webhook ayrı bir imza şeması kullanır. Üçü de HMAC-SHA256 üretir ve sonucu onaltılık kodlar:
| İmza | Nerede | İmzalanan |
|---|---|---|
Authorization |
istek başlığı | randomKey + uriPath + gövde → IYZWSv2 base64(apiKey:…&randomKey:…&signature:…) |
| Yanıt / callback | gövdedeki signature |
uca göre değişen alanlar, : ayraçlı |
| Webhook | X-IYZ-SIGNATURE-V3 başlığı |
secretKey + eventType + …, ayraçsız |
Yanıt imzasında alan sırası uca göre sabittir; örneğin 3DS callback'i
conversationData:conversationId:mdStatus:paymentId:status sırasını kullanır.
Tutarlardaki sondaki sıfırlar imzadan önce atılır (10.50 → 10.5).
IYZICO_API_KEY=xxx IYZICO_SECRET_KEY=xxx IYZICO_BASE_URL=https://sandbox-api.iyzipay.com IYZICO_CALLBACK_URL=https://shop.test/anadolupay/webhook/iyzico
İade /v2/payment/refund ucundan yapılır:
AnadoluPay::driver('iyzico')->refund(new RefundPaymentData( paymentId: '12345', amount: 49.90, metadata: ['conversation_id' => 'SIPARIS-123'], ));
Test ortamı
Preset'lerdeki uç noktalar canlı ortamı gösterir. Test için ilgili
*_PAYMENT_API / *_GATEWAY_3D değişkenlerini bankanızın test adresiyle
değiştirin ve *_TEST_MODE=true yapın.
GARANTI_TEST_MODE=true GARANTI_PAYMENT_API=https://sanalposprovtest.garantibbva.com.tr/VPServlet GARANTI_GATEWAY_3D=https://sanalposprovtest.garantibbva.com.tr/servlet/gt3dengine AKBANK_PAYMENT_API=https://entegrasyon.asseco-see.com.tr/fim/api AKBANK_GATEWAY_3D=https://entegrasyon.asseco-see.com.tr/fim/est3Dgate YAPIKREDI_PAYMENT_API=https://setmpos.ykb.com/PosnetWebService/XML YAPIKREDI_GATEWAY_3D=https://setmpos.ykb.com/3DSWebService/YKBPaymentService VAKIFBANK_PAYMENT_API=https://onlineodemetest.vakifbank.com.tr:4443/VposService/v3/Vposreq.aspx VAKIFBANK_GATEWAY_3D=https://3dsecuretest.vakifbank.com.tr:4443/MPIAPI/MPI_Enrollment.aspx DENIZBANK_PAYMENT_API=https://test.inter-vpos.com.tr/mpi/Default.aspx DENIZBANK_GATEWAY_3D=https://test.inter-vpos.com.tr/mpi/Default.aspx QNB_PAYFOR_PAYMENT_API=https://vpostest.qnb.com.tr/Gateway/XMLGate.aspx QNB_PAYFOR_GATEWAY_3D=https://vpostest.qnb.com.tr/Gateway/Default.aspx KUVEYTTURK_PAYMENT_API=https://boatest.kuveytturk.com.tr/boa.virtualpos.services/Home ALBARAKA_PAYMENT_API=https://epostest.albarakaturk.com.tr/ALBMerchantService/MerchantJSONAPI.svc TOSLA_PAYMENT_API=https://prepentegrasyon.tosla.com/api/Payment PARAM_PAYMENT_API=https://test-dmz.param.com.tr/turkpos.ws/service_turkpos_test.asmx AKBANK_POS_PAYMENT_API=https://apipre.akbank.com/api/v1/payment/virtualpos
Sahte driver
Gerçek istek atmadan akışı denemek için fake driver'ını kullanın. Gerçek
driver'ların yetenek arayüzlerini uygular ve yaptığı işlemleri bellekte
tutar — ödeyip sonra status() sorarsanız gerçekten paid döner, iade
ederseniz refunded olur.
$gateway = AnadoluPay::driver('fake'); $gateway->createPayment($data); $gateway->status('SIPARIS-123')->isPaid(); // true $gateway->refund(new RefundPaymentData('SIPARIS-123')); $gateway->status('SIPARIS-123')->isRefunded(); // true
Varsayılan olarak her işlem başarılıdır; testlerin rastgele kırılmaması için sahte geçidin öngörülebilir olması gerekir. Hata yollarını denemek isterseniz:
config(['anadolupay.fake.success_rate' => 0]); // her zaman başarısız
Güvenlik
- Kart verisi (
CardData) saklanmamalıdır. Kendi loglarınızda göstermeniz gerekiyorsaCardData::masked()kullanın; paketin istek/yanıt logları zaten maskelidir. CardDatanesnelerinidd(),var_dump()veya exception raporlarına vermeyin — bunlar maskelemeden geçmez.verify_hashyalnızca bankanın hash'i tutarsız ürettiği bilinen kurulumlarda kapatılmalıdır. Kapalıyken sahte callback'lere açıksınızdır.verify_sslher zamantruekalmalıdır.- 3D Host modeli kart verisini sunucunuzdan tamamen uzak tutar; PCI kapsamını daraltmak istiyorsanız en iyi seçenektir.
Güvenlik açığı bildirimi: security@voxyfy.com
Yeni banka eklemek
AbstractBankGateway sınıfını genişletin ve yedi metodu implement edin:
class YeniBankaGateway extends AbstractBankGateway { protected function build3dFormFields(CreatePaymentData $data): array { … } protected function checkCallbackHash(array $payload): bool { … } protected function is3dAuthSuccess(array $payload): bool { … } protected function provision(array $payload): array { … } protected function mapCallbackResponse(array $payload): VerificationResponse { … } protected function mapProvisionResponse(array $payload, array $provision): VerificationResponse { … } protected function extractOrderId(array $payload): ?string { … } }
Sonra config/anadolupay.php içindeki banks dizisine bir preset ekleyin.
Akış, hata yönetimi, HTTP ve loglama temel sınıftan gelir.
İmza için test yazın. Sabit girdilerle üretilmiş bir özet değerine
kilitleyin — mevcut driver'ların hepsinde örneği var (tests/Bank/HashTest.php).
İmza sessizce bozulabilen tek şeydir.
Yol haritası
Bilinen eksikler. Bir maddeye başlamadan önce issue açmanız çakışmayı önler.
Öncelikli
-
iyzico imza şemasını doğrula.Üç şema da (Authorization, yanıt/callback, webhook) resmi dokümantasyondan teyit edilip düzeltildi ve testle kilitlendi. -
iyzico iadesi./v2/payment/refundile tam ve kısmi iade. -
Tutarları kuruş cinsinden tam sayıya taşı.Moneyvalue object;floatgirdi geriye dönük uyumlu olarak destekleniyor.
İşlem kapsamı
-
Yetenekler artık arayüzlerle bildiriliyor — bkz. Yetenekler.cancel()vestatus()'ü sözleşmeye taşı. -
EksikKuveyt Türk (ayrı SOAP servisi) ve Param eklendi. PayTR iptal API'si sunmuyor; sağlayıcı sınırı.cancel(). -
Eksik13 driver'a yayıldı. Akbank POS tekil durum sorgusu sunmuyor; yerine işlem geçmişi var.status(). -
Kuveyt Türk iade/iptal. -
Ön provizyon / provizyon kapama.12 driver. -
İşlem geçmişi, taksit oranı ve BIN sorgulama. -
Tekrarlayan ödeme.Asseco, Garanti, PayFlex, Akbank POS.
Yeni bankalar
Çoğu mevcut NestPay driver'ını kullanır; yeni kod değil, preset ve doğrulanmış uç nokta gerekir.
- ING Bank · Anadolubank · Alternatif Bank · Odeabank · Türkiye Finans · Fibabanka · Burgan Bank
- Emlak Katılım — hangi altyapıyı kullandığı araştırılmalı
Yeni ödeme kuruluşları
- Sipay — imza şeması güvenilir bir public kaynaktan doğrulanamadığı için bilinçli olarak eklenmedi.
- Craftgate · Moka · Paratika/MSU · PayU Türkiye · Vallet · Paycell
Altyapı
-
PSR-3 loglama (maskeli)— bkz. Loglama -
Event'ler— bkz. Event'ler -
Idempotency— bkz. Mükerrer ödeme koruması -
Retry politikası— bkz. Hata yönetimi ve yeniden deneme -
Hata sınıflandırması—TransportExceptionilePaymentFailedExceptionartık ayrı; aynı bölüme bakın.
Katkı
composer test # Pest composer format # Pint vendor/bin/phpstan # Larastan, level 5
Detaylar için CONTRIBUTING, sürüm geçmişi için CHANGELOG.
Lisans
MIT — bkz. LICENSE.