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

@@ -1,50 +1,89 @@
import { NextRequest, NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
import { supabaseAdmin } from '@/lib/supabase';
import { supabaseAdmin } from '@/lib/supabase-admin';
import { PaymentProviderFactory } from '@/lib/payment-providers';
export async function POST(req: NextRequest) {
try {
const { amount, currency, ref_id, callback_url, customer_name, customer_phone } = await req.json();
const { amount, currency, ref_id, callback_url, customer_name, customer_phone, merchant_id } = await req.json();
if (!amount || !currency) {
if (!amount || !currency || !merchant_id) {
return NextResponse.json(
{ error: 'Tutar ve para birimi zorunludur.' },
{ error: 'Tutar, para birimi ve firma ID zorunludur.' },
{ status: 400 }
);
}
const useMock = process.env.NEXT_PUBLIC_USE_MOCK_PAYMENTS === 'true';
let clientSecret = 'mock_secret_' + Math.random().toString(36).substring(7);
let stripeId = 'mock_pi_' + Math.random().toString(36).substring(7);
// 1. Fetch Merchant to check provider (Support both UUID and Short ID)
const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(merchant_id);
if (!useMock) {
// 1. Create PaymentIntent in Stripe
const paymentIntent = await stripe.paymentIntents.create({
amount: Math.round(amount * 100), // Stripe uses subunits (e.g., cents)
currency: currency.toLowerCase(),
metadata: {
ref_id,
callback_url,
customer_name,
customer_phone,
},
});
clientSecret = paymentIntent.client_secret!;
stripeId = paymentIntent.id;
const query = supabaseAdmin
.from('merchants')
.select('*');
if (isUUID) {
query.eq('id', merchant_id);
} else {
query.eq('short_id', merchant_id);
}
// 2. Log transaction in Supabase with 'pending' status
const { data: merchant, error: merchantError } = await query.single();
if (merchantError || !merchant) {
return NextResponse.json({ error: 'Firma bulunamadı.' }, { status: 404 });
}
// Use the actual UUID for DB operations
const resolvedMerchantId = merchant.id;
const provider = merchant.payment_provider || 'stripe';
const useMock = process.env.NEXT_PUBLIC_USE_MOCK_PAYMENTS === 'true';
let clientSecret = '';
let providerTxId = '';
let nextAction = 'none';
let redirectUrl = '';
if (useMock) {
clientSecret = 'mock_secret_' + Math.random().toString(36).substring(7);
providerTxId = clientSecret;
} else {
// 2. Use Factory to create intent based on provider
const intent = await PaymentProviderFactory.createIntent(provider, {
amount,
currency,
merchantId: resolvedMerchantId,
refId: ref_id,
customerName: customer_name,
customerPhone: customer_phone,
callbackUrl: callback_url,
providerConfig: merchant.provider_config
});
clientSecret = intent.clientSecret;
providerTxId = intent.providerTxId;
nextAction = intent.nextAction || 'none';
redirectUrl = intent.redirectUrl || '';
}
// 3. Log transaction in Supabase
const { error: dbError } = await supabaseAdmin
.from('transactions')
.insert({
amount,
currency,
status: 'pending',
stripe_pi_id: stripeId,
stripe_pi_id: providerTxId, // We keep using this column for now or we could use provider_tx_id if we updated schema
source_ref_id: ref_id,
customer_name,
customer_phone,
callback_url,
merchant_id: resolvedMerchantId,
provider: provider,
metadata: {
nextAction,
redirectUrl
}
});
if (dbError) {
@@ -53,6 +92,9 @@ export async function POST(req: NextRequest) {
return NextResponse.json({
clientSecret: clientSecret,
nextAction,
redirectUrl,
provider
});
} catch (err: any) {
console.error('Internal Error:', err);