diff --git a/apps/web/components/eventtype/EventAppsTab.tsx b/apps/web/components/eventtype/EventAppsTab.tsx index 72e041744f..6c96d701cd 100644 --- a/apps/web/components/eventtype/EventAppsTab.tsx +++ b/apps/web/components/eventtype/EventAppsTab.tsx @@ -47,7 +47,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => { }; }; - const getAppDataSetter = (appId: EventTypeAppsList): SetAppData => { + const getAppDataSetter = (appId: EventTypeAppsList, credentialId?: number): SetAppData => { return function (key, value) { // Always get latest data available in Form because consequent calls to setData would update the Form but not allAppsData(it would update during next render) const allAppsDataFromForm = methods.getValues("metadata")?.apps || {}; @@ -57,6 +57,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => { [appId]: { ...appData, [key]: value, + credentialId, }, }); }; @@ -76,7 +77,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => { appCards.push( { appCards.push( { return ( , page: Page) { await page.goto(`/event-types/${eventType?.id}?tabName=apps`); - await page.locator("div > .ml-auto").first().click(); + await page.locator("[data-testid='app-switch']").first().click(); await page.getByPlaceholder("Price").fill("100"); await page.getByTestId("update-eventtype").click(); } diff --git a/apps/web/playwright/integrations-stripe.e2e.ts b/apps/web/playwright/integrations-stripe.e2e.ts index cd67112bc7..25a1a33fa6 100644 --- a/apps/web/playwright/integrations-stripe.e2e.ts +++ b/apps/web/playwright/integrations-stripe.e2e.ts @@ -1,6 +1,10 @@ import { expect } from "@playwright/test"; import type Prisma from "@prisma/client"; +import prisma from "@calcom/prisma"; +import { SchedulingType } from "@calcom/prisma/enums"; +import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; + import { test } from "./lib/fixtures"; import type { Fixtures } from "./lib/fixtures"; import { todo, selectFirstAvailableTimeSlotNextMonth } from "./lib/testUtils"; @@ -34,6 +38,95 @@ test.describe("Stripe integration", () => { }); }); + test("when enabling Stripe, credentialId is included", async ({ page, users }) => { + const user = await users.create(); + await user.apiLogin(); + await page.goto("/apps/installed"); + + await user.getPaymentCredential(); + + const eventType = user.eventTypes.find((e) => e.slug === "paid") as Prisma.EventType; + await user.setupEventWithPrice(eventType); + + // Need to wait for the DB to be updated with the metadata + await page.waitForResponse((res) => res.url().includes("update") && res.status() === 200); + + // Check event type metadata to see if credentialId is included + const eventTypeMetadata = await prisma.eventType.findFirst({ + where: { + id: eventType.id, + }, + select: { + metadata: true, + }, + }); + + const metadata = EventTypeMetaDataSchema.parse(eventTypeMetadata?.metadata); + + const stripeAppMetadata = metadata?.apps?.stripe; + + expect(stripeAppMetadata).toHaveProperty("credentialId"); + expect(typeof stripeAppMetadata?.credentialId).toBe("number"); + }); + + test("when enabling Stripe, team credentialId is included", async ({ page, users }) => { + const ownerObj = { username: "pro-user", name: "pro-user" }; + const teamMatesObj = [ + { name: "teammate-1" }, + { name: "teammate-2" }, + { name: "teammate-3" }, + { name: "teammate-4" }, + ]; + + const owner = await users.create(ownerObj, { + hasTeam: true, + teammates: teamMatesObj, + schedulingType: SchedulingType.COLLECTIVE, + }); + await owner.apiLogin(); + const { team } = await owner.getFirstTeam(); + const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id); + + const teamEvent = await owner.getFirstTeamEvent(team.id); + + await page.goto("/apps/stripe"); + + /** We start the Stripe flow */ + await Promise.all([ + page.waitForURL("https://connect.stripe.com/oauth/v2/authorize?*"), + page.click('[data-testid="install-app-button"]'), + page.click('[data-testid="anything else"]'), + ]); + + await Promise.all([ + page.waitForURL("/apps/installed/payment?hl=stripe"), + /** We skip filling Stripe forms (testing mode only) */ + page.click('[id="skip-account-app"]'), + ]); + + await owner.setupEventWithPrice(teamEvent); + + // Need to wait for the DB to be updated with the metadata + await page.waitForResponse((res) => res.url().includes("update") && res.status() === 200); + + // Check event type metadata to see if credentialId is included + const eventTypeMetadata = await prisma.eventType.findFirst({ + where: { + id: teamEvent.id, + }, + select: { + metadata: true, + }, + }); + + const metadata = EventTypeMetaDataSchema.parse(eventTypeMetadata?.metadata); + + const stripeAppMetadata = metadata?.apps?.stripe; + + expect(stripeAppMetadata).toHaveProperty("credentialId"); + expect(typeof stripeAppMetadata?.credentialId).toBe("number"); + }); + test("Can book a paid booking", async ({ page, users }) => { const user = await users.create(); const eventType = user.eventTypes.find((e) => e.slug === "paid") as Prisma.EventType; diff --git a/packages/app-store/_components/AppCard.tsx b/packages/app-store/_components/AppCard.tsx index cf06eeba10..e946aab684 100644 --- a/packages/app-store/_components/AppCard.tsx +++ b/packages/app-store/_components/AppCard.tsx @@ -90,6 +90,7 @@ export default function AppCard({ {app?.isInstalled || app.credentialOwner ? (
{ if (switchOnClick) { diff --git a/packages/app-store/alby/zod.ts b/packages/app-store/alby/zod.ts index 0983fe0ce7..249451dc77 100644 --- a/packages/app-store/alby/zod.ts +++ b/packages/app-store/alby/zod.ts @@ -29,6 +29,7 @@ export const appDataSchema = eventTypeAppCardZod.merge( currency: z.string(), paymentOption: z.string().optional(), enabled: z.boolean().optional(), + credentialId: z.number().optional(), }) ); export const appKeysSchema = z.object({