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
Andres Vargas 2023-07-01 13:18:27 -04:00 committed by GitHub
parent 70b8a38b95
commit 0e022b013c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 68 additions and 32 deletions

View File

@ -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");

View File

@ -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");