`nestpay` — NestPay (Asseco SEE)

nestpay — NestPay (Asseco SEE)

> Tip: Banka POS bayisi (12 banka aynı altyapı) > Adapter: InvumPosGatewayNestPayNestPayGateway (planlanan, Hafta 17 sprint) > Son senkron: 2026-05-05 (clean-room spec — CP.VPOS v2 referans) > Last-known-good API: Asseco-SEE EST/CC5 v1 (XML over HTTPS) + EST 3D Gate (form-POST hash ver3)

NestPay (eski adıyla EST) Asseco SEE'nin sanal POS altyapısı — Türkiye'de 12 banka aynı API'yı kullanıyor (her bankanın kendi sub-domain'i, ama endpoint path'leri / request body / response shape'i identical). Tek adapter ile 12 PG kazanılır; per-bank fark sadece base URL ve credential.

> NestPay olmayan Akbank: Akbank Mart 2024'te kendi modern REST > API'sına geçti (api.akbank.com/api/v1/payment/virtualpos/...). > Eski NestPay endpoint'i (sanalakpos.com/fim/api) hâlâ çalışıyor > ama merchant'lar yeni başvurularda doğrudan REST API'a yönlendiriliyor. > Faz 2 backlog: ayrı AkbankGateway adapter'ı (NestPay'den ayrı).


1. Bağlantı bilgileri

1.1. Per-bank base URL'ler

NestPay altyapısını kullanan bankalar (CP.VPOS v2 referansından, 2026-05-05 itibariyle):

| Banka | Production base | Sandbox base | |————————-|————————————————–|———————————————————–| | Akbank (legacy) | https://www.sanalakpos.com | https://entegrasyon.asseco-see.com.tr (genel sandbox) | | AlternatifBank | https://sanalpos.abank.com.tr | https://entegrasyon.asseco-see.com.tr | | Anadolubank | https://anadolusanalpos.est.com.tr | https://entegrasyon.asseco-see.com.tr | | Cardplus | https://sanalpos.card-plus.net | https://entegrasyon.asseco-see.com.tr | | Halkbank | https://sanalpos.halkbank.com.tr | https://entegrasyon.asseco-see.com.tr | | ING Bank | https://sanalpos.ingbank.com.tr | https://entegrasyon.asseco-see.com.tr | | İşbank | https://sanalpos.isbank.com.tr | https://istest.asseco-see.com.tr ⚠️ banka-spesifik | | QNB Finansbank | https://www.fbwebpos.com | https://entegrasyon.asseco-see.com.tr | | Şekerbank | https://sanalpos.sekerbank.com.tr | https://entegrasyon.asseco-see.com.tr | | TEB | https://sanalpos.teb.com.tr | https://entegrasyon.asseco-see.com.tr | | Türkiye Finans | https://sanalpos.turkiyefinans.com.tr | https://entegrasyon.asseco-see.com.tr | | Ziraat Bankası | https://sanalpos2.ziraatbank.com.tr | https://torus-stage-ziraat.asseco-see.com.tr ⚠️ banka-spesifik |

Path'ler (her banka için aynı):

  • /fim/api — XML API (sale, refund, cancel, sale-finalize, status query)
  • /fim/est3Dgate — 3D Secure form-POST gateway

Sandbox notu: 12 bankanın 10'u Asseco SEE'nin paylaşımlı sandbox'ı (entegrasyon.asseco-see.com.tr) — herhangi bir test merchant credential'ı ile çalışılır. İşbank ve Ziraat kendi sandbox host'unu kullanıyor (banka-spesifik test merchant gerek).

1.2. Yetkili / dev portal

Her banka kendi POS başvuru sürecini yürütür; ortak Asseco SEE dev portal yok. Banka temsilcisinden store key (4. credential — 3D hash secret) ve client ID + Name + Password alınır.

Bizim görevimiz: her merchant kendi bankasından credential aldıktan sonra, WCGateway settings'e enter eder. Adapter slug nestpay_<bank> şeması ile çoklu instance kaydedilir (WC'nin per-method seçeneği gibi).


2. Auth şeması

> Kategori: XML body POST + 3DS form-POST hash (hashAlgorithm=ver3)

NestPay iki ayrı request shape kullanıyor:

2.1. XML API endpoint (/fim/api)

Auth basit: her XML body içine Name / Password / ClientId üç field gömülür. Ek imza/hash YOK — TLS + credential bilgilerinin gizliliği kâfi sayılıyor.

Form-encoded body wrapper:

