Müşteri kartından çekimi SMS'le görüyor, mağaza panelinde sipariş hâlâ “Ödeme bekleniyor”. Birkaç saat sonra WooCommerce siparişi iptal ediyor. Sorun bankada değil; sonucun mağazaya hangi yoldan ulaştığında.
Türk bankalarının 3D Host ve 3D Pay modellerinde ödeme sonucu mağazaya müşterinin tarayıcısı üzerinden, başarı/başarısızlık adresine yapılan bir POST ile döner. Müşteri banka sayfasında ödedikten sonra sekmeyi kapatırsa, bağlantısı koparsa ya da yönlendirme takılırsa bu istek hiç gelmez: para çekilmiştir, sipariş beklemede kalır. Çözüm tarayıcıya güvenmemek ve bankanın sipariş sorgulama servisine sunucudan sormaktır (mutabakat): ödeme denemesinden birkaç dakika sonra ve müşteri teşekkür sayfasına döndüğünde. Tutar ve iptal durumu doğrulanmadan sipariş tamamlanmaz.
Şikâyet genellikle telefonla gelir: “Kartımdan para çekildi ama siparişim onaylanmadı.” Panelde sipariş “Ödeme bekleniyor” durumundadır; bazen de çoktan “İptal edildi”ye düşmüştür. Hata kaydında bir şey yoktur, çünkü sunucu açısından ortada bir hata yoktur — sunucuya hiçbir şey ulaşmamıştır.
Türk bankalarının yaygın sanal POS modellerinde (Nestpay altyapısı, QNB'nin PayFor'u ve benzerleri) 3D Host/3D Pay akışı şöyledir:
Kırılgan adım üçüncüsüdür. O istek müşterinin cihazından çıkar. Mobil veri kesilirse, müşteri “ödeme tamam” yazısını görüp sekmeyi kapatırsa, banka sayfası yavaşlayıp müşteri geri tuşuna basarsa, ya da uygulama içi tarayıcı yönlendirmeyi yutarsa mağaza sonucu hiç öğrenmez. Çekim gerçekleşmiştir; bilgi yolda kaybolmuştur.
Bazı ödeme kuruluşları bunun için sunucudan sunucuya bir bildirim adresi sunar ve sonucu müşteriden bağımsız olarak gönderir. Bankaların klasik sanal POS altyapılarının çoğunda böyle güvenilir bir bildirim yoktur; sonucu öğrenmenin tek sağlam yolu sormaktır.
Durumu ağırlaştıran bir WooCommerce ayarı vardır: Ayarlar → Ürünler → Envanter → Stok bekletme (dakika). Stok yönetimi açıksa, ödeme sayfasından oluşturulup bu süre boyunca “Ödeme bekleniyor”da kalan siparişler otomatik olarak iptal edilir. Yani dönüşü kaybolan bir ödeme, bir saat sonra “parası alınmış ama iptal edilmiş sipariş”e dönüşür.
Mutabakatı bu süreden önce çalıştırın. Ayrıca mutabakat, kendi geçidinizle oluşturulmuş ve yakın zamanda iptal edilmiş siparişleri de sorgulayabilmelidir; payment_complete() iptal edilmiş bir siparişi de geçerli ödeme durumuna taşıyabilir.
Çözümün fikri basittir: sonucu tarayıcıdan beklemek yerine, bankanın sipariş sorgulama servisine sunucudan sormak. İki tetikleyici yeterlidir:
// Ödeme denemesi başlarken
$ids = (array) $order->get_meta( '_pos_order_ids' );
$ids[] = $bank_order_id; // bu denemenin banka sipariş numarası
$order->update_meta_data( '_pos_order_ids', $ids );
$order->save();
wp_schedule_single_event( time() + 600, 'mypos_reconcile', array( $order->get_id() ) );
add_action( 'mypos_reconcile', 'mypos_reconcile' );
add_action( 'woocommerce_thankyou_my_gateway', 'mypos_reconcile' );
function mypos_reconcile( $order_id ) {
$order = wc_get_order( $order_id );
if ( ! $order || $order->is_paid() ) {
return;
}
foreach ( array_reverse( (array) $order->get_meta( '_pos_order_ids' ) ) as $bank_id ) {
$r = mypos_inquiry( $bank_id ); // bankaya sunucudan sorgu
if ( ! $r || '00' !== $r['code'] || $r['voided'] ) {
continue;
}
if ( $r['amount'] !== wc_format_decimal( $order->get_total(), 2 ) ) {
$order->add_order_note( 'Banka farklı tutar bildirdi — elle kontrol edin.' );
return;
}
$order->payment_complete( $r['ref'] );
$order->add_order_note( 'Mutabakat: banka ödemeyi doğruladı.' );
return;
}
}
WP-Cron ziyaretle tetiklenir; trafiği az olan sitelerde zamanlanmış iş gecikebilir. Güvenilirlik için sistem cron'undan wp cron event run --due-now çalıştırın.
Sorgu, ödeme formundan farklı olarak sunucudan sunucuya yapılır ve API rolündeki kullanıcıyla kimlik doğrular. Örneğin QNB'nin PayFor altyapısında XmlGate.aspx adresine şu istek gönderilir:
<?xml version="1.0" encoding="UTF-8"?>
<PayforRequest>
<MbrId>5</MbrId>
<MerchantId>…</MerchantId>
<UserCode>…</UserCode>
<UserPass>…</UserPass>
<OrgOrderId>SIP1234A2</OrgOrderId>
<SecureType>Inquiry</SecureType>
<TxnType>OrderInquiry</TxnType>
<Lang>TR</Lang>
</PayforRequest>
Cevapta ProcReturnCode 00, TxnType Auth ve VoidDate boş ya da sıfırsa ödeme geçerli bir satıştır; PurchAmount tutarı verir. Nestpay altyapısında aynı iş, istekteki Extra alanına ORDERSTATUS=QUERY eklenerek yapılır. İsimler bankaya göre değişir, fikir aynıdır.
İki pratik not: bankalar bu servise erişimi çoğunlukla IP ile kısıtlar, sunucunuzun IP'sini bankaya tanımlatın. Ve kimlik bilgisi hatalıysa servis HTTP 200 ile bir hata kodu döner — cevabın gövdesini okumayan bir entegrasyon bunu “ödenmemiş” sanır.
Bankalar aynı sipariş numarasıyla ikinci bir işlemi kabul etmez; bu yüzden her ödeme denemesi için yeni bir banka sipariş numarası üretilir (SIP1234A1, SIP1234A2…). Burada sessiz bir çifte tahsilat riski vardır:
Müşteri ilk denemede öder ama dönüş kaybolur; sipariş beklemede görünür, müşteri tekrar dener ve ikinci deneme başarısız olur. Yalnızca son numarayı sorgulayan bir mutabakat “ödenmedi” der ve müşteri bir kez daha öder. Bu yüzden siparişe ait bütün deneme numaralarını saklayın ve yeniden eskiye doğru hepsini sorgulayın.
payment_complete() kullanın: durumu elle değiştirmek stok düşümünü ve e-postaları atlarCastor Coffee'nin QNB sanal POS entegrasyonuna bu katmanı ekledik: her denemenin numarası saklanıyor, on dakika sonra ve teşekkür sayfasında sorgu yapılıyor, tutar uyuşmazlığında sipariş otomatik tamamlanmıyor.
3D ödemede sonuç, en güvenilmez halkadan — müşterinin tarayıcısından — geçerek gelir. Bu halka koptuğunda para çekilmiş, sipariş askıda kalmıştır ve stok bekletme süresi dolunca iptal edilir. Kalıcı çözüm: bankaya sunucudan sormak. Her denemenin numarasını saklayın, stok bekletme süresinden önce sorgulayın, tutarı ve iptal durumunu doğrulamadan tamamlamayın. Dönüş adresi iyi günde çalışan bir kolaylıktır; mutabakat ise kötü günün sigortasıdır.
XmlGate.aspx · SecureType=Inquiry · TxnType=OrderInquiryExtra → ORDERSTATUS=QUERY3D ödemede banka sonucu mağazaya müşterinin tarayıcısı üzerinden, başarı adresine yapılan bir POST ile bildirir. Müşteri ödeme sonrasında sekmeyi kapatırsa ya da bağlantısı koparsa bu istek hiç gelmez. Çekim gerçekleşmiştir ama mağaza bundan haberdar olmamıştır.
WooCommerce'in stok bekletme ayarı yüzünden. Stok yönetimi açıksa, belirlenen dakika boyunca ödeme bekleyen siparişler otomatik iptal edilir. Mutabakatı bu süreden önce çalıştırın ve yakın zamanda iptal edilmiş kendi siparişlerinizi de sorgulayın.
Bankanın sipariş sorgulama servisine erişim: API rolünde bir kullanıcı ve sunucunuzun IP adresinin banka tarafında tanımlanması. Sorgu sunucudan sunucuya yapılır; müşterinin tarayıcısına bağlı değildir.
Her denemede yeni bir banka sipariş numarası kullanılır. İlk deneme başarılı olup dönüşü kaybolmuşsa ve ikinci deneme başarısızsa, yalnızca son numaraya bakan bir kontrol “ödenmedi” der ve müşteri bir kez daha öder. Bütün numaraları yeniden eskiye doğru sorgulamak çifte tahsilatı önler.