import { expect, test, type Page } from "@playwright/test"; import { mockCoreApis } from "@e2e/fixtures/api-mocks"; import { apiEnvelope } from "@e2e/fixtures/data/common"; import { e2eEmailUser } from "@e2e/fixtures/data/user"; import { clearBrowserState, seedEmailSession, } from "@e2e/fixtures/test-helpers"; const phpPlans = { plans: [ { planId: "dol_1000", planName: "1,000 Credits", orderType: "dol", amountCents: 49_990, originalAmountCents: 74_990, dailyPriceCents: null, currency: "PHP", vipDays: null, dolAmount: 1_000, creditBalance: 1_000, }, ], }; async function registerPhilippinesPaymentMocks( page: Page, response: "hosted" | "qr", ) { let createOrderCount = 0; await page.route("**/api/user/profile", async (route) => { await route.fulfill({ json: apiEnvelope({ ...e2eEmailUser, countryCode: "PH" }), }); }); await page.route("**/api/payment/plans**", async (route) => { await route.fulfill({ json: apiEnvelope(phpPlans) }); }); await page.route("**/api/payment/create-order", async (route) => { createOrderCount += 1; await route.fulfill({ json: apiEnvelope({ orderId: `order_gcash_${response}`, payParams: { provider: "ezpay", countryCode: "PH", channelCode: "PH_QRPH_DYNAMIC", channelType: "QR", payData: "000201010212ph-qr-payload", ...(response === "hosted" ? { cashierUrl: "https://pay.example/gcash-hosted" } : {}), firstChargeAmountCents: 49_990, currency: "PHP", }, }), }); }); await page.route("**/api/payment/order-status**", async (route) => { await route.fulfill({ json: apiEnvelope({ orderId: `order_gcash_${response}`, status: "pending", orderType: "dol", planId: "dol_1000", creditsAdded: 0, }), }); }); return { getCreateOrderCount: () => createOrderCount, }; } async function preparePhilippinesUser(page: Page) { await seedEmailSession(page); await page.evaluate(() => { const rawUser = localStorage.getItem("cozsweet:user"); const user = rawUser ? JSON.parse(rawUser) : {}; localStorage.setItem( "cozsweet:user", JSON.stringify({ ...user, countryCode: "PH" }), ); }); } test.beforeEach(async ({ baseURL, context, page }) => { await clearBrowserState(context, page, baseURL); await mockCoreApis(page); }); test("Philippines checkout prefers the hosted GCash URL even for a QR response", async ({ page, }) => { const payment = await registerPhilippinesPaymentMocks(page, "hosted"); await preparePhilippinesUser(page); await page.goto("/subscription?type=topup&character=elio"); await expect(page.getByRole("button", { name: "GCash" })).toHaveAttribute( "aria-pressed", "true", ); await page.getByRole("button", { name: "Pay and Top Up" }).click(); await expect( page.getByRole("alertdialog", { name: "Continue to payment?" }), ).toBeVisible(); await expect( page.getByRole("dialog", { name: "Pay with GCash / QR Ph" }), ).toHaveCount(0); await expect( page.getByRole("dialog", { name: "Scan to pay with QRIS" }), ).toHaveCount(0); expect(payment.getCreateOrderCount()).toBe(1); await page.getByRole("button", { name: "Cancel" }).click(); await expect(page.getByRole("button", { name: "Stripe" })).toBeEnabled(); expect(payment.getCreateOrderCount()).toBe(1); }); test("Philippines QR-only fallback is labeled GCash / QR Ph and can switch to Stripe", async ({ page, }) => { await page.setViewportSize({ width: 390, height: 844 }); const payment = await registerPhilippinesPaymentMocks(page, "qr"); await preparePhilippinesUser(page); await page.goto("/subscription?type=topup&character=elio"); await page.getByRole("button", { name: "Pay and Top Up" }).click(); const dialog = page.getByRole("dialog", { name: "Pay with GCash / QR Ph", }); await expect(dialog).toBeVisible(); await expect(dialog).toContainText(/₱\s*499\.90/); await expect(dialog).not.toContainText("QRIS"); await expect( dialog.getByRole("img", { name: "GCash / QR Ph payment QR code" }), ).toBeVisible(); await dialog.getByRole("button", { name: "Close" }).click(); await expect(dialog).toBeHidden(); await expect(page.getByRole("button", { name: "Stripe" })).toBeEnabled(); await page.getByRole("button", { name: "Stripe" }).click(); await expect(page.getByRole("button", { name: "Stripe" })).toHaveAttribute( "aria-pressed", "true", ); expect(payment.getCreateOrderCount()).toBe(1); });