Skip to content

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:

  1. Stripe Connect — Desteklenen ülkelerde otomatik ödeme
  2. Payoneer — Stripe Connect’in desteklenmediği ülkelerde (örn. Türkiye) veya Payoneer tercih eden eğitmenler için manuel doğrulama
  3. 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.

┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ Ö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 │ │
│ └─────────────────────┘ └─────────────────────┘ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘

Stripe Connect’in desteklendiği ülkelerde eğitmenler bu yöntemi kullanabilir.

Akış:

  1. Eğitmen /instructor/payouts?tab=payment sayfasında “Stripe” seçer
  2. “Stripe ile Banka Bağla” butonuna tıklar
  3. Stripe Onboarding sayfasına yönlendirilir
  4. Stripe’da hesap bilgilerini tamamlar
  5. Başarılı dönüşte isConnectOnboardingCompleted = true olarak işaretlenir
  6. Eğitmen artık ödeme talebi oluşturabilir

Doğrulama Kontrolü:

const isStripeVerified = user?.isConnectOnboardingCompleted ?? false;

Stripe Connect’in desteklenmediği ülkelerde veya Payoneer tercih eden eğitmenler için.

Akış:

  1. Eğitmen “Payoneer” seçer
  2. KYC formu doldurur (yasal ad, vergi no, Payoneer e-posta, kimlik belgeleri)
  3. Kimlik belgeleri Bunny Storage’a yüklenir
  4. kycStatus = "pending" olarak kaydedilir
  5. Admin /admin/kyc-requests panelinde başvuruyu inceler
  6. Admin onaylar → kycStatus = "approved"
  7. Eğitmen artık ödeme talebi oluşturabilir

Doğrulama Kontrolü:

const isPayoneerVerified = user?.kycStatus === "approved" &&
!isStripeVerified &&
user?.payoutMethod === "payoneer";

Mobil ödeme platformu Cenoa’yı tercih eden eğitmenler için.

Akış:

  1. Eğitmen “Cenoa” seçer
  2. KYC formu doldurur (yasal ad, vergi no, Cenoa telefon numarası, kimlik belgeleri)
  3. Kimlik belgeleri Bunny Storage’a yüklenir
  4. kycStatus = "pending", payoutMethod = "cenoa" olarak kaydedilir
  5. Admin /admin/kyc-requests panelinde başvuruyu inceler
  6. Admin onaylar → kycStatus = "approved"
  7. Eğitmen artık ödeme talebi oluşturabilir

Doğrulama Kontrolü:

const isCenoaVerified = user?.kycStatus === "approved" &&
!isStripeVerified &&
user?.payoutMethod === "cenoa";
-- app/db/schema.ts
export const kycStatusEnum = pgEnum("kyc_status", ["none", "pending", "approved", "rejected"]);
DurumAçıklama
noneHenüz KYC başvurusu yapılmamış (varsayılan).
pendingBaşvuru yapıldı, admin onayı bekleniyor.
approvedAdmin onayladı, eğitmen ödeme talep edebilir.
rejectedAdmin reddetti, eğitmen tekrar başvurabilir.
// app/db/schema.ts - users tablosu
export 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'
});
AlanTipAçıklama
kycStatusenumKYC durumu.
legalNametextKimlik belgesindeki tam ad.
taxIdtextUlusal kimlik/vergi numarası.
idDocumentUrltextKimlik ön yüz fotoğrafı URL’i.
idDocumentBackUrltextKimlik arka yüz fotoğrafı URL’i.
manualPayoutDetailstextPayoneer e-posta veya Cenoa telefon numarası.
kycRejectionReasontextAdmin tarafından girilen red sebebi.
stripeConnectIdtextStripe Connect hesap ID’si.
isConnectOnboardingCompletedbooleanStripe Connect onboarding tamamlandı mı?
payoutMethodtextSeçilen ödeme yöntemi (stripe_connect, payoneer, cenoa).

