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
This commit is contained in:
Anik Dhabal Babu
2025-07-16 09:45:49 +00:00
committed by GitHub
parent a84e8987cb
commit fb59342de4
6 changed files with 48 additions and 13 deletions
@@ -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<string | null>(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 && (
<div className="mt-4 flex text-red-500">
<Icon name="triangle-alert" className="mr-2 h-5 w-5 " aria-hidden="true" />
<p className="text-sm">{t("error_charging_card")}</p>
<p className="text-sm">{chargeError}</p>
</div>
)}
@@ -60,12 +62,13 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
<DialogClose />
<Button
data-testid="send_request"
disabled={chargeCardMutation.isPending || chargeError}
onClick={() =>
disabled={chargeCardMutation.isPending}
onClick={() => {
setChargeError(null);
chargeCardMutation.mutate({
bookingId,
})
}>
});
}}>
{t("charge_attendee", currencyStringParams)}
</Button>
</DialogFooter>
@@ -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",
@@ -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);
}
}
@@ -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", () => {
@@ -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:
@@ -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),
});
}
};