Skip to content
This repository was archived by the owner on Nov 23, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions src/app/components/PaymentGateway.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
'use client'

import React, { useState } from 'react';
import payHereService from '../../services/payhereService';
import paymentService, { PaymentInitiationRequest } from '../../services/paymentService';

interface PaymentGatewayProps {
amount: number;
itemDescription: string;
customerEmail: string;
customerPhone: string;
customerFirstName: string;
customerLastName: string;
customerAddress?: string;
customerCity?: string;
onSuccess?: (result: { paymentId: string; orderId: string; amount: number; status: string }) => void;
onError?: (error: string) => void;
onCancel?: () => void;
}

const CreditCardIcon = () => (
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
</svg>
);

export default function PaymentGateway({
amount,
itemDescription,
customerEmail,
customerPhone,
customerFirstName,
customerLastName,
customerAddress = 'Colombo',
customerCity = 'Colombo',
onSuccess,
onError,
onCancel
}: PaymentGatewayProps) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false);

const handlePayment = async () => {
setError(null);
setLoading(true);
setSuccess(false);

try {
// Generate unique order ID
const orderId = `ORDER_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;

// Step 1: Initiate payment with backend to get hash and payment details
const paymentRequest: PaymentInitiationRequest = {
orderId,
amount,
currency: 'LKR',
itemDescription,
customerFirstName,
customerLastName,
customerEmail,
customerPhone,
customerAddress,
customerCity
};

console.log('Initiating payment with backend...');
const paymentData = await paymentService.initiatePayment(paymentRequest);
console.log('Payment initiated, received hash from backend');

// Step 2: Start PayHere payment with the received data
await payHereService.startPayment(
paymentData,
(result) => {
console.log('Payment successful:', result);
setSuccess(true);
setLoading(false);
if (onSuccess) {
onSuccess(result);
}
},
(errorMsg) => {
console.error('Payment error:', errorMsg);
setError(errorMsg);
setLoading(false);
if (onError) {
onError(errorMsg);
}
},
() => {
console.log('Payment cancelled');
setError('Payment was cancelled');
setLoading(false);
if (onCancel) {
onCancel();
}
}
);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Payment initiation failed';
console.error('Payment initiation error:', err);
setError(errorMessage);
setLoading(false);
if (onError) {
onError(errorMessage);
}
}
};

return (
<div className="w-full">
{/* Payment Summary Card */}
<div className="automotive-card p-6 mb-6">
<h3 className="text-lg font-bold theme-text-primary mb-4">Payment Summary</h3>
<div className="space-y-3">
<div className="flex justify-between">
<span className="theme-text-muted">Item</span>
<span className="theme-text-primary font-semibold">{itemDescription}</span>
</div>
<div className="flex justify-between">
<span className="theme-text-muted">Amount</span>
<span className="theme-text-primary font-bold text-xl">LKR {amount.toFixed(2)}</span>
</div>
<div className="pt-3 border-t theme-border">
<div className="flex justify-between">
<span className="theme-text-muted">Customer</span>
<span className="theme-text-primary">{customerFirstName} {customerLastName}</span>
</div>
<div className="flex justify-between mt-2">
<span className="theme-text-muted">Email</span>
<span className="theme-text-primary text-sm">{customerEmail}</span>
</div>
</div>
</div>
</div>

{/* Error Message */}
{error && (
<div className="mb-6 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
</div>
)}

{/* Success Message */}
{success && (
<div className="mb-6 p-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg">
<p className="text-sm text-green-600 dark:text-green-400 font-semibold">Payment completed successfully!</p>
</div>
)}

{/* Pay Button */}
<button
type="button"
onClick={handlePayment}
disabled={loading || success}
className="theme-button-primary w-full flex items-center justify-center"
>
<CreditCardIcon />
{loading ? 'Processing...' : success ? 'Payment Completed' : 'Pay with PayHere'}
</button>

{/* Payment Info */}
<div className="mt-4 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
<p className="text-xs theme-text-muted text-center">
Secure payment powered by PayHere. Your payment information is encrypted and secure.
</p>
</div>
</div>
);
}
189 changes: 189 additions & 0 deletions src/app/payment-gateway/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
'use client'

import React, { useState, useEffect, Suspense } from 'react';
import Link from 'next/link';
import { useRouter, useSearchParams } from 'next/navigation';
import PaymentGateway from '../components/PaymentGateway';
import ThemeToggle from '../components/ThemeToggle';

// Icon Components
const Icon = ({ d, size = 10 }: { d: string; size?: number }) => (
<svg className={`w-${size} h-${size} text-white`} fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={d} />
</svg>
);
const BoltIcon = ({size = 10}) => <Icon d="M13 10V3L4 14h7v7l9-11h-7z" size={size} />;

const CheckCircleIcon = () => (
<svg className="w-16 h-16 text-green-500 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
);

const XCircleIcon = () => (
<svg className="w-16 h-16 text-red-500 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
);

function PaymentGatewayContent() {
const router = useRouter();
const searchParams = useSearchParams();
const status = searchParams.get('status');

const [paymentStatus, setPaymentStatus] = useState<'pending' | 'success' | 'cancel' | null>(
status as 'pending' | 'success' | 'cancel' | null
);

// Demo payment data - in real app, this would come from props or state
const [paymentData] = useState({
amount: 5000.00,
itemDescription: 'Auto Service Package',
customerEmail: 'customer@example.com',
customerPhone: '+94771234567',
customerFirstName: 'John',
customerLastName: 'Doe',
customerAddress: 'Colombo',
customerCity: 'Colombo'
});

useEffect(() => {
if (status) {
setPaymentStatus(status as 'success' | 'cancel');
}
}, [status]);

const handlePaymentSuccess = (result: { paymentId: string; orderId: string; amount: number; status: string }) => {
console.log('Payment completed successfully:', result);
setPaymentStatus('success');
router.push('/payment-gateway?status=success');
};

const handlePaymentError = (error: string) => {
console.error('Payment failed:', error);
setPaymentStatus('cancel');
};

const handlePaymentCancel = () => {
console.log('Payment cancelled by user');
setPaymentStatus('cancel');
};

return (
<div className="min-h-screen theme-bg-primary font-sans">
{/* Header */}
<header className="sticky-header shadow-lg border-b theme-border">
<div className="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center">
<Link href="/" className="flex items-center space-x-3 group">
<div className="w-12 h-12 rounded-full bg-gradient-to-r from-blue-500 to-cyan-400 flex items-center justify-center shadow-lg transform transition-transform duration-300 group-hover:scale-110">
<BoltIcon size={6} />
</div>
<h1 className="text-2xl font-bold theme-text-primary hidden sm:block">
TechTorque Auto
</h1>
</Link>
<div className="flex items-center space-x-4">
<Link href="/dashboard" className="theme-button-secondary">
Dashboard
</Link>
<ThemeToggle />
</div>
</div>
</div>
</header>

{/* Main Content */}
<main className="flex items-center justify-center py-20 px-4 sm:px-6 lg:px-8 automotive-hero">
<div className="absolute inset-0 bg-grid-pattern opacity-20 dark:opacity-10"></div>
<div className="w-full max-w-2xl relative z-10">
<div className="automotive-card p-8 md:p-12">
{/* Payment Status: Success */}
{paymentStatus === 'success' && (
<div className="text-center">
<CheckCircleIcon />
<h2 className="text-3xl font-black theme-text-primary mt-6 mb-4">
Payment Successful!
</h2>
<p className="text-lg theme-text-muted mb-8">
Your payment has been processed successfully. Thank you for your purchase.
</p>
<div className="space-x-4">
<Link href="/dashboard" className="theme-button-primary">
Go to Dashboard
</Link>
<button
onClick={() => setPaymentStatus(null)}
className="theme-button-secondary"
>
Make Another Payment
</button>
</div>
</div>
)}

{/* Payment Status: Cancelled */}
{paymentStatus === 'cancel' && (
<div className="text-center">
<XCircleIcon />
<h2 className="text-3xl font-black theme-text-primary mt-6 mb-4">
Payment Cancelled
</h2>
<p className="text-lg theme-text-muted mb-8">
Your payment was cancelled. No charges were made to your account.
</p>
<div className="space-x-4">
<button
onClick={() => setPaymentStatus(null)}
className="theme-button-primary"
>
Try Again
</button>
<Link href="/dashboard" className="theme-button-secondary">
Go to Dashboard
</Link>
</div>
</div>
)}

{/* Payment Gateway */}
{!paymentStatus && (
<div>
<div className="text-center mb-8">
<h2 className="text-4xl font-black theme-text-primary">
Payment Gateway
</h2>
<p className="mt-4 text-lg theme-text-muted">
Complete your payment securely with PayHere
</p>
</div>

<PaymentGateway
amount={paymentData.amount}
itemDescription={paymentData.itemDescription}
customerEmail={paymentData.customerEmail}
customerPhone={paymentData.customerPhone}
customerFirstName={paymentData.customerFirstName}
customerLastName={paymentData.customerLastName}
customerAddress={paymentData.customerAddress}
customerCity={paymentData.customerCity}
onSuccess={handlePaymentSuccess}
onError={handlePaymentError}
onCancel={handlePaymentCancel}
/>
</div>
)}
</div>
</div>
</main>
</div>
);
}

export default function PaymentGatewayPage() {
return (
<Suspense fallback={<div>Loading...</div>}>
<PaymentGatewayContent />
</Suspense>
);
}
Loading
Loading