TitanNav — Uygulama İçi Onay Akışı (Geliştirici Kılavuzu)
Bu belge runtime'da
company_infotablosundan render edilir.{{company.xxx}}ifadeleri admin panelden doldurulur.
Amaç: Flutter uygulamasında KVKK + 6502 uyumlu onay akışlarının nasıl implemente edileceğini açıklar.
Hedef: Her onay ayrı kayıt, IP+zaman+versiyon bilgisi ile ispat edilebilir.
1. DATA MODEL
-- Kullanıcı rızaları tablosu
CREATE TABLE consents (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id),
consent_type text NOT NULL,
-- 'aydinlatma_okundu'
-- 'kullanim_sartlari_kabul'
-- 'acik_riza_yurtdisi_aktarim'
-- 'acik_riza_pazarlama_email'
-- 'acik_riza_pazarlama_sms'
-- 'acik_riza_pazarlama_push'
-- 'acik_riza_ses_kaydi'
-- 'acik_riza_analitik'
-- 'acik_riza_surucu_veri' (filo sürücüsü için)
-- 'mesafeli_satis_sozlesmesi'
-- 'on_bilgilendirme_formu'
-- 'cerez_analitik'
-- 'cerez_pazarlama'
granted boolean NOT NULL, -- true=kabul, false=red/geri çekildi
granted_at timestamptz NOT NULL DEFAULT NOW(),
revoked_at timestamptz, -- geri çekme olduysa
ip_address inet NOT NULL,
user_agent text NOT NULL,
device_id text,
app_version text NOT NULL,
document_version text NOT NULL, -- örn "aydinlatma_v1.0"
document_hash text NOT NULL, -- onay anındaki belgenin SHA-256 hash'i
context jsonb, -- ek meta
created_at timestamptz DEFAULT NOW()
);
CREATE INDEX idx_consents_user ON consents(user_id);
CREATE INDEX idx_consents_type ON consents(consent_type);
-- Belge versiyon tablosu
CREATE TABLE legal_documents (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
document_code text NOT NULL, -- 'aydinlatma_metni', 'kullanim_sartlari', vs.
version text NOT NULL, -- '1.0'
lang text NOT NULL, -- 'tr', 'en'
content text NOT NULL, -- markdown/html
hash text NOT NULL, -- SHA-256
published_at timestamptz NOT NULL,
active boolean DEFAULT true,
UNIQUE (document_code, version, lang)
);
-- Silme logu (KVKK Madde 7 ispatı)
CREATE TABLE data_deletion_log (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL,
deleted_tables text[] NOT NULL,
deletion_reason text NOT NULL, -- 'trial_expired', 'subscription_ended', 'user_requested', 'admin_action'
triggered_by text, -- 'user_id:xxx' or 'cron' or 'admin_id:xxx'
deleted_at timestamptz DEFAULT NOW(),
ip_address inet,
context jsonb
);
2. KAYIT AKIŞI (ONBOARDING)
┌─────────────────────────────────────────────────────┐
│ EKRAN 1: Splash + Dil seç │
│ [TR] [EN] │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ EKRAN 2: AYDINLATMA METNİ │
│ │
│ [Scrollable metin — tam içerik görünür] │
│ │
│ ☐ Aydınlatma metnini okudum, anladım [ZORUNLU] │
│ │
│ [DEVAM ET] │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ EKRAN 3: KULLANIM ŞARTLARI + ÜYELİK │
│ │
│ [Scrollable] │
│ │
│ ☐ Kullanım şartlarını kabul ediyorum [ZORUNLU] │
│ │
│ [DEVAM ET] │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ EKRAN 4: AÇIK RIZA (her madde ayrı toggle) │
│ │
│ Yurt dışı veri aktarımı (HERE/Google/Apple): │
│ [■●] Kabul ediyorum [TEMEL ÖZELLİKLER ŞART] │
│ │
│ Lokasyon verimin işlenmesi: │
│ [■●] Kabul ediyorum [ZORUNLU] │
│ │
│ Pazarlama e-postası: │
│ [○■] Kabul etmiyorum [opsiyonel] │
│ │
│ Pazarlama SMS: │
│ [○■] Kabul etmiyorum [opsiyonel] │
│ │
│ Anonim analitik: │
│ [■●] Kabul ediyorum [opsiyonel] │
│ │
│ [DEVAM ET] │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ EKRAN 5: Telefon + e-posta + şifre │
│ EKRAN 6: SMS OTP doğrulama │
│ EKRAN 7: Hoş geldin + sistem izinleri │
│ (location, push — OS düzeyinde) │
└─────────────────────────────────────────────────────┘
3. KAYIT TARAFINDA LOG TUTMA
Her onay bağımsız olarak log'a yazılır:
// Flutter (pseudo-code)
Future<void> recordConsent({
required String userId,
required String consentType,
required bool granted,
required String documentVersion,
required String documentHash,
}) async {
await api.post('/consents', {
'user_id': userId,
'consent_type': consentType,
'granted': granted,
'granted_at': DateTime.now().toIso8601String(),
'ip_address': await getIpAddress(),
'user_agent': await getDeviceInfo(),
'device_id': await getDeviceId(),
'app_version': packageInfo.version,
'document_version': documentVersion,
'document_hash': documentHash,
});
}
// Kullanım
await recordConsent(
userId: user.id,
consentType: 'aydinlatma_okundu',
granted: true,
documentVersion: 'aydinlatma_v1.0_tr',
documentHash: await sha256OfDocument(doc),
);
4. RIZA YÖNETİMİ EKRANI
Ayarlar > Gizlilik > Rızalarım
───────────────────────────────────────────
Aydınlatma Metni v1.0 Okundu ✓ (12.04.2026)
Kullanım Şartları v1.0 Kabul ✓ (12.04.2026)
Açık Rıza Maddeleri:
─────────────────────────────────────────
Yurt dışı veri aktarımı [■●] Aktif
↳ 12.04.2026'da verildi
[Geri Çek]
Pazarlama e-posta [○■] Kapalı
[Aktif Et]
Pazarlama SMS [○■] Kapalı
[Aktif Et]
Analitik veri [■●] Aktif
↳ 12.04.2026'da verildi
[Geri Çek]
[Tüm onay geçmişimi göster]
[Yeni KVKK Başvurusu Oluştur]
"Geri Çek" basıldığında:
- Pop-up: "Bu rızayı geri çekmek üzeresiniz. [konsekans açıklaması]. Emin misiniz?"
- Onay →
UPDATE consents SET revoked_at = NOW() WHERE ... - Yeni kayıt açılır:
consent_type=X, granted=false(ispat için) - İlgili özellik anında devre dışı
- Kullanıcıya "Rıza geri çekildi. [bu özellik durdu]" bildirim
5. PREMİUM SATIN ALMA AKIŞI
Plan seç → Ön Bilgilendirme → Sözleşme → Ödeme
// 1. Ön Bilgilendirme Formu göster
showPreInfoForm(plan: selectedPlan);
await waitForConfirm(); // ☐ Okudum, onayladım
await recordConsent('on_bilgilendirme_formu', ...);
// 2. Mesafeli Satış Sözleşmesi
showDistanceSalesContract(plan: selectedPlan);
await waitForConfirm(); // ☐ Kabul ediyorum
await recordConsent('mesafeli_satis_sozlesmesi', ...);
// 3. Ödeme akışı
final result = await paymentProvider.charge(plan: selectedPlan);
// 4. Başarılıysa
if (result.success) {
await emailService.send(
template: 'premium_purchase',
attachments: [
'on_bilgilendirme_formu.pdf',
'mesafeli_satis_sozlesmesi.pdf',
'cayma_formu.pdf',
],
);
showSuccessScreen(withCaymaInfo: true);
}
6. CAYMA AKIŞI
Ayarlar > Abonelik > Cayma Hakkımı Kullan
// 1. Kullanıcıyı uyar
final confirmed = await showDialog(
title: 'Cayma Hakkınızı Kullanıyorsunuz',
content: '14 gün içinde cayma hakkınız vardır. '
'$daysUsed gün kullanım yapılmıştır. '
'İade edilecek tutar: \$X.XX',
actions: ['İPTAL', 'DEVAM ET'],
);
if (!confirmed) return;
// 2. Form doldurulmuş hali göster (gerekiyorsa)
final formData = await showCaymaForm();
// 3. API call
final result = await api.post('/cayma', {
'user_id': user.id,
'plan_id': activePlan.id,
'reason_text': formData.reason, // opsiyonel
'refund_method': formData.refundMethod,
'iban': formData.iban,
});
// 4. Kullanıcıya onay
showDialog(
title: 'Cayma Talebiniz Alındı',
content: 'Talebiniz 14 gün içinde değerlendirilecek, '
'iade 14 iş günü içinde yapılacaktır. '
'E-postanızla takip edebilirsiniz.',
);
// 5. PDF sözleşme + onay ile e-posta gönderilir
7. FİLO SÜRÜCÜSÜ ONBOARDING
Filo yöneticisi "Sürücü Ekle" → SMS/e-posta davet
Sürücü linki açar → TitanNav app'e yönlendirilir
↓
SUBLU EKRAN 1: "Merhaba [Ad], [Firma] sizi TitanNav'a davet etti"
↓
EKRAN 2: SURUCU-IZIN-FORMU (14-SURUCU-IZIN-FORMU.md içeriği)
☐ Çalışma saatleri içinde konum paylaşımını kabul ediyorum
☐ Sefer/olay verilerimin işlenmesini kabul ediyorum
↓
EKRAN 3: Standart Aydınlatma + Açık Rıza (normal akış)
↓
Kayıt + filo bağlantısı otomatik
8. VERİ SİLME TALEBİ
Ayarlar > Hesap > Hesabı Sil
// 1. Ağır uyarı
final confirmed = await showDialog(
title: '⚠️ Hesabınızı Silmek Üzeresiniz',
content: '''
Bu işlem sonrası:
- Tüm rota/yakıt/araç geçmişiniz 7 gün içinde silinecek
- 7 gün içinde geri alınabilir
- Fatura ve ödeme kayıtları VUK gereği 10 yıl saklanacak
- İletişim bilgileriniz (telefon/e-posta) 2 yıl daha saklanacak
(tüketici zamanaşımı)
- Aktif aboneliğiniz varsa ÖNCE aboneliği iptal etmelisiniz
''',
confirmText: 'EVET, SIL',
);
if (!confirmed) return;
// 2. Eğer aktif abonelik varsa önce iptal yönlendirmesi
if (activeSubscription) {
redirectToSubscriptionCancel();
return;
}
// 3. Silme talebini oluştur
await api.post('/account/delete', {
'user_id': user.id,
'reason': optional_reason,
});
// 4. data_deletion_log tablosuna kayıt
// 5. Soft-delete damgalama başlar (backend cron)
// 6. T+7'de hard-delete
// 7. Kullanıcıya
showDialog(
title: 'Hesap Silme Talebiniz Alındı',
content: '''
7 gün içinde işleme alınacak.
Bu süre içinde giriş yaparsanız talep iptal edilir.
''',
);
9. BELGE VERSİYON GÜNCELLEMESİ
Aydınlatma / Açık Rıza güncellenirse:
// Uygulama açılışında kontrol
final latestVersion = await api.get('/legal/latest-version/aydinlatma_metni');
if (latestVersion > user.lastConsented('aydinlatma_metni')) {
// Zorunlu yeniden onay ekranı göster
await showRequiredReConsentFlow(latestVersion);
}
10. TEST CASE'LERİ
| Senaryo | Beklenen |
|---|---|
| Kayıt sırasında zorunlu onay vermezse | "Devam Et" disabled |
| Onay verildikten sonra consents tablosu | 1 satır eklenmiş |
| Rıza geri çekildiğinde | revoked_at dolu + yeni satır (granted=false) |
| Belge versiyonu güncelleştirildiğinde | Açılışta zorunlu yeniden onay |
| Cayma sonrası | Abonelik iptal + iade başlar + data_deletion_log |
| Hesap silme 7 gün sonra | DELETE çalıştı, data_deletion_log satırı |
| Hesap silme T+3 gün giriş | "İptal etmek istiyor musunuz?" pop-up |
11. ADMIN PANELİ BAĞLANTISI
Admin panelinde Legal > Belgeler menüsünde:
- Tüm belgeler CRUD editable (WYSIWYG)
- Yeni versiyon yayınlama
- Versiyon geçmişi
- Hangi kullanıcılar hangi versiyonu onayladı (toplu raporlama)
- Belge hash + SHA-256 otomatik hesaplanır
Son güncelleme: [[TARİH]]