diff --git a/apps/web/pages/api/integrations/subscriptions/webhook.ts b/apps/web/pages/api/integrations/subscriptions/webhook.ts index 4ecb134dcf..c2c9f8c565 100644 --- a/apps/web/pages/api/integrations/subscriptions/webhook.ts +++ b/apps/web/pages/api/integrations/subscriptions/webhook.ts @@ -6,7 +6,7 @@ import stripe from "@calcom/features/ee/payments/server/stripe"; import { IS_PRODUCTION } from "@calcom/lib/constants"; import { getErrorFromUnknown } from "@calcom/lib/errors"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; -import prisma from "@calcom/prisma"; +import { prisma } from "@calcom/prisma"; export const config = { api: { @@ -27,7 +27,10 @@ const handleSubscriptionUpdate = async (event: Stripe.Event) => { }); if (!app) { - throw new HttpCode({ statusCode: 202, message: "Received and discarded" }); + throw new HttpCode({ + statusCode: 202, + message: `No credential found with subscription ID ${subscription.id}`, + }); } await prisma.credential.update({ @@ -51,7 +54,10 @@ const handleSubscriptionDeleted = async (event: Stripe.Event) => { }); if (!app) { - throw new HttpCode({ statusCode: 202, message: "Received and discarded" }); + throw new HttpCode({ + statusCode: 202, + message: `No credential found with subscription ID ${subscription.id}`, + }); } // should we delete the credential here rather than marking as inactive? @@ -103,7 +109,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) } } catch (_err) { const err = getErrorFromUnknown(_err); - console.error(`Webhook Error: ${err.message}`); + if (!err.message.includes("No credential found with subscription ID")) { + console.error(`Webhook Error: ${err.message}`); + } res.status(err.statusCode ?? 500).send({ message: err.message, stack: IS_PRODUCTION ? undefined : err.stack, diff --git a/packages/app-store/stripepayment/lib/PaymentService.ts b/packages/app-store/stripepayment/lib/PaymentService.ts index 167098fa82..5dd16cb879 100644 --- a/packages/app-store/stripepayment/lib/PaymentService.ts +++ b/packages/app-store/stripepayment/lib/PaymentService.ts @@ -46,8 +46,14 @@ export class PaymentService implements IAbstractPaymentService { private async getPayment(where: Prisma.PaymentWhereInput) { const payment = await prisma.payment.findFirst({ where }); - if (!payment) throw new Error("Payment not found"); - if (!payment.externalId) throw new Error("Payment externalId not found"); + // if payment isn't found, return null. + if (!payment) { + return null; + } + // if it is found, but there's no externalId - it indicates invalid state and an error should be thrown. + if (!payment.externalId) { + throw new Error("Payment externalId not found"); + } return { ...payment, externalId: payment.externalId }; } @@ -326,13 +332,21 @@ export class PaymentService implements IAbstractPaymentService { throw new Error("Method not implemented."); } - async refund(paymentId: Payment["id"]): Promise { + async refund(paymentId: Payment["id"]): Promise { + const payment = await this.getPayment({ + id: paymentId, + }); + if (!payment) { + return null; + } + if (!payment.success) { + throw new Error("Unable to refund failed payment"); + } + if (payment.refunded) { + // refunded already, bail early as success without throwing an error. + return payment; + } try { - const payment = await this.getPayment({ - id: paymentId, - success: true, - refunded: false, - }); const refund = await this.stripe.refunds.create( { payment_intent: payment.externalId, @@ -399,8 +413,12 @@ export class PaymentService implements IAbstractPaymentService { const payment = await this.getPayment({ id: paymentId, }); - const stripeAccount = (payment.data as unknown as StripePaymentData).stripeAccount; + // no payment found, return false. + if (!payment) { + return false; + } + const stripeAccount = (payment.data as unknown as StripePaymentData).stripeAccount; if (!stripeAccount) { throw new Error("Stripe account not found"); } diff --git a/packages/features/credentials/handleDeleteCredential.ts b/packages/features/credentials/handleDeleteCredential.ts index c7f32af94f..3581a4fe10 100644 --- a/packages/features/credentials/handleDeleteCredential.ts +++ b/packages/features/credentials/handleDeleteCredential.ts @@ -2,6 +2,7 @@ import z from "zod"; import { getCalendar } from "@calcom/app-store/_utils/getCalendar"; import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData"; +import { DailyLocationType } from "@calcom/app-store/locations"; import { type EventTypeAppMetadataSchema, eventTypeAppMetadataOptionalSchema, @@ -14,7 +15,6 @@ import { deleteWebhookScheduledTriggers } from "@calcom/features/webhooks/lib/sc import { buildNonDelegationCredential } from "@calcom/lib/delegationCredential/server"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; -import { DailyLocationType } from "@calcom/app-store/locations"; import { getTranslation } from "@calcom/lib/server/i18n"; import { bookingMinimalSelect, prisma } from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; @@ -284,11 +284,7 @@ const handleDeleteCredential = async ({ }); for (const payment of booking.payment) { - try { - await deletePayment(payment.id, credential); - } catch (e) { - console.error(e); - } + await deletePayment(payment.id, credential); await prisma.payment.delete({ where: { id: payment.id, @@ -353,7 +349,7 @@ const handleDeleteCredential = async ({ seatsPerTimeSlot: booking.eventType?.seatsPerTimeSlot, seatsShowAttendees: booking.eventType?.seatsShowAttendees, hideOrganizerEmail: booking.eventType?.hideOrganizerEmail, - team: !!booking.eventType?.team + team: booking.eventType?.team ? { name: booking.eventType.team.name, id: booking.eventType.team.id, diff --git a/packages/types/PaymentService.d.ts b/packages/types/PaymentService.d.ts index b1f51d674d..e9c42fb18a 100644 --- a/packages/types/PaymentService.d.ts +++ b/packages/types/PaymentService.d.ts @@ -39,7 +39,10 @@ export interface IAbstractPaymentService { ): Promise; update(paymentId: Payment["id"], data: Partial): Promise; - refund(paymentId: Payment["id"]): Promise; + /** + * @returns Payment if successful, null if payment to refund does not exist (anymore) + */ + refund(paymentId: Payment["id"]): Promise; getPaymentPaidStatus(): Promise; getPaymentDetails(): Promise; afterPayment(