Flutter’da Türk Ödeme Sistemleri Entegrasyonu: tr_payment_hub ile Tek API, Çoklu Sağlayıcı
Bir e-ticaret uygulaması geliştiriyorsunuz. Müşterilerinizin bir kısmı iyzico, bir kısmı PayTR tercih ediyor. Yarın Param veya Sipay de…
Flutter’da Türk Ödeme Sistemleri Entegrasyonu: tr_payment_hub ile Tek API, Çoklu Sağlayıcı
Bir e-ticaret uygulaması geliştiriyorsunuz. Müşterilerinizin bir kısmı iyzico, bir kısmı PayTR tercih ediyor. Yarın Param veya Sipay de eklemek isteyebilirsiniz. Her sağlayıcı için ayrı SDK, ayrı implementasyon, ayrı error handling…
Sonuç? Kod tabanınızda if-else cehennemi, bakımı zor bir yapı ve her yeni sağlayıcı eklediğinizde haftalarca süren entegrasyon çalışması.
tr_payment_hub tam olarak bu sorunu çözmek için geliştirildi. Tek bir arayüz üzerinden tüm Türk ödeme sağlayıcılarına erişim. Sağlayıcı değiştirmek? Tek satır kod değişikliği.
Neden Birleşik Bir Ödeme Kütüphanesine İhtiyaç Var?
Türkiye’de faaliyet gösteren bir mobil uygulama geliştirdiğinizde, ödeme entegrasyonu kaçınılmaz. Ancak her sağlayıcının kendine özgü API yapısı, farklı authentication yöntemleri ve tutarsız response formatları var.
iyzico REST API kullanırken belirli bir hash algoritması bekliyor. PayTR farklı bir token yapısı ve callback mekanizması kullanıyor. Param ve Sipay ise tamamen farklı yaklaşımlar benimsiyor.
Bu durum geliştiriciler için ciddi sorunlar yaratıyor:
Tekrarlayan kod: Her sağlayıcı için benzer iş mantığını tekrar tekrar yazıyorsunuz. Kart bilgisi validasyonu, 3D Secure akışı, hata yönetimi…
Bakım maliyeti: Bir sağlayıcı API’sini güncellediğinde, ilgili tüm kodları güncellemeniz gerekiyor. Test yazmanız, regression kontrolü yapmanız gerekiyor.
Geçiş zorluğu: Bir sağlayıcıdan diğerine geçmek istediğinizde, tüm ödeme akışını yeniden yazmanız gerekiyor.
Tutarsız hata yönetimi: Her sağlayıcı hataları farklı formatla döndürüyor. “Yetersiz bakiye” hatası iyzico’da farklı, PayTR’da farklı kod ve mesajla geliyor.
tr_payment_hub: Tek API, Tüm Sağlayıcılar
tr_payment_hub, bu sorunları çözmek için tasarlanmış açık kaynaklı bir Flutter/Dart kütüphanesi. Temel felsefesi basit: Bir kez öğren, her yerde kullan.
Temel Özellikler
Unified API: Hangi sağlayıcıyı kullanırsanız kullanın, aynı metodları çağırıyorsunuz. createPayment(), init3DSPayment(), refund() - hepsi aynı imzaya sahip.
Type Safe: Dart’ın null safety özelliğini tam destekliyor. Compile-time hata yakalama, IDE otomatik tamamlama desteği.
Güvenli Loglama: Hassas verilerin (kart numarası, CVV) loglara sızmasını önleyen LogSanitizer sistemi. Production ortamında güvenlik açığı oluşturmadan debug yapabilirsiniz.
Test Edilebilir: Built-in MockPaymentProvider ile unit testlerinizi gerçek API’ye bağlanmadan yazabilirsiniz. Başarılı ve başarısız senaryoları simüle edebilirsiniz.
Cross Platform: iOS, Android, Web ve Desktop platformlarında çalışıyor.
Kurulum ve Yapılandırma
1. Paketi Ekleyin
pubspec.yaml dosyanıza ekleyin:
dependencies:
tr_payment_hub: ^1.0.2
Ardından:
dart pub get
2. Sağlayıcı Oluşturun
import 'package:tr_payment_hub/tr_payment_hub.dart';
// iyzico için
final provider = TrPaymentHub.create(ProviderType.iyzico);
// PayTR için
final provider = TrPaymentHub.create(ProviderType.paytr);
// Test için (Mock)
final provider = TrPaymentHub.createMock(shouldSucceed: true);
Bu basit factory pattern, sağlayıcı değişikliğini tek satıra indiriyor. Yarın PayTR’dan iyzico’ya geçmek isterseniz sadece ProviderType.paytr'ı ProviderType.iyzico olarak değiştirmeniz yeterli.
3. Konfigürasyon
Her sağlayıcının kendine özgü ayarları var, ancak konfigürasyon yapısı tutarlı:
iyzico Konfigürasyonu:
final config = IyzicoConfig(
merchantId: 'YOUR_MERCHANT_ID',
apiKey: 'YOUR_API_KEY',
secretKey: 'YOUR_SECRET_KEY',
isSandbox: true, // Production için false
);
await provider.initialize(config);
PayTR Konfigürasyonu:
final config = PayTRConfig(
merchantId: 'YOUR_MERCHANT_ID',
apiKey: 'YOUR_MERCHANT_KEY',
secretKey: 'YOUR_MERCHANT_SALT',
successUrl: 'https://yoursite.com/success',
failUrl: 'https://yoursite.com/fail',
callbackUrl: 'https://yoursite.com/callback',
isSandbox: true,
);
await provider.initialize(config);
Environment Variables ile Güvenli Saklama:
API anahtarlarını kod içinde hardcode etmek güvenlik riski oluşturur. Bunun yerine:
// .env dosyası ya da Dart Define Methodunu kullanabilirsiniz
//Basitlik açısından env üzerinden gösterim yapacağım
IYZICO_API_KEY=your_api_key
IYZICO_SECRET_KEY=your_secret_key
IYZICO_MERCHANT_ID=your_merchant_id
// Dart kodu
import 'package:flutter_dotenv/flutter_dotenv.dart';
final config = IyzicoConfig(
merchantId: dotenv.env['IYZICO_MERCHANT_ID']!,
apiKey: dotenv.env['IYZICO_API_KEY']!,
secretKey: dotenv.env['IYZICO_SECRET_KEY']!,
isSandbox: true,
);
Temel Kullanım Senaryoları
Tek Çekim Ödeme (Non-3DS)
En basit ödeme senaryosu. Kullanıcı kart bilgilerini girer, ödeme anında işlenir:
final request = PaymentRequest(
orderId: 'ORDER_${DateTime.now().millisecondsSinceEpoch}',
amount: 150.0,
currency: Currency.tryLira,
installment: 1, // Tek çekim
card: CardInfo(
cardHolderName: 'Ahmet Yılmaz',
cardNumber: '5528790000000008', // Test kartı
expireMonth: '12',
expireYear: '2030',
cvc: '123',
),
buyer: BuyerInfo(
id: 'BUYER_123',
name: 'Ahmet',
surname: 'Yılmaz',
email: 'ahmet@example.com',
phone: '+905551234567',
ip: '192.168.1.1',
city: 'İstanbul',
country: 'Turkey',
address: 'Kadıköy, İstanbul',
),
basketItems: [
BasketItem(
id: 'ITEM_001',
name: 'Flutter Eğitimi',
category: 'Eğitim',
price: 150.0,
itemType: ItemType.virtual,
),
],
);
try {
final result = await provider.createPayment(request);
if (result.isSuccess) {
print('Ödeme başarılı!');
print('İşlem ID: ${result.transactionId}');
// Kullanıcıyı başarı sayfasına yönlendir
}
} on PaymentException catch (e) {
print('Ödeme hatası: ${e.message}');
// Kullanıcıya hata mesajı göster
}
3D Secure Ödeme
3D Secure, kart sahibinin bankası tarafından doğrulama yapılmasını sağlar. Türkiye’de yasal zorunluluk nedeniyle çoğu işlem 3DS gerektirir:
// Adım 1: 3DS başlat
final threeDSResult = await provider.init3DSPayment(
request.copyWith(
callbackUrl: 'https://yourapi.com/payment/callback',
),
);
if (threeDSResult.needsWebView) {
// Adım 2: WebView'da göster
// iyzico: HTML içeriğini WebView'a yükle
// PayTR: Redirect URL'e yönlendir
if (threeDSResult.htmlContent != null) {
// iyzico için HTML içeriği
_showWebView(htmlContent: threeDSResult.htmlContent!);
} else if (threeDSResult.redirectUrl != null) {
// PayTR için yönlendirme
_showWebView(url: threeDSResult.redirectUrl!);
}
}
// Adım 3: Callback'ten sonra tamamla
// Bu genellikle callback URL'inizdeki backend'den gelir
void onPaymentCallback(Map<String, dynamic> callbackData) async {
final result = await provider.complete3DSPayment(
threeDSResult.transactionId!,
callbackData: callbackData,
);
if (result.isSuccess) {
// Ödeme başarılı
_navigateToSuccessPage();
} else {
// Ödeme başarısız
_showErrorDialog(result.errorMessage);
}
}
Taksit Sorgulama
Kullanıcının kartına göre mevcut taksit seçeneklerini göstermek için:
// Kart numarasının ilk 6 hanesi (BIN)
final binNumber = cardNumber.substring(0, 6);
final installments = await provider.getInstallments(
binNumber: binNumber,
amount: 1000.0,
);
print('Banka: ${installments.bankName}');
print('Kart Ailesi: ${installments.cardFamily}');
// Taksit seçeneklerini listele
for (final option in installments.options) {
print('${option.installmentNumber} Taksit:');
print(' - Taksit Tutarı: ${option.installmentPrice} TL');
print(' - Toplam: ${option.totalPrice} TL');
if (option.installmentNumber > 1) {
final commission = option.totalPrice - 1000.0;
print(' - Komisyon: $commission TL');
}
}
UI’da taksit seçeneklerini göstermek için:
Widget _buildInstallmentOptions(InstallmentInfo installments) {
return ListView.builder(
itemCount: installments.options.length,
itemBuilder: (context, index) {
final option = installments.options[index];
return ListTile(
title: Text('${option.installmentNumber} Taksit'),
subtitle: Text('Aylık ${option.installmentPrice.toStringAsFixed(2)} TL'),
trailing: Text(
'${option.totalPrice.toStringAsFixed(2)} TL',
style: TextStyle(fontWeight: FontWeight.bold),
),
onTap: () => _selectInstallment(option.installmentNumber),
);
},
);
}
İade İşlemi
Tam veya kısmi iade yapabilirsiniz:
// Tam iade
final fullRefund = await provider.refund(RefundRequest(
transactionId: 'ORIGINAL_TRANSACTION_ID',
amount: 150.0, // Orijinal tutar
));
// Kısmi iade
final partialRefund = await provider.refund(RefundRequest(
transactionId: 'ORIGINAL_TRANSACTION_ID',
amount: 50.0, // Kısmi tutar
));
if (fullRefund.isSuccess) {
print('İade başarılı!');
print('İade ID: ${fullRefund.refundId}');
}
Hata Yönetimi
tr_payment_hub, tüm sağlayıcılardan gelen hataları standart bir formata dönüştürür. Bu sayede sağlayıcı fark etmeksizin aynı hata kodlarıyla çalışabilirsiniz:
try {
await provider.createPayment(request);
} on PaymentException catch (e) {
switch (e.code) {
case 'insufficient_funds':
_showError('Yetersiz bakiye. Lütfen farklı bir kart deneyin.');
break;
case 'invalid_card':
_showError('Geçersiz kart bilgileri. Lütfen kontrol edin.');
break;
case 'expired_card':
_showError('Kartınızın süresi dolmuş.');
break;
case 'threeds_failed':
_showError('3D Secure doğrulaması başarısız.');
break;
case 'network_error':
_showError('Bağlantı hatası. Lütfen tekrar deneyin.');
break;
case 'invalid_merchant':
// Bu genellikle konfigürasyon hatası
_logError('Merchant konfigürasyonu hatalı: ${e.message}');
_showError('Ödeme sistemi geçici olarak kullanılamıyor.');
break;
default:
_logError('Beklenmeyen hata: ${e.code} - ${e.message}');
_showError('Bir hata oluştu. Lütfen tekrar deneyin.');
}
}
Önceden Tanımlı Hata Türleri
PaymentException sınıfı, yaygın hata senaryoları için factory metodlar sunar:
// Kütüphane içinde kullanılan factory metodlar
PaymentException.insufficientFunds()
PaymentException.invalidCard()
PaymentException.expiredCard()
PaymentException.threeDSFailed()
PaymentException.networkError()
PaymentException.invalidMerchant()
Güvenlik Best Practice’leri
Ödeme entegrasyonunda güvenlik kritik önem taşır. tr_payment_hub bazı güvenlik önlemlerini otomatik alır, ancak geliştiricinin de dikkat etmesi gereken noktalar var:
1. Hassas Verilerin Loglanması
Kart numaraları, CVV gibi bilgiler asla loglara yazılmamalı. tr_payment_hub’ın LogSanitizer’ı bunu otomatik yapar:
// Güvenli loglama
final requestData = {
'cardNumber': '5528790000000008',
'cvv': '123',
'amount': 100.0,
};
// Bu kart numarasını maskeler
final safeData = LogSanitizer.sanitizeMap(requestData);
print(safeData);
// Output: {cardNumber: ****0008, cvv: ***, amount: 100.0}
2. API Anahtarlarının Saklanması
API anahtarlarını asla:
- Git repository’sine commit etmeyin
- Client-side koda hardcode etmeyin
- Loglamalara yazdırmayın
Bunun yerine:
- Dart define methodu kullanın
- Güvenli storage (Keychain/Keystore) tercih edin
- Backend üzerinden proxy yapın (önerilir)
// Yanlış
final config = IyzicoConfig(
apiKey: 'sk_live_abc123', // Asla bunu yapmayın!
);
// Doğru
final config = IyzicoConfig(
apiKey: await SecureStorage.read('iyzico_api_key'),
);
3. Callback URL’leri
3DS callback URL’leri her zaman HTTPS olmalı:
// Yanlış
callbackUrl: 'http://yoursite.com/callback'
// Doğru
callbackUrl: 'https://yoursite.com/callback'
4. Server-Side Doğrulama
Client-side ödeme sonuçlarına asla güvenmeyin. Her ödemeyi backend’de doğrulayın:
// Client'tan gelen ödeme sonucu
if (result.isSuccess) {
// Backend'e doğrulama isteği at
final verified = await api.verifyPayment(result.transactionId);
if (verified) {
// Gerçekten başarılı, işleme devam et
} else {
// Muhtemel sahtecilik girişimi
_logSecurityAlert(result);
}
}
Test Yazımı
tr_payment_hub, MockPaymentProvider ile unit testleri kolaylaştırır:
import 'package:flutter_test/flutter_test.dart';
import 'package:tr_payment_hub/tr_payment_hub.dart';
void main() {
group('Payment Tests', () {
late PaymentProvider provider;
setUp(() {
// Başarılı senaryolar için
provider = TrPaymentHub.createMock(
shouldSucceed: true,
delay: Duration(milliseconds: 100),
);
});
test('Successful payment returns transaction ID', () async {
final request = _createTestRequest();
final result = await provider.createPayment(request);
expect(result.isSuccess, isTrue);
expect(result.transactionId, isNotNull);
});
test('Failed payment throws PaymentException', () async {
// Başarısız senaryo için yeni mock
final failingProvider = TrPaymentHub.createMock(
shouldSucceed: false,
customError: PaymentException.insufficientFunds(),
);
final request = _createTestRequest();
expect(
() => failingProvider.createPayment(request),
throwsA(isA<PaymentException>()),
);
});
test('3DS flow initializes correctly', () async {
final request = _createTestRequest();
final result = await provider.init3DSPayment(request);
expect(result.needsWebView, isTrue);
expect(
result.htmlContent != null || result.redirectUrl != null,
isTrue,
);
});
});
}
PaymentRequest _createTestRequest() {
return PaymentRequest(
orderId: 'TEST_ORDER',
amount: 100.0,
currency: Currency.tryLira,
installment: 1,
card: CardInfo(
cardHolderName: 'Test User',
cardNumber: '5528790000000008',
expireMonth: '12',
expireYear: '2030',
cvc: '123',
),
buyer: BuyerInfo(
id: 'TEST_BUYER',
name: 'Test',
surname: 'User',
email: 'test@example.com',
phone: '+905551234567',
ip: '127.0.0.1',
city: 'Istanbul',
country: 'Turkey',
address: 'Test Address',
),
basketItems: [
BasketItem(
id: 'TEST_ITEM',
name: 'Test Product',
category: 'Test',
price: 100.0,
itemType: ItemType.physical,
),
],
);
}
Flutter Web Notu
Türk ödeme API’lerinin çoğu CORS kısıtlamalarına sahip. Flutter Web’de doğrudan çalışmayabilir. Çözümler:
- Backend Proxy (Önerilen): Ödeme isteklerini kendi backend’iniz üzerinden yönlendirin
- Cloud Functions: Firebase Functions veya AWS Lambda kullanın
- Sadece Mobil: Web desteği kritik değilse, sadece iOS/Android hedefleyin
// Backend proxy örneği
class PaymentService {
Future<PaymentResult> processPayment(PaymentRequest request) async {
if (kIsWeb) {
// Web'de backend proxy kullan
return _processViaBackend(request);
} else {
// Mobil'de direkt SDK kullan
return provider.createPayment(request);
}
}
}
Sık Sorulan Sorular
S: Sandbox’tan production’a geçerken ne değişmeli?
Sadece konfigürasyondaki isSandbox: false yapın ve gerçek API anahtarlarınızı kullanın. Kod değişikliği gerekmez.
S: Yeni bir sağlayıcı ne zaman eklenecek?
Param ve Sipay roadmap’te. GitHub Issues üzerinden talep açabilir veya pull request gönderebilirsiniz.
S: Stored card (kayıtlı kart) desteği var mı?
Şu anki versiyonda yok, ancak gelecek sürümlerde planlanıyor.
S: Abonelik/tekrarlayan ödeme desteği var mı?
Henüz yok. Sağlayıcıların subscription API’leri oldukça farklı olduğu için dikkatli bir tasarım gerektiriyor.
tr_payment_hub, Flutter geliştiricileri için Türk ödeme sistemleri entegrasyonunu önemli ölçüde basitleştiriyor. Tek bir API öğrenerek birden fazla sağlayıcıyı destekleyebilir, sağlayıcı değişikliklerini minimal kod değişikliğiyle yapabilir ve test edilebilir bir kod tabanı oluşturabilirsiniz.
Kütüphane aktif geliştirme altında. Param ve Sipay desteği yakında eklenecek. Katkıda bulunmak, bug raporlamak veya özellik talep etmek için GitHub repository’sini ziyaret edebilirsiniz.
Kaynaklar:
- pub.dev/packages/tr_payment_hub
- GitHub Repository
- iyzico Developer Portal
- PayTR Entegrasyon Dökümanı
Not: Bu paket, iyzico veya PayTR’ın resmi ürünü değildir. Topluluk tarafından geliştirilen açık kaynak bir projedir.
메타데이터
- post_id
- d418fbfbcb49
- slug
- flutterda-türk-ödeme-sistemleri-entegrasyonu-tr-payment-hub-ile-tek-api-çoklu-sağlayıcı-d418fbfbcb49
- url
- https://medium.com/@abdullahtas/flutterda-t%C3%BCrk-%C3%B6deme-sistemleri-entegrasyonu-tr-payment-hub-ile-tek-api-%C3%A7oklu-sa%C4%9Flay%C4%B1c%C4%B1-d418fbfbcb49
- canonical_url
- https://medium.com/@abdullahtas/flutterda-t%C3%BCrk-%C3%B6deme-sistemleri-entegrasyonu-tr-payment-hub-ile-tek-api-%C3%A7oklu-sa%C4%9Flay%C4%B1c%C4%B1-d418fbfbcb49
- author_url
- https://medium.com/@abdullahtas
- status
- ok
- fetched_at
- 2026-07-13 20:57:02