/** * Stripe Payment Error Handling Utility * * Maps Stripe decline codes to user-friendly messages for both owners and renters. * Provides structured error information for frontend handling. */ const DECLINE_MESSAGES = { authentication_required: { ownerMessage: "The renter's card requires additional authentication.", renterMessage: "Your card requires authentication to complete this payment.", canOwnerRetry: false, requiresNewPaymentMethod: false, requires3DS: true, }, insufficient_funds: { ownerMessage: "The renter's card has insufficient funds.", renterMessage: "Your card has insufficient funds.", canOwnerRetry: false, requiresNewPaymentMethod: false, // renter might add funds }, card_declined: { ownerMessage: "The renter's card was declined by their bank.", renterMessage: "Your card was declined. Please contact your bank or try a different card.", canOwnerRetry: false, requiresNewPaymentMethod: true, }, generic_decline: { ownerMessage: "The renter's card was declined.", renterMessage: "Your card was declined. Please try a different card.", canOwnerRetry: false, requiresNewPaymentMethod: true, }, expired_card: { ownerMessage: "The renter's card has expired.", renterMessage: "Your card has expired. Please add a new payment method.", canOwnerRetry: false, requiresNewPaymentMethod: true, }, processing_error: { ownerMessage: "A payment processing error occurred. This is usually temporary.", renterMessage: "A temporary error occurred processing your payment.", canOwnerRetry: true, // Owner can retry immediately requiresNewPaymentMethod: false, }, lost_card: { ownerMessage: "The renter's card cannot be used.", renterMessage: "Your card cannot be used. Please add a different payment method.", canOwnerRetry: false, requiresNewPaymentMethod: true, }, stolen_card: { ownerMessage: "The renter's card cannot be used.", renterMessage: "Your card cannot be used. Please add a different payment method.", canOwnerRetry: false, requiresNewPaymentMethod: true, }, incorrect_cvc: { ownerMessage: "Payment verification failed.", renterMessage: "Your card couldn't be verified. Please re-enter your payment details.", canOwnerRetry: false, requiresNewPaymentMethod: true, }, invalid_cvc: { ownerMessage: "Payment verification failed.", renterMessage: "Your card couldn't be verified. Please re-enter your payment details.", canOwnerRetry: false, requiresNewPaymentMethod: true, }, fraudulent: { ownerMessage: "This payment was blocked for security reasons.", renterMessage: "Your payment was blocked by our security system. Please try a different card.", canOwnerRetry: false, requiresNewPaymentMethod: true, }, incorrect_zip: { ownerMessage: "Billing address verification failed.", renterMessage: "Your billing address couldn't be verified. Please update your payment method.", canOwnerRetry: false, requiresNewPaymentMethod: true, }, card_velocity_exceeded: { ownerMessage: "Too many payment attempts on this card. Please try again later.", renterMessage: "Too many attempts on your card. Please wait or try a different card.", canOwnerRetry: true, // After delay requiresNewPaymentMethod: false, }, do_not_honor: { ownerMessage: "The renter's card was declined by their bank.", renterMessage: "Your card was declined. Please contact your bank or try a different card.", canOwnerRetry: false, requiresNewPaymentMethod: true, }, invalid_account: { ownerMessage: "The renter's card account is invalid.", renterMessage: "Your card account is invalid. Please use a different card.", canOwnerRetry: false, requiresNewPaymentMethod: true, }, new_account_information_available: { ownerMessage: "The renter's card information needs to be updated.", renterMessage: "Your card information needs to be updated. Please re-enter your payment details.", canOwnerRetry: false, requiresNewPaymentMethod: true, }, card_not_supported: { ownerMessage: "The renter's card type is not supported.", renterMessage: "This card type is not supported. Please use a different card.", canOwnerRetry: false, requiresNewPaymentMethod: true, }, currency_not_supported: { ownerMessage: "The renter's card doesn't support this currency.", renterMessage: "Your card doesn't support USD payments. Please use a different card.", canOwnerRetry: false, requiresNewPaymentMethod: true, }, try_again_later: { ownerMessage: "The payment processor is temporarily unavailable. Please try again.", renterMessage: "A temporary error occurred. The owner can try again shortly.", canOwnerRetry: true, requiresNewPaymentMethod: false, }, }; // Default error for unknown decline codes const DEFAULT_ERROR = { ownerMessage: "The payment could not be processed.", renterMessage: "Your payment could not be processed. Please try a different payment method.", canOwnerRetry: false, requiresNewPaymentMethod: true, }; /** * Parse a Stripe error and return structured error information * @param {Error} error - The error object from Stripe * @returns {Object} Structured error with code, messages, and retry info */ function parseStripeError(error) { // Check if this is a Stripe error if (error.type === "StripeCardError" || error.code === "card_declined") { const declineCode = error.decline_code || error.code || "card_declined"; const errorInfo = DECLINE_MESSAGES[declineCode] || DEFAULT_ERROR; return { code: declineCode, ownerMessage: errorInfo.ownerMessage, renterMessage: errorInfo.renterMessage, canOwnerRetry: errorInfo.canOwnerRetry, requiresNewPaymentMethod: errorInfo.requiresNewPaymentMethod, // Include original error info for logging (not for users) _originalMessage: error.message, _stripeCode: error.code, }; } // Handle other Stripe error types if (error.type === "StripeInvalidRequestError") { return { code: "invalid_request", ownerMessage: "There was a problem processing this payment.", renterMessage: "There was a problem with your payment method.", canOwnerRetry: false, requiresNewPaymentMethod: true, _originalMessage: error.message, _stripeCode: error.code, }; } if (error.type === "StripeAPIError" || error.type === "StripeConnectionError") { return { code: "api_error", ownerMessage: "A temporary error occurred. Please try again.", renterMessage: "A temporary error occurred processing your payment.", canOwnerRetry: true, requiresNewPaymentMethod: false, _originalMessage: error.message, _stripeCode: error.code, }; } if (error.type === "StripeRateLimitError") { return { code: "rate_limit", ownerMessage: "Too many requests. Please wait a moment and try again.", renterMessage: "Please wait a moment and try again.", canOwnerRetry: true, requiresNewPaymentMethod: false, _originalMessage: error.message, _stripeCode: error.code, }; } // Default fallback for unknown errors return { code: "unknown_error", ...DEFAULT_ERROR, _originalMessage: error.message, _stripeCode: error.code, }; } /** * Custom PaymentError class for structured payment failures */ class PaymentError extends Error { constructor(parsedError) { super(parsedError.ownerMessage); this.name = "PaymentError"; this.code = parsedError.code; this.ownerMessage = parsedError.ownerMessage; this.renterMessage = parsedError.renterMessage; this.canOwnerRetry = parsedError.canOwnerRetry; this.requiresNewPaymentMethod = parsedError.requiresNewPaymentMethod; this._originalMessage = parsedError._originalMessage; this._stripeCode = parsedError._stripeCode; } toJSON() { return { code: this.code, ownerMessage: this.ownerMessage, renterMessage: this.renterMessage, canOwnerRetry: this.canOwnerRetry, requiresNewPaymentMethod: this.requiresNewPaymentMethod, }; } } module.exports = { parseStripeError, PaymentError, DECLINE_MESSAGES, };