DATA=<xml-body>

Content-Type: application/x-www-form-urlencoded. Yani XML form-data field değeri olarak yollanır (HTTP body bütün olarak XML değil).

2.2. 3DS gateway endpoint (/fim/est3Dgate)

Bu endpoint browser'dan form POST alır (auto-submit form, kart bilgileri müşterinin browser'ından bankaya). Her parametre + storeKey üzerinden SHA512 hash üretilir, hash field olarak eklenir.

Hash formülü (ver3):

  1. Tüm request param'larını alfabetik sort et (key'e göre)
  2. Her value içinde | ve karakterlerini escape et:

||\

  1. Sort edilmiş value'ları | ile join et
  2. Sonuna |<storeKey> ekle
  3. UTF-8 olarak SHA512 al, Base64 encode
hash_input = "value1|value2|value3|...|valueN|storeKey"
hash       = base64( sha512_utf8( hash_input ) )

Sırlar (encrypted): client_id (ClientId), merchant_user (Name), merchant_password (Password), store_key (3D hash secret).


3. Endpoint tablosu

Her banka aynı path'leri kullanır; sadece base URL değişir.

| İşlem | Method | Path | İstek field'ları (kritik) | Yanıt field'ları (kritik) | |———————-|——–|———————|———————————————————————————————————-|————————————————————————| | 3D init | POST | /fim/est3Dgate | clientid, oid, amount, currency, taksit, installment, okUrl, failUrl, rnd, storetype=3d, lang=tr, islemtipi=Auth, hashAlgorithm=ver3, pan, cv2, Ecom_Payment_Card_ExpDate_{Year,Month}, hash | HTML auto-submit form (browser bank ACS'e yönlendirilir) | | 3D callback | (POST'a bizim endpoint) | (bizim /wp-json/invum-pos/v1/nestpay/callback) | mdStatus (1=success), oid, md, xid, eci, cavv, clientIp, installment | (no response — biz redirect ederiz) | | Sale finalize | POST | /fim/api | Name, Password, ClientId, IPAddress, OrderId, Taksit, Type=Auth, Number=md, PayerTxnId=xid, PayerSecurityLevel=eci, PayerAuthenticationCode=cavv | <Response>Approved/Error/Decline</Response>, <TransId>...</TransId>, <ErrMsg>...</ErrMsg> | | Sale (non-3D) | POST | /fim/api | Name, Password, ClientId, Type=Auth, OrderId, Taksit, Total, Currency, Number=PAN, Expires=MM/YYYY, Cvv2Val | <Response>...</Response>, <TransId>...</TransId> | | Refund | POST | /fim/api | Name, Password, ClientId, Type=Credit, TransId, Total | <Response>Approved/Error</Response>, <ErrMsg>...</ErrMsg> | | Cancel (void) | POST | /fim/api | Name, Password, ClientId, Type=Void, TransId | <Response>...</Response> | | Status query | POST | /fim/api | Name, Password, ClientId, OrderId, Extra: { ORDERSTATUS: QUERY }. Wrapper root tag <CC5Request>. | <CC5Response><Response>, <TransId>, <Extra> (CAPTURE_AMT, CAPTURE_DTTM, TRANS_STAT: S=Paid, V=Voided) |

Test kartları: Asseco SEE genel sandbox için banka-spesifik test kartı listesi — her banka kendi başvuru paketinde verir. NestPay'in ortak bir test PAN'ı YOK (banka temsilcisinden istenecek).


4. Bilinen quirk'ler

> Üretimde / sandbox'ta yaşanmış spesifik problemler. Banka POS sprint'i > başlamadan, CP.VPOS implementation'ından spec çıkarımları:

#1 — Amount format Türkçe-locale, sonra normalleştirme

Amount string "N2" Türkçe locale formatlanıyor (1.234,56) → sonra nokta silip virgül noktaya çevirme (1234.56) yapılıyor:

1234.56 (decimal) → "1.234,56" (tr-TR N2) → "1234,56" (replace ".") → "1234.56" (replace ",")

Sonuç 2-haneli decimal, dot separator, thousand separator yok.

number_format( $amount, 2, '.', '' ) PHP'de aynı sonucu üretir, ama locale formatı süreciyle eşleştiğine emin ol — bazı eski merchant'lar "1234,56" beklemeye programlanmış olabilir.

#2 — Hash escape sırası kritik

| ve escape sırası önemli:

  1. ÖNCE ||
  2. SONRA \

