← Back to list

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…

AbdullahTaş · 2025-12-29 07:51 · 6 claps · 6.3 min read
#flutter #payments #pubdev #flutter-app-development #dart
Open on Medium ↗
Wiki topics: FIN · Fintech & Banking 📱 · Mobile Development 🛠️ · Crafts & DIY

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.paytrProviderType.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:

  1. Backend Proxy (Önerilen): Ödeme isteklerini kendi backend’iniz üzerinden yönlendirin
  2. Cloud Functions: Firebase Functions veya AWS Lambda kullanın
  3. 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:

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