340 lines
12 KiB
TypeScript
340 lines
12 KiB
TypeScript
/**
|
|
* PendingPaymentView - Full-page view for new users with pending payment
|
|
*
|
|
* This is shown to users who:
|
|
* - Just signed up with a paid plan
|
|
* - Have account.status === 'pending_payment'
|
|
* - Have NOT made any successful payments yet
|
|
*
|
|
* Payment methods are shown based on user's country:
|
|
* - Global: Stripe (Credit/Debit Card) + PayPal
|
|
* - Pakistan (PK): Stripe (Credit/Debit Card) + Bank Transfer
|
|
*/
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import {
|
|
CreditCardIcon,
|
|
Building2Icon,
|
|
CheckCircleIcon,
|
|
AlertCircleIcon,
|
|
Loader2Icon,
|
|
ArrowLeftIcon,
|
|
LockIcon,
|
|
} from '../../icons';
|
|
import { Card } from '../ui/card';
|
|
import Badge from '../ui/badge/Badge';
|
|
import Button from '../ui/button/Button';
|
|
import { useToast } from '../ui/toast/ToastContainer';
|
|
import BankTransferForm from './BankTransferForm';
|
|
import {
|
|
Invoice,
|
|
getAvailablePaymentGateways,
|
|
subscribeToPlan,
|
|
type PaymentGateway,
|
|
} from '../../services/billing.api';
|
|
|
|
// PayPal icon component
|
|
const PayPalIcon = ({ className }: { className?: string }) => (
|
|
<svg className={className} viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M7.076 21.337H2.47a.641.641 0 0 1-.633-.74L4.944.901C5.026.382 5.474 0 5.998 0h7.46c2.57 0 4.578.543 5.69 1.81 1.01 1.15 1.304 2.42 1.012 4.287-.023.143-.047.288-.077.437-.983 5.05-4.349 6.797-8.647 6.797h-2.19c-.524 0-.968.382-1.05.9l-1.12 7.106zm14.146-14.42a3.35 3.35 0 0 0-.607-.541c-.013.076-.026.175-.041.254-.93 4.778-4.005 7.201-9.138 7.201h-2.19a.563.563 0 0 0-.556.479l-1.187 7.527h-.506l-.24 1.516a.56.56 0 0 0 .554.647h3.882c.46 0 .85-.334.922-.788.06-.26.76-4.852.816-5.09a.932.932 0 0 1 .923-.788h.58c3.76 0 6.705-1.528 7.565-5.946.36-1.847.174-3.388-.777-4.471z"/>
|
|
</svg>
|
|
);
|
|
|
|
interface PaymentOption {
|
|
id: string;
|
|
type: PaymentGateway;
|
|
name: string;
|
|
description: string;
|
|
icon: React.ReactNode;
|
|
}
|
|
|
|
interface PendingPaymentViewProps {
|
|
invoice: Invoice | null;
|
|
userCountry: string;
|
|
planName: string;
|
|
planPrice: string;
|
|
onPaymentSuccess: () => void;
|
|
}
|
|
|
|
export default function PendingPaymentView({
|
|
invoice,
|
|
userCountry,
|
|
planName,
|
|
planPrice,
|
|
onPaymentSuccess,
|
|
}: PendingPaymentViewProps) {
|
|
const toast = useToast();
|
|
const [selectedGateway, setSelectedGateway] = useState<PaymentGateway>('stripe');
|
|
const [loading, setLoading] = useState(false);
|
|
const [gatewaysLoading, setGatewaysLoading] = useState(true);
|
|
const [paymentOptions, setPaymentOptions] = useState<PaymentOption[]>([]);
|
|
const [showBankTransfer, setShowBankTransfer] = useState(false);
|
|
|
|
const isPakistan = userCountry === 'PK';
|
|
|
|
// Load available payment gateways
|
|
useEffect(() => {
|
|
const loadGateways = async () => {
|
|
setGatewaysLoading(true);
|
|
try {
|
|
const gateways = await getAvailablePaymentGateways();
|
|
const options: PaymentOption[] = [];
|
|
|
|
// Always show Stripe (Credit Card) if available
|
|
if (gateways.stripe) {
|
|
options.push({
|
|
id: 'stripe',
|
|
type: 'stripe',
|
|
name: 'Credit/Debit Card',
|
|
description: 'Pay securely with Visa, Mastercard, or other cards',
|
|
icon: <CreditCardIcon className="w-6 h-6" />,
|
|
});
|
|
}
|
|
|
|
// For Pakistan: show Bank Transfer
|
|
// For Global: show PayPal
|
|
if (isPakistan) {
|
|
if (gateways.manual) {
|
|
options.push({
|
|
id: 'bank_transfer',
|
|
type: 'manual',
|
|
name: 'Bank Transfer',
|
|
description: 'Pay via local bank transfer (PKR)',
|
|
icon: <Building2Icon className="w-6 h-6" />,
|
|
});
|
|
}
|
|
} else {
|
|
if (gateways.paypal) {
|
|
options.push({
|
|
id: 'paypal',
|
|
type: 'paypal',
|
|
name: 'PayPal',
|
|
description: 'Pay with your PayPal account',
|
|
icon: <PayPalIcon className="w-6 h-6" />,
|
|
});
|
|
}
|
|
}
|
|
|
|
setPaymentOptions(options);
|
|
if (options.length > 0) {
|
|
setSelectedGateway(options[0].type);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load payment gateways:', error);
|
|
// Fallback to Stripe
|
|
setPaymentOptions([{
|
|
id: 'stripe',
|
|
type: 'stripe',
|
|
name: 'Credit/Debit Card',
|
|
description: 'Pay securely with Visa, Mastercard, or other cards',
|
|
icon: <CreditCardIcon className="w-6 h-6" />,
|
|
}]);
|
|
} finally {
|
|
setGatewaysLoading(false);
|
|
}
|
|
};
|
|
|
|
loadGateways();
|
|
}, [isPakistan]);
|
|
|
|
const handlePayNow = async () => {
|
|
if (!invoice) {
|
|
toast?.error?.('No invoice found');
|
|
return;
|
|
}
|
|
|
|
// For bank transfer, show the bank transfer form
|
|
if (selectedGateway === 'manual') {
|
|
setShowBankTransfer(true);
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
try {
|
|
// Get plan ID from invoice subscription
|
|
const planId = invoice.subscription?.plan?.id;
|
|
if (!planId) {
|
|
throw new Error('Plan information not found');
|
|
}
|
|
|
|
// Create checkout session
|
|
const { redirect_url } = await subscribeToPlan(planId.toString(), selectedGateway);
|
|
|
|
// Redirect to payment gateway
|
|
window.location.href = redirect_url;
|
|
} catch (error: any) {
|
|
console.error('Payment initiation failed:', error);
|
|
toast?.error?.(error.message || 'Failed to start payment process');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// If showing bank transfer form
|
|
if (showBankTransfer && invoice) {
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-12 px-4">
|
|
<div className="max-w-2xl mx-auto">
|
|
<button
|
|
onClick={() => setShowBankTransfer(false)}
|
|
className="flex items-center gap-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white mb-6"
|
|
>
|
|
<ArrowLeftIcon className="w-4 h-4" />
|
|
Back to payment options
|
|
</button>
|
|
|
|
<BankTransferForm
|
|
invoice={invoice}
|
|
onSuccess={() => {
|
|
setShowBankTransfer(false);
|
|
onPaymentSuccess();
|
|
}}
|
|
onCancel={() => setShowBankTransfer(false)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-gray-50 via-white to-brand-50 dark:from-gray-900 dark:via-gray-900 dark:to-brand-950 py-12 px-4">
|
|
<div className="max-w-xl mx-auto">
|
|
{/* Header */}
|
|
<div className="text-center mb-8">
|
|
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-brand-100 dark:bg-brand-900/30 mb-4">
|
|
<CheckCircleIcon className="w-8 h-8 text-brand-600 dark:text-brand-400" />
|
|
</div>
|
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
|
|
Account Created!
|
|
</h1>
|
|
<p className="text-gray-600 dark:text-gray-400">
|
|
Complete your payment to activate your <strong>{planName}</strong> plan
|
|
</p>
|
|
</div>
|
|
|
|
{/* Plan Summary Card */}
|
|
<Card className="p-6 mb-6 border border-gray-200 dark:border-gray-700">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">Order Summary</h2>
|
|
<Badge variant="outline" tone="brand">{planName}</Badge>
|
|
</div>
|
|
|
|
<div className="space-y-3 py-4 border-t border-b border-gray-200 dark:border-gray-700">
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-gray-600 dark:text-gray-400">{planName} Plan (Monthly)</span>
|
|
<span className="text-gray-900 dark:text-white">${planPrice}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-between mt-4">
|
|
<span className="text-lg font-semibold text-gray-900 dark:text-white">Total</span>
|
|
<span className="text-2xl font-bold text-brand-600 dark:text-brand-400">${planPrice}</span>
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Payment Method Selection */}
|
|
<Card className="p-6 mb-6 border border-gray-200 dark:border-gray-700">
|
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
|
Select Payment Method
|
|
</h2>
|
|
|
|
{gatewaysLoading ? (
|
|
<div className="flex items-center justify-center py-8">
|
|
<Loader2Icon className="w-6 h-6 animate-spin text-brand-500 mr-2" />
|
|
<span className="text-gray-500">Loading payment options...</span>
|
|
</div>
|
|
) : paymentOptions.length === 0 ? (
|
|
<div className="p-4 bg-warning-50 border border-warning-200 rounded-lg dark:bg-warning-900/20 dark:border-warning-800">
|
|
<div className="flex items-center gap-2">
|
|
<AlertCircleIcon className="w-5 h-5 text-warning-600" />
|
|
<span className="text-warning-800 dark:text-warning-200">No payment methods available</span>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{paymentOptions.map((option) => (
|
|
<button
|
|
key={option.id}
|
|
type="button"
|
|
onClick={() => setSelectedGateway(option.type)}
|
|
className={`relative w-full p-4 rounded-xl border-2 text-left transition-all ${
|
|
selectedGateway === option.type
|
|
? 'border-brand-500 bg-brand-50 dark:bg-brand-900/20'
|
|
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600 bg-white dark:bg-gray-800'
|
|
}`}
|
|
>
|
|
{selectedGateway === option.type && (
|
|
<div className="absolute top-3 right-3">
|
|
<div className="w-5 h-5 bg-brand-500 rounded-full flex items-center justify-center">
|
|
<CheckCircleIcon className="w-3 h-3 text-white" />
|
|
</div>
|
|
</div>
|
|
)}
|
|
<div className="flex items-center gap-4">
|
|
<div className={`flex items-center justify-center w-12 h-12 rounded-lg ${
|
|
selectedGateway === option.type
|
|
? 'bg-brand-500 text-white'
|
|
: 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400'
|
|
}`}>
|
|
{option.icon}
|
|
</div>
|
|
<div>
|
|
<h3 className={`font-semibold ${
|
|
selectedGateway === option.type
|
|
? 'text-brand-700 dark:text-brand-400'
|
|
: 'text-gray-900 dark:text-white'
|
|
}`}>
|
|
{option.name}
|
|
</h3>
|
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
|
{option.description}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Security Badge */}
|
|
<div className="flex items-center gap-2 mt-4 pt-4 border-t border-gray-200 dark:border-gray-700">
|
|
<LockIcon className="w-4 h-4 text-green-600" />
|
|
<span className="text-xs text-gray-500 dark:text-gray-400">
|
|
Your payment information is secure and encrypted
|
|
</span>
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Pay Now Button */}
|
|
<Button
|
|
variant="primary"
|
|
tone="brand"
|
|
size="lg"
|
|
onClick={handlePayNow}
|
|
disabled={loading || gatewaysLoading || paymentOptions.length === 0}
|
|
className="w-full"
|
|
>
|
|
{loading ? (
|
|
<span className="flex items-center justify-center">
|
|
<Loader2Icon className="w-5 h-5 animate-spin mr-2" />
|
|
Processing...
|
|
</span>
|
|
) : selectedGateway === 'manual' ? (
|
|
'Continue to Bank Transfer'
|
|
) : (
|
|
`Pay $${planPrice} Now`
|
|
)}
|
|
</Button>
|
|
|
|
{/* Info text */}
|
|
<p className="mt-4 text-center text-sm text-gray-500 dark:text-gray-400">
|
|
{selectedGateway === 'manual'
|
|
? 'You will receive bank details to complete your transfer'
|
|
: 'You will be redirected to complete payment securely'
|
|
}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|