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.

Maintainers

Package info

github.com/Voxyfy/anadolupay

pkg:composer/voxyfy/anadolupay

Transparency log

Statistics

Installs: 17

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.0.6 2026-08-07 08:56 UTC

This package is not auto-updated.

Last update: 2026-08-07 09:00:55 UTC


README

Latest Version on Packagist Tests Total Downloads

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. iyzico driver'ı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) veya akbank-pos (yeni JSON API)
  • Ziraat → ziraat (NestPay) veya ziraat-payflex (PayFlex)
  • QNB Finansbank → qnb (NestPay) veya qnb-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() ve status() şu an PaymentGatewayInterface'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.9019990). 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övdeIYZWSv2 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.5010.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 gerekiyorsa CardData::masked() kullanın; paketin istek/yanıt logları zaten maskelidir.
  • CardData nesnelerini dd(), var_dump() veya exception raporlarına vermeyin — bunlar maskelemeden geçmez.
  • verify_hash yalnı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_ssl her zaman true kalmalı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/refund ile tam ve kısmi iade.
  • Tutarları kuruş cinsinden tam sayıya taşı. Money value object; float girdi geriye dönük uyumlu olarak destekleniyor.

İşlem kapsamı

  • cancel() ve status()'ü sözleşmeye taşı. Yetenekler artık arayüzlerle bildiriliyor — bkz. Yetenekler.
  • Eksik cancel(). Kuveyt Türk (ayrı SOAP servisi) ve Param eklendi. PayTR iptal API'si sunmuyor; sağlayıcı sınırı.
  • Eksik status(). 13 driver'a yayıldı. Akbank POS tekil durum sorgusu sunmuyor; yerine işlem geçmişi var.
  • 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ı

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.