diff --git a/src/app/components/PaymentGateway.tsx b/src/app/components/PaymentGateway.tsx new file mode 100644 index 0000000..43d7ede --- /dev/null +++ b/src/app/components/PaymentGateway.tsx @@ -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 = () => ( + + + +); + +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(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 ( +
+ {/* Payment Summary Card */} +
+

Payment Summary

+
+
+ Item + {itemDescription} +
+
+ Amount + LKR {amount.toFixed(2)} +
+
+
+ Customer + {customerFirstName} {customerLastName} +
+
+ Email + {customerEmail} +
+
+
+
+ + {/* Error Message */} + {error && ( +
+

{error}

+
+ )} + + {/* Success Message */} + {success && ( +
+

Payment completed successfully!

+
+ )} + + {/* Pay Button */} + + + {/* Payment Info */} +
+

+ Secure payment powered by PayHere. Your payment information is encrypted and secure. +

+
+
+ ); +} diff --git a/src/app/payment-gateway/page.tsx b/src/app/payment-gateway/page.tsx new file mode 100644 index 0000000..15140e7 --- /dev/null +++ b/src/app/payment-gateway/page.tsx @@ -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 }) => ( + + + +); +const BoltIcon = ({size = 10}) => ; + +const CheckCircleIcon = () => ( + + + +); + +const XCircleIcon = () => ( + + + +); + +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 ( +
+ {/* Header */} +
+
+
+ +
+ +
+

+ TechTorque Auto +

+ +
+ + Dashboard + + +
+
+
+
+ + {/* Main Content */} +
+
+
+
+ {/* Payment Status: Success */} + {paymentStatus === 'success' && ( +
+ +

+ Payment Successful! +

+

+ Your payment has been processed successfully. Thank you for your purchase. +

+
+ + Go to Dashboard + + +
+
+ )} + + {/* Payment Status: Cancelled */} + {paymentStatus === 'cancel' && ( +
+ +

+ Payment Cancelled +

+

+ Your payment was cancelled. No charges were made to your account. +

+
+ + + Go to Dashboard + +
+
+ )} + + {/* Payment Gateway */} + {!paymentStatus && ( +
+
+

+ Payment Gateway +

+

+ Complete your payment securely with PayHere +

+
+ + +
+ )} +
+
+
+
+ ); +} + +export default function PaymentGatewayPage() { + return ( + Loading...}> + + + ); +} diff --git a/src/services/payhereService.ts b/src/services/payhereService.ts new file mode 100644 index 0000000..e40d9fc --- /dev/null +++ b/src/services/payhereService.ts @@ -0,0 +1,167 @@ +// services/payhereService.ts - PayHere Integration for TechTorque + +interface PayHerePayment { + sandbox: boolean; + merchant_id: string; + return_url: string; + cancel_url: string; + notify_url: string; + order_id: string; + items: string; + amount: string; + currency: string; + hash: string; + first_name: string; + last_name: string; + email: string; + phone: string; + address: string; + city: string; + country: string; +} + +interface PaymentData { + orderId: string; + amount: number; + currency: string; + itemDescription: string; + customerFirstName: string; + customerLastName: string; + customerEmail: string; + customerPhone: string; + customerAddress: string; + customerCity: string; + hash: string; + merchantId: string; + returnUrl: string; + cancelUrl: string; + notifyUrl: string; + sandbox: boolean; +} + +interface PayHereSDK { + startPayment: (payment: PayHerePayment) => void; + onCompleted: (paymentId: string) => void; + onDismissed: () => void; + onError: (error: string) => void; +} + +interface PaymentResult { + paymentId: string; + orderId: string; + transactionId: string; + method: string; + status: string; + amount: number; + currency: string; + paidAt: string; +} + +declare global { + interface Window { + payhere: PayHereSDK; + } +} + +class PayHereService { + // Load PayHere JS SDK + loadPayHereScript(): Promise { + return new Promise((resolve, reject) => { + if (window.payhere) { + console.log('PayHere already loaded'); + resolve(); + return; + } + + const script = document.createElement('script'); + script.src = 'https://www.payhere.lk/lib/payhere.js'; + + script.onload = () => { + console.log('PayHere JS SDK loaded'); + resolve(); + }; + + script.onerror = () => { + console.error('Failed to load PayHere SDK'); + reject(new Error('Failed to load PayHere SDK')); + }; + + document.head.appendChild(script); + }); + } + + // Start payment with PayHere + async startPayment( + paymentData: PaymentData, + onSuccess: (paymentResult: PaymentResult) => void, + onError: (error: string) => void, + onCancel: () => void + ) { + try { + // Load PayHere SDK + await this.loadPayHereScript(); + + const payment: PayHerePayment = { + sandbox: paymentData.sandbox, + merchant_id: paymentData.merchantId, + return_url: paymentData.returnUrl, + cancel_url: paymentData.cancelUrl, + notify_url: paymentData.notifyUrl, + order_id: paymentData.orderId, + items: paymentData.itemDescription, + amount: paymentData.amount.toFixed(2), + currency: paymentData.currency, + hash: paymentData.hash, + first_name: paymentData.customerFirstName, + last_name: paymentData.customerLastName, + email: paymentData.customerEmail, + phone: paymentData.customerPhone, + address: paymentData.customerAddress, + city: paymentData.customerCity, + country: 'Sri Lanka' + }; + + console.log('Starting PayHere payment:', { + sandbox: payment.sandbox, + orderId: payment.order_id, + amount: payment.amount, + merchantId: payment.merchant_id + }); + + // Setup callbacks + window.payhere.onCompleted = function (paymentId: string) { + console.log('Payment completed:', paymentId); + onSuccess({ + paymentId, + orderId: payment.order_id, + transactionId: paymentId, + method: 'payhere', + status: 'completed', + amount: parseFloat(payment.amount), + currency: payment.currency, + paidAt: new Date().toISOString() + }); + }; + + window.payhere.onDismissed = function () { + console.log('Payment cancelled'); + onCancel(); + }; + + window.payhere.onError = function (error: string) { + console.error('Payment error:', error); + onError(`PayHere Error: ${error}`); + }; + + // Start payment + window.payhere.startPayment(payment); + + } catch (error) { + console.error('PayHere error:', error); + onError(`Payment failed: ${error instanceof Error ? error.message : 'Unknown error'}`); + } + } +} + +const payHereService = new PayHereService(); +export default payHereService; diff --git a/src/services/paymentService.ts b/src/services/paymentService.ts new file mode 100644 index 0000000..12f08e5 --- /dev/null +++ b/src/services/paymentService.ts @@ -0,0 +1,53 @@ +"use client"; +import api from '../lib/apiClient'; + +export interface PaymentInitiationRequest { + orderId: string; + amount: number; + currency: string; + itemDescription: string; + customerFirstName: string; + customerLastName: string; + customerEmail: string; + customerPhone: string; + customerAddress: string; + customerCity: string; +} + +export interface PaymentInitiationResponse { + merchantId: string; + orderId: string; + amount: number; + currency: string; + hash: string; + itemDescription: string; + returnUrl: string; + cancelUrl: string; + notifyUrl: string; + customerFirstName: string; + customerLastName: string; + customerEmail: string; + customerPhone: string; + customerAddress: string; + customerCity: string; + sandbox: boolean; +} + +export const paymentService = { + async initiatePayment(payload: PaymentInitiationRequest): Promise { + const res = await api.post('/payments/initiate', payload); + return res.data; + }, + + async getPaymentHistory() { + const res = await api.get('/payments'); + return res.data; + }, + + async getPaymentDetails(paymentId: string) { + const res = await api.get(`/payments/${paymentId}`); + return res.data; + } +}; + +export default paymentService;