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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user