Eğitmen ödemeler sayfasında (/instructor/payouts?tab=payment) ödeme yöntemi seçim alanı gösterilir.

// Kullanıcının ülkesine göre varsayılan yöntem
const [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

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>
  1. Yasal Ad ve Soyad (legalName) - Kimlik belgesindeki tam isim
  2. Ulusal Kimlik / Vergi No (taxId) - TCKN, SSN, PAN vb.
  3. Payoneer E-posta (payoneerEmail) - Payoneer hesabına kayıtlı e-posta
  4. Kimlik Belgesi Ön Yüz (idDocumentFront) - JPG, PNG veya PDF (max 5MB)
  5. Kimlik Belgesi Arka Yüz (idDocumentBack) - JPG, PNG veya PDF (max 5MB)
  1. Yasal Ad ve Soyad (legalName) - Kimlik belgesindeki tam isim
  2. Ulusal Kimlik / Vergi No (taxId) - TCKN, SSN, PAN vb.
  3. Cenoa Telefon Numarası (cenoaPhone) - Cenoa hesabına kayıtlı telefon (+90 5XX XXX XX XX)
  4. Kimlik Belgesi Ön Yüz (idDocumentFront) - JPG, PNG veya PDF (max 5MB)
  5. 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.

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ı" }

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.jpg
  • id-cards/TR/abc123/1709123456789-back-kimlik.jpg

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>
)}
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...
};
// app/graphql/schema.ts - requestPayout mutation
requestPayout: 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...
};

URL: /admin/kyc-requests
Dosya: app/routes/admin.kyc-requests.tsx

  • 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
<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>

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ı

URL: /admin/payouts
Dosya: app/routes/admin.payouts.tsx

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)
{/* Ö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>
)}
DurumUyarı
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 /admin/payouts panelinde ödeme talebini onaylarken, sistem eğitmenin doğrulama yöntemini kontrol eder:

// Doğrulama yöntemi belirleme
const isStripeVerified = instructor?.isConnectOnboardingCompleted ?? false;
const isPayoneerVerified = instructor?.kycStatus === "approved" &&
!isStripeVerified &&
instructor?.payoutMethod === "payoneer";
const isCenoaVerified = instructor?.kycStatus === "approved" &&
!isStripeVerified &&
instructor?.payoutMethod === "cenoa";
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;
}
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üncellenir
await 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.

Ödeme YöntemiAna RenkTema
Stripeİndigo (#6366F1)bg-indigo-50, text-indigo-600, border-indigo-200
PayoneerTuruncu (#F97316)bg-orange-50, text-orange-600, border-orange-200
CenoaMavi (#0052FF)bg-blue-50, text-blue-600, border-blue-200
  • 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
  • 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
// Sadece resim ve PDF kabul edilir
if (!file.type.startsWith("image/") && file.type !== "application/pdf") {
return { error: "Sadece resim (JPG/PNG) veya PDF yükleyebilirsiniz." };
}
// Maksimum 5MB
if (file.size > 5 * 1024 * 1024) {
return { error: "Dosya boyutu 5MB'dan küçük olmalıdır." };
}
DosyaAçıklama
app/db/schema.tsKYC alanları ve kycStatusEnum tanımı
app/routes/api.instructor.submit-kyc.tsKYC başvuru API endpoint’i
app/routes/admin.kyc-requests.tsxAdmin KYC liste paneli
app/routes/admin.kyc.$userId.preview.tsxAdmin KYC detay/onay sayfası
app/routes/admin.payouts.tsxAdmin ödeme talepleri paneli
app/routes/instructor.payouts.tsxEğitmen ödemeler sayfası
app/routes/api.stripe.connect-onboarding.tsStripe Connect onboarding endpoint’i
app/routes.tsRoute tanımları
public/images/stripe.webpStripe logosu
public/images/payoneer.pngPayoneer logosu
public/images/cenoa.pngCenoa logosu
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 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 │
└──────────────────┘ └──────────────────┘ └──────────────────┘