extrapaytr-crypto 0.1.1

Digest, HMAC, constant-time comparison and at-rest sealing helpers for ExtraPayTR
Documentation

ExtraPayTR

Türk ödeme sağlayıcıları için framework'ten bağımsız, asenkron bir Rust SDK'sı. Sadece resmi sağlayıcı dokümantasyonlarından, sıfırdan ("clean-room") yazıldı — bu kodu üretmek için mevcut hiçbir Node.js/TypeScript/PHP ödeme SDK'sı okunmadı veya kopyalanmadı.

Durum: erken geliştirme aşaması. Prodüksiyona hazır değil, PCI denetimi yapılmamış. Tek bir sağlayıcının (iyzico) uygulanan tüm işlemleri — Charge (3DS'siz ve 3DS'li), CallbackVerification, Query, Refund, Cancel — gerçek bir sandbox'a karşı çalıştırıldı; geri kalan her şey çalıştırılmadı. Aşağıdaki Doğrulama durumu bölümüne ve her sağlayıcının docs/providers/*.md dosyasına bakarak tam olarak neyin test edilip neyin edilmediğini görebilirsiniz.

Workspace düzeni

extrapaytr/
├── crates/
│   ├── extrapaytr-core/      domain tipleri: Money, request/response'lar, Gateway trait'leri, hatalar, secret'lar
│   ├── extrapaytr-http/      HttpTransport trait'i + reqwest/rustls implementasyonu
│   ├── extrapaytr-crypto/    HMAC-SHA256/SHA-512, base64, hex, sabit-zamanlı karşılaştırma
│   ├── extrapaytr-gateways/  gerçek sağlayıcı adaptörleri, sağlayıcı başına bir Cargo feature'ı
│   ├── extrapaytr/           yukarıdakileri yeniden export eden ince bir facade
│   └── extrapaytr-node/      napi-rs Node.js bindings (JS'ten ayrı bir paket, bkz. aşağıdaki JS bölümü)
└── docs/providers/           sağlayıcı başına bir dosya: incelenen dokümanlar, endpoint'ler, eksikler, durum

extrapaytr-core, reqwest, serde_json veya herhangi bir sağlayıcıya özel crate'e bağımlı değildir — sağlayıcıya özgü wire formatları tamamen extrapaytr-gateways içinde yaşar.

Sağlayıcılar

Sağlayıcı Protokol 3DS Refund Cancel Query Doğrulama
iyzico REST/JSON Var (init+complete+callback, hepsi SandboxVerified) Var Var Var Uygulanan her işlem için (Charge 3DS'siz, Charge 3DS'li, CallbackVerification, Query, Refund, Cancel) SandboxVerified — gerçek sandbox çalıştırması 2026-08-12 (bkz. docs/providers/iyzico.md)
PayTR Form-post + host edilen 3DS yönlendirmesi Var (HTML challenge + notify_url callback'i, ayrı bir "complete" çağrısı yok) Var Yok (ayrı endpoint yok; tam tutarlı iade kullanılıyor) Var FixtureTested (bkz. docs/providers/paytr.md)
Paratika Sunucudan sunucuya, düz metin kimlik bilgileri Yok (bloklandı — dökümana bakın) Yok (bloklandı — dökümana bakın) Yok (bloklandı — dökümana bakın) Yok (bloklandı — dökümana bakın) Sadece response-hash (sdSha512) doğrulaması: FixtureTested; geri kalan her şey: Skeleton (bkz. docs/providers/paratika.md)
Moka REST/JSON, sunucudan sunucuya Var (sadece yönlendirme; son-callback hash'i doğrulanmadı) Yok (sadece pazaryeri'ne özel bir endpoint bulundu) Yok (endpoint doğrulandı ama response'ta amount alanı yok) Var (status kodları yorumlanmadı) Charge/Query için FixtureTested; Refund/Cancel uygulanmadı (bkz. docs/providers/moka.md)
Garanti BBVA (GVPS) XML sunucudan sunucuya + 3DS tarayıcı form-post'u Var (sadece form-post başlatma; son-callback hash'i doğrulanmadı) Var (bir doğrulanmamış hash-alan varsayımı — dökümana bakın) Yok (endpoint doğrulandı ama core'un CancelRequest'inin taşımadığı bir amount alanı gerekiyor) Yok (order-inquiry Type değeri doğrulanmadı) Charge/Refund için FixtureTested (bkz. docs/providers/garanti.md)
NestPay/EST (Ziraat profili) 3D Pay Hosting tarayıcı form-post'u Var (form-post başlatma + callback doğrulaması; 3DS'siz akış doğrulanmadı) Yok (sunucudan sunucuya /fim/api şekli doğrulanmadı) Yok (aynı) Yok (aynı) Charge/CallbackVerification için FixtureTested (bkz. docs/providers/nestpay.md). Paylaşılan bir motor — birçok başka banka tarafından da kullanılıyor, ama sadece Ziraat'in host'u doğrulandı; QNB bu motoru kullanmıyor (kendine ait ayrı bir gateway'i var, araştırılmadı).
Kuveyt Türk (Freepos) XML sunucudan sunucuya, sadece 3D Secure Model Var (Request 1 → challenge → callback → Request 2, hepsi uygulandı) Yok (endpoint/alan şekli doğrulanmadı) Yok (aynı) Yok (aynı) Charge/complete_three_ds/CallbackVerification için FixtureTested — resmi dokümanın kendi 5 çalışan hash örneğinden 4'ü birebir yeniden üretildi (bkz. docs/providers/kuveytturk.md)
Craftgate Bloklandı: geliştirici portalı tamamen giriş gerektiriyor (headless fetch ve gerçek bir render edilmiş tarayıcı ile doğrulandı); herkese açık döküman veya OpenAPI spec'i bulunamadı. Bu turda atlandı.
PosNet (Yapı Kredi) Bloklandı: bulunan tek resmi PDF entegrasyon kılavuzu (m.yapikredipos.com.tr) süresi dolmuş bir TLS sertifikası üzerinden sunuluyor — iki bağımsız araçla doğrulandı (WebFetch, tarayıcı navigasyonu), kullanılabilir bir yansı (mirror) bulunamadı. Bu turda atlandı.
Akbank Sanal Pos Bloklandı: web araması üzerinden erişilebilir resmi entegrasyon dokümantasyonu bulunamadı. Bu turda atlandı.

Orijinal proje kontrol listesinde adı geçen diğer tüm sağlayıcılar (PayFor/PayFlex aileleri, ...) başlanmadı.

Hızlı başlangıç

use std::sync::Arc;
use extrapaytr::core::{Charge, ChargeRequest, Currency, Customer, Money, OrderId, PaymentMethod, RawCard};
use extrapaytr::gateways::iyzico::{Environment, IyzicoConfig, IyzicoGateway};
use extrapaytr::http::{ReqwestTransport, ReqwestTransportConfig};

# async fn example() -> Result<(), Box<dyn std::error::Error>> {
let transport = ReqwestTransport::new(ReqwestTransportConfig::default())?;
let config = IyzicoConfig::builder()
    .environment(Environment::Sandbox)
    .api_key(std::env::var("IYZICO_API_KEY")?)
    .secret_key(std::env::var("IYZICO_SECRET_KEY")?)
    .transport(Arc::new(transport))
    .build()?;
let iyzico = IyzicoGateway::new(config);

let request = ChargeRequest::builder()
    .order_id(OrderId::new("ORDER-123")?)
    .amount(Money::try_from_major("249.90", Currency::TRY)?)
    .customer(Customer::default())
    .payment_method(PaymentMethod::Raw(RawCard::new(
        "Jane Doe", "4111111111111111", 12, 2030, "123",
    )?))
    .build()?;

let result = iyzico.charge(request).await?;
# let _ = result;
# Ok(())
# }

Sağlayıcı başına ilgili feature gerekir (gateway-iyzico, gateway-paytr, gateway-paratika, gateway-moka, gateway-garanti, gateway-nestpay, gateway-kuveytturk, ya da uygulanan hepsi için all-gateways):

cargo add extrapaytr --features gateway-iyzico,gateway-paytr,gateway-paratika,gateway-moka,gateway-garanti,gateway-nestpay,gateway-kuveytturk

Sağlayıcıyı çalışma zamanında seçmek

Yukarıdaki hızlı başlangıçta IyzicoGateway kaynak kodda doğrudan isimlendirilir — bu, sağlayıcı derleme zamanında biliniyorsa doğru seçimdir: somut tip yalnızca uyguladığı işlemleri açığa çıkarır, bu yüzden desteklenmeyen bir çağrı derleme hatasıdır. Sağlayıcı bunun yerine yapılandırmadan geliyorsa (bir env değişkeni, kiracı başına bir veritabanı kolonu), registry'yi kullanın:

use extrapaytr::gateways::registry::{ProviderRegistry, ProviderSettings};

let registry = ProviderRegistry::with_builtin();
let settings = ProviderSettings::new()
    .with("environment", "sandbox")
    .with("api_key", std::env::var("IYZICO_API_KEY")?)
    .with("secret_key", std::env::var("IYZICO_SECRET_KEY")?);

let gateway = registry.build("iyzico", &settings, transport)?;
let response = gateway.charge(request).await?;

build, her işlemi tek biçimde açığa çıkaran bir GatewayHandle döndürür. Dispatch'ten önce adaptörün beyan ettiği yetenekleri (capabilities) kontrol eder, böylece bir sağlayıcının yapmadığı bir işlem her zaman UnsupportedOperation ile cevap verir — adaptör trait'i tamamen atlamış olsun ya da hata veren bir stub olarak uygulamış olsun fark etmez (PayTR'ın Cancel'i ikincisi). Anahtar isimlerinin koda gömülü olmasına gerek kalmasın diye ProviderRegistry::spec, her sağlayıcının hangi ayarları kabul ettiğini raporlar. Bkz. docs/registry.md.

JavaScript / Node.js

crates/extrapaytr-node, ince bir napi-rs bağlama (binding) katmanıdır — burada hiçbir ödeme mantığı yaşamaz, her metod bir JS değerini içeri alır, yukarıda dokümante edilen aynı Rust API'sini çağırır ve sonucu geri dönüştürür. Rust API'si gibi registry-tabanlıdır: JS yüzeyi koda gömülü bir Iyzico tipi yerine bir sağlayıcı id string'i alır.

const { ProviderRegistry } = require('extrapaytr');

const registry = new ProviderRegistry();
const gateway = registry.build('iyzico', {
  environment: 'sandbox',
  api_key: process.env.IYZICO_API_KEY,
  secret_key: process.env.IYZICO_SECRET_KEY,
});

const response = await gateway.charge({
  orderId: 'ORDER-123',
  amount: '249.90',   // decimal string, asla JS number değil — aşağıya bakın
  currency: 'TRY',
  customer: { name: 'Jane', surname: 'Doe', email: 'jane@example.com' },
  card: { holderName: 'Jane Doe', pan: '4111111111111111', expireMonth: 12, expireYear: 2030, cvv: '123' },
});

Para birimi, sınırı her zaman bir decimal string artı bir ISO para birimi kodu olarak geçer — asla bir JS number değil — Rust API'sinin hiçbir zaman float kullanmamasıyla aynı sebepten. Gateway, tam işlem setini açığa çıkarır (charge, refund, cancel, query, completeThreeDs, verifyCallback, storeCard, listCards, deleteCard); bir sağlayıcının desteklemediği işlem, Rust tarafındaki gibi throw eder.

Bu pakette sadece "iyzico" SandboxVerified — yukarıdaki sağlayıcı tablosuyla aynı iddia, aynı çekince; bindings yeni bir yetenek, yeni bir doğrulama değil. Desteklenen platformlar: sadece Linux ve Windows — macOS bilinçli olarak kapsam dışı bırakıldı. Bu paket bu geliştirme makinesinde hiç derlenmedi (aktif Windows toolchain'i napi-rs'in yerelde ihtiyaç duyduğu parçalardan yoksun — crate'in kendi notlarına bakın), ama .github/workflows/napi-build.yml üzerinden gerçek CI'da hem windows-latest (MSVC) hem ubuntu-latest'te derlendi ve kendi JS test suite'i geçti — asıl doğrulama bu, yukarıdaki açıklama değil.

Build ve test

cargo fmt --all -- --check

cargo clippy --workspace --all-targets --all-features -- -D warnings

cargo test --workspace --all-features

cargo doc --workspace --all-features --no-deps

cargo build --workspace --no-default-features

.github/workflows/ci.yml, yukarıdakilerin hepsini her push/PR'da çalıştırır, ayrıca EmbarkStudios/cargo-deny-action üzerinden cargo-deny (lisans/advisory/yinelenen-bağımlılık kontrolleri) çalıştırır. deny.toml yapılandırması bu turda yeni ve yerelde hiç çalıştırılmadı — bu geliştirme ortamında cargo-deny kurulu değil — bu yüzden ilk CI çalıştırmasını bir formalite değil, asıl ilk kontrol olarak kabul edin.

Tasarım ilkeleri

  • Para asla float değildir. Money, para birimi başına tam sayı minor-unit sayısı (kuruş/cent) tutar; bkz. extrapaytr-core::money.
  • Secret'lar tiplidir. API anahtarları, secret anahtarlar, PAN ve CVV, redakte edilmiş Debug çıktısına sahip secrecy::SecretString tipindedir.
  • success: true'ya kör güven yok. Bu SDK'nın onaylanmış bir charge/refund/query sonucu olarak kabul ettiği her response, önce sağlayıcının verdiği imza doğrulamasından geçer (istisnalar için her sağlayıcının dökümanına bakın, örn. iyzico'nun /payment/cancel'i — bu bir gözden kaçırma değil, dokümante edilmiş bir eksik).
  • Belirsiz sağlayıcı response'ları hiçbir zaman tahmini bir Succeeded'a değil, PaymentStatus::Unknown'a eşlenir.
  • Uydurulmuş protokol detayı yok. Resmi dokümanların bir alanın formatını/zorunluluğunu belirtmediği yerlerde, kod ya kapalı şekilde başarısız olur (bir hata döner) ya da TODO(PROVIDER-DOC-GAP) ile işaretlenir — bkz. her sağlayıcının dökümanı.

Doğrulama durumu

extrapaytr_core::VerificationStatus'a göre:

MockOnly            sadece repo içi bir sahte (fake) implementasyona karşı kullanılabilir, gerçek protokol yok
Skeleton             tipler/imzalar var, request/response eşlemesi yok
ProtocolImplemented   eşleme + imzalama resmi dokümanları takip eder, doğrulanmadı
FixtureTested         wiremock + golden/pinlenmiş vektörlere karşı çalıştırıldı
SandboxVerified        sağlayıcının gerçek sandbox'ına karşı çalıştırıldı
ProductionVerified      prodüksiyonda çalıştırıldı

iyzico'nun uyguladığı her işlem — Charge (3DS'siz ve tam 3DS'li), CallbackVerification, Query, Refund ve CancelSandboxVerified: 2026-08-12'de iyzico'nun gerçek sandbox'ına karşı uçtan uca çalıştırıldı, buna iyzico'nun sahte 3DS SMS challenge'ını tamamlayan gerçek bir tarayıcı ve yakalanmış, imzası doğrulanmış bir callback de dahil. Bunun ortaya çıkardığı ve düzeltilen gerçek protokol eksiklikleri için — bunlara bir threeDSHtmlContent base64-decode hatası, dokümanların var olduğunu ima etmesine rağmen aslında var olmadığı ortaya çıkan bir CallbackVerification implementasyonu ve iyzico'nun hata-üretici test kartlarından yakalanan tam errorGroup sözlüğü dahil — bkz. docs/providers/iyzico.md. Bu repodaki diğer tüm sağlayıcı/işlem en fazla FixtureTested — gerçek bir sandbox veya prodüksiyon endpoint'ine karşı çalıştırılmadı.

PCI-DSS kapsam uyarısı

Bir RawCard (düz metin PAN/CVV) oluşturan, saklayan veya ileten her kod yolu PCI-DSS kapsamındadır. Bu SDK PCI uyumluluğu sağlamaz — bu, onu kullanan sistemlerin sorumluluğundadır. Bir sağlayıcı destekliyorsa tokenize edilmiş kart saklamayı (TokenizedCard) tercih edin.

Somut olarak, CardStorage ile (bugün iyzico): PAN, store_card'da bir kez tel üzerinden geçer ve asla geri döndürülmez — sonrasında sadece bir token artı görüntüleme parçaları (BIN, son dört hane, marka) elinizde kalır, böylece saklanan bir kartı göstermek kart numarasına ihtiyaç duymaz.

Ama token'ı bir referans değil, bir kimlik bilgisi (credential) olarak muamele edin. iyzico'da saklı-kart ile yapılan bir ödeme ne CVV ne de ikinci faktör ister, bu yüzden (cardUserKey, cardToken) çiftine sahip olan herkes o kartı çekebilir. Bunu dinlenme halindeyken şifreleyin ve erişimini bir API anahtarına yaklaşacağınız şekilde kapsamlandırın — sealing feature'ı tam olarak bunun için AES-256-GCM sağlar:

use extrapaytr::crypto::SealingKey;

let key = SealingKey::from_hex(&std::env::var("TOKEN_SEALING_KEY")?)?;
let sealed = key.seal(stored.card.token().expose_secret().as_bytes())?;
// ...`sealed`'ı kaydedin; geri almak için `key.open(&sealed)?`.

Tam tehdit modeli, cardUserKey/cardToken eşleştirme tehlikesi ve hangi sağlayıcı response'larının imza doğrulaması yapılmadığı için bkz. SECURITY.md.

Bu turdaki bilinen eksikler

  • CI (.github/workflows/ci.yml) ve bir cargo-deny yapılandırması (deny.toml) artık mevcut ama hiç gerçekten çalışmadı (bu turda henüz bir remote'a push yapılmadı, ve cargo-deny yerelde kurulu değil) — ilk gerçek CI çalıştırmasını bir formalite değil, ilk kontrol olarak kabul edin. Henüz coverage tooling'i yok.
  • Card-on-file sadece iyzico için uygulandı (sandbox-verified); başka hiçbir adaptörün kart saklaması araştırılmadı, bu yüzden PaymentMethod::Tokenized geri kalanında hâlâ UnsupportedOperation döndürüyor. iyzico'nun kart-saklama response'ları sağlayıcı tarafından imzalanmamış — dokümante edilmiş bir eksik, gözden kaçırma değil.
  • İki fazlı auth: Authorize/Capture trait'leri core'da var ama hiçbir adaptör bunları uygulamıyor (her biri authorize: false beyan ediyor), bu yüzden pre-auth/settle akışları kullanılamaz.
  • crates.io'ya veya npm'e yayınlanmadı.
  • Node.js bindings'i (crates/extrapaytr-node) bu turda yeni; yazan geliştirme makinesinde hiç derlenemedi (bkz. crate'in kendi notları) ama gerçek CI'da (windows-latest/MSVC ve ubuntu-latest) derlendi ve kendi JS test suite'i geçti. Desteklenen platformlar bilinçli olarak sadece Linux ve Windows — macOS kapsam dışı.
  • Gerçek iş yapılan yedi sağlayıcı (iyzico, PayTR ve Kuveyt Türk tam; Paratika, Moka, Garanti BBVA ve NestPay/EST kısmi — tam olarak neyin eksik olduğu ve nedeni için kendi dökümanlarına bakın); Craftgate, PosNet (Yapı Kredi) ve Akbank bloklandı (sırasıyla giriş-gerektiren dokümanlar / süresi dolmuş TLS sertifikası / herkese açık döküman bulunamadı); bkz. yukarıdaki sağlayıcı tablosu. Bunlardan sadece iyzico gerçek bir sandbox'a karşı çalıştırıldı — bkz. Doğrulama durumu.