T.ME/BIBIL_0DAY
CasperSecurity


Server : Apache/2
System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64
User : gositeme ( 1004)
PHP Version : 8.2.29
Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Directory :  /home/gositeme/domains/lavocat.ca/private_html/src/components/payments/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.ca/private_html/src/components/payments/PaymentMethodsManager.tsx
'use client';

import React, { useState, useEffect } from 'react';
import { loadStripe } from '@stripe/stripe-js';
import {
  Elements,
  CardElement,
  useStripe,
  useElements
} from '@stripe/react-stripe-js';
import { useSession } from 'next-auth/react';
import { toast } from 'react-hot-toast';
import {
  CreditCard,
  Plus,
  Edit,
  Trash2,
  Shield,
  CheckCircle,
  AlertCircle,
  Lock
} from 'lucide-react';

// Initialize Stripe
const getStripePromise = () => {
  const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
  
  if (!publishableKey || 
      publishableKey === 'pk_test_your_stripe_publishable_key_here' ||
      publishableKey === 'pk_live_your_stripe_publishable_key_here') {
    console.warn('Stripe publishable key not properly configured.');
    return null;
  }
  
  return loadStripe(publishableKey);
};

const stripePromise = getStripePromise();

interface PaymentMethod {
  id: string;
  type: string;
  last4: string;
  brand: string;
  expMonth: number;
  expYear: number;
  isDefault: boolean;
  country?: string;
  fingerprint?: string;
}

interface PaymentMethodsManagerProps {
  onMethodAdded?: (method: PaymentMethod) => void;
  onMethodRemoved?: (methodId: string) => void;
  onMethodUpdated?: (method: PaymentMethod) => void;
}

