# 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](#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
```text
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](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](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](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](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](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](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](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ıç
```rust
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`):
```bash
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:
```rust
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](docs/registry.md).
## JavaScript / Node.js
[`crates/extrapaytr-node`](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.
```js
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`](.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
```bash
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`](.github/workflows/ci.yml), yukarıdakilerin
hepsini her push/PR'da çalıştırır, ayrıca `EmbarkStudios/cargo-deny-action`
üzerinden [`cargo-deny`](deny.toml) (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:
```text
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 `Cancel` — **SandboxVerified**:
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](docs/providers/iyzico.md#live-sandbox-findings-2026-08-12).
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:
```rust
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](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](#doğrulama-durumu).