Skip to content

Commit fb59342

Browse files
authored
fix: Show the Stripe payment error message and change the status to 400 for charge card issues (calcom#22463)
* fix: show stripe payment error message * Update PaymentService.ts * Update chargeCard.handler.ts * Update chargeCard.handler.ts * Update PaymentService.ts * update * update
1 parent a84e898 commit fb59342

6 files changed

Lines changed: 48 additions & 13 deletions

File tree

apps/web/components/dialog/ChargeCardDialog.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,17 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
2121
const { t } = useLocale();
2222
const utils = trpc.useUtils();
2323
const { isOpenDialog, setIsOpenDialog, bookingId } = props;
24-
const [chargeError, setChargeError] = useState(false);
24+
const [chargeError, setChargeError] = useState<string | null>(null);
25+
2526
const chargeCardMutation = trpc.viewer.payments.chargeCard.useMutation({
2627
onSuccess: () => {
2728
utils.viewer.bookings.invalidate();
2829
setIsOpenDialog(false);
30+
setChargeError(null);
2931
showToast("Charge successful", "success");
3032
},
31-
onError: () => {
32-
setChargeError(true);
33+
onError: (error) => {
34+
setChargeError(error.message || t("error_charging_card"));
3335
},
3436
});
3537

@@ -52,20 +54,21 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
5254
{chargeError && (
5355
<div className="mt-4 flex text-red-500">
5456
<Icon name="triangle-alert" className="mr-2 h-5 w-5 " aria-hidden="true" />
55-
<p className="text-sm">{t("error_charging_card")}</p>
57+
<p className="text-sm">{chargeError}</p>
5658
</div>
5759
)}
5860

5961
<DialogFooter>
6062
<DialogClose />
6163
<Button
6264
data-testid="send_request"
63-
disabled={chargeCardMutation.isPending || chargeError}
64-
onClick={() =>
65+
disabled={chargeCardMutation.isPending}
66+
onClick={() => {
67+
setChargeError(null);
6568
chargeCardMutation.mutate({
6669
bookingId,
67-
})
68-
}>
70+
});
71+
}}>
6972
{t("charge_attendee", currencyStringParams)}
7073
</Button>
7174
</DialogFooter>

apps/web/public/static/locales/en/common.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2196,6 +2196,10 @@
21962196
"error_charging_card": "Something went wrong charging the no-show fee. Please try again later.",
21972197
"collect_no_show_fee": "Collect no-show fee",
21982198
"no_show_fee_charged": "No-show fee charged",
2199+
"your_card_was_declined": "Payment declined. Please try a different card or contact your bank for assistance.",
2200+
"your_card_does_not_support_this_type_of_purchase": "This card type isn't supported for this purchase. Please use a different payment method.",
2201+
"amount_must_convert_to_at_least": "Minimum payment amount is $0.50 USD. Please increase your payment amount.",
2202+
"could_not_charge_card": "Could not charge card for payment.",
21992203
"insights": "Insights",
22002204
"routing_forms": "Routing Forms",
22012205
"testing_workflow_info_message": "When testing this workflow, be aware that Emails and SMS can only be scheduled at least 1 hour in advance",