Yanlış sırayla yapılırsa (önce , sonra |) escape karakterlerinin kendileri tekrar escape edilir → hash mismatch. Adapter'da test için fixture: "a|bc""a|b\c".

#3 — installment ve taksit aynı değer (legacy duplicate)

3DS init form'unda hem installment hem taksit field'ları var, ikisi de aynı değer alır. Eski API versiyonlarından kalma duplicate; ikisi gönderilmezse bazı bankalar reject ediyor.

installment=3 → form: installment=3 + taksit=3
installment=1 (peşin) → form: installment="" + taksit=""  (boş string!)

Tek çekim için boş string zorunlu — 0 veya 1 gönderirsen banka "taksit yapısı geçersiz" döndürebilir.

#4 — Currency ISO-4217 numeric (string olarak)

Currency field numeric ISO 4217:

  • TRY = 949
  • USD = 840
  • EUR = 978

CP.VPOS ((int)Currency).ToString() ile gönderiyor — int → string. Bizim adapter (string) 949 ile aynı sonucu verir.

#5 — <CC5Request> / <CC5Response> wrapper sadece query'de

Sale, refund, cancel, sale-finalize tek root tag <CC5Request> veya <CC5Response> ile sarılmıyor — direct param'lar XML root'unda. Sadece SaleQuery için CC5 wrapper kullanılıyor (Extra: { ORDERSTATUS: QUERY } field'ı sebebiyle). Bu küçük tutarsızlık spec'te dökümante değil ama gerçek API davranışı.

#6 — Response field değerleri: Approved / Error / Decline / Declined

Hem Decline hem Declined görüyor (Asseco'nun farklı sürümlerinde farklı string). Adapter ikisini de tanımalı — sadece Error veya Decline* başlangıçlı stringleri fail saymalı, geri kalanı Approved varsay. CP.VPOS pattern'i:

if ( in_array( $response, ['Error', 'Decline', 'Declined'], true ) ) { /* fail */ }
elseif ( $response === 'Approved' ) { /* success */ }

#7 — TLS 1.2 zorunlu (eski .NET workaround)

CP.VPOS ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072 ile TLS 1.2'yi forcelluyor. PHP 8.1+ default zaten TLS 1.2+ kullanır, ama wp_remote_post sslverify=true flag'i ile sertifika doğrulaması açık tutulmalı.

#8 — hashAlgorithm=ver3 zorunlu, eski hash'leri kullanma

NestPay eskiden 3 farklı hash algoritması destekliyordu (ver1, ver2, ver3). Yeni başvurularda sadece ver3 kullanılır; ver1/ver2 deprecated. Hash formülü §2.2'deki gibi.

#9 — Akbank legacy vs modern ayrımı

Akbank Mart 2024'te kendi REST API'sını yayınladı (api.akbank.com/api/v1/payment/virtualpos/...). Yeni başvuru yapan merchant'lar buraya yönlendiriliyor. Ama eski NestPay endpoint'i (sanalakpos.com/fim/api) hâlâ çalışıyor — eski sözleşmesi olan merchant'lar için.

Bizim nestpay_akbank slug'ı eski endpoint'i temsil eder. Yeni Akbank REST için ayrı bir akbank slug'ı (Faz 2 backlog) yazılacak.

#10 — IPAddress field 3D-finalize'da zorunlu

Sale-finalize XML'inde IPAddress field callback'ten gelen clientIp ile dolu olmak zorunda. Boş string geçilirse banka "fraud risk" reddi döndürüyor. CP.VPOS callback'teki responseArray["clientIp"]'i alıp finalize'a yansıtıyor — bizim de aynısını yapmalıyız.


5. Bizim implementasyon notları

  • WCGateway sınıfı: InvumPosGatewayNestPayNestPayWCGateway (per-bank instance: nestpay_akbank, nestpay_halkbank, …)
  • Charge adapter: InvumPosGatewayNestPayNestPayGateway (charge + callback verify + status, per-bank URL constructor injection)
  • Refund adapter: InvumPosGatewayNestPayNestPayRefund (RefundInterface, R3 router IPTAL/IADE same-day vs post-settle)
  • Status query: InvumPosGatewayNestPayNestPayStatusQuery (StatusQueryInterface, CC5Request wrapper)
  • Hash builder: InvumPosGatewayNestPayNestPayHash::ver3() — pure function, byte-pinned tests
  • Callback controller: InvumPosGatewayNestPayNestPayCallbackController — REST /wp-json/invum-pos/v1/nestpay/callback (per-bank dispatch via order meta _invum_pos_nestpay_bank)

Mimari kararı (henüz kesin değil): 12 banka için tek WCGateway mi yoksa 12 ayrı WCGateway mi?

  • Tek: WC gateway_id = invum_pos_nestpay, settings'te per-bank

credential setleri. Admin UI'da dropdown ile banka seçilir. Daha az WC menu kalemi, daha temiz UX.

  • Çoklu: Her banka kendi WCGateway sınıfı (InvumPosNestPayAkbank,

…). WC admin'de 12 ayrı entry. Per-bank enable/disable, per-bank başlık. Daha kalabalık ama merchant açısından net.

Önerim: Çoklu — merchant'ın kafasını karıştırmaz, "Akbank" yazınca Akbank-only logo göstermek mümkün. NestPayWCGateway'i abstract base yapıp 12 thin subclass türetilir (per-bank URL + brand label override).

Persist edilen field'lar (transactions tablosu):

  • transaction_id = <TransId> (NestPay'den dönen)
  • payment_transaction_id = boş (NestPay tek-kalem refund yapıyor;

per-item refund anchor gerekmez)

  • gateway = nestpay_<bank> (per-bank slug, refund'da banka için

doğru base URL'yi resolve etmek için)

Şu an desteklenmeyen (Faz 1.5+ backlog):

  • BIN-bazlı taksit sorgusu (BINInstallmentQuery) — CP.VPOS'ta zaten

default confirm=false (her banka kendi merchant paneli kuralları)

  • Saved cards / vault — NestPay altyapısı saved-card desteklemiyor

(PCI scope SAQ A-EP, kart bizim formumuzda toplandığı için biz kart token'ı saklayamayız direkt; bankalar genelde kendi vault'larını ayrı PSP-as-a-service olarak satıyor)

  • Akbank yeni REST API (Faz 2 — ayrı akbank slug)
  • 3DS hash'in ver1/ver2 legacy desteği (sadece ver3 ship)

6. Test connection (Pro modülü)

ProModuleTestConnectionNestPayConnectionTest ne kontrol edecek (planlanan):

  • Sandbox /fim/api POST query — basit OrderId araması, response

shape XML mı? <Response> field var mı?

  • 3D hash ver3 builder fixture — sabit input → sabit hash output

byte-pin (offline test)


7. Sandbox testi açık sorular (Hafta 17 başında)

  1. Asseco SEE genel sandbox'a (entegrasyon.asseco-see.com.tr) tek

credential ile mi, yoksa banka-spesifik test merchant başvurusu ile mi giriliyor?

  1. lang=tr yerine lang=en 3DS form'unu İngilizce'ye çeviriyor mu

(uluslararası müşteri için)?

  1. Same-day void için Type=Void denedik diye banka aynı OrderId'i

tekrar sale için kabul eder mi (idempotency)?

  1. Ziraat ve İşbank kendi sandbox'larında entegrasyon.asseco-see.com.tr

credential'ı çalışıyor mu, yoksa banka-spesifik istek mi?

  1. NestPay 13 bankanın hangileri aynı tarihte test edilecek (sprint

prioritization — ilk 3-5 banka pilot)?

  1. Akbank legacy NestPay endpoint'i hâlâ aktif mi (yoksa sadece eski

merchant'lar için)? Yeni başvuru için akbank slug zorunlu mu?


8. Kaynaklar

  • CP.VPOS reference repo: https://github.com/cempehlivan/CP.VPOS

CP.VPOS/Integrations/Banks/Nestpay/NestpayAbstract.cs — ortak abstract sınıf (Sale/Sale3D/Cancel/Refund/Query) – CP.VPOS/Integrations/Banks/Nestpay/{Akbank,Halkbank,IsBankasi,…}.cs — per-bank URL override (15-18 satır her biri)

  • Asseco SEE EST: official dev portal yok — banka temsilcisinden talep edilecek
  • Türkçe forum/blog: NestPay 3DS hash sorunları için entegrasyon.asseco-see.com.tr test deneyimleri (community knowledge — sandbox testi sırasında derlenmeli)

9. Değişiklik geçmişi

| Tarih | Sürüm | Değişiklik | Senkron eden | |————|———————-|—————————————————————————–|————–| | 2026-05-05 | EST/CC5 v1 + ver3 | İlk doldurma — CP.VPOS v2 clean-room reference, 12 banka spec özeti | Claude+Cenk |

Alışveriş Sepeti