Files
rentall-app/backend/utils/stripeErrors.js
2026-01-06 16:13:58 -05:00

240 lines
8.0 KiB
JavaScript

/**
* 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 = {
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,
};