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:
co-authored by
Joe Au-Yeung
Joe Au-Yeung
parent
0ddab30340
commit
d66095bf9c
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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" });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user