Skip to content

Türkiye (TR) Özel Payout Akışı

Stripe kısıtlamalarını aşmak için Payoneer/Cenoa/IBAN toplama, KYC doğrulama, admin onay ve payout_requests tablosu.

Türkiye’de Stripe Connect’in sınırlı olması nedeniyle Achidemy, TR eğitmenleri için Stripe yerine Payoneer, Cenoa veya manuel banka havalesi akışı sunar. Bu sayfa, bu mantığın nasıl çalıştığını, KYC doğrulama sürecini ve admin onay mekanizmasını açıklar.

  • Stripe Connect, Türkiye’de tam desteklenmediği veya kısıtlamaları olduğu için, TR’deki eğitmenlere Stripe Transfer ile ödeme yapılamıyor.
  • Çözüm: Eğitmen Payoneer veya Cenoa bilgisini platforma girer; KYC doğrulaması tamamlandıktan sonra ödeme talebi oluşturabilir; admin talebi onayladıktan sonra manuel havale/EFT ile ödeme yapar.

Doğrulama Yöntemleri Karşılaştırması

Section titled “Doğrulama Yöntemleri Karşılaştırması”
ÖzellikStripe ConnectPayoneer (KYC)Cenoa (KYC)
Desteklenen ÜlkelerABD, AB, UK, vb.Tüm ülkelerTüm ülkeler
Doğrulama SüreciStripe OnboardingManuel KYC (admin onayı)Manuel KYC (admin onayı)
Ödeme İşlemiOtomatik Stripe TransferManuel havale/EFTManuel Cenoa transfer
İletişim BilgisiStripe tarafından alınırE-posta adresiTelefon numarası
Kimlik BelgesiStripe tarafından alınırPlatform üzerinden yüklenirPlatform üzerinden yüklenir
Admin MüdahalesiSadece onay butonuOnay + manuel ödemeOnay + manuel ödeme
┌─────────────────────────────────────────────────────────────────────────┐
│ TR EĞİTMEN ÖDEME AKIŞI │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Eğitmen /instructor/payouts?tab=payment sayfasına gider │
│ ↓ │
│ 2. "Payoneer" veya "Cenoa" seçeneğini seçer │
│ ↓ │
│ 3. KYC formu doldurur: │
│ - Yasal ad/soyad │
│ - TC Kimlik No │
│ - Payoneer e-posta VEYA Cenoa telefon numarası │
│ - Kimlik belgesi (ön + arka yüz) │
│ ↓ │
│ 4. Kimlik belgeleri Bunny Storage'a yüklenir │
│ ↓ │
│ 5. kycStatus = "pending", payoutMethod = "payoneer"/"cenoa" │
│ ↓ │
│ 6. Admin /admin/kyc-requests panelinde başvuruyu inceler │
│ ↓ │
│ 7. Admin onaylar → kycStatus = "approved" │
│ (veya reddeder → kycStatus = "rejected") │
│ ↓ │
│ 8. Eğitmen artık ödeme talebi oluşturabilir │
│ ↓ │
│ 9. Ödeme talebi /admin/payouts paneline düşer │
│ ↓ │
│ 10. Admin Payoneer/Cenoa bilgilerini görür ve manuel ödeme yapar │
│ ↓ │
│ 11. Admin talebi "Tamamlandı" olarak işaretler │
│ │
└─────────────────────────────────────────────────────────────────────────┘
AlanTipAçıklama
kycStatusenumnone / pending / approved / rejected
legalNametextKimlik belgesindeki yasal ad
taxIdtextTC Kimlik No
idDocumentUrltextKimlik ön yüz URL’i (Bunny Storage)
idDocumentBackUrltextKimlik arka yüz URL’i (Bunny Storage)
manualPayoutDetailstextPayoneer e-posta veya Cenoa telefon numarası
kycRejectionReasontextRed sebebi (admin notu)
payoutMethodtext'payoneer' veya 'cenoa' veya 'bank_transfer'
AlanTipAçıklama
idUUIDPrimary key
instructor_idUUIDTalep sahibi eğitmen
amountnumericTalep edilen tutar (USD)
statustextpending / processing / completed / rejected
methodtext'stripe_connect' veya 'bank_transfer'
destinationtextStripe Connect ID veya Payoneer/Cenoa bilgisi
processed_attimestampİşlenme zamanı
stripe_transfer_idtextStripe Transfer ID (sadece Stripe için)

