Müsait — Yerel Dijital Ürünler
HCA · Studio
TR — İletişim ↗
Teknik Not 13 · Sanal POS

Para çekildi, sipariş hâlâ bekliyor

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.

Konu
3D ödeme dönüşü
Dil
PHP
Belirti
Ödendi ama beklemede
Kısa cevap

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.

Belirti

Ş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.

Sonuç nereden gelir

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:

  1. Mağaza, imzalı bir formu müşterinin tarayıcısıyla bankanın ödeme sayfasına gönderir
  2. Müşteri kart bilgisini ve 3D Secure kodunu bankada girer; banka çekimi yapar
  3. Banka sonucu, müşterinin tarayıcısını mağazanın başarı/başarısızlık adresine POST ettirerek bildirir
  4. Mağaza hash'i doğrular ve siparişi tamamlar

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.

Stok bekletme tuzağı

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.

Mutabakat

Çözümün fikri basittir: sonucu tarayıcıdan beklemek yerine, bankanın sipariş sorgulama servisine sunucudan sormak. İki tetikleyici yeterlidir:

  • Zamanlanmış kontrol: her ödeme denemesinden birkaç dakika sonra tek seferlik bir iş
  • Teşekkür sayfası: müşteri döndüğünde sipariş hâlâ ödenmemiş görünüyorsa anında sorgu
// Ö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.

Bankaya sorgu

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.

Birden fazla deneme

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.

Tamamlamadan önce

  • Tutar: bankanın bildirdiği tutar sipariş toplamıyla eşleşmeli; eşleşmiyorsa otomatik tamamlamayın, not düşüp elle kontrole bırakın
  • İptal: aynı gün iptal edilmiş bir işlem “başarılı” kodla dönebilir; iptal alanını ayrıca kontrol edin
  • Tekrar: sipariş zaten ödenmişse hiçbir şey yapmayın; dönüş ile mutabakat aynı anda çalışabilir
  • payment_complete() kullanın: durumu elle değiştirmek stok düşümünü ve e-postaları atlar

Castor 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.

Özet

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.

Hızlı referans
Belirti
Kart çekimi var, sipariş “Ödeme bekleniyor” ya da iptal
Sebep
3D sonucu müşterinin tarayıcısıyla döner; sekme kapanırsa hiç gelmez
Çözüm
Bankanın sipariş sorgu servisine sunucudan sorgu (mutabakat)
Ne zaman
Denemeden ~10 dk sonra + teşekkür sayfasında
PayFor (QNB)
XmlGate.aspx · SecureType=Inquiry · TxnType=OrderInquiry
Nestpay
İstekte Extra → ORDERSTATUS=QUERY
Tuzak
Stok bekletme süresi dolunca bekleyen sipariş otomatik iptal olur
Kural
Bütün deneme numaralarını sorgula; tutar ve iptali doğrulamadan tamamlama
Sık Sorulan Sorular

Sanal POS mutabakatı hakkında.

Müşterinin kartından para çekildi ama WooCommerce siparişi ödeme bekliyor. Neden?

3D ö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.

Bu siparişler neden bir süre sonra iptal oluyor?

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.

Mutabakat için ne gerekir?

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.

Neden bütün ödeme denemelerini sorgulamak gerekiyor?

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.

Teklif ve Uygunluk

Bir fikriniz mi var?
Yarım cümle de olur.