feat: Connect Payments page to real WooCommerce API
✅ Phase 1 Frontend Complete! 🎨 Payments.tsx - Complete Rewrite: - Replaced mock data with real API calls - useQuery to fetch gateways from /payments/gateways - useMutation for toggle and save operations - Optimistic updates for instant UI feedback - Refetch on window focus (5 min stale time) - Manual refresh button - Loading states with spinner - Empty states with helpful messages - Error handling with toast notifications 🏗️ Gateway Categorization: - Manual methods (Bank Transfer, COD, Check) - Payment providers (Stripe, PayPal, etc.) - Other WC-compliant gateways - Auto-discovers all installed gateways 🎯 Features: - Enable/disable toggle with optimistic updates - Manage button opens settings modal - GenericGatewayForm for configuration - Requirements checking (SSL, extensions) - Link to WC settings for complex cases - Responsive design - Keyboard accessible 📋 Checklist Progress: - [x] PaymentGatewaysProvider.php - [x] PaymentsController.php - [x] GenericGatewayForm.tsx - [x] Update Payments.tsx with real API - [ ] Test with real WooCommerce (next) 🎉 Backend + Frontend integration complete! Ready for testing with actual WooCommerce installation.
This commit is contained in:
@@ -37,7 +37,11 @@ interface GenericGatewayFormProps {
|
|||||||
gateway: {
|
gateway: {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
settings: GatewaySettings;
|
settings: {
|
||||||
|
basic: Record<string, GatewayField>;
|
||||||
|
api: Record<string, GatewayField>;
|
||||||
|
advanced: Record<string, GatewayField>;
|
||||||
|
};
|
||||||
wc_settings_url: string;
|
wc_settings_url: string;
|
||||||
};
|
};
|
||||||
onSave: (settings: Record<string, unknown>) => Promise<void>;
|
onSave: (settings: Record<string, unknown>) => Promise<void>;
|
||||||
|
|||||||
@@ -1,237 +1,352 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { api } from '@/lib/api';
|
||||||
import { SettingsLayout } from './components/SettingsLayout';
|
import { SettingsLayout } from './components/SettingsLayout';
|
||||||
import { SettingsCard } from './components/SettingsCard';
|
import { SettingsCard } from './components/SettingsCard';
|
||||||
import { ToggleField } from './components/ToggleField';
|
import { ToggleField } from './components/ToggleField';
|
||||||
|
import { GenericGatewayForm } from '@/components/settings/GenericGatewayForm';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { CreditCard, DollarSign, Banknote, Settings } from 'lucide-react';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
|
import { CreditCard, Banknote, Settings, RefreshCw, ExternalLink, Loader2, AlertTriangle } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
interface PaymentProvider {
|
interface GatewayField {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
type: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
default: string | boolean;
|
||||||
|
placeholder?: string;
|
||||||
|
required: boolean;
|
||||||
|
options?: Record<string, string>;
|
||||||
|
custom_attributes?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PaymentGateway {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
icon: React.ReactNode;
|
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
connected: boolean;
|
type: 'manual' | 'provider' | 'other';
|
||||||
fees?: string;
|
icon: string;
|
||||||
testMode?: boolean;
|
method_title: string;
|
||||||
|
method_description: string;
|
||||||
|
supports: string[];
|
||||||
|
requirements: {
|
||||||
|
met: boolean;
|
||||||
|
missing: string[];
|
||||||
|
};
|
||||||
|
settings: {
|
||||||
|
basic: Record<string, GatewayField>;
|
||||||
|
api: Record<string, GatewayField>;
|
||||||
|
advanced: Record<string, GatewayField>;
|
||||||
|
};
|
||||||
|
has_fields: boolean;
|
||||||
|
webhook_url: string | null;
|
||||||
|
has_custom_ui: boolean;
|
||||||
|
wc_settings_url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PaymentsPage() {
|
export default function PaymentsPage() {
|
||||||
const [testMode, setTestMode] = useState(false);
|
const queryClient = useQueryClient();
|
||||||
const [providers] = useState<PaymentProvider[]>([
|
const [selectedGateway, setSelectedGateway] = useState<PaymentGateway | null>(null);
|
||||||
{
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
id: 'stripe',
|
|
||||||
name: 'Stripe Payments',
|
|
||||||
description: 'Accept Visa, Mastercard, Amex, and more',
|
|
||||||
icon: <CreditCard className="h-6 w-6" />,
|
|
||||||
enabled: false,
|
|
||||||
connected: false,
|
|
||||||
fees: '2.9% + $0.30 per transaction',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'paypal',
|
|
||||||
name: 'PayPal',
|
|
||||||
description: 'Accept PayPal payments worldwide',
|
|
||||||
icon: <DollarSign className="h-6 w-6" />,
|
|
||||||
enabled: true,
|
|
||||||
connected: true,
|
|
||||||
fees: '3.49% + fixed fee per transaction',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [manualMethods, setManualMethods] = useState([
|
// Fetch all payment gateways
|
||||||
{ id: 'bacs', name: 'Bank Transfer (BACS)', enabled: true },
|
const { data: gateways = [], isLoading, refetch } = useQuery({
|
||||||
{ id: 'cod', name: 'Cash on Delivery', enabled: true },
|
queryKey: ['payment-gateways'],
|
||||||
{ id: 'cheque', name: 'Check Payments', enabled: false },
|
queryFn: () => api.get('/payments/gateways'),
|
||||||
]);
|
refetchOnWindowFocus: true,
|
||||||
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||||
|
});
|
||||||
|
|
||||||
const handleSave = async () => {
|
// Toggle gateway mutation
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
const toggleMutation = useMutation({
|
||||||
toast.success('Payment settings have been updated successfully.');
|
mutationFn: ({ id, enabled }: { id: string; enabled: boolean }) =>
|
||||||
|
api.post(`/payments/gateways/${id}/toggle`, { enabled }),
|
||||||
|
onMutate: async ({ id, enabled }) => {
|
||||||
|
// Optimistic update
|
||||||
|
await queryClient.cancelQueries({ queryKey: ['payment-gateways'] });
|
||||||
|
const previous = queryClient.getQueryData(['payment-gateways']);
|
||||||
|
|
||||||
|
queryClient.setQueryData(['payment-gateways'], (old: PaymentGateway[]) =>
|
||||||
|
old.map((g) => (g.id === id ? { ...g, enabled } : g))
|
||||||
|
);
|
||||||
|
|
||||||
|
return { previous };
|
||||||
|
},
|
||||||
|
onError: (_err, _variables, context) => {
|
||||||
|
queryClient.setQueryData(['payment-gateways'], context?.previous);
|
||||||
|
toast.error('Failed to update gateway');
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('Gateway updated successfully');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save gateway settings mutation
|
||||||
|
const saveMutation = useMutation({
|
||||||
|
mutationFn: ({ id, settings }: { id: string; settings: Record<string, unknown> }) =>
|
||||||
|
api.post(`/payments/gateways/${id}`, settings),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['payment-gateways'] });
|
||||||
|
toast.success('Settings saved successfully');
|
||||||
|
setIsModalOpen(false);
|
||||||
|
setSelectedGateway(null);
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast.error('Failed to save settings');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleToggle = (id: string, enabled: boolean) => {
|
||||||
|
toggleMutation.mutate({ id, enabled });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleManageGateway = (gateway: PaymentGateway) => {
|
||||||
|
setSelectedGateway(gateway);
|
||||||
|
setIsModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
const toggleManualMethod = (id: string) => {
|
const handleSaveGateway = async (settings: Record<string, unknown>) => {
|
||||||
setManualMethods((prev) =>
|
if (!selectedGateway) return;
|
||||||
prev.map((m) => (m.id === id ? { ...m, enabled: !m.enabled } : m))
|
await saveMutation.mutateAsync({ id: selectedGateway.id, settings });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Categorize gateways
|
||||||
|
const manualGateways = gateways.filter((g: PaymentGateway) => g.type === 'manual');
|
||||||
|
const providerGateways = gateways.filter((g: PaymentGateway) => g.type === 'provider');
|
||||||
|
const otherGateways = gateways.filter((g: PaymentGateway) => g.type === 'other');
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<SettingsLayout title="Payments" description="Manage how you get paid">
|
||||||
|
<div className="flex items-center justify-center py-12">
|
||||||
|
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
</SettingsLayout>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsLayout
|
<>
|
||||||
title="Payments"
|
<SettingsLayout title="Payments" description="Manage how you get paid">
|
||||||
description="Manage how you get paid"
|
{/* Refresh button */}
|
||||||
onSave={handleSave}
|
<div className="flex justify-end mb-4">
|
||||||
>
|
<Button
|
||||||
{/* Manual Payment Methods - First priority */}
|
variant="outline"
|
||||||
<SettingsCard
|
size="sm"
|
||||||
title="Manual Payment Methods"
|
onClick={() => refetch()}
|
||||||
description="Accept payments outside your online store"
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<RefreshCw className="h-4 w-4 mr-2" />
|
||||||
{manualMethods.map((method) => (
|
Refresh
|
||||||
<div
|
</Button>
|
||||||
key={method.id}
|
|
||||||
className="border rounded-lg p-4"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="p-2 bg-muted rounded-lg">
|
|
||||||
<Banknote className="h-5 w-5 text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-medium">{method.name}</h3>
|
|
||||||
{method.enabled && (
|
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
|
||||||
Customers can choose this at checkout
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{method.enabled && (
|
|
||||||
<Button variant="ghost" size="sm">
|
|
||||||
<Settings className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<ToggleField
|
|
||||||
id={method.id}
|
|
||||||
label=""
|
|
||||||
checked={method.enabled}
|
|
||||||
onCheckedChange={() => toggleManualMethod(method.id)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</SettingsCard>
|
|
||||||
|
|
||||||
{/* Payment Providers - Second priority */}
|
{/* Manual Payment Methods - First priority */}
|
||||||
<SettingsCard
|
<SettingsCard
|
||||||
title="Payment Providers"
|
title="Manual Payment Methods"
|
||||||
description="Accept credit cards and digital wallets"
|
description="Accept payments outside your online store"
|
||||||
>
|
>
|
||||||
<div className="space-y-4">
|
{manualGateways.length === 0 ? (
|
||||||
{providers.map((provider) => (
|
<p className="text-sm text-muted-foreground">No manual payment methods available</p>
|
||||||
<div
|
) : (
|
||||||
key={provider.id}
|
<div className="space-y-4">
|
||||||
className="border rounded-lg p-4 hover:border-primary/50 transition-colors"
|
{manualGateways.map((gateway: PaymentGateway) => (
|
||||||
>
|
<div
|
||||||
<div className="flex items-start justify-between">
|
key={gateway.id}
|
||||||
<div className="flex items-start gap-4 flex-1">
|
className="border rounded-lg p-4"
|
||||||
<div className="p-2 bg-primary/10 rounded-lg text-primary">
|
>
|
||||||
{provider.icon}
|
<div className="flex items-center justify-between">
|
||||||
</div>
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex-1">
|
<div className="p-2 bg-muted rounded-lg">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<Banknote className="h-5 w-5 text-muted-foreground" />
|
||||||
<h3 className="font-semibold">{provider.name}</h3>
|
</div>
|
||||||
{provider.connected ? (
|
<div>
|
||||||
<Badge variant="default" className="bg-green-500">
|
<h3 className="font-medium">{gateway.title}</h3>
|
||||||
● Connected
|
{gateway.description && (
|
||||||
</Badge>
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
) : (
|
{gateway.description}
|
||||||
<Badge variant="secondary">○ Not connected</Badge>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{gateway.enabled && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleManageGateway(gateway)}
|
||||||
|
>
|
||||||
|
<Settings className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<ToggleField
|
||||||
|
id={gateway.id}
|
||||||
|
label=""
|
||||||
|
checked={gateway.enabled}
|
||||||
|
onCheckedChange={(checked) => handleToggle(gateway.id, checked)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground mb-2">
|
|
||||||
{provider.description}
|
|
||||||
</p>
|
|
||||||
{provider.fees && (
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{provider.fees}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
))}
|
||||||
{provider.connected ? (
|
</div>
|
||||||
<>
|
)}
|
||||||
<Button variant="outline" size="sm">
|
</SettingsCard>
|
||||||
|
|
||||||
|
{/* Payment Providers - Second priority */}
|
||||||
|
<SettingsCard
|
||||||
|
title="Payment Providers"
|
||||||
|
description="Accept credit cards and digital wallets"
|
||||||
|
>
|
||||||
|
{providerGateways.length === 0 ? (
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
No payment providers installed.{' '}
|
||||||
|
<a
|
||||||
|
href="https://woocommerce.com/product-category/woocommerce-extensions/payment-gateways/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline inline-flex items-center gap-1"
|
||||||
|
>
|
||||||
|
Browse payment gateways
|
||||||
|
<ExternalLink className="h-3 w-3" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{providerGateways.map((gateway: PaymentGateway) => (
|
||||||
|
<div
|
||||||
|
key={gateway.id}
|
||||||
|
className="border rounded-lg p-4 hover:border-primary/50 transition-colors"
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<div className="flex items-start gap-4 flex-1">
|
||||||
|
<div className="p-2 bg-primary/10 rounded-lg text-primary">
|
||||||
|
<CreditCard className="h-6 w-6" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center gap-2 mb-1">
|
||||||
|
<h3 className="font-semibold">{gateway.title}</h3>
|
||||||
|
{gateway.enabled ? (
|
||||||
|
<Badge variant="default" className="bg-green-500">
|
||||||
|
● Enabled
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<Badge variant="secondary">○ Disabled</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground mb-2">
|
||||||
|
{gateway.description}
|
||||||
|
</p>
|
||||||
|
{!gateway.requirements.met && (
|
||||||
|
<Alert variant="destructive" className="mt-2">
|
||||||
|
<AlertTriangle className="h-4 w-4" />
|
||||||
|
<AlertDescription>
|
||||||
|
Requirements not met: {gateway.requirements.missing.join(', ')}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleManageGateway(gateway)}
|
||||||
|
>
|
||||||
<Settings className="h-4 w-4 mr-2" />
|
<Settings className="h-4 w-4 mr-2" />
|
||||||
Manage
|
Manage
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="sm">
|
<ToggleField
|
||||||
Disconnect
|
id={gateway.id}
|
||||||
</Button>
|
label=""
|
||||||
</>
|
checked={gateway.enabled}
|
||||||
) : (
|
onCheckedChange={(checked) => handleToggle(gateway.id, checked)}
|
||||||
<Button size="sm">Set up {provider.name}</Button>
|
disabled={!gateway.requirements.met}
|
||||||
)}
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
</div>
|
</div>
|
||||||
))}
|
)}
|
||||||
|
</SettingsCard>
|
||||||
|
|
||||||
<Button variant="outline" className="w-full">
|
{/* Other Gateways */}
|
||||||
+ Add payment provider
|
{otherGateways.length > 0 && (
|
||||||
</Button>
|
<SettingsCard
|
||||||
</div>
|
title="Other Payment Methods"
|
||||||
</SettingsCard>
|
description="Additional payment gateways"
|
||||||
|
>
|
||||||
{/* Payment Settings - Third priority (test mode, capture, etc) */}
|
<div className="space-y-4">
|
||||||
<SettingsCard
|
{otherGateways.map((gateway: PaymentGateway) => (
|
||||||
title="Payment Settings"
|
<div
|
||||||
description="General payment options"
|
key={gateway.id}
|
||||||
>
|
className="border rounded-lg p-4"
|
||||||
{/* Test Mode Banner */}
|
>
|
||||||
{testMode && (
|
<div className="flex items-center justify-between">
|
||||||
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4 mb-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="p-2 bg-muted rounded-lg">
|
||||||
<span className="text-yellow-600 dark:text-yellow-400 font-semibold">
|
<CreditCard className="h-5 w-5 text-muted-foreground" />
|
||||||
⚠️ Test Mode Active
|
</div>
|
||||||
</span>
|
<div>
|
||||||
<span className="text-sm text-yellow-700 dark:text-yellow-300">
|
<h3 className="font-medium">{gateway.title}</h3>
|
||||||
No real charges will be processed
|
{gateway.description && (
|
||||||
</span>
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
{gateway.description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={gateway.wc_settings_url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<ExternalLink className="h-4 w-4 mr-2" />
|
||||||
|
Configure in WooCommerce
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
<ToggleField
|
||||||
|
id={gateway.id}
|
||||||
|
label=""
|
||||||
|
checked={gateway.enabled}
|
||||||
|
onCheckedChange={(checked) => handleToggle(gateway.id, checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</SettingsCard>
|
||||||
)}
|
)}
|
||||||
|
</SettingsLayout>
|
||||||
|
|
||||||
<ToggleField
|
{/* Gateway Settings Modal */}
|
||||||
id="testMode"
|
{selectedGateway && (
|
||||||
label="Test mode"
|
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
|
||||||
description="Process test transactions without real charges"
|
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
||||||
checked={testMode}
|
<DialogHeader>
|
||||||
onCheckedChange={setTestMode}
|
<DialogTitle>{selectedGateway.title} Settings</DialogTitle>
|
||||||
/>
|
</DialogHeader>
|
||||||
|
<GenericGatewayForm
|
||||||
<div className="pt-4 border-t">
|
gateway={selectedGateway}
|
||||||
<div className="space-y-2">
|
onSave={handleSaveGateway}
|
||||||
<label className="text-sm font-medium">Payment capture</label>
|
onCancel={() => setIsModalOpen(false)}
|
||||||
<p className="text-sm text-muted-foreground">
|
/>
|
||||||
Choose when to capture payment from customers
|
</DialogContent>
|
||||||
</p>
|
</Dialog>
|
||||||
<div className="space-y-2 mt-2">
|
)}
|
||||||
<label className="flex items-center gap-2 cursor-pointer">
|
</>
|
||||||
<input type="radio" name="capture" value="automatic" defaultChecked />
|
|
||||||
<span className="text-sm">
|
|
||||||
<strong>Authorize and capture</strong> - Charge immediately when order is placed
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<label className="flex items-center gap-2 cursor-pointer">
|
|
||||||
<input type="radio" name="capture" value="manual" />
|
|
||||||
<span className="text-sm">
|
|
||||||
<strong>Authorize only</strong> - Manually capture payment later
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SettingsCard>
|
|
||||||
|
|
||||||
{/* Help Card */}
|
|
||||||
<div className="bg-muted/50 border rounded-lg p-4">
|
|
||||||
<p className="text-sm font-medium mb-2">💡 Need help setting up payments?</p>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Our setup wizard can help you connect Stripe or PayPal in minutes.
|
|
||||||
</p>
|
|
||||||
<Button variant="link" className="px-0 mt-2">
|
|
||||||
Start setup wizard →
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</SettingsLayout>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user