TR eğitmenleri için “Payoneer” ve “Cenoa” seçenekleri gösterilir (Stripe devre dışı):

app/routes/instructor.payouts.tsx
const isTurkey = user?.country === "TR";
const [selectedMethod, setSelectedMethod] = useState<"stripe" | "payoneer" | "cenoa">(
useStripe ? "stripe" : "payoneer"
);
{/* 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>
// Hangi ödeme yöntemi ile doğrulama yapıldığını belirle
const isStripeVerified = user?.isConnectOnboardingCompleted ?? false;
const isPayoneerVerified = user?.kycStatus === "approved" && !isStripeVerified && user?.payoutMethod === "payoneer";
const isCenoaVerified = user?.kycStatus === "approved" && !isStripeVerified && user?.payoutMethod === "cenoa";
// Genel onay durumu
const isApproved = isPayoneerVerified || isCenoaVerified || isStripeVerified;

Payoneer veya Cenoa ile doğrulanan eğitmen için UI (logo + “ile doğrulandı”):

{(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>
)}
{(isPayoneerVerified || isCenoaVerified || isStripeVerified) && (
<div className="text-right">
<p className="text-[10px] font-black text-blue-600 uppercase">
Alıcı Hesap
</p>
<div className="flex items-center justify-end gap-2">
<img
src={isStripeVerified ? "/images/stripe.webp" : isCenoaVerified ? "/images/cenoa.png" : "/images/payoneer.png"}
alt={isStripeVerified ? "Stripe" : isCenoaVerified ? "Cenoa" : "Payoneer"}
className="h-4 w-auto object-contain"
/>
{!isStripeVerified && (
<span className="text-xs font-bold text-blue-700 truncate max-w-[150px]">
{user?.manualPayoutDetails || "-"}
</span>
)}
</div>
</div>
)}

URL: /admin/kyc-requests

TR eğitmenlerinin KYC başvuruları bu panelde listelenir:

  • Yasal ad, ülke, e-posta
  • Vergi/TC No
  • Ödeme yöntemi (logo ile gösterilir)
  • Payoneer e-posta veya Cenoa telefon
  • Kimlik belgeleri (ön + arka yüz)

Detay Sayfası: /admin/kyc/:userId/preview

  • Tüm KYC bilgileri
  • Kimlik belgesi görüntüleme (modal)
  • Onay/Red butonları

URL: /admin/payouts

<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>
</>
) : null}
</div>
{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 whitespace-pre-wrap break-all">
{payout.instructor.manualPayoutDetails}
</p>
</div>
)}
{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 whitespace-pre-wrap break-all">
{payout.instructor.manualPayoutDetails}
</p>
</div>
)}
{/* Payoneer uyarısı */}
{payout.isPayoneerVerified && payout.status === "pending" && (
<div className="mt-3 bg-emerald-50 border border-emerald-200 rounded-lg p-3">
<div className="flex items-start gap-2">
<AlertCircle className="text-emerald-600 mt-0.5" size={14} />
<p className="text-xs text-emerald-800">
<strong>Manuel Ödeme Gerekli:</strong>{" "}
Bu eğitmen Payoneer ile doğrulandı. Yukarıdaki Payoneer/IBAN
bilgilerine manuel olarak ödeme yapmanız gerekmektedir.
</p>
</div>
</div>
)}
{/* Cenoa uyarısı */}
{payout.isCenoaVerified && payout.status === "pending" && (
<div className="mt-3 bg-blue-50 border border-blue-200 rounded-lg p-3">
<div className="flex items-start gap-2">
<AlertCircle className="text-blue-600 mt-0.5" size={14} />
<p className="text-xs text-blue-800">
<strong>Manuel Ödeme Gerekli:</strong>{" "}
Bu eğitmen Cenoa ile doğrulandı. Yukarıdaki telefon numarasına
Cenoa üzerinden manuel olarak ödeme yapmanız gerekmektedir.
</p>
</div>
</div>
)}

