billing realted PK bank transfer settigns
This commit is contained in:
@@ -559,11 +559,10 @@ export default function PlansAndBillingPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fall back to manual/bank transfer flow
|
||||
await createSubscription({ plan_id: planId, payment_method: selectedPaymentMethod });
|
||||
toast?.success?.('Plan upgraded successfully!');
|
||||
// For manual/bank transfer - use Stripe to create invoice, then pay via bank transfer
|
||||
// This creates the invoice which user can then pay using bank transfer from billing history
|
||||
toast?.info?.('Bank transfer requires an invoice. Please select Card payment to create an invoice, then use "Pay Now" with bank transfer option from Billing History.');
|
||||
setShowUpgradeModal(false);
|
||||
await loadData();
|
||||
} catch (err: any) {
|
||||
handleError(err, 'Failed to upgrade plan');
|
||||
} finally {
|
||||
@@ -597,10 +596,36 @@ export default function PlansAndBillingPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fall back to manual/bank transfer flow
|
||||
await purchaseCreditPackage({ package_id: packageId, payment_method: selectedPaymentMethod as any || 'manual' });
|
||||
toast?.success?.('Credits purchased successfully!');
|
||||
await loadData();
|
||||
// For manual/bank transfer - create invoice and show payment modal
|
||||
const result = await purchaseCreditPackage({
|
||||
package_id: packageId,
|
||||
payment_method: 'bank_transfer'
|
||||
});
|
||||
|
||||
if (result.invoice_id) {
|
||||
// Find the package to get details for the invoice display
|
||||
const pkg = packages.find(p => p.id === packageId);
|
||||
|
||||
// Create invoice object for PayInvoiceModal
|
||||
const invoiceForModal: Invoice = {
|
||||
id: result.invoice_id,
|
||||
invoice_number: result.invoice_number || '',
|
||||
total: result.total_amount || '0',
|
||||
total_amount: result.total_amount || '0',
|
||||
currency: 'USD',
|
||||
status: result.status || 'pending',
|
||||
payment_method: 'bank_transfer',
|
||||
subscription: null, // Credit purchase - no subscription
|
||||
};
|
||||
|
||||
// Show the payment modal
|
||||
setSelectedInvoice(invoiceForModal);
|
||||
setShowPayInvoiceModal(true);
|
||||
|
||||
toast?.success?.(result.message || 'Invoice created! Please submit your payment confirmation.');
|
||||
} else {
|
||||
toast?.error?.(result.message || 'Failed to create invoice');
|
||||
}
|
||||
} catch (err: any) {
|
||||
handleError(err, 'Failed to purchase credits');
|
||||
} finally {
|
||||
@@ -674,12 +699,22 @@ export default function PlansAndBillingPage() {
|
||||
? Math.round((creditBalance.credits_used_this_month / creditBalance.plan_credits_per_month) * 100)
|
||||
: 0;
|
||||
|
||||
// Upgrade plans (exclude current and free)
|
||||
const upgradePlans = plans.filter(p => {
|
||||
// All paid plans for the upgrade panel (includes current plan)
|
||||
const allPaidPlans = plans.filter(p => {
|
||||
const price = Number(p.price) || 0;
|
||||
return price > 0 && p.id !== effectivePlanId;
|
||||
return price > 0;
|
||||
}).sort((a, b) => (Number(a.price) || 0) - (Number(b.price) || 0));
|
||||
|
||||
// Check if user is on the highest plan (by price)
|
||||
const highestPlanPrice = allPaidPlans.length > 0
|
||||
? Math.max(...allPaidPlans.map(p => Number(p.price) || 0))
|
||||
: 0;
|
||||
const currentPlanPrice = Number(currentPlan?.price) || 0;
|
||||
const isOnHighestPlan = currentPlanPrice >= highestPlanPrice && highestPlanPrice > 0;
|
||||
|
||||
// Upgrade plans for modal (exclude current)
|
||||
const upgradePlans = allPaidPlans.filter(p => p.id !== effectivePlanId);
|
||||
|
||||
// PAYMENT PROCESSING OVERLAY - Beautiful full-page loading with breathing badge
|
||||
if (paymentProcessing?.active) {
|
||||
const stageConfig = {
|
||||
@@ -860,14 +895,16 @@ export default function PlansAndBillingPage() {
|
||||
Manage Billing
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="primary"
|
||||
tone="brand"
|
||||
onClick={() => setShowUpgradeModal(true)}
|
||||
startIcon={<ArrowUpIcon className="w-4 h-4" />}
|
||||
>
|
||||
Upgrade
|
||||
</Button>
|
||||
{!isOnHighestPlan && (
|
||||
<Button
|
||||
variant="primary"
|
||||
tone="brand"
|
||||
onClick={() => setShowUpgradeModal(true)}
|
||||
startIcon={<ArrowUpIcon className="w-4 h-4" />}
|
||||
>
|
||||
Upgrade
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1043,14 +1080,14 @@ export default function PlansAndBillingPage() {
|
||||
) : (
|
||||
<>
|
||||
{/* Payment Method Selector - Clear buttons */}
|
||||
{(availableGateways.stripe || availableGateways.paypal) && (
|
||||
{(availableGateways.stripe || availableGateways.paypal || availableGateways.manual) && (
|
||||
<div className="mb-4">
|
||||
<Label className="text-xs text-gray-500 dark:text-gray-400 mb-2 block">Payment Method</Label>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{availableGateways.stripe && (
|
||||
<button
|
||||
onClick={() => setSelectedGateway('stripe')}
|
||||
className={`flex-1 flex items-center justify-center gap-2 px-4 py-2 rounded-lg border-2 transition-all ${
|
||||
className={`flex-1 min-w-[140px] flex items-center justify-center gap-2 px-4 py-2 rounded-lg border-2 transition-all ${
|
||||
selectedGateway === 'stripe'
|
||||
? 'border-brand-500 bg-brand-50 dark:bg-brand-900/20 text-brand-700 dark:text-brand-300'
|
||||
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
|
||||
@@ -1063,7 +1100,7 @@ export default function PlansAndBillingPage() {
|
||||
{availableGateways.paypal && (
|
||||
<button
|
||||
onClick={() => setSelectedGateway('paypal')}
|
||||
className={`flex-1 flex items-center justify-center gap-2 px-4 py-2 rounded-lg border-2 transition-all ${
|
||||
className={`flex-1 min-w-[140px] flex items-center justify-center gap-2 px-4 py-2 rounded-lg border-2 transition-all ${
|
||||
selectedGateway === 'paypal'
|
||||
? 'border-brand-500 bg-brand-50 dark:bg-brand-900/20 text-brand-700 dark:text-brand-300'
|
||||
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
|
||||
@@ -1073,6 +1110,19 @@ export default function PlansAndBillingPage() {
|
||||
<span className="text-sm font-medium">PayPal</span>
|
||||
</button>
|
||||
)}
|
||||
{availableGateways.manual && (
|
||||
<button
|
||||
onClick={() => setSelectedGateway('manual')}
|
||||
className={`flex-1 min-w-[140px] flex items-center justify-center gap-2 px-4 py-2 rounded-lg border-2 transition-all ${
|
||||
selectedGateway === 'manual'
|
||||
? 'border-brand-500 bg-brand-50 dark:bg-brand-900/20 text-brand-700 dark:text-brand-300'
|
||||
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
|
||||
}`}
|
||||
>
|
||||
<Building2Icon className={`w-5 h-5 ${selectedGateway === 'manual' ? 'text-brand-600' : 'text-gray-500'}`} />
|
||||
<span className="text-sm font-medium">Bank Transfer</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -1117,56 +1167,87 @@ export default function PlansAndBillingPage() {
|
||||
<ShootingStarIcon className="w-5 h-5 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900 dark:text-white">Upgrade Plan</h3>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">Get more features & credits</p>
|
||||
<h3 className="font-semibold text-gray-900 dark:text-white">
|
||||
{isOnHighestPlan ? 'Your Plan' : 'Upgrade Plan'}
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{isOnHighestPlan ? 'You are on the highest plan' : 'Get more features & credits'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
tone="brand"
|
||||
onClick={() => setShowUpgradeModal(true)}
|
||||
>
|
||||
View All
|
||||
</Button>
|
||||
{!isOnHighestPlan && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
tone="brand"
|
||||
onClick={() => setShowUpgradeModal(true)}
|
||||
>
|
||||
View All
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{upgradePlans.slice(0, 3).map((plan) => (
|
||||
<div
|
||||
key={plan.id}
|
||||
className="p-4 border border-gray-200 dark:border-gray-700 rounded-xl flex items-center justify-between hover:border-purple-300 dark:hover:border-purple-700 transition-colors"
|
||||
>
|
||||
<div>
|
||||
<div className="font-semibold text-gray-900 dark:text-white">{plan.name}</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{plan.included_credits?.toLocaleString() || 0} credits/mo
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="font-bold text-gray-900 dark:text-white">
|
||||
{formatCurrency(plan.price, 'USD')}/mo
|
||||
</div>
|
||||
{billingCountry === 'PK' && (
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||
≈ PKR {convertUSDToPKR(plan.price).toLocaleString()}/mo
|
||||
{allPaidPlans.map((plan) => {
|
||||
const isCurrentPlan = plan.id === effectivePlanId;
|
||||
const currentPriceVal = Number(currentPlan?.price) || 0;
|
||||
const thisPlanPrice = Number(plan.price) || 0;
|
||||
const isUpgrade = thisPlanPrice > currentPriceVal;
|
||||
const isDowngrade = thisPlanPrice < currentPriceVal;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={plan.id}
|
||||
className={`p-4 border rounded-xl flex items-center justify-between transition-colors ${
|
||||
isCurrentPlan
|
||||
? 'border-brand-500 bg-brand-50/50 dark:bg-brand-900/20'
|
||||
: 'border-gray-200 dark:border-gray-700 hover:border-purple-300 dark:hover:border-purple-700'
|
||||
}`}
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-semibold text-gray-900 dark:text-white">{plan.name}</span>
|
||||
{isCurrentPlan && (
|
||||
<Badge variant="soft" tone="brand" size="sm">Current</Badge>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
tone="brand"
|
||||
onClick={() => handleSelectPlan(plan.id)}
|
||||
disabled={planLoadingId === plan.id}
|
||||
>
|
||||
{planLoadingId === plan.id ? <Loader2Icon className="w-4 h-4 animate-spin" /> : 'Select'}
|
||||
</Button>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{plan.included_credits?.toLocaleString() || 0} credits/mo
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="font-bold text-gray-900 dark:text-white">
|
||||
{formatCurrency(plan.price, 'USD')}/mo
|
||||
</div>
|
||||
{billingCountry === 'PK' && (
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||
≈ PKR {convertUSDToPKR(plan.price).toLocaleString()}/mo
|
||||
</div>
|
||||
)}
|
||||
{!isCurrentPlan && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
tone={isUpgrade ? 'brand' : 'neutral'}
|
||||
onClick={() => handleSelectPlan(plan.id)}
|
||||
disabled={planLoadingId === plan.id}
|
||||
>
|
||||
{planLoadingId === plan.id ? (
|
||||
<Loader2Icon className="w-4 h-4 animate-spin" />
|
||||
) : isUpgrade ? (
|
||||
'Upgrade'
|
||||
) : (
|
||||
'Downgrade'
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{upgradePlans.length === 0 && (
|
||||
);
|
||||
})}
|
||||
{allPaidPlans.length === 0 && (
|
||||
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
<CheckCircleIcon className="w-8 h-8 mx-auto mb-2 text-success-500" />
|
||||
<p>You're on the best plan!</p>
|
||||
<p>No plans available</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -1360,8 +1441,10 @@ export default function PlansAndBillingPage() {
|
||||
|
||||
{/* Upgrade Modal */}
|
||||
{showUpgradeModal && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white dark:bg-gray-900 rounded-2xl shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-auto">
|
||||
<div className="fixed inset-0 z-99999 flex items-center justify-center overflow-y-auto modal">
|
||||
{/* Backdrop */}
|
||||
<div className="fixed inset-0 h-full w-full bg-gray-400/50 backdrop-blur-[32px] dark:bg-gray-900/70" onClick={() => setShowUpgradeModal(false)}></div>
|
||||
<div className="relative bg-white dark:bg-gray-900 rounded-2xl shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-auto mx-4">
|
||||
<div className="sticky top-0 bg-white dark:bg-gray-900 flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700 z-10">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-gray-900 dark:text-white">Choose Your Plan</h2>
|
||||
@@ -1449,7 +1532,7 @@ export default function PlansAndBillingPage() {
|
||||
</div>
|
||||
|
||||
{/* Plans Grid */}
|
||||
<div className="p-6 grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="p-6 grid grid-cols-1 md:grid-cols-2 gap-4 max-w-3xl mx-auto">
|
||||
{upgradePlans.map((plan, index) => {
|
||||
const isPopular = index === 1;
|
||||
const planPrice = Number(plan.price) || 0;
|
||||
|
||||
Reference in New Issue
Block a user