test: adding confirmed payment test cases on Stripe (#10243)

Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
This commit is contained in:
Wesley
2023-08-21 11:12:39 +02:00
committed by GitHub
co-authored by Joe Au-Yeung Joe Au-Yeung
parent 0ddab30340
commit d66095bf9c
4 changed files with 95 additions and 11 deletions
@@ -368,7 +368,7 @@ function BookingListItem(booking: BookingItemProps) {
{t("error_collecting_card")}
</Badge>
) : booking.paid ? (
<Badge className="ltr:mr-2 rtl:ml-2" variant="green">
<Badge className="ltr:mr-2 rtl:ml-2" variant="green" data-testid="paid_badge">
{booking.payment[0].paymentOption === "HOLD" ? t("card_held") : t("paid")}
</Badge>
) : null}
+35 -3
View File
@@ -5,6 +5,7 @@ import { hashSync as hash } from "bcryptjs";
import type { API } from "mailhog";
import dayjs from "@calcom/dayjs";
import stripe from "@calcom/features/ee/payments/server/stripe";
import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability";
import { prisma } from "@calcom/prisma";
import { MembershipRole, SchedulingType } from "@calcom/prisma/enums";
@@ -416,8 +417,8 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => {
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),
bookAndPayEvent: async (eventType: Pick<Prisma.EventType, "slug">) =>
bookAndPayEvent(user, eventType, store.page),
makePaymentUsingStripe: async () => makePaymentUsingStripe(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>) => {
@@ -427,6 +428,7 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => {
});
},
delete: async () => await prisma.user.delete({ where: { id: store.user.id } }),
confirmPendingPayment: async () => confirmPendingPayment(store.page),
};
};
@@ -468,6 +470,36 @@ const createUser = (workerInfo: WorkerInfo, opts?: CustomUserOpts | null): Prism
};
};
async function confirmPendingPayment(page: Page) {
await page.waitForURL(new RegExp("/booking/*"));
const url = page.url();
const params = new URLSearchParams(url.split("?")[1]);
const id = params.get("payment_intent");
if (!id) throw new Error(`Payment intent not found in url ${url}`);
const payload = JSON.stringify(
{ type: "payment_intent.succeeded", data: { object: { id } }, account: "e2e_test" },
null,
2
);
const signature = stripe.webhooks.generateTestHeaderString({
payload,
secret: process.env.STRIPE_WEBHOOK_SECRET as string,
});
const response = await page.request.post("/api/integrations/stripepayment/webhook", {
data: payload,
headers: { "stripe-signature": signature },
});
if (response.status() !== 200) throw new Error(`Failed to confirm payment. Response: ${response.text()}`);
}
// login using a replay of an E2E routine.
export async function login(
user: Pick<Prisma.User, "username"> & Partial<Pick<Prisma.User, "password" | "email">>,
@@ -519,7 +551,7 @@ export async function setupEventWithPrice(eventType: Pick<Prisma.EventType, "id"
await page.getByTestId("update-eventtype").click();
}
export async function bookAndPaidEvent(
export async function bookAndPayEvent(
user: Pick<Prisma.User, "username">,
eventType: Pick<Prisma.EventType, "slug">,
page: Page
+56 -6
View File
@@ -1,6 +1,7 @@
import { expect } from "@playwright/test";
import type Prisma from "@prisma/client";
import type { Fixtures } from "./lib/fixtures";
import { test } from "./lib/fixtures";
import { todo, selectFirstAvailableTimeSlotNextMonth } from "./lib/testUtils";
@@ -41,7 +42,7 @@ test.describe("Stripe integration", () => {
await user.getPaymentCredential();
await user.setupEventWithPrice(eventType);
await user.bookAndPaidEvent(eventType);
await user.bookAndPayEvent(eventType);
// success
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
});
@@ -78,7 +79,7 @@ test.describe("Stripe integration", () => {
await user.getPaymentCredential();
await user.setupEventWithPrice(eventType);
await user.bookAndPaidEvent(eventType);
await user.bookAndPayEvent(eventType);
// Rescheduling the event
await Promise.all([page.waitForURL("/booking/*"), page.click('[data-testid="reschedule-link"]')]);
@@ -92,8 +93,57 @@ test.describe("Stripe integration", () => {
await user.makePaymentUsingStripe();
});
todo("Payment should confirm pending payment booking");
todo("Payment should trigger a BOOKING_PAID webhook");
todo("Paid booking should be able to be cancelled");
todo("Cancelled paid booking should be refunded");
test("Paid booking should be able to be cancelled", 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);
await user.bookAndPayEvent(eventType);
await page.click('[data-testid="cancel"]');
await page.click('[data-testid="confirm_cancel"]');
await expect(await page.locator('[data-testid="cancelled-headline"]').first()).toBeVisible();
});
test.describe("When event is paid and confirmed", () => {
let user: Awaited<ReturnType<Fixtures["users"]["create"]>>;
let eventType: Prisma.EventType;
test.beforeEach(async ({ page, users }) => {
user = await users.create();
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);
await user.bookAndPayEvent(eventType);
await user.confirmPendingPayment();
});
test("Cancelled paid booking should be refunded", async ({ page, users, request }) => {
await page.click('[data-testid="cancel"]');
await page.click('[data-testid="confirm_cancel"]');
await expect(await page.locator('[data-testid="cancelled-headline"]').first()).toBeVisible();
await expect(page.getByText("This booking payment has been refunded")).toBeVisible();
});
test("Payment should confirm pending payment booking", async ({ page, users }) => {
await page.goto("/bookings/upcoming");
const paidBadge = page.locator('[data-testid="paid_badge"]').first();
await expect(paidBadge).toBeVisible();
expect(await paidBadge.innerText()).toBe("Paid");
});
todo("Payment should trigger a BOOKING_PAID webhook");
todo("Paid and confirmed booking should be able to be rescheduled");
});
});
+3 -1
View File
@@ -398,7 +398,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const event = stripe.webhooks.constructEvent(payload, sig, process.env.STRIPE_WEBHOOK_SECRET);
if (!event.account) {
// bypassing this validation for e2e tests
// in order to successfully confirm the payment
if (!event.account && !process.env.NEXT_PUBLIC_IS_E2E) {
throw new HttpCode({ statusCode: 202, message: "Incoming connected account" });
}