packages/app-store/stripepayment/lib/PaymentService.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import z from "zod";
66
import { sendAwaitingPaymentEmailAndSMS } from "@calcom/emails";
77
import { ErrorCode } from "@calcom/lib/errorCodes";
88
import { getErrorFromUnknown } from "@calcom/lib/errors";
9+
import { ErrorWithCode } from "@calcom/lib/errors";
910
import logger from "@calcom/lib/logger";
1011
import { safeStringify } from "@calcom/lib/safeStringify";
1112
import prisma from "@calcom/prisma";
@@ -296,7 +297,28 @@ export class PaymentService implements IAbstractPaymentService {
296297
return paymentData;
297298
} catch (error) {
298299
log.error("Stripe: Could not charge card for payment", _bookingId, safeStringify(error));
299-
throw new Error(ErrorCode.ChargeCardFailure);
300+
301+
const errorMappings = {
302+
"your card was declined": "your_card_was_declined",
303+
"your card does not support this type of purchase":
304+
"your_card_does_not_support_this_type_of_purchase",
305+
"amount must convert to at least": "amount_must_convert_to_at_least",
306+
};
307+
308+
let userMessage = "could_not_charge_card";
309+
310+
if (error instanceof Error) {
311+
const errorMessage = error.message.toLowerCase();
312+
313+
for (const [key, message] of Object.entries(errorMappings)) {
314+
if (errorMessage.includes(key)) {
315+
userMessage = message;
316+
break;
317+
}
318+
}
319+
}
320+
321+
throw new ErrorWithCode(ErrorCode.ChargeCardFailure, userMessage);
300322
}
301323
}
302324

packages/lib/server/getServerErrorFromUnknown.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const test400Codes = [
1616
ErrorCode.BookingNotAllowedByRestrictionSchedule,
1717
ErrorCode.BookerLimitExceeded,
1818
ErrorCode.BookerLimitExceededReschedule,
19+
ErrorCode.ChargeCardFailure,
1920
];
2021

2122
const test404Codes = [
@@ -33,7 +34,6 @@ const test409Codes = [
3334
ErrorCode.NotEnoughAvailableSeats,
3435
ErrorCode.BookingConflict,
3536
ErrorCode.PaymentCreationFailure,
36-
ErrorCode.ChargeCardFailure,
3737
];
3838

3939
describe("getServerErrorFromUnknown", () => {

packages/lib/server/getServerErrorFromUnknown.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ function getStatusCode(cause: Error | ErrorWithCode): number {
112112
case ErrorCode.BookerLimitExceeded:
113113
case ErrorCode.BookerLimitExceededReschedule:
114114
case ErrorCode.EventTypeNoHosts:
115+
case ErrorCode.ChargeCardFailure:
115116
return 400;
116117
// 409 Conflict
117118
case ErrorCode.NoAvailableUsersFound:
@@ -122,7 +123,6 @@ function getStatusCode(cause: Error | ErrorWithCode): number {
122123
case ErrorCode.NotEnoughAvailableSeats:
123124
case ErrorCode.BookingConflict:
124125
case ErrorCode.PaymentCreationFailure:
125-
case ErrorCode.ChargeCardFailure:
126126
return 409;
127127
// 404 Not Found
128128
case ErrorCode.EventTypeNotFound:

packages/trpc/server/routers/viewer/payments/chargeCard.handler.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import appStore from "@calcom/app-store";
22
import dayjs from "@calcom/dayjs";
33
import { sendNoShowFeeChargedEmail } from "@calcom/emails";
4+
import { ErrorCode } from "@calcom/lib/errorCodes";
5+
import { ErrorWithCode } from "@calcom/lib/errors";
46
import { getTranslation } from "@calcom/lib/server/i18n";
57
import type { PrismaClient } from "@calcom/prisma";
68
import type { EventTypeMetadata } from "@calcom/prisma/zod-utils";
@@ -141,9 +143,13 @@ export const chargeCardHandler = async ({ ctx, input }: ChargeCardHandlerOptions
141143

142144
return paymentData;
143145
} catch (err) {
146+
let errorMessage = `Error processing payment with error ${err}`;
147+
if (err instanceof ErrorWithCode && err.code === ErrorCode.ChargeCardFailure) {
148+
errorMessage = err.message;
149+
}
144150
throw new TRPCError({
145-
code: "INTERNAL_SERVER_ERROR",
146-
message: `Error processing payment with error ${err}`,
151+
code: "BAD_REQUEST",
152+
message: tOrganizer(errorMessage),
147153
});
148154
}
149155
};

0 commit comments

Comments
 (0)