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 <noreply@anthropic.com>
This commit is contained in:
Adrian Flores Cortes 2026-01-26 19:50:19 -06:00
parent b6654f27ae
commit 3f98938972
2 changed files with 0 additions and 274 deletions

View File

@ -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<PaymentMethodFormProps> = ({
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<string | null>(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 (
<form onSubmit={handleSubmit} className="space-y-4">
{/* Card Number */}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Card Number
</label>
<div className="relative">
<CreditCard className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
<input
type="text"
value={cardNumber}
onChange={(e) => 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"
/>
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-sm text-gray-400">
{cardType}
</span>
</div>
</div>
{/* Cardholder Name */}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Cardholder Name
</label>
<input
type="text"
value={cardholderName}
onChange={(e) => 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"
/>
</div>
{/* Expiry & CVC */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Expiry Date
</label>
<input
type="text"
value={expiry}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
CVC
</label>
<div className="relative">
<input
type="text"
value={cvc}
onChange={(e) => 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"
/>
<Lock className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500" />
</div>
</div>
</div>
{/* Set as Default */}
<label className="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
checked={makeDefault}
onChange={(e) => setMakeDefault(e.target.checked)}
className="w-4 h-4 rounded border-gray-600 bg-gray-900 text-blue-600 focus:ring-blue-500"
/>
<span className="text-sm text-gray-300">Set as default payment method</span>
</label>
{/* Error */}
{error && (
<div className="flex items-center gap-2 p-3 bg-red-500/10 border border-red-500/30 rounded-lg">
<AlertCircle className="w-5 h-5 text-red-400 flex-shrink-0" />
<span className="text-sm text-red-400">{error}</span>
</div>
)}
{/* Security Note */}
<div className="flex items-center gap-2 p-3 bg-gray-900/50 rounded-lg">
<Shield className="w-5 h-5 text-green-400" />
<p className="text-sm text-gray-400">
Your card information is encrypted and secure
</p>
</div>
{/* Actions */}
<div className="flex items-center gap-3 pt-2">
{onCancel && (
<button
type="button"
onClick={onCancel}
disabled={isSubmitting}
className="flex-1 py-3 bg-gray-700 hover:bg-gray-600 text-white rounded-lg font-medium transition-colors disabled:opacity-50"
>
Cancel
</button>
)}
<button
type="submit"
disabled={isSubmitting}
className="flex-1 flex items-center justify-center gap-2 py-3 bg-blue-600 hover:bg-blue-500 text-white rounded-lg font-medium transition-colors disabled:opacity-50"
>
{isSubmitting ? (
<>
<Loader2 className="w-5 h-5 animate-spin" />
Adding...
</>
) : (
<>
<Check className="w-5 h-5" />
Add Card
</>
)}
</button>
</div>
</form>
);
};
export default PaymentMethodForm;

View File

@ -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';