Admin /admin/payouts panelinde ödeme talebini onaylarken, sistem eğitmenin doğrulama yöntemini kontrol eder:

// app/routes/admin.payouts.tsx - action
if (intent === "approve") {
const [instructor] = await db
.select({
country: users.country,
stripeConnectId: users.stripeConnectId,
manualPayoutDetails: users.manualPayoutDetails,
kycStatus: users.kycStatus,
isConnectOnboardingCompleted: users.isConnectOnboardingCompleted,
payoutMethod: users.payoutMethod,
})
.from(users)
.where(eq(users.id, payoutRow.instructorId));
// Hangi ödeme yöntemi ile doğrulama yapıldığını belirle
const isStripeVerified = instructor?.isConnectOnboardingCompleted ?? false;
const isPayoneerVerified = instructor?.kycStatus === "approved" &&
!isStripeVerified &&
instructor?.payoutMethod === "payoneer";
const isCenoaVerified = instructor?.kycStatus === "approved" &&
!isStripeVerified &&
instructor?.payoutMethod === "cenoa";
let stripeTransferId: string | null = null;
if (isStripeVerified && instructor?.stripeConnectId) {
// STRIPE CONNECT: Otomatik transfer
const transfer = await stripe.transfers.create({
amount: Math.round(Number(payoutRow.amount) * 100),
currency: "usd",
destination: instructor.stripeConnectId,
});
stripeTransferId = transfer.id;
} else if (isPayoneerVerified) {
// PAYONEER: Manuel ödeme - Stripe Transfer YAPILMAZ
console.log("Payoneer doğrulamalı eğitmen için manuel ödeme onaylandı");
} else if (isCenoaVerified) {
// CENOA: Manuel ödeme - Stripe Transfer YAPILMAZ
console.log("Cenoa doğrulamalı eğitmen için manuel ödeme onaylandı");
}
// Durumu güncelle (her durumda)
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.

Eğitmen ödemeler sayfasında bakiye ve kazançlar yerel para biriminde (TRY / ₺) gösterilir:

// Döviz kuru çevrimi
const displayCurrency = getDisplayCurrencyFromCountry(user?.country); // "TRY"
const localAmount = convertUsdToCurrency(amountUsd, displayCurrency, rates);
const localSymbol = DISPLAY_SYMBOLS[displayCurrency]; // "₺"

Kurlar Döviz Kuru API ile alınır (open.er-api.com, Cloudflare KV önbelleği).

Ödeme YöntemiAna RenkTema
PayoneerTuruncu (#F97316)bg-orange-50, text-orange-600, border-orange-200
CenoaMavi (#0052FF)bg-blue-50, text-blue-600, border-blue-200
AdımAçıklama
1TR eğitmeni KYC başvurusu yapar (yasal ad, TC no, Payoneer e-posta veya Cenoa telefon, kimlik belgesi).
2Admin KYC başvurusunu /admin/kyc-requests panelinde inceler ve onaylar.
3kycStatus = "approved"isPayoneerVerified veya isCenoaVerified = true
4Eğitmen ödeme talebi oluşturur (tutar girer, gönderir).
5payout_requests tablosuna kayıt eklenir.
6Admin /admin/payouts panelinde talebi görür.
7Admin Payoneer/Cenoa bilgilerini görür ve manuel ödeme yapar.
8Admin talebi “Onayla” ile “Tamamlandı” olarak işaretler.
9Eğitmene bildirim gönderilir.
DosyaAçıklama
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/lib/exchange-rates.tsDöviz kuru fonksiyonları
public/images/payoneer.pngPayoneer logosu
public/images/cenoa.pngCenoa logosu