KYC Onboarding Sistemi
Eğitmen kimlik doğrulama (KYC) akışı, Stripe Connect, Payoneer ve Cenoa entegrasyonu, admin onay paneli ve veritabanı yapısı.
Achidemy, eğitmenler için üç farklı ödeme yöntemi doğrulama sistemi sunar:
- Stripe Connect — Desteklenen ülkelerde otomatik ödeme
- Payoneer — Stripe Connect’in desteklenmediği ülkelerde (örn. Türkiye) veya Payoneer tercih eden eğitmenler için manuel doğrulama
- Cenoa — Mobil ödeme platformu tercih eden eğitmenler için manuel doğrulama
Bu sayfa, her üç doğrulama yönteminin nasıl çalıştığını, ödeme talebi oluşturma akışını ve admin panelini açıklar.
Genel Bakış
Section titled “Genel Bakış”┌─────────────────────────────────────────────────────────────────────────────────────────┐│ ÖDEME YÖNTEMİ DOĞRULAMA AKIŞI │├─────────────────────────────────────────────────────────────────────────────────────────┤│ ││ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ ││ │ STRIPE CONNECT │ │ PAYONEER │ │ CENOA │ ││ │ (Desteklenen Ülkeler)│ │ (TR ve Diğerleri) │ │ (Mobil Ödeme) │ ││ ├─────────────────────┤ ├─────────────────────┤ ├─────────────────────┤ ││ │ │ │ │ │ │ ││ │ 1. "Stripe" seçer │ │ 1. "Payoneer" seçer │ │ 1. "Cenoa" seçer │ ││ │ ↓ │ │ ↓ │ │ ↓ │ ││ │ 2. Stripe Onboarding│ │ 2. KYC Formu: │ │ 2. KYC Formu: │ ││ │ sayfasına gider │ │ - legalName │ │ - legalName │ ││ │ ↓ │ │ - taxId │ │ - taxId │ ││ │ 3. Stripe'da hesap │ │ - payoneerEmail │ │ - cenoaPhone │ ││ │ oluşturur │ │ - idDocument │ │ - idDocument │ ││ │ ↓ │ │ ↓ │ │ ↓ │ ││ │ 4. isConnectOnboard │ │ 3. Bunny Storage'a │ │ 3. Bunny Storage'a │ ││ │ Completed = true │ │ yüklenir │ │ yüklenir │ ││ │ ↓ │ │ ↓ │ │ ↓ │ ││ │ 5. Ödeme talebi │ │ 4. kycStatus = │ │ 4. kycStatus = │ ││ │ oluşturabilir │ │ "pending" │ │ "pending" │ ││ │ ↓ │ │ ↓ │ │ ↓ │ ││ │ 6. Admin onaylar → │ │ 5. Admin onaylar │ │ 5. Admin onaylar │ ││ │ Stripe Transfer │ │ → approved │ │ → approved │ ││ │ otomatik │ │ ↓ │ │ ↓ │ ││ │ │ │ 6. Ödeme talebi │ │ 6. Ödeme talebi │ ││ │ │ │ oluşturabilir │ │ oluşturabilir │ ││ │ │ │ ↓ │ │ ↓ │ ││ │ │ │ 7. Admin manuel │ │ 7. Admin manuel │ ││ │ │ │ ödeme yapar │ │ ödeme yapar │ ││ └─────────────────────┘ └─────────────────────┘ └─────────────────────┘ ││ │└─────────────────────────────────────────────────────────────────────────────────────────┘Doğrulama Yöntemleri
Section titled “Doğrulama Yöntemleri”1. Stripe Connect Doğrulaması
Section titled “1. Stripe Connect Doğrulaması”Stripe Connect’in desteklendiği ülkelerde eğitmenler bu yöntemi kullanabilir.
Akış:
- Eğitmen
/instructor/payouts?tab=paymentsayfasında “Stripe” seçer - “Stripe ile Banka Bağla” butonuna tıklar
- Stripe Onboarding sayfasına yönlendirilir
- Stripe’da hesap bilgilerini tamamlar
- Başarılı dönüşte
isConnectOnboardingCompleted = trueolarak işaretlenir - Eğitmen artık ödeme talebi oluşturabilir
Doğrulama Kontrolü:
const isStripeVerified = user?.isConnectOnboardingCompleted ?? false;2. Payoneer Doğrulaması
Section titled “2. Payoneer Doğrulaması”Stripe Connect’in desteklenmediği ülkelerde veya Payoneer tercih eden eğitmenler için.
Akış:
- Eğitmen “Payoneer” seçer
- KYC formu doldurur (yasal ad, vergi no, Payoneer e-posta, kimlik belgeleri)
- Kimlik belgeleri Bunny Storage’a yüklenir
kycStatus = "pending"olarak kaydedilir- Admin
/admin/kyc-requestspanelinde başvuruyu inceler - Admin onaylar →
kycStatus = "approved" - Eğitmen artık ödeme talebi oluşturabilir
Doğrulama Kontrolü:
const isPayoneerVerified = user?.kycStatus === "approved" && !isStripeVerified && user?.payoutMethod === "payoneer";3. Cenoa Doğrulaması
Section titled “3. Cenoa Doğrulaması”Mobil ödeme platformu Cenoa’yı tercih eden eğitmenler için.
Akış:
- Eğitmen “Cenoa” seçer
- KYC formu doldurur (yasal ad, vergi no, Cenoa telefon numarası, kimlik belgeleri)
- Kimlik belgeleri Bunny Storage’a yüklenir
kycStatus = "pending",payoutMethod = "cenoa"olarak kaydedilir- Admin
/admin/kyc-requestspanelinde başvuruyu inceler - Admin onaylar →
kycStatus = "approved" - Eğitmen artık ödeme talebi oluşturabilir
Doğrulama Kontrolü:
const isCenoaVerified = user?.kycStatus === "approved" && !isStripeVerified && user?.payoutMethod === "cenoa";Veritabanı Şeması
Section titled “Veritabanı Şeması”KYC Status Enum
Section titled “KYC Status Enum”-- app/db/schema.tsexport const kycStatusEnum = pgEnum("kyc_status", ["none", "pending", "approved", "rejected"]);| Durum | Açıklama |
|---|---|
none | Henüz KYC başvurusu yapılmamış (varsayılan). |
pending | Başvuru yapıldı, admin onayı bekleniyor. |
approved | Admin onayladı, eğitmen ödeme talep edebilir. |
rejected | Admin reddetti, eğitmen tekrar başvurabilir. |
Users Tablosundaki İlgili Alanlar
Section titled “Users Tablosundaki İlgili Alanlar”// app/db/schema.ts - users tablosuexport const users = pgTable("user", { // ... diğer alanlar
// KYC Alanları kycStatus: kycStatusEnum("kyc_status").default("none").notNull(), legalName: text("legal_name"), // Yasal ad/soyad (kimlikteki) taxId: text("tax_id"), // TCKN, SSN, PAN vb. idDocumentUrl: text("id_document_url"), // Kimlik ön yüz URL'i (Bunny Storage) idDocumentBackUrl: text("id_document_back_url"), // Kimlik arka yüz URL'i (Bunny Storage) manualPayoutDetails: text("manual_payout_details"), // Payoneer e-posta veya Cenoa telefon kycRejectionReason: text("kyc_rejection_reason"), // Red sebebi (admin notu)
// Stripe Connect Alanları stripeConnectId: text("stripe_connect_id"), // Stripe Connect hesap ID'si isConnectOnboardingCompleted: boolean("is_connect_onboarding_completed").default(false),
// Ödeme Yöntemi payoutMethod: text("payout_method"), // 'stripe_connect' | 'payoneer' | 'cenoa' | 'bank_transfer'});| Alan | Tip | Açıklama |
|---|---|---|
kycStatus | enum | KYC durumu. |
legalName | text | Kimlik belgesindeki tam ad. |
taxId | text | Ulusal kimlik/vergi numarası. |
idDocumentUrl | text | Kimlik ön yüz fotoğrafı URL’i. |
idDocumentBackUrl | text | Kimlik arka yüz fotoğrafı URL’i. |
manualPayoutDetails | text | Payoneer e-posta veya Cenoa telefon numarası. |
kycRejectionReason | text | Admin tarafından girilen red sebebi. |
stripeConnectId | text | Stripe Connect hesap ID’si. |
isConnectOnboardingCompleted | boolean | Stripe Connect onboarding tamamlandı mı? |
payoutMethod | text | Seçilen ödeme yöntemi (stripe_connect, payoneer, cenoa). |
Eğitmen Tarafı: Ödeme Yöntemi Seçimi
Section titled “Eğitmen Tarafı: Ödeme Yöntemi Seçimi”UI Konumu
Section titled “UI Konumu”Eğitmen ödemeler sayfasında (/instructor/payouts?tab=payment) ödeme yöntemi seçim alanı gösterilir.
Yöntem Seçimi
Section titled “Yöntem Seçimi”// Kullanıcının ülkesine göre varsayılan yöntemconst [selectedMethod, setSelectedMethod] = useState<"stripe" | "payoneer" | "cenoa">( useStripe ? "stripe" : "payoneer");- Stripe desteklenen ülkeler: Üç seçenek sunulur (Stripe, Payoneer, Cenoa)
- Stripe desteklenmeyen ülkeler (örn. TR): Sadece Payoneer ve Cenoa seçenekleri
Ödeme Yöntemi Seçim UI’ı
Section titled “Ödeme Yöntemi Seçim UI’ı”Her ödeme yöntemi için gerçek marka logoları kullanılır:
{/* Stripe Seçeneği */}<div className={`cursor-pointer rounded-2xl border-2 p-6 transition-all ${ selectedMethod === "stripe" ? "border-indigo-500 bg-indigo-50/50" : "border-slate-200 hover:border-slate-300"}`}> <img src="/images/stripe.webp" alt="Stripe" className="h-10 w-auto object-contain" /></div>
{/* Payoneer Seçeneği */}<div className={`cursor-pointer rounded-2xl border-2 p-6 transition-all ${ selectedMethod === "payoneer" ? "border-orange-500 bg-orange-50/50" : "border-slate-200 hover:border-slate-300"}`}> <img src="/images/payoneer.png" alt="Payoneer" className="h-8 w-auto object-contain" /></div>
{/* Cenoa Seçeneği */}<div className={`cursor-pointer rounded-2xl border-2 p-6 transition-all ${ selectedMethod === "cenoa" ? "border-[#0052FF] bg-blue-50/50" : "border-slate-200 hover:border-slate-300"}`}> <img src="/images/cenoa.png" alt="Cenoa" className="h-8 w-auto object-contain" /></div>KYC Form Alanları
Section titled “KYC Form Alanları”Payoneer Formu
Section titled “Payoneer Formu”- Yasal Ad ve Soyad (
legalName) - Kimlik belgesindeki tam isim - Ulusal Kimlik / Vergi No (
taxId) - TCKN, SSN, PAN vb. - Payoneer E-posta (
payoneerEmail) - Payoneer hesabına kayıtlı e-posta - Kimlik Belgesi Ön Yüz (
idDocumentFront) - JPG, PNG veya PDF (max 5MB) - Kimlik Belgesi Arka Yüz (
idDocumentBack) - JPG, PNG veya PDF (max 5MB)
Cenoa Formu
Section titled “Cenoa Formu”- Yasal Ad ve Soyad (
legalName) - Kimlik belgesindeki tam isim - Ulusal Kimlik / Vergi No (
taxId) - TCKN, SSN, PAN vb. - Cenoa Telefon Numarası (
cenoaPhone) - Cenoa hesabına kayıtlı telefon (+90 5XX XXX XX XX) - Kimlik Belgesi Ön Yüz (
idDocumentFront) - JPG, PNG veya PDF (max 5MB) - Kimlik Belgesi Arka Yüz (
idDocumentBack) - JPG, PNG veya PDF (max 5MB)
Not: Kullanıcı her iki yüzü de yüklemeden form gönderilemez. Yüklenen görseller anında önizlenebilir.
KYC API Endpoint
Section titled “KYC API Endpoint”Dosya: app/routes/api.instructor.submit-kyc.ts
Method: POST
Content-Type: multipart/form-data
// Request FormData{ legalName: string; // "Ahmet Yılmaz" taxId: string; // "12345678901" payoneerEmail?: string; // "ahmet@email.com" (Payoneer için) cenoaPhone?: string; // "+905551234567" (Cenoa için) paymentMethod: string; // "payoneer" | "cenoa" idDocumentFront: File; // Kimlik ön yüz dosyası idDocumentBack: File; // Kimlik arka yüz dosyası}
// Response (Success){ success: true }
// Response (Error){ error: "Hata mesajı" }Dosya Yükleme Yapısı
Section titled “Dosya Yükleme Yapısı”Kimlik belgeleri Bunny Storage’a yüklenir:
id-cards/{COUNTRY_CODE}/{USER_ID}/{TIMESTAMP}-front-{FILENAME}id-cards/{COUNTRY_CODE}/{USER_ID}/{TIMESTAMP}-back-{FILENAME}Örnek:
id-cards/TR/abc123/1709123456789-front-kimlik.jpgid-cards/TR/abc123/1709123456789-back-kimlik.jpg
Ödeme Talebi Oluşturma
Section titled “Ödeme Talebi Oluşturma”Doğrulama Sonrası UI
Section titled “Doğrulama Sonrası UI”Eğitmen doğrulandıktan sonra ödeme talebi formu gösterilir. Her doğrulama yöntemi için logo + “ile doğrulandı” formatında gösterim:
{/* Payoneer veya Cenoa ile doğrulandıysa */}{(isPayoneerVerified || isCenoaVerified) && ( <div className="bg-white border-2 border-slate-100 rounded-3xl p-8 space-y-6"> <div className="flex items-center gap-3 mb-2"> <div className={`p-2 rounded-lg ${isCenoaVerified ? "bg-blue-50" : "bg-orange-50"}`}> <CheckCircle2 className={isCenoaVerified ? "text-blue-600" : "text-orange-600"} /> </div> <div> <h4>Ödeme Talebi Oluştur</h4> <div className="flex items-center gap-2 mt-1"> <img src={isCenoaVerified ? "/images/cenoa.png" : "/images/payoneer.png"} alt={isCenoaVerified ? "Cenoa" : "Payoneer"} className="h-4 w-auto object-contain" /> <span className={`text-xs font-bold ${isCenoaVerified ? "text-blue-600" : "text-orange-600"}`}> ile doğrulandı </span> </div> </div> </div> {/* Tutar girişi ve gönder butonu */} </div>)}
{/* Stripe Connect ile doğrulandıysa */}{isStripeVerified && ( <div className="bg-white border-2 border-slate-100 rounded-3xl p-8 space-y-6"> <div className="flex items-center gap-3 mb-2"> <div className="p-2 bg-indigo-50 rounded-lg"> <CheckCircle2 className="text-indigo-600" /> </div> <div> <h4>Ödeme Talebi Oluştur</h4> <div className="flex items-center gap-2 mt-1"> <img src="/images/stripe.webp" alt="Stripe" className="h-4 w-auto object-contain" /> <span className="text-xs font-bold text-indigo-600">ile doğrulandı</span> </div> </div> </div> {/* Tutar girişi ve gönder butonu */} </div>)}Ödeme Talebi Validasyonu
Section titled “Ödeme Talebi Validasyonu”const handleRequestPayout = async () => { // Tutar kontrolü if (!amount || amountNum <= 0 || amountUsd < minPayoutAmount) { toast.error("Minimum tutar hatası"); return; }
// Aylık limit kontrolü (max 2 talep/ay) if (limitReached) { toast.error("Aylık limit aşıldı"); return; }
// Ödeme yöntemi doğrulaması if (isPayoneerVerified || isCenoaVerified) { // Payoneer veya Cenoa ile doğrulandıysa, manualPayoutDetails kontrolü if (!manualPayoutDetails) { const methodName = isCenoaVerified ? "Cenoa telefon numaranızı" : "Payoneer bilgilerinizi"; toast.error(`Lütfen önce ${methodName} kaydedin.`); return; } } else if (isStripeVerified) { // Stripe Connect ile doğrulandıysa, ek kontrol gerekmez }
// Talep oluştur...};GraphQL requestPayout Mutation
Section titled “GraphQL requestPayout Mutation”// app/graphql/schema.ts - requestPayout mutationrequestPayout: async (_: any, { amount }: any, context: any) => { const { db, user } = context; // ... validasyonlar ...
let method: string; let destination: string;
// Manuel ödeme yöntemi kontrolü (Payoneer veya Cenoa) const isManualPayoutVerified = dbUser.kycStatus === "approved" && (dbUser.payoutMethod === "payoneer" || dbUser.payoutMethod === "cenoa") && dbUser.manualPayoutDetails;
if (isTurkey || isManualPayoutVerified) { method = "bank_transfer"; destination = dbUser.manualPayoutDetails || ""; if (!destination.trim()) { const methodName = dbUser.payoutMethod === "cenoa" ? "Cenoa telefon numaranızı" : "Payoneer veya IBAN bilgilerinizi"; throw new Error(`Ödeme talebi için lütfen ${methodName} kaydedin.`); } } else { method = "stripe_connect"; destination = dbUser.stripeConnectId || dbUser.payoutDetails || ""; if (!destination.trim()) { throw new Error("Ödeme talebi oluşturabilmek için önce Stripe Connect hesabınızı bağlamalısınız."); } }
// Talep oluştur...};Admin Tarafı
Section titled “Admin Tarafı”KYC Onay Paneli
Section titled “KYC Onay Paneli”URL: /admin/kyc-requests
Dosya: app/routes/admin.kyc-requests.tsx
Liste Görünümü
Section titled “Liste Görünümü”- Bekleyen, onaylanan ve reddedilen başvurular filtrelenebilir
- Her başvuru için: yasal ad, ülke, e-posta, vergi no, ödeme yöntemi bilgisi
- Ödeme yöntemi logo ile gösterilir
- “İncele” butonu detay sayfasına yönlendirir
Ödeme Yöntemi Gösterimi (Liste)
Section titled “Ödeme Yöntemi Gösterimi (Liste)”<td className="p-4"> <div className="flex flex-col gap-1"> <div className="flex items-center gap-2"> {req.payoutMethod === "payoneer" ? ( <img src="/images/payoneer.png" alt="Payoneer" className="h-4 w-auto object-contain" /> ) : req.payoutMethod === "cenoa" ? ( <img src="/images/cenoa.png" alt="Cenoa" className="h-4 w-auto object-contain" /> ) : req.payoutMethod === "stripe_connect" ? ( <img src="/images/stripe.webp" alt="Stripe" className="h-4 w-auto object-contain" /> ) : ( <span className="text-xs font-bold text-slate-600 uppercase">{req.payoutMethod || "-"}</span> )} </div> <span className={`text-xs font-medium truncate max-w-[180px] ${ req.payoutMethod === "cenoa" ? "text-blue-600" : "text-orange-600" }`}> {req.manualPayoutDetails || "-"} </span> </div></td>Detay Sayfası
Section titled “Detay Sayfası”URL: /admin/kyc/:userId/preview
Dosya: app/routes/admin.kyc.$userId.preview.tsx
- Eğitmen bilgileri (ad, e-posta, ülke)
- Kişisel bilgiler (yasal ad, vergi no)
- Ödeme bilgileri (Payoneer e-posta veya Cenoa telefon)
- Kimlik belgeleri (ön ve arka yüz görüntüleme)
- Onay/Red butonları
Ödeme Talepleri Paneli
Section titled “Ödeme Talepleri Paneli”URL: /admin/payouts
Dosya: app/routes/admin.payouts.tsx
Görüntülenen Bilgiler
Section titled “Görüntülenen Bilgiler”Her ödeme talebi için:
- Tutar (USD ve yerel para birimi)
- Eğitmen bilgileri
- Doğrulama yöntemi (logo ile gösterilir)
- Ödeme bilgileri:
- Payoneer: E-posta (turuncu/yeşil kutuda)
- Cenoa: Telefon numarası (mavi kutuda)
- Stripe: Connect ID (indigo kutuda)
Doğrulama Yöntemine Göre UI
Section titled “Doğrulama Yöntemine Göre UI”{/* Ödeme yöntemi gösterimi - Logo ile */}<div className="flex items-center gap-2"> {payout.isPayoneerVerified ? ( <> <img src="/images/payoneer.png" alt="Payoneer" className="h-5 w-auto object-contain" /> <span className="bg-emerald-100 text-emerald-700 border-emerald-200"> <CheckCircle2 size={10} className="mr-1" /> Doğrulandı </span> </> ) : payout.isCenoaVerified ? ( <> <img src="/images/cenoa.png" alt="Cenoa" className="h-5 w-auto object-contain" /> <span className="bg-blue-100 text-blue-700 border-blue-200"> <CheckCircle2 size={10} className="mr-1" /> Doğrulandı </span> </> ) : payout.isStripeVerified ? ( <> <img src="/images/stripe.webp" alt="Stripe" className="h-5 w-auto object-contain" /> <span className="bg-indigo-100 text-indigo-700 border-indigo-200"> <CheckCircle2 size={10} className="mr-1" /> Doğrulandı </span> </> ) : null}</div>
{/* Payoneer bilgileri kutusu */}{payout.isPayoneerVerified && payout.instructor?.manualPayoutDetails && ( <div className="mt-2 p-3 bg-emerald-50 border border-emerald-200 rounded-lg"> <p className="text-[10px] font-bold text-emerald-700 uppercase mb-1"> Payoneer / IBAN Bilgileri </p> <p className="text-xs text-emerald-800 font-mono"> {payout.instructor.manualPayoutDetails} </p> </div>)}
{/* Cenoa bilgileri kutusu */}{payout.isCenoaVerified && payout.instructor?.manualPayoutDetails && ( <div className="mt-2 p-3 bg-blue-50 border border-blue-200 rounded-lg"> <p className="text-[10px] font-bold text-blue-700 uppercase mb-1"> Cenoa Telefon Numarası </p> <p className="text-xs text-blue-800 font-mono"> {payout.instructor.manualPayoutDetails} </p> </div>)}
{/* Stripe Connect bilgileri kutusu */}{payout.isStripeVerified && payout.instructor?.stripeConnectId && ( <div className="mt-2 p-3 bg-indigo-50 border border-indigo-200 rounded-lg"> <p className="text-[10px] font-bold text-indigo-700 uppercase mb-1"> Stripe Connect ID </p> <p className="text-xs text-indigo-800 font-mono"> {payout.instructor.stripeConnectId} </p> </div>)}Uyarı Mesajları
Section titled “Uyarı Mesajları”| Durum | Uyarı |
|---|---|
| Stripe Connect + Bekleyen | ”Otomatik İşlenebilir: Bu ödeme Stripe Connect ile otomatik olarak işlenecektir.” |
| Payoneer + Bekleyen | ”Manuel Ödeme Gerekli: Bu eğitmen Payoneer ile doğrulandı. Yukarıdaki Payoneer/IBAN bilgilerine manuel olarak ödeme yapmanız gerekmektedir.” |
| Cenoa + Bekleyen | ”Manuel Ödeme Gerekli: Bu eğitmen Cenoa ile doğrulandı. Yukarıdaki telefon numarasına Cenoa üzerinden manuel olarak ödeme yapmanız gerekmektedir.” |
Admin Onay/Red Akışı
Section titled “Admin Onay/Red Akışı”Admin /admin/payouts panelinde ödeme talebini onaylarken, sistem eğitmenin doğrulama yöntemini kontrol eder:
// Doğrulama yöntemi belirlemeconst isStripeVerified = instructor?.isConnectOnboardingCompleted ?? false;const isPayoneerVerified = instructor?.kycStatus === "approved" && !isStripeVerified && instructor?.payoutMethod === "payoneer";const isCenoaVerified = instructor?.kycStatus === "approved" && !isStripeVerified && instructor?.payoutMethod === "cenoa";Stripe Connect Talebi Onaylama
Section titled “Stripe Connect Talebi Onaylama”if (isStripeVerified && instructor?.stripeConnectId) { // Otomatik Stripe Transfer const stripe = new Stripe(stripeSecretKey); const transfer = await stripe.transfers.create({ amount: Math.round(Number(payoutRow.amount) * 100), currency: "usd", destination: instructor.stripeConnectId, description: `Achidemy Ödemesi - Talep ID: ${payoutRow.id}`, }); stripeTransferId = transfer.id;}Payoneer veya Cenoa Talebi Onaylama
Section titled “Payoneer veya Cenoa Talebi Onaylama”else if (isPayoneerVerified) { // Manuel ödeme - Admin Payoneer/IBAN'a gönderir // Stripe Transfer YAPILMAZ, sadece durum güncellenir console.log("Payoneer doğrulamalı eğitmen için manuel ödeme onaylandı");} else if (isCenoaVerified) { // Manuel ödeme - Admin Cenoa telefon numarasına gönderir // Stripe Transfer YAPILMAZ, sadece durum güncellenir console.log("Cenoa doğrulamalı eğitmen için manuel ödeme onaylandı");}
// Her durumda durum güncellenirawait db.update(payoutRequests).set({ status: "completed", processedAt: now, ...(stripeTransferId ? { stripeTransferId } : {}),}).where(eq(payoutRequests.id, payoutId));Önemli: Payoneer veya Cenoa doğrulamalı eğitmenler için “Onayla” butonuna basıldığında Stripe API çağrısı yapılmaz. Admin önce ilgili ödeme bilgilerine manuel ödeme yapar, ardından “Onayla” ile talebi tamamlandı olarak işaretler.
Renk Temaları
Section titled “Renk Temaları”| Ödeme Yöntemi | Ana Renk | Tema |
|---|---|---|
| Stripe | İndigo (#6366F1) | bg-indigo-50, text-indigo-600, border-indigo-200 |
| Payoneer | Turuncu (#F97316) | bg-orange-50, text-orange-600, border-orange-200 |
| Cenoa | Mavi (#0052FF) | bg-blue-50, text-blue-600, border-blue-200 |
Güvenlik Önlemleri
Section titled “Güvenlik Önlemleri”Kimlik Belgesi Depolama
Section titled “Kimlik Belgesi Depolama”- Belgeler Bunny Storage üzerinde saklanır
- Klasör yapısı:
id-cards/{COUNTRY}/{USER_ID}/ - Dosya adı sanitize edilir ve timestamp eklenir
- Sadece admin’ler belge URL’ine erişebilir
Erişim Kontrolü
Section titled “Erişim Kontrolü”- KYC Submit: Sadece giriş yapmış kullanıcılar
- Admin KYC Panel: Sadece
role === "admin"olan kullanıcılar - Admin Payouts Panel: Sadece
role === "admin"olan kullanıcılar
Dosya Validasyonu
Section titled “Dosya Validasyonu”// Sadece resim ve PDF kabul edilirif (!file.type.startsWith("image/") && file.type !== "application/pdf") { return { error: "Sadece resim (JPG/PNG) veya PDF yükleyebilirsiniz." };}
// Maksimum 5MBif (file.size > 5 * 1024 * 1024) { return { error: "Dosya boyutu 5MB'dan küçük olmalıdır." };}İlgili Dosyalar
Section titled “İlgili Dosyalar”| Dosya | Açıklama |
|---|---|
app/db/schema.ts | KYC alanları ve kycStatusEnum tanımı |
app/routes/api.instructor.submit-kyc.ts | KYC başvuru API endpoint’i |
app/routes/admin.kyc-requests.tsx | Admin KYC liste paneli |
app/routes/admin.kyc.$userId.preview.tsx | Admin KYC detay/onay sayfası |
app/routes/admin.payouts.tsx | Admin ödeme talepleri paneli |
app/routes/instructor.payouts.tsx | Eğitmen ödemeler sayfası |
app/routes/api.stripe.connect-onboarding.ts | Stripe Connect onboarding endpoint’i |
app/routes.ts | Route tanımları |
public/images/stripe.webp | Stripe logosu |
public/images/payoneer.png | Payoneer logosu |
public/images/cenoa.png | Cenoa logosu |
İlgili Dokümantasyon
Section titled “İlgili Dokümantasyon”- TR Payout Akışı - Türkiye özel ödeme akışı
- Stripe Connect - Stripe Connect entegrasyonu
- Exchange Rates - Döviz kuru API’si
- Admin Console - Admin paneli genel yapısı
Özet Akış Şeması
Section titled “Özet Akış Şeması”┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐│ EĞİTMEN │ │ SİSTEM │ │ ADMİN │├──────────────────┤ ├──────────────────┤ ├──────────────────┤│ │ │ │ │ ││ 1. Yöntem seç: │ │ │ │ ││ - Stripe │ │ │ │ ││ - Payoneer │ │ │ │ ││ - Cenoa │ │ │ │ ││ ↓ │ │ │ │ ││ │ │ │ │ ││ [STRIPE] │ │ │ │ ││ 2. Stripe │────▶│ 3. Onboarding │ │ ││ Onboarding │ │ tamamlandı │ │ ││ ↓ │ │ flag = true │ │ ││ │ │ │ │ ││ [PAYONEER/CENOA] │ │ │ │ ││ 2. KYC formu │────▶│ 3. Bunny'ye │ │ ││ + Kimlik yükle│ │ yükle │ │ ││ ↓ │ │ ↓ │ │ ││ │ │ 4. kycStatus = │────▶│ 5. Başvuruyu ││ │ │ "pending" │ │ incele ││ │ │ ↓ │◀────│ 6. Onayla/Reddet ││ │ │ │ │ ││ 7. Doğrulandı! │◀────│ isPayoneerVerified│ │ ││ veya │ │ veya isCenoaVerified │ ││ isStripeVerified │ veya isStripeVerified │ ││ ↓ │ │ │ │ ││ 8. Ödeme talebi │────▶│ 9. payout_ │────▶│ 10. Onayla ││ oluştur │ │ requests │ │ - Stripe: ││ │ │ tablosuna │ │ otomatik ││ │ │ kaydet │ │ - Payoneer/ ││ │ │ │ │ Cenoa: ││ │ │ │ │ manuel │└──────────────────┘ └──────────────────┘ └──────────────────┘