Files
HRM-System/resources/js/components/payment/payment-processor.tsx
2026-04-13 08:16:56 +08:00

629 lines
21 KiB
TypeScript
Executable File

import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Card, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Tag, Loader2 } from 'lucide-react';
import { toast } from '@/components/custom-toast';
import { StripePaymentForm } from './stripe-payment-form';
import { PayPalPaymentForm } from './paypal-payment-form';
import { BankTransferForm } from './bank-transfer-form';
import { RazorpayPaymentForm } from './razorpay-payment-form';
import { MercadoPagoPaymentForm } from './mercadopago-payment-form';
import { PaystackPaymentForm } from './paystack-payment-form';
import { FlutterwavePaymentForm } from './flutterwave-payment-form';
import { PayTabsPaymentForm } from './paytabs-payment-form';
import { SkrillPaymentForm } from './skrill-payment-form';
import { CoinGatePaymentForm } from './coingate-payment-form';
import { PayfastPaymentForm } from './payfast-payment-form';
import { ToyyibPayPaymentForm } from './toyyibpay-payment-form';
import { PayTRPaymentForm } from './paytr-payment-form';
import { MolliePaymentForm } from './mollie-payment-form';
import { CashfreePaymentForm } from './cashfree-payment-form';
import { IyzipayPaymentForm } from './iyzipay-payment-form';
import { BenefitPaymentForm } from './benefit-payment-form';
import { OzowPaymentForm } from './ozow-payment-form';
import { EasebuzzPaymentForm } from './easebuzz-payment-form';
import { KhaltiPaymentForm } from './khalti-payment-form';
import { AuthorizeNetPaymentForm } from './authorizenet-payment-form';
import { FedaPayPaymentForm } from './fedapay-payment-form';
import { PayHerePaymentForm } from './payhere-payment-form';
import { CinetPayPaymentForm } from './cinetpay-payment-form';
import { PaiementPaymentForm } from './paiement-payment-form';
import { NepalstePaymentForm } from './nepalste-payment-form';
import { YooKassaPaymentForm } from './yookassa-payment-form';
import { AamarpayPaymentForm } from './aamarpay-payment-form';
import { MidtransPaymentForm } from './midtrans-payment-form';
import { PaymentWallPaymentForm } from './paymentwall-payment-form';
import { SSPayPaymentForm } from './sspay-payment-form';
import { TapPaymentForm } from './tap-payment-form';
import { XenditPaymentForm } from './xendit-payment-form';
interface PaymentMethod {
id: string;
name: string;
icon: React.ReactNode;
enabled: boolean;
currency?: string;
currencySymbol?: string;
}
interface PaymentProcessorProps {
plan: {
id: number;
name: string;
price: string | number;
duration: string;
paymentMethods?: any;
};
billingCycle: 'monthly' | 'yearly';
paymentMethods: PaymentMethod[];
currencySymbol?: string;
onSuccess: () => void;
onCancel: () => void;
}
export function PaymentProcessor({
plan,
billingCycle,
paymentMethods,
currencySymbol = '$',
onSuccess,
onCancel
}: PaymentProcessorProps) {
const { t } = useTranslation();
// Helper function to safely format currency
const formatCurrency = (amount: string | number) => {
if (typeof window !== 'undefined' && window.appSettings?.formatCurrency) {
const numericAmount = typeof amount === 'number' ? amount : parseFloat(amount);
return window.appSettings.formatCurrency(numericAmount, { showSymbol: true });
}
return amount;
};
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<string>('');
const [couponCode, setCouponCode] = useState('');
const [appliedCoupon, setAppliedCoupon] = useState<any>(null);
const [couponLoading, setCouponLoading] = useState(false);
const [showPaymentForm, setShowPaymentForm] = useState(false);
const originalPrice = Number(plan.price);
const discountAmount = appliedCoupon ? (appliedCoupon.type === 'percentage' ? (originalPrice * appliedCoupon.value / 100) : appliedCoupon.value) : 0;
const finalPrice = Math.max(0, originalPrice - discountAmount);
const handleApplyCoupon = async () => {
if (!couponCode.trim()) {
toast.error(t('Please enter a coupon code'));
return;
}
setCouponLoading(true);
try {
const response = await fetch(route('coupons.validate'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
},
body: JSON.stringify({
coupon_code: couponCode,
plan_id: plan.id,
amount: originalPrice
})
});
const data = await response.json();
if (response.ok && data.valid) {
setAppliedCoupon(data.coupon);
toast.success(t('Coupon applied successfully'));
} else {
toast.error(data.message || t('Invalid coupon code'));
setAppliedCoupon(null);
}
} catch (error) {
toast.error(t('Failed to validate coupon'));
setAppliedCoupon(null);
} finally {
setCouponLoading(false);
}
};
const handleRemoveCoupon = () => {
setAppliedCoupon(null);
setCouponCode('');
};
const handlePayNow = () => {
if (!selectedPaymentMethod) {
toast.error(t('Please select a payment method'));
return;
}
setShowPaymentForm(true);
};
const handlePaymentCancel = () => {
setShowPaymentForm(false);
setSelectedPaymentMethod('');
};
const enabledPaymentMethods = paymentMethods.filter(method => method.enabled);
const renderPaymentForm = () => {
const commonProps = {
planId: plan.id,
couponCode,
billingCycle,
onSuccess,
onCancel: handlePaymentCancel,
};
switch (selectedPaymentMethod) {
case 'stripe':
return (
<StripePaymentForm
{...commonProps}
stripeKey={plan.paymentMethods?.stripe_key || ''}
/>
);
case 'paypal':
return (
<PayPalPaymentForm
{...commonProps}
planPrice={finalPrice}
paypalClientId={plan.paymentMethods?.paypal_client_id || ''}
currency={plan.paymentMethods?.defaultCurrency || 'usd'}
/>
);
case 'bank':
return (
<BankTransferForm
{...commonProps}
planPrice={finalPrice}
bankDetails={plan.paymentMethods?.bank_detail || ''}
currencySymbol={currencySymbol}
/>
);
case 'razorpay':
return (
<RazorpayPaymentForm
{...commonProps}
planPrice={finalPrice}
razorpayKey={plan.paymentMethods?.razorpay_key || ''}
currency={plan.paymentMethods?.currency || 'INR'}
/>
);
case 'mercadopago':
return (
<MercadoPagoPaymentForm
{...commonProps}
planPrice={finalPrice}
accessToken={plan.paymentMethods?.mercadopago_access_token || ''}
currency={plan.paymentMethods?.currency || 'BRL'}
/>
);
case 'paystack':
return (
<PaystackPaymentForm
{...commonProps}
planPrice={finalPrice}
paystackKey={plan.paymentMethods?.paystack_public_key || ''}
currency={plan.paymentMethods?.currency || 'NGN'}
/>
);
case 'flutterwave':
return (
<FlutterwavePaymentForm
{...commonProps}
planPrice={finalPrice}
flutterwaveKey={plan.paymentMethods?.flutterwave_public_key || ''}
currency={plan.paymentMethods?.currency || 'NGN'}
/>
);
case 'paytabs':
return (
<PayTabsPaymentForm
{...commonProps}
planPrice={finalPrice}
paytabsClientKey={''}
currency={plan.paymentMethods?.currency || 'USD'}
/>
);
case 'skrill':
return (
<SkrillPaymentForm
{...commonProps}
planPrice={finalPrice}
skrillMerchantId={plan.paymentMethods?.skrill_merchant_id || ''}
currency={plan.paymentMethods?.currency || 'USD'}
/>
);
case 'coingate':
return (
<CoinGatePaymentForm
{...commonProps}
planPrice={finalPrice}
coinGateApiToken={plan.paymentMethods?.coingate_api_token || ''}
currency={plan.paymentMethods?.currency || 'USD'}
/>
);
case 'payfast':
return (
<PayfastPaymentForm
{...commonProps}
planPrice={finalPrice}
payfastMerchantId={plan.paymentMethods?.payfast_merchant_id || ''}
currency={plan.paymentMethods?.currency || 'ZAR'}
/>
);
case 'toyyibpay':
return (
<ToyyibPayPaymentForm
{...commonProps}
planPrice={finalPrice}
toyyibpayCategoryCode={plan.paymentMethods?.toyyibpay_category_code || ''}
currency={plan.paymentMethods?.currency || 'MYR'}
/>
);
case 'paytr':
return (
<PayTRPaymentForm
{...commonProps}
planPrice={finalPrice}
paytrMerchantId={plan.paymentMethods?.paytr_merchant_id || ''}
currency={plan.paymentMethods?.currency || 'TRY'}
/>
);
case 'mollie':
return (
<MolliePaymentForm
{...commonProps}
planPrice={finalPrice}
mollieApiKey={plan.paymentMethods?.mollie_api_key || ''}
currency={plan.paymentMethods?.currency || 'EUR'}
/>
);
case 'cashfree':
return (
<CashfreePaymentForm
{...commonProps}
planPrice={finalPrice}
cashfreeAppId={plan.paymentMethods?.cashfree_public_key || ''}
mode={plan.paymentMethods?.cashfree_mode || 'sandbox'}
currency={plan.paymentMethods?.currency || 'INR'}
/>
);
case 'iyzipay':
return (
<IyzipayPaymentForm
{...commonProps}
planPrice={finalPrice}
iyzipayPublicKey={plan.paymentMethods?.iyzipay_public_key || ''}
currency={plan.paymentMethods?.currency || 'USD'}
/>
);
case 'benefit':
return (
<BenefitPaymentForm
{...commonProps}
planPrice={finalPrice}
benefitPublicKey={plan.paymentMethods?.benefit_public_key || ''}
currency={plan.paymentMethods?.currency || 'BHD'}
/>
);
case 'ozow':
return (
<OzowPaymentForm
{...commonProps}
planPrice={finalPrice}
ozowSiteKey={plan.paymentMethods?.ozow_site_key || ''}
currency={plan.paymentMethods?.currency || 'ZAR'}
/>
);
case 'easebuzz':
return (
<EasebuzzPaymentForm
{...commonProps}
planPrice={finalPrice}
easebuzzMerchantKey={plan.paymentMethods?.easebuzz_merchant_key || ''}
currency={plan.paymentMethods?.currency || 'INR'}
/>
);
case 'khalti':
return (
<KhaltiPaymentForm
{...commonProps}
planPrice={finalPrice}
khaltiPublicKey={plan.paymentMethods?.khalti_public_key || ''}
currency={plan.paymentMethods?.currency || 'NPR'}
/>
);
case 'authorizenet':
return (
<AuthorizeNetPaymentForm
{...commonProps}
planPrice={finalPrice}
authorizenetMerchantId={plan.paymentMethods?.authorizenet_merchant_id || ''}
currency={plan.paymentMethods?.currency || 'USD'}
/>
);
case 'fedapay':
return (
<FedaPayPaymentForm
{...commonProps}
planPrice={finalPrice}
fedapayPublicKey={plan.paymentMethods?.fedapay_public_key || ''}
currency={plan.paymentMethods?.currency || 'XOF'}
/>
);
case 'payhere':
return (
<PayHerePaymentForm
{...commonProps}
planPrice={finalPrice}
payhereMerchantId={plan.paymentMethods?.payhere_merchant_id || ''}
currency={plan.paymentMethods?.currency || 'LKR'}
/>
);
case 'cinetpay':
return (
<CinetPayPaymentForm
{...commonProps}
planPrice={finalPrice}
cinetpaySiteId={plan.paymentMethods?.cinetpay_site_id || ''}
currency={plan.paymentMethods?.currency || 'XOF'}
/>
);
case 'paiement':
return (
<PaiementPaymentForm
{...commonProps}
planPrice={finalPrice}
paiementMerchantId={plan.paymentMethods?.paiement_merchant_id || ''}
currency={plan.paymentMethods?.currency || 'XOF'}
/>
);
case 'nepalste':
return (
<NepalstePaymentForm
{...commonProps}
planPrice={finalPrice}
nepalstePublicKey={plan.paymentMethods?.nepalste_public_key || ''}
currency={plan.paymentMethods?.currency || 'NPR'}
/>
);
case 'yookassa':
return (
<YooKassaPaymentForm
{...commonProps}
planPrice={finalPrice}
yookassaShopId={plan.paymentMethods?.yookassa_shop_id || ''}
currency={plan.paymentMethods?.currency || 'RUB'}
/>
);
case 'aamarpay':
return (
<AamarpayPaymentForm
{...commonProps}
planPrice={finalPrice}
aamarpayStoreId={plan.paymentMethods?.aamarpay_store_id || ''}
currency={plan.paymentMethods?.currency || 'BDT'}
/>
);
case 'midtrans':
return (
<MidtransPaymentForm
{...commonProps}
planPrice={finalPrice}
midtransSecretKey={plan.paymentMethods?.midtrans_secret_key || ''}
currency={plan.paymentMethods?.currency || 'IDR'}
/>
);
case 'paymentwall':
return (
<PaymentWallPaymentForm
{...commonProps}
planPrice={finalPrice}
paymentwallPublicKey={plan.paymentMethods?.paymentwall_public_key || ''}
currency={plan.paymentMethods?.currency || 'USD'}
/>
);
case 'sspay':
return (
<SSPayPaymentForm
{...commonProps}
planPrice={finalPrice}
sspaySecretKey={plan.paymentMethods?.sspay_secret_key || ''}
currency={plan.paymentMethods?.currency || 'MYR'}
/>
);
case 'tap':
return (
<TapPaymentForm
{...commonProps}
planPrice={finalPrice}
tapSecretKey={plan.paymentMethods?.tap_secret_key || ''}
currency={plan.paymentMethods?.currency || 'USD'}
/>
);
case 'xendit':
return (
<XenditPaymentForm
{...commonProps}
planPrice={finalPrice}
xenditApiKey={plan.paymentMethods?.xendit_api_key || ''}
currency={plan.paymentMethods?.currency || 'PHP'}
/>
);
default:
return null;
}
};
if (showPaymentForm) {
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="font-medium">{t('Complete Payment')}</h3>
<Button variant="outline" size="sm" onClick={handlePaymentCancel}>
{t('Back')}
</Button>
</div>
{renderPaymentForm()}
</div>
);
}
return (
<div className="space-y-6">
{/* Plan Summary */}
<Card>
<CardContent className="p-4">
<div className="flex justify-between items-center">
<div>
<h3 className="font-medium">{plan.name}</h3>
<p className="text-sm text-muted-foreground">
{t(billingCycle)} {t('subscription')}
</p>
</div>
<div className="text-right">
<div className="text-lg font-bold">{currencySymbol} {plan.price}</div>
<div className="text-sm text-muted-foreground">
/{t(plan.duration.toLowerCase())}
</div>
</div>
</div>
</CardContent>
</Card>
{/* Payment Methods */}
<div className="space-y-3">
<Label>{t('Select Payment Method')}</Label>
{enabledPaymentMethods.length === 0 ? (
<p className="text-sm text-muted-foreground">
{t('No payment methods available')}
</p>
) : (
<div className="space-y-2">
{enabledPaymentMethods.map((method, index) => (
<Card
key={`${method.id}-${index}`}
className={`cursor-pointer transition-colors ${
selectedPaymentMethod === method.id
? 'border-primary bg-primary/5'
: 'hover:border-gray-300'
}`}
onClick={() => setSelectedPaymentMethod(method.id)}
>
<CardContent className="p-3">
<div className="flex items-center gap-3">
<div className="text-primary">{method.icon}</div>
<span className="font-medium">{method.name}</span>
{selectedPaymentMethod === method.id && (
<Badge variant="secondary" className="ml-auto">
{t('Selected')}
</Badge>
)}
</div>
</CardContent>
</Card>
))}
</div>
)}
</div>
{/* Coupon Code */}
<div className="space-y-3">
<Label htmlFor="coupon">{t('Coupon Code')} ({t('Optional')})</Label>
<div className="flex gap-2">
<div className="relative flex-1">
<Input
id="coupon"
value={couponCode}
onChange={(e) => setCouponCode(e.target.value)}
placeholder={t('Enter coupon code')}
className="pr-10"
disabled={!!appliedCoupon}
/>
<Tag className="absolute right-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
</div>
{!appliedCoupon ? (
<Button
type="button"
variant="outline"
onClick={handleApplyCoupon}
disabled={!couponCode.trim() || couponLoading}
>
{couponLoading ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
t('Apply')
)}
</Button>
) : (
<Button
type="button"
variant="outline"
onClick={handleRemoveCoupon}
>
{t('Remove')}
</Button>
)}
</div>
{appliedCoupon && (
<div className="bg-green-50 border border-green-200 rounded-lg p-3">
<div className="flex items-center justify-between text-sm">
<span className="text-green-700 font-medium">
{t('Coupon Applied')}: {appliedCoupon.code}
</span>
<span className="text-green-600">
-{appliedCoupon.type === 'percentage' ? `${appliedCoupon.value}%` : `${currencySymbol}${appliedCoupon.value}`}
</span>
</div>
</div>
)}
</div>
{/* Price Summary */}
<Card>
<CardContent className="p-4">
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span>{t('Subtotal')}</span>
<span>{currencySymbol}{originalPrice}</span>
</div>
{appliedCoupon && (
<div className="flex justify-between text-sm text-green-600">
<span>{t('Discount')}</span>
<span>-{currencySymbol}{discountAmount}</span>
</div>
)}
<div className="border-t pt-2">
<div className="flex justify-between font-medium">
<span>{t('Total')}</span>
<span>{currencySymbol}{finalPrice.toFixed(2)}</span>
</div>
</div>
</div>
</CardContent>
</Card>
{/* Actions */}
<div className="flex gap-3">
<Button variant="outline" onClick={onCancel} className="flex-1">
{t('Cancel')}
</Button>
<Button
onClick={handlePayNow}
disabled={enabledPaymentMethods.length === 0}
className="flex-1"
>
{t('Pay')} {currencySymbol} {finalPrice}
</Button>
</div>
</div>
);
}