From 3f98938972604dce263ee910d05940213ec87415 Mon Sep 17 00:00:00 2001 From: Adrian Flores Cortes Date: Mon, 26 Jan 2026 19:50:19 -0600 Subject: [PATCH] feat(payments): Remove insecure PaymentMethodForm component (ST4.2.1) - Delete PaymentMethodForm.tsx (PCI-DSS violation) - Remove export from components/payments/index.ts - Component was NOT in use (legacy/demo code) Violation: Component handled card data directly (PAN, CVV, expiry) in state and sent raw data to backend. Compliant alternatives: - Stripe Customer Portal (add payment methods) - CardElement + Payment Intents (one-time payments) Blocker: BLOCKER-002 (ST4.2 PCI-DSS Compliance) Epic: OQI-005 Co-Authored-By: Claude Opus 4.5 --- src/components/payments/PaymentMethodForm.tsx | 273 ------------------ src/components/payments/index.ts | 1 - 2 files changed, 274 deletions(-) delete mode 100644 src/components/payments/PaymentMethodForm.tsx diff --git a/src/components/payments/PaymentMethodForm.tsx b/src/components/payments/PaymentMethodForm.tsx deleted file mode 100644 index f06d178..0000000 --- a/src/components/payments/PaymentMethodForm.tsx +++ /dev/null @@ -1,273 +0,0 @@ -/** - * PaymentMethodForm Component - * Add payment methods using Stripe Elements - */ - -import React, { useState } from 'react'; -import { - CreditCard, - Check, - Loader2, - AlertCircle, - Shield, - Lock, -} from 'lucide-react'; -import { addPaymentMethod } from '../../services/payment.service'; - -interface PaymentMethodFormProps { - onSuccess: (paymentMethodId: string) => void; - onCancel?: () => void; - onError?: (error: string) => void; - setAsDefault?: boolean; -} - -const PaymentMethodForm: React.FC = ({ - onSuccess, - onCancel, - onError, - setAsDefault = true, -}) => { - const [cardNumber, setCardNumber] = useState(''); - const [expiry, setExpiry] = useState(''); - const [cvc, setCvc] = useState(''); - const [cardholderName, setCardholderName] = useState(''); - const [makeDefault, setMakeDefault] = useState(setAsDefault); - const [isSubmitting, setIsSubmitting] = useState(false); - const [error, setError] = useState(null); - - // Format card number with spaces - const formatCardNumber = (value: string) => { - const v = value.replace(/\s+/g, '').replace(/[^0-9]/gi, ''); - const matches = v.match(/\d{4,16}/g); - const match = (matches && matches[0]) || ''; - const parts = []; - for (let i = 0, len = match.length; i < len; i += 4) { - parts.push(match.substring(i, i + 4)); - } - return parts.length ? parts.join(' ') : value; - }; - - // Format expiry as MM/YY - const formatExpiry = (value: string) => { - const v = value.replace(/\s+/g, '').replace(/[^0-9]/gi, ''); - if (v.length >= 2) { - return v.substring(0, 2) + '/' + v.substring(2, 4); - } - return v; - }; - - // Get card type based on number - const getCardType = (number: string): string => { - const cleanNumber = number.replace(/\s/g, ''); - if (/^4/.test(cleanNumber)) return 'Visa'; - if (/^5[1-5]/.test(cleanNumber)) return 'Mastercard'; - if (/^3[47]/.test(cleanNumber)) return 'Amex'; - if (/^6(?:011|5)/.test(cleanNumber)) return 'Discover'; - return 'Card'; - }; - - const validateForm = (): boolean => { - const cleanCardNumber = cardNumber.replace(/\s/g, ''); - - if (!cardholderName.trim()) { - setError('Please enter the cardholder name'); - return false; - } - - if (cleanCardNumber.length < 13 || cleanCardNumber.length > 19) { - setError('Please enter a valid card number'); - return false; - } - - const [month, year] = expiry.split('/'); - if (!month || !year || parseInt(month) < 1 || parseInt(month) > 12) { - setError('Please enter a valid expiry date'); - return false; - } - - // Check if card is not expired - const now = new Date(); - const currentYear = now.getFullYear() % 100; - const currentMonth = now.getMonth() + 1; - if (parseInt(year) < currentYear || (parseInt(year) === currentYear && parseInt(month) < currentMonth)) { - setError('This card has expired'); - return false; - } - - if (cvc.length < 3 || cvc.length > 4) { - setError('Please enter a valid CVC'); - return false; - } - - return true; - }; - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setError(null); - - if (!validateForm()) return; - - setIsSubmitting(true); - - try { - // In a real implementation, this would use Stripe.js to create a PaymentMethod - // For now, we simulate the API call - const result = await addPaymentMethod({ - type: 'card', - card: { - number: cardNumber.replace(/\s/g, ''), - exp_month: parseInt(expiry.split('/')[0]), - exp_year: 2000 + parseInt(expiry.split('/')[1]), - cvc, - }, - billing_details: { - name: cardholderName, - }, - setAsDefault: makeDefault, - }); - - onSuccess(result.id); - } catch (err) { - const errorMessage = err instanceof Error ? err.message : 'Failed to add payment method'; - setError(errorMessage); - onError?.(errorMessage); - } finally { - setIsSubmitting(false); - } - }; - - const cardType = getCardType(cardNumber); - - return ( -
- {/* Card Number */} -
- -
- - setCardNumber(formatCardNumber(e.target.value))} - placeholder="1234 5678 9012 3456" - maxLength={19} - className="w-full pl-11 pr-20 py-3 bg-gray-900 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-blue-500" - /> - - {cardType} - -
-
- - {/* Cardholder Name */} -
- - setCardholderName(e.target.value)} - placeholder="John Doe" - className="w-full px-4 py-3 bg-gray-900 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-blue-500" - /> -
- - {/* Expiry & CVC */} -
-
- - setExpiry(formatExpiry(e.target.value))} - placeholder="MM/YY" - maxLength={5} - className="w-full px-4 py-3 bg-gray-900 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-blue-500" - /> -
-
- -
- setCvc(e.target.value.replace(/\D/g, '').slice(0, 4))} - placeholder="123" - maxLength={4} - className="w-full px-4 py-3 bg-gray-900 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-blue-500" - /> - -
-
-
- - {/* Set as Default */} - - - {/* Error */} - {error && ( -
- - {error} -
- )} - - {/* Security Note */} -
- -

- Your card information is encrypted and secure -

-
- - {/* Actions */} -
- {onCancel && ( - - )} - -
-
- ); -}; - -export default PaymentMethodForm; diff --git a/src/components/payments/index.ts b/src/components/payments/index.ts index 56f7836..1b87062 100644 --- a/src/components/payments/index.ts +++ b/src/components/payments/index.ts @@ -14,7 +14,6 @@ export { WalletWithdrawModal } from './WalletWithdrawModal'; // Form Components export { default as CouponForm } from './CouponForm'; export type { CouponInfo } from './CouponForm'; -export { default as PaymentMethodForm } from './PaymentMethodForm'; export { default as BillingInfoForm } from './BillingInfoForm'; export type { BillingInfo } from './BillingInfoForm';