feat: implement merchant dashboard, secure auth, and short_id system

- Added dedicated merchant dashboard with analytics and transactions
- Implemented API Key based authentication for merchants
- Introduced 8-character Short IDs for merchants to use in URLs
- Refactored checkout and payment intent APIs to support multi-gateway
- Enhanced Landing Page with Merchant Portal access and marketing copy
- Fixed Next.js 15 async params build issues
- Updated internal branding to P2CGateway
- Added AyrisTech credits to footer
This commit is contained in:
mstfyldz
2026-01-20 21:58:41 +03:00
parent af09543374
commit 3562e10713
46 changed files with 3505 additions and 414 deletions

View File

@@ -7,7 +7,6 @@ import { useSearchParams } from 'next/navigation';
import CheckoutForm from '@/components/checkout/CheckoutForm';
import MockCheckoutForm from '@/components/checkout/MockCheckoutForm';
import { Loader2, AlertCircle, ArrowLeft, UserCircle } from 'lucide-react';
import Image from 'next/image';
import Link from 'next/link'; // Added Link import
function CheckoutContent() {
@@ -16,8 +15,10 @@ function CheckoutContent() {
const currency = searchParams.get('currency') || 'TL';
const refId = searchParams.get('ref_id') || 'SEC-99231-TX';
const callbackUrl = searchParams.get('callback_url') || '/';
const merchantId = searchParams.get('merchant_id') || null;
const [clientSecret, setClientSecret] = useState<string | null>(null);
const [paymentData, setPaymentData] = useState<any>(null);
const [error, setError] = useState<string | null>(null);
const isMock = process.env.NEXT_PUBLIC_USE_MOCK_PAYMENTS === 'true';
@@ -31,7 +32,13 @@ function CheckoutContent() {
fetch('/api/create-payment-intent', {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ amount, currency, ref_id: refId, callback_url: callbackUrl }),
body: JSON.stringify({
amount,
currency,
ref_id: refId,
callback_url: callbackUrl,
merchant_id: merchantId
}),
})
.then((res) => res.json())
.then((data) => {
@@ -39,6 +46,14 @@ function CheckoutContent() {
setError(data.error);
} else {
setClientSecret(data.clientSecret);
setPaymentData(data);
// Auto-redirect if it's a redirect action
if (data.nextAction === 'redirect' && data.redirectUrl) {
setTimeout(() => {
window.location.href = data.redirectUrl;
}, 2000); // 2 second delay to show the message
}
}
})
.catch(() => setError('Ödeme başlatılamadı. Lütfen tekrar deneyin.'));
@@ -68,7 +83,7 @@ function CheckoutContent() {
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
<div className="w-4 h-4 bg-white rotate-45 transform"></div>
</div>
<span className="font-bold text-gray-900 text-lg tracking-tight">froydPay</span>
<span className="font-bold text-gray-900 text-lg tracking-tight">P2CGateway</span>
</div>
<div className="w-8 h-8 bg-orange-100 rounded-full flex items-center justify-center text-orange-500">
<UserCircle size={24} />
@@ -76,37 +91,47 @@ function CheckoutContent() {
</nav>
{/* Main Content */}
<div className="flex-1 flex flex-col lg:flex-row items-stretch max-w-7xl mx-auto w-full px-6 py-12 gap-12">
{/* Left Column: Product Info */}
<div className="flex-1 flex flex-col justify-center items-center lg:items-end space-y-8 order-2 lg:order-1">
<div className="relative group perspective-1000">
<div className="w-full max-w-[400px] aspect-square relative rounded-[40px] overflow-hidden shadow-2xl shadow-blue-200/50 transform group-hover:rotate-y-6 transition-transform duration-500 border-8 border-white">
<Image
src="/digital_nft_asset.png"
alt="Digital NFT Product"
fill
className="object-cover"
priority
/>
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/80 to-transparent p-8 pt-20">
<span className="text-blue-400 text-[10px] font-bold uppercase tracking-widest mb-2 block">Premium Dijital Varlık</span>
<h3 className="text-white text-2xl font-black tracking-tight uppercase">CyberCube #082</h3>
<p className="text-gray-300 text-sm mt-2 line-clamp-2">froyd ına ömür boyu erişim sağlayan özel, yüksek sadakatli 3D üretken dijital koleksiyon parçası.</p>
</div>
<div className="flex-1 flex flex-col lg:flex-row items-stretch w-full overflow-hidden">
{/* Left Column: Product Info (Cover Image) */}
<div className="flex-1 min-h-[500px] lg:min-h-0 relative group order-2 lg:order-1">
<img
src="/digital_nft_asset.png"
alt="Digital NFT Product"
className="absolute inset-0 w-full h-full object-cover"
/>
{/* Overlay Gradient */}
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/20 to-transparent pointer-events-none" />
{/* Content on Image */}
<div className="relative h-full flex flex-col justify-between p-12">
<div className="flex items-center gap-2 bg-white/10 backdrop-blur-md w-fit px-4 py-2 rounded-full border border-white/20">
<span className="w-2 h-2 bg-blue-500 rounded-full animate-pulse" />
<span className="text-white text-[10px] font-bold uppercase tracking-widest">Premium Dijital Varlık</span>
</div>
{/* Gloss Effect */}
<div className="absolute inset-0 rounded-[40px] bg-gradient-to-tr from-white/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none"></div>
</div>
<div className="space-y-6">
<div>
<h3 className="text-white text-5xl font-black tracking-tight uppercase leading-none">CyberCube #082</h3>
<p className="text-gray-300 text-lg mt-4 max-w-lg leading-relaxed">P2C ına ömür boyu erişim sağlayan özel, yüksek sadakatli 3D üretken dijital koleksiyon parçası.</p>
</div>
<div className="text-center lg:text-right space-y-2 hidden lg:block">
<p className="text-gray-400 text-sm font-medium">Satıcı: <span className="text-gray-900 uppercase">Froyd Digital Media INC.</span></p>
<p className="text-gray-400 text-sm">Müşteri Desteği: <span className="text-blue-600 hover:underline cursor-pointer">help@froyd.io</span></p>
<div className="pt-8 border-t border-white/10 flex flex-col sm:flex-row sm:items-center gap-8">
<div>
<p className="text-gray-400 text-[10px] font-bold uppercase tracking-wider mb-1">Satıcı</p>
<p className="text-white font-medium text-sm">Ayris Digital Media INC.</p>
</div>
<div>
<p className="text-gray-400 text-[10px] font-bold uppercase tracking-wider mb-1">Destek</p>
<p className="text-blue-400 font-medium text-sm hover:underline cursor-pointer">help@ayris.dev</p>
</div>
</div>
</div>
</div>
</div>
{/* Right Column: Payment Form */}
<div className="flex-1 flex flex-col justify-center items-center lg:items-start order-1 lg:order-2">
<div className="flex-1 flex flex-col justify-center items-center lg:items-start order-1 lg:order-2 p-8 lg:p-20">
{!clientSecret ? (
<div className="flex flex-col items-center justify-center p-20 bg-white rounded-3xl border border-gray-100 shadow-sm w-full max-w-md">
<Loader2 className="w-10 h-10 text-blue-600 animate-spin mb-4" />
@@ -114,9 +139,26 @@ function CheckoutContent() {
</div>
) : (
<div className="w-full">
{isMock ? (
{paymentData?.nextAction === 'redirect' ? (
<div className="p-12 bg-white rounded-[40px] border border-gray-100 shadow-sm text-center space-y-8 animate-in zoom-in duration-500">
<div className="w-20 h-20 bg-blue-50 rounded-3xl flex items-center justify-center mx-auto text-blue-600">
<Loader2 className="animate-spin" size={40} />
</div>
<div className="space-y-3">
<h3 className="text-2xl font-black text-gray-900 leading-tight">Ödeme Sayfasına Yönlendiriliyorsunuz</h3>
<p className="text-gray-400 font-bold uppercase tracking-widest text-[10px]">Lütfen tarayıcınızı kapatmayın</p>
</div>
<p className="text-sm text-gray-400 max-w-xs mx-auto">Sizi güvenli ödeme adımına aktarıyoruz. Bu işlem birkaç saniye sürebilir.</p>
<button
onClick={() => window.location.href = paymentData.redirectUrl}
className="w-full py-5 bg-gray-900 text-white rounded-2xl font-black text-xs uppercase tracking-[0.2em] hover:bg-black transition shadow-xl"
>
Hemen Git
</button>
</div>
) : isMock ? (
<MockCheckoutForm amount={amount} currency={currency} callbackUrl={callbackUrl} clientSecret={clientSecret} refId={refId} />
) : (
) : paymentData?.provider === 'stripe' ? (
<Elements stripe={getStripe()} options={{ clientSecret, appearance: { theme: 'stripe' } }}>
<CheckoutForm
amount={amount}
@@ -125,6 +167,12 @@ function CheckoutContent() {
piId={clientSecret.split('_secret')[0]}
/>
</Elements>
) : (
<div className="p-12 bg-white rounded-[40px] border border-gray-100 shadow-sm text-center">
<AlertCircle className="w-12 h-12 text-blue-600 mx-auto mb-4" />
<h3 className="text-xl font-black text-gray-900">{paymentData?.provider.toUpperCase()} Entegrasyonu</h3>
<p className="text-gray-400 text-sm mt-2 uppercase font-bold tracking-widest">Bu ödeme yöntemi geliştirme aşamasındadır.</p>
</div>
)}
<div className="mt-8 flex justify-center lg:justify-start">
@@ -141,7 +189,7 @@ function CheckoutContent() {
{/* Footer */}
<footer className="py-12 border-t border-gray-100 text-center">
<p className="text-[#94A3B8] text-[10px] font-medium tracking-tight uppercase">
© 2026 froydPay Inc. Tüm hakları saklıdır.
© 2026 P2CGateway Inc. Tüm hakları saklıdır.
</p>
</footer>
</div>