failed payment method handling
This commit is contained in:
239
backend/utils/stripeErrors.js
Normal file
239
backend/utils/stripeErrors.js
Normal file
@@ -0,0 +1,239 @@
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
Reference in New Issue
Block a user