From fb59342de4aca4d1cab643bdcf65bd468e3c8e2a Mon Sep 17 00:00:00 2001 From: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> Date: Wed, 16 Jul 2025 15:15:49 +0530 Subject: [PATCH] fix: Show the Stripe payment error message and change the status to 400 for charge card issues (#22463) * fix: show stripe payment error message * Update PaymentService.ts * Update chargeCard.handler.ts * Update chargeCard.handler.ts * Update PaymentService.ts * update * update --- .../components/dialog/ChargeCardDialog.tsx | 19 ++++++++------- apps/web/public/static/locales/en/common.json | 4 ++++ .../stripepayment/lib/PaymentService.ts | 24 ++++++++++++++++++- .../server/getServerErrorFromUnknown.test.ts | 2 +- .../lib/server/getServerErrorFromUnknown.ts | 2 +- .../viewer/payments/chargeCard.handler.ts | 10 ++++++-- 6 files changed, 48 insertions(+), 13 deletions(-) diff --git a/apps/web/components/dialog/ChargeCardDialog.tsx b/apps/web/components/dialog/ChargeCardDialog.tsx index 8006c2c17b..1de62f399e 100644 --- a/apps/web/components/dialog/ChargeCardDialog.tsx +++ b/apps/web/components/dialog/ChargeCardDialog.tsx @@ -21,15 +21,17 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => { const { t } = useLocale(); const utils = trpc.useUtils(); const { isOpenDialog, setIsOpenDialog, bookingId } = props; - const [chargeError, setChargeError] = useState(false); + const [chargeError, setChargeError] = useState(null); + const chargeCardMutation = trpc.viewer.payments.chargeCard.useMutation({ onSuccess: () => { utils.viewer.bookings.invalidate(); setIsOpenDialog(false); + setChargeError(null); showToast("Charge successful", "success"); }, - onError: () => { - setChargeError(true); + onError: (error) => { + setChargeError(error.message || t("error_charging_card")); }, }); @@ -52,7 +54,7 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => { {chargeError && (
)} @@ -60,12 +62,13 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => { diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 9cc19f8650..52c8f34730 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -2196,6 +2196,10 @@ "error_charging_card": "Something went wrong charging the no-show fee. Please try again later.", "collect_no_show_fee": "Collect no-show fee", "no_show_fee_charged": "No-show fee charged", + "your_card_was_declined": "Payment declined. Please try a different card or contact your bank for assistance.", + "your_card_does_not_support_this_type_of_purchase": "This card type isn't supported for this purchase. Please use a different payment method.", + "amount_must_convert_to_at_least": "Minimum payment amount is $0.50 USD. Please increase your payment amount.", + "could_not_charge_card": "Could not charge card for payment.", "insights": "Insights", "routing_forms": "Routing Forms", "testing_workflow_info_message": "When testing this workflow, be aware that Emails and SMS can only be scheduled at least 1 hour in advance", diff --git a/packages/app-store/stripepayment/lib/PaymentService.ts b/packages/app-store/stripepayment/lib/PaymentService.ts index 0bca603768..02ba3c86d0 100644 --- a/packages/app-store/stripepayment/lib/PaymentService.ts +++ b/packages/app-store/stripepayment/lib/PaymentService.ts @@ -6,6 +6,7 @@ import z from "zod"; import { sendAwaitingPaymentEmailAndSMS } from "@calcom/emails"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { getErrorFromUnknown } from "@calcom/lib/errors"; +import { ErrorWithCode } from "@calcom/lib/errors"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import prisma from "@calcom/prisma"; @@ -296,7 +297,28 @@ export class PaymentService implements IAbstractPaymentService { return paymentData; } catch (error) { log.error("Stripe: Could not charge card for payment", _bookingId, safeStringify(error)); - throw new Error(ErrorCode.ChargeCardFailure); + + const errorMappings = { + "your card was declined": "your_card_was_declined", + "your card does not support this type of purchase": + "your_card_does_not_support_this_type_of_purchase", + "amount must convert to at least": "amount_must_convert_to_at_least", + }; + + let userMessage = "could_not_charge_card"; + + if (error instanceof Error) { + const errorMessage = error.message.toLowerCase(); + + for (const [key, message] of Object.entries(errorMappings)) { + if (errorMessage.includes(key)) { + userMessage = message; + break; + } + } + } + + throw new ErrorWithCode(ErrorCode.ChargeCardFailure, userMessage); } } diff --git a/packages/lib/server/getServerErrorFromUnknown.test.ts b/packages/lib/server/getServerErrorFromUnknown.test.ts index 0e54136034..10996e37e4 100644 --- a/packages/lib/server/getServerErrorFromUnknown.test.ts +++ b/packages/lib/server/getServerErrorFromUnknown.test.ts @@ -16,6 +16,7 @@ const test400Codes = [ ErrorCode.BookingNotAllowedByRestrictionSchedule, ErrorCode.BookerLimitExceeded, ErrorCode.BookerLimitExceededReschedule, + ErrorCode.ChargeCardFailure, ]; const test404Codes = [ @@ -33,7 +34,6 @@ const test409Codes = [ ErrorCode.NotEnoughAvailableSeats, ErrorCode.BookingConflict, ErrorCode.PaymentCreationFailure, - ErrorCode.ChargeCardFailure, ]; describe("getServerErrorFromUnknown", () => { diff --git a/packages/lib/server/getServerErrorFromUnknown.ts b/packages/lib/server/getServerErrorFromUnknown.ts index df5b63f5a4..1d31cffc55 100644 --- a/packages/lib/server/getServerErrorFromUnknown.ts +++ b/packages/lib/server/getServerErrorFromUnknown.ts @@ -112,6 +112,7 @@ function getStatusCode(cause: Error | ErrorWithCode): number { case ErrorCode.BookerLimitExceeded: case ErrorCode.BookerLimitExceededReschedule: case ErrorCode.EventTypeNoHosts: + case ErrorCode.ChargeCardFailure: return 400; // 409 Conflict case ErrorCode.NoAvailableUsersFound: @@ -122,7 +123,6 @@ function getStatusCode(cause: Error | ErrorWithCode): number { case ErrorCode.NotEnoughAvailableSeats: case ErrorCode.BookingConflict: case ErrorCode.PaymentCreationFailure: - case ErrorCode.ChargeCardFailure: return 409; // 404 Not Found case ErrorCode.EventTypeNotFound: diff --git a/packages/trpc/server/routers/viewer/payments/chargeCard.handler.ts b/packages/trpc/server/routers/viewer/payments/chargeCard.handler.ts index 840f7aa28e..4bf38bca46 100644 --- a/packages/trpc/server/routers/viewer/payments/chargeCard.handler.ts +++ b/packages/trpc/server/routers/viewer/payments/chargeCard.handler.ts @@ -1,6 +1,8 @@ import appStore from "@calcom/app-store"; import dayjs from "@calcom/dayjs"; import { sendNoShowFeeChargedEmail } from "@calcom/emails"; +import { ErrorCode } from "@calcom/lib/errorCodes"; +import { ErrorWithCode } from "@calcom/lib/errors"; import { getTranslation } from "@calcom/lib/server/i18n"; import type { PrismaClient } from "@calcom/prisma"; import type { EventTypeMetadata } from "@calcom/prisma/zod-utils"; @@ -141,9 +143,13 @@ export const chargeCardHandler = async ({ ctx, input }: ChargeCardHandlerOptions return paymentData; } catch (err) { + let errorMessage = `Error processing payment with error ${err}`; + if (err instanceof ErrorWithCode && err.code === ErrorCode.ChargeCardFailure) { + errorMessage = err.message; + } throw new TRPCError({ - code: "INTERNAL_SERVER_ERROR", - message: `Error processing payment with error ${err}`, + code: "BAD_REQUEST", + message: tOrganizer(errorMessage), }); } };