fix: Stripe integration (#9462)
* Add Stripe integration tests * Add Stripe payment feature * Update Stripe payment form with valid card info * Fill postal code if visible in Stripe test * Implementing new e2e test * lint fix --------- Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: alannnc <alannnc@gmail.com>pull/9476/head^2
parent
70b8a38b95
commit
0e022b013c
|
@ -8,6 +8,7 @@ import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/avail
|
|||
import { prisma } from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { selectFirstAvailableTimeSlotNextMonth } from "../lib/testUtils";
|
||||
import type { TimeZoneEnum } from "./types";
|
||||
|
||||
// Don't import hashPassword from app as that ends up importing next-auth and initializing it before NEXTAUTH_URL can be updated during tests.
|
||||
|
@ -279,6 +280,10 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => {
|
|||
await page.goto("/auth/logout");
|
||||
},
|
||||
getPaymentCredential: async () => getPaymentCredential(store.page),
|
||||
setupEventWithPrice: async (eventType: Pick<Prisma.EventType, "id">) =>
|
||||
setupEventWithPrice(eventType, store.page),
|
||||
bookAndPaidEvent: async (eventType: Pick<Prisma.EventType, "slug">) =>
|
||||
bookAndPaidEvent(user, eventType, store.page),
|
||||
// ths is for developemnt only aimed to inject debugging messages in the metadata field of the user
|
||||
debug: async (message: string | Record<string, JSONValue>) => {
|
||||
await prisma.user.update({
|
||||
|
@ -372,6 +377,39 @@ export async function apiLogin(
|
|||
});
|
||||
}
|
||||
|
||||
export async function setupEventWithPrice(eventType: Pick<Prisma.EventType, "id">, page: Page) {
|
||||
await page.goto(`/event-types/${eventType?.id}?tabName=apps`);
|
||||
await page.locator("div > .ml-auto").first().click();
|
||||
await page.getByPlaceholder("Price").fill("100");
|
||||
await page.getByTestId("update-eventtype").click();
|
||||
}
|
||||
|
||||
export async function bookAndPaidEvent(
|
||||
user: Pick<Prisma.User, "username">,
|
||||
eventType: Pick<Prisma.EventType, "slug">,
|
||||
page: Page
|
||||
) {
|
||||
// booking process with stripe integration
|
||||
await page.goto(`${user.username}/${eventType?.slug}`);
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
// --- fill form
|
||||
await page.fill('[name="name"]', "Stripe Stripeson");
|
||||
await page.fill('[name="email"]', "test@example.com");
|
||||
|
||||
await Promise.all([page.waitForURL("/payment/*"), page.press('[name="email"]', "Enter")]);
|
||||
|
||||
const stripeFrame = page.frameLocator("iframe").first();
|
||||
await stripeFrame.locator('[name="number"]').fill("4242 4242 4242 4242");
|
||||
const now = new Date();
|
||||
await stripeFrame.locator('[name="expiry"]').fill(`${now.getMonth()} / ${now.getFullYear() + 1}`);
|
||||
await stripeFrame.locator('[name="cvc"]').fill("111");
|
||||
const postcalCodeIsVisible = await stripeFrame.locator('[name="postalCode"]').isVisible();
|
||||
if (postcalCodeIsVisible) {
|
||||
await stripeFrame.locator('[name="postalCode"]').fill("111111");
|
||||
}
|
||||
await page.click('button:has-text("Pay now")');
|
||||
}
|
||||
|
||||
export async function getPaymentCredential(page: Page) {
|
||||
await page.goto("/apps/stripe");
|
||||
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
import { expect } from "@playwright/test";
|
||||
import type Prisma from "@prisma/client";
|
||||
|
||||
import { test } from "./lib/fixtures";
|
||||
import { selectFirstAvailableTimeSlotNextMonth, todo } from "./lib/testUtils";
|
||||
import { todo, selectFirstAvailableTimeSlotNextMonth } from "./lib/testUtils";
|
||||
|
||||
test.describe.configure({ mode: "parallel" });
|
||||
test.afterEach(({ users }) => users.deleteAll());
|
||||
|
||||
const IS_STRIPE_ENABLED = !!(
|
||||
process.env.STRIPE_CLIENT_ID &&
|
||||
process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY &&
|
||||
process.env.STRIPE_PRIVATE_KEY
|
||||
process.env.STRIPE_CLIENT_ID &&
|
||||
process.env.STRIPE_PRIVATE_KEY &&
|
||||
process.env.PAYMENT_FEE_FIXED &&
|
||||
process.env.PAYMENT_FEE_PERCENTAGE
|
||||
);
|
||||
|
||||
// TODO: No longer up to date, rewrite needed.
|
||||
|
||||
// eslint-disable-next-line playwright/no-skipped-test
|
||||
test.skip();
|
||||
|
||||
test.describe("Stripe integration", () => {
|
||||
// eslint-disable-next-line playwright/no-skipped-test
|
||||
test.skip(!IS_STRIPE_ENABLED, "It should only run if Stripe is installed");
|
||||
|
@ -29,20 +27,35 @@ test.describe("Stripe integration", () => {
|
|||
|
||||
await user.getPaymentCredential();
|
||||
|
||||
/** If Stripe is added correctly we should see the "Disconnect" button */
|
||||
await expect(
|
||||
page.locator(`li:has-text("Stripe") >> [data-testid="stripe_payment-integration-disconnect-button"]`)
|
||||
).toContainText("");
|
||||
await expect(page.locator(`h3:has-text("Stripe")`)).toBeVisible();
|
||||
await page.getByRole("list").getByRole("button").click();
|
||||
await expect(page.getByRole("button", { name: "Remove App" })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test("Can book a paid booking", async ({ page, users }) => {
|
||||
const user = await users.create();
|
||||
const eventType = user.eventTypes.find((e) => e.slug === "paid");
|
||||
const eventType = user.eventTypes.find((e) => e.slug === "paid") as Prisma.EventType;
|
||||
await user.apiLogin();
|
||||
await page.goto("/apps/installed");
|
||||
await user.getPaymentCredential();
|
||||
|
||||
await user.getPaymentCredential();
|
||||
await user.setupEventWithPrice(eventType);
|
||||
await user.bookAndPaidEvent(eventType);
|
||||
// success
|
||||
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
|
||||
});
|
||||
|
||||
test("Pending payment booking should not be confirmed by default", async ({ page, users }) => {
|
||||
const user = await users.create();
|
||||
const eventType = user.eventTypes.find((e) => e.slug === "paid") as Prisma.EventType;
|
||||
await user.apiLogin();
|
||||
await page.goto("/apps/installed");
|
||||
|
||||
await user.getPaymentCredential();
|
||||
await user.setupEventWithPrice(eventType);
|
||||
|
||||
// booking process without payment
|
||||
await page.goto(`${user.username}/${eventType?.slug}`);
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
// --- fill form
|
||||
|
@ -51,26 +64,11 @@ test.describe("Stripe integration", () => {
|
|||
|
||||
await Promise.all([page.waitForURL("/payment/*"), page.press('[name="email"]', "Enter")]);
|
||||
|
||||
const stripeFrame = page
|
||||
.frameLocator('iframe[src^="https://js.stripe.com/v3/elements-inner-card-"]')
|
||||
.first();
|
||||
await page.goto(`/bookings/upcoming`);
|
||||
|
||||
// Fill [placeholder="Card number"]
|
||||
await stripeFrame.locator('[placeholder="Card number"]').fill("4242 4242 4242 4242");
|
||||
// Fill [placeholder="MM / YY"]
|
||||
await stripeFrame.locator('[placeholder="MM / YY"]').fill("12 / 24");
|
||||
// Fill [placeholder="CVC"]
|
||||
await stripeFrame.locator('[placeholder="CVC"]').fill("111");
|
||||
// Fill [placeholder="ZIP"]
|
||||
await stripeFrame.locator('[placeholder="ZIP"]').fill("11111");
|
||||
// Click button:has-text("Pay now")
|
||||
await page.click('button:has-text("Pay now")');
|
||||
|
||||
// Make sure we're navigated to the success page
|
||||
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
|
||||
await expect(page.getByText("Unconfirmed")).toBeVisible();
|
||||
await expect(page.getByText("Pending payment").last()).toBeVisible();
|
||||
});
|
||||
|
||||
todo("Pending payment booking should not be confirmed by default");
|
||||
todo("Payment should confirm pending payment booking");
|
||||
todo("Payment should trigger a BOOKING_PAID webhook");
|
||||
todo("Paid booking should be able to be rescheduled");
|
||||
|
|
Loading…
Reference in New Issue