const PaymentMethodsManager: React.FC<PaymentMethodsManagerProps> = ({
  onMethodAdded,
  onMethodRemoved,
  onMethodUpdated
}) => {
  const { data: session } = useSession();
  const [paymentMethods, setPaymentMethods] = useState<PaymentMethod[]>([]);
  const [loading, setLoading] = useState(true);
  const [showAddForm, setShowAddForm] = useState(false);
  const [editingMethod, setEditingMethod] = useState<PaymentMethod | null>(null);
  const [isProcessing, setIsProcessing] = useState(false);

  useEffect(() => {
    fetchPaymentMethods();
  }, []);

  const fetchPaymentMethods = async () => {
    try {
      setLoading(true);
      const response = await fetch('/api/user/payment-methods');
      
      if (response.ok) {
        const data = await response.json();
        setPaymentMethods(data.paymentMethods || []);
      } else {
        console.error('Failed to fetch payment methods');
      }
    } catch (error) {
      console.error('Error fetching payment methods:', error);
      toast.error('Failed to load payment methods');
    } finally {
      setLoading(false);
    }
  };

  const handleAddMethod = async (paymentMethod: any) => {
    try {
      setIsProcessing(true);
      const response = await fetch('/api/user/payment-methods', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          paymentMethodId: paymentMethod.id
        })
      });

      if (response.ok) {
        const newMethod = await response.json();
        setPaymentMethods(prev => [...prev, newMethod.paymentMethod]);
        setShowAddForm(false);
        toast.success('Payment method added successfully');
        onMethodAdded?.(newMethod.paymentMethod);
      } else {
        const error = await response.json();
        toast.error(error.message || 'Failed to add payment method');
      }
    } catch (error) {
      console.error('Error adding payment method:', error);
      toast.error('Failed to add payment method');
    } finally {
      setIsProcessing(false);
    }
  };

  const handleRemoveMethod = async (methodId: string) => {
    if (!confirm('Are you sure you want to remove this payment method?')) {
      return;
    }

    try {
      const response = await fetch(`/api/user/payment-methods/${methodId}`, {
        method: 'DELETE'
      });

      if (response.ok) {
        setPaymentMethods(prev => prev.filter(method => method.id !== methodId));
        toast.success('Payment method removed successfully');
        onMethodRemoved?.(methodId);
      } else {
        const error = await response.json();
        toast.error(error.message || 'Failed to remove payment method');
      }
    } catch (error) {
      console.error('Error removing payment method:', error);
      toast.error('Failed to remove payment method');
    }
  };

  const handleSetDefault = async (methodId: string) => {
    try {
      const response = await fetch(`/api/user/payment-methods/${methodId}/default`, {
        method: 'POST'
      });

      if (response.ok) {
        setPaymentMethods(prev => 
          prev.map(method => ({
            ...method,
            isDefault: method.id === methodId
          }))
        );
        toast.success('Default payment method updated');
        onMethodUpdated?.(paymentMethods.find(m => m.id === methodId)!);
      } else {
        const error = await response.json();
        toast.error(error.message || 'Failed to update default payment method');
      }
    } catch (error) {
      console.error('Error setting default payment method:', error);
      toast.error('Failed to update default payment method');
    }
  };

  const getCardIcon = (brand: string) => {
    switch (brand.toLowerCase()) {
      case 'visa':
        return '💳';
      case 'mastercard':
        return '💳';
      case 'amex':
        return '💳';
      case 'discover':
        return '💳';
      default:
        return '💳';
    }
  };

  const formatExpiry = (month: number, year: number) => {
    return `${month.toString().padStart(2, '0')}/${year.toString().slice(-2)}`;
  };

  if (!stripePromise) {
    return (
      <div className="text-center p-8">
        <AlertCircle className="mx-auto h-12 w-12 text-gray-400" />
        <h3 className="mt-2 text-sm font-medium text-gray-900">Payment System Unavailable</h3>
        <p className="mt-1 text-sm text-gray-500">
          Stripe payment processing is not properly configured.
        </p>
      </div>
    );
  }

  if (loading) {
    return (
      <div className="flex items-center justify-center p-8">
        <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
        <span className="ml-3 text-gray-600">Loading payment methods...</span>
      </div>
    );
  }

  return (
    <div className="space-y-6">
      {/* Header */}
      <div className="flex justify-between items-center">
        <div>
          <h2 className="text-2xl font-bold text-gray-900">Payment Methods</h2>
          <p className="text-gray-600">Manage your saved payment methods</p>
        </div>
        <button
          onClick={() => setShowAddForm(true)}
          className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
        >
          <Plus className="h-4 w-4 mr-2" />
          Add Payment Method
        </button>
      </div>

      {/* Payment Methods List */}
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        {paymentMethods.map((method) => (
          <div
            key={method.id}
            className={`bg-white rounded-lg border-2 p-6 ${
              method.isDefault ? 'border-blue-500 bg-blue-50' : 'border-gray-200'
            }`}
          >
            <div className="flex items-center justify-between mb-4">
              <div className="flex items-center">
                <span className="text-2xl mr-2">{getCardIcon(method.brand)}</span>
                <div>
                  <p className="font-medium text-gray-900">
                    {method.brand.charAt(0).toUpperCase() + method.brand.slice(1)} •••• {method.last4}
                  </p>
                  <p className="text-sm text-gray-500">
                    Expires {formatExpiry(method.expMonth, method.expYear)}
                  </p>
                </div>
              </div>
              {method.isDefault && (
                <span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
                  <CheckCircle className="h-3 w-3 mr-1" />
                  Default
                </span>
              )}
            </div>

            <div className="flex items-center justify-between">
              <div className="flex space-x-2">
                {!method.isDefault && (
                  <button
                    onClick={() => handleSetDefault(method.id)}
                    className="text-sm text-blue-600 hover:text-blue-800"
                  >
                    Set Default
                  </button>
                )}
                <button
                  onClick={() => setEditingMethod(method)}
                  className="text-sm text-gray-600 hover:text-gray-800"
                >
                  <Edit className="h-4 w-4" />
                </button>
              </div>
              <button
                onClick={() => handleRemoveMethod(method.id)}
                className="text-sm text-red-600 hover:text-red-800"
                disabled={method.isDefault}
              >
                <Trash2 className="h-4 w-4" />
              </button>
            </div>
          </div>
        ))}
      </div>

      {/* Empty State */}
      {paymentMethods.length === 0 && !showAddForm && (
        <div className="text-center py-12">
          <CreditCard className="mx-auto h-12 w-12 text-gray-400" />
          <h3 className="mt-2 text-sm font-medium text-gray-900">No payment methods</h3>
          <p className="mt-1 text-sm text-gray-500">
            Get started by adding a payment method for faster checkout.
          </p>
          <div className="mt-6">
            <button
              onClick={() => setShowAddForm(true)}
              className="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
            >
              <Plus className="h-4 w-4 mr-2" />
              Add Payment Method
            </button>
          </div>
        </div>
      )}

      {/* Add Payment Method Form */}
      {showAddForm && (
        <div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
          <div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
            <div className="mt-3">
              <h3 className="text-lg font-medium text-gray-900 mb-4">Add Payment Method</h3>
              <Elements stripe={stripePromise}>
                <AddPaymentMethodForm
                  onSuccess={handleAddMethod}
                  onCancel={() => setShowAddForm(false)}
                  isProcessing={isProcessing}
                />
              </Elements>
            </div>
          </div>
        </div>
      )}

      {/* Edit Payment Method Form */}
      {editingMethod && (
        <div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
          <div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
            <div className="mt-3">
              <h3 className="text-lg font-medium text-gray-900 mb-4">Edit Payment Method</h3>
              <EditPaymentMethodForm
                method={editingMethod}
                onSuccess={(updatedMethod) => {
                  setPaymentMethods(prev => 
                    prev.map(m => m.id === updatedMethod.id ? updatedMethod : m)
                  );
                  setEditingMethod(null);
                  onMethodUpdated?.(updatedMethod);
                }}
                onCancel={() => setEditingMethod(null)}
              />
            </div>
          </div>
        </div>
      )}

      {/* Security Notice */}
      <div className="bg-blue-50 border border-blue-200 rounded-md p-4">
        <div className="flex">
          <Shield className="h-5 w-5 text-blue-400 mt-0.5" />
          <div className="ml-3">
            <h3 className="text-sm font-medium text-blue-800">Secure Payment Storage</h3>
            <div className="mt-2 text-sm text-blue-700">
              <p>
                Your payment information is securely stored and encrypted. We never store your full card details on our servers.
              </p>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

// Add Payment Method Form Component
interface AddPaymentMethodFormProps {
  onSuccess: (paymentMethod: any) => void;
  onCancel: () => void;
  isProcessing: boolean;
}

function AddPaymentMethodForm({ onSuccess, onCancel, isProcessing }: AddPaymentMethodFormProps) {
  const stripe = useStripe();
  const elements = useElements();
  const { data: session } = useSession();

  const handleSubmit = async (event: React.FormEvent) => {
    event.preventDefault();

    if (!stripe || !elements) {
      toast.error('Payment system not ready');
      return;
    }

    const cardElement = elements.getElement(CardElement);
    if (!cardElement) {
      toast.error('Card element not found');
      return;
    }

    try {
      // Create payment method
      const { error, paymentMethod } = await stripe.createPaymentMethod({
        type: 'card',
        card: cardElement,
        billing_details: {
          name: session?.user?.name || 'Anonymous',
          email: session?.user?.email || undefined,
        },
      });

      if (error) {
        toast.error(error.message || 'Failed to create payment method');
      } else if (paymentMethod) {
        onSuccess(paymentMethod);
      }
    } catch (error) {
      console.error('Error creating payment method:', error);
      toast.error('Failed to create payment method');
    }
  };

  const cardElementOptions = {
    style: {
      base: {
        fontSize: '16px',
        color: '#424770',
        '::placeholder': {
          color: '#aab7c4',
        },
        fontFamily: 'Inter, system-ui, sans-serif',
      },
      invalid: {
        color: '#9e2146',
      },
    },
    hidePostalCode: false,
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <div>
        <label className="block text-sm font-medium text-gray-700 mb-2">
          Card Information
        </label>
        <div className="p-3 border border-gray-300 rounded-md focus-within:ring-2 focus-within:ring-blue-500 focus-within:border-blue-500">
          <CardElement options={cardElementOptions} />
        </div>
      </div>

      <div className="flex justify-end space-x-3">
        <button
          type="button"
          onClick={onCancel}
          className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200"
        >
          Cancel
        </button>
        <button
          type="submit"
          disabled={!stripe || isProcessing}
          className={`px-4 py-2 text-sm font-medium rounded-md text-white ${
            isProcessing || !stripe
              ? 'bg-gray-400 cursor-not-allowed'
              : 'bg-blue-600 hover:bg-blue-700'
          }`}
        >
          {isProcessing ? 'Adding...' : 'Add Payment Method'}
        </button>
      </div>
    </form>
  );
}

// Edit Payment Method Form Component
interface EditPaymentMethodFormProps {
  method: PaymentMethod;
  onSuccess: (method: PaymentMethod) => void;
  onCancel: () => void;
}

function EditPaymentMethodForm({ method, onSuccess, onCancel }: EditPaymentMethodFormProps) {
  const [isDefault, setIsDefault] = useState(method.isDefault);

  const handleSubmit = async (event: React.FormEvent) => {
    event.preventDefault();
    
    try {
      const response = await fetch(`/api/user/payment-methods/${method.id}`, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          isDefault
        })
      });

      if (response.ok) {
        const updatedMethod = { ...method, isDefault };
        onSuccess(updatedMethod);
        toast.success('Payment method updated successfully');
      } else {
        const error = await response.json();
        toast.error(error.message || 'Failed to update payment method');
      }
    } catch (error) {
      console.error('Error updating payment method:', error);
      toast.error('Failed to update payment method');
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <div className="bg-gray-50 rounded-md p-4">
        <div className="flex items-center mb-3">
          <span className="text-2xl mr-2">💳</span>
          <div>
            <p className="font-medium text-gray-900">
              {method.brand.charAt(0).toUpperCase() + method.brand.slice(1)} •••• {method.last4}
            </p>
            <p className="text-sm text-gray-500">
              Expires {method.expMonth.toString().padStart(2, '0')}/{method.expYear.toString().slice(-2)}
            </p>
          </div>
        </div>
      </div>

      <div className="flex items-center">
        <input
          type="checkbox"
          id="isDefault"
          checked={isDefault}
          onChange={(e) => setIsDefault(e.target.checked)}
          className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
        />
        <label htmlFor="isDefault" className="ml-2 block text-sm text-gray-900">
          Set as default payment method
        </label>
      </div>

      <div className="flex justify-end space-x-3">
        <button
          type="button"
          onClick={onCancel}
          className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200"
        >
          Cancel
        </button>
        <button
          type="submit"
          className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700"
        >
          Update Payment Method
        </button>
      </div>
    </form>
  );
}

export default PaymentMethodsManager; 

CasperSecurity Mini