247 lines
8.2 KiB
JavaScript
247 lines
8.2 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 = {
|
|
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,
|
|
};
|