156 lines
4.9 KiB
TypeScript
156 lines
4.9 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
||
import { execSync } from 'child_process';
|
||
|
||
const LOGIN_EMAIL = 'checkout-e2e@example.com';
|
||
const LOGIN_PASSWORD = 'Password123!';
|
||
|
||
test.describe('Checkout Payment Step – Paddle flow', () => {
|
||
test.beforeAll(async () => {
|
||
execSync(
|
||
`php artisan tenant:add-dummy --email=${LOGIN_EMAIL} --password=${LOGIN_PASSWORD} --first_name=Checkout --last_name=Tester --address="Playwrightstr. 1" --phone="+4912345678"`
|
||
);
|
||
|
||
execSync(
|
||
`php artisan tinker --execute="App\\\\Models\\\\User::where('email', '${LOGIN_EMAIL}')->update(['email_verified_at' => now()]);"`
|
||
);
|
||
});
|
||
|
||
test.beforeEach(async ({ page }) => {
|
||
await page.goto('/login');
|
||
await page.fill('input[name="email"]', LOGIN_EMAIL);
|
||
await page.fill('input[name="password"]', LOGIN_PASSWORD);
|
||
await page.getByRole('button', { name: /Anmelden|Login/ }).click();
|
||
await expect(page).toHaveURL(/dashboard/);
|
||
});
|
||
|
||
test('opens Paddle checkout and shows success notice', async ({ page }) => {
|
||
await page.route('**/paddle/create-checkout', async (route) => {
|
||
const request = route.request();
|
||
const postData = request.postDataJSON() as { inline?: boolean } | null;
|
||
const inline = Boolean(postData?.inline);
|
||
|
||
if (inline) {
|
||
await route.fulfill({
|
||
status: 200,
|
||
contentType: 'application/json',
|
||
body: JSON.stringify({
|
||
mode: 'inline',
|
||
items: [
|
||
{ priceId: 'pri_123', quantity: 1 },
|
||
],
|
||
custom_data: {
|
||
tenant_id: '1',
|
||
package_id: '2',
|
||
checkout_session_id: 'cs_123',
|
||
},
|
||
customer: {
|
||
email: LOGIN_EMAIL,
|
||
},
|
||
}),
|
||
});
|
||
return;
|
||
}
|
||
|
||
await route.fulfill({
|
||
status: 200,
|
||
contentType: 'application/json',
|
||
body: JSON.stringify({
|
||
checkout_url: 'https://paddle.test/checkout/success',
|
||
}),
|
||
});
|
||
});
|
||
|
||
await page.route('https://cdn.paddle.com/paddle/v2/paddle.js', async (route) => {
|
||
await route.fulfill({
|
||
status: 200,
|
||
contentType: 'application/javascript',
|
||
body: `
|
||
window.Paddle = {
|
||
Environment: { set: function(env) { window.__paddleEnv = env; } },
|
||
Initialize: function(opts) { window.__paddleInit = opts; },
|
||
Checkout: {
|
||
open: function(config) {
|
||
window.__paddleOpenConfig = config;
|
||
}
|
||
}
|
||
};
|
||
`,
|
||
});
|
||
});
|
||
|
||
await openCheckoutPaymentStep(page);
|
||
|
||
await page.evaluate(() => {
|
||
window.__openedUrls = [];
|
||
window.open = (url: string, target?: string | null, features?: string | null) => {
|
||
window.__openedUrls.push({ url, target: target ?? null, features: features ?? null });
|
||
return null;
|
||
};
|
||
});
|
||
|
||
await page.getByRole('button', { name: /Continue with Paddle|Weiter mit Paddle/ }).click();
|
||
|
||
await expect(
|
||
page.locator(
|
||
'text=/Paddle checkout is running in a secure overlay|Der Paddle-Checkout läuft jetzt in einem Overlay/'
|
||
)
|
||
).toBeVisible();
|
||
|
||
await expect.poll(async () => {
|
||
return page.evaluate(() => window.__paddleOpenConfig?.items?.[0]?.priceId ?? null);
|
||
}).toBe('pri_123');
|
||
|
||
await expect.poll(async () => {
|
||
return page.evaluate(() => window.__openedUrls?.length ?? 0);
|
||
}).toBe(0);
|
||
});
|
||
|
||
test('shows error state when Paddle checkout creation fails', async ({ page }) => {
|
||
await page.route('**/paddle/create-checkout', async (route) => {
|
||
await route.fulfill({
|
||
status: 500,
|
||
contentType: 'application/json',
|
||
body: JSON.stringify({ message: 'test-error' }),
|
||
});
|
||
});
|
||
|
||
await openCheckoutPaymentStep(page);
|
||
|
||
await page.getByRole('button', { name: /Continue with Paddle|Weiter mit Paddle/ }).click();
|
||
|
||
await expect(
|
||
page.locator('text=/Paddle checkout could not be started|Paddle-Checkout konnte nicht gestartet werden/')
|
||
).toBeVisible();
|
||
});
|
||
});
|
||
|
||
async function openCheckoutPaymentStep(page: import('@playwright/test').Page) {
|
||
await page.goto('/packages');
|
||
const checkoutLink = page.locator('a[href^="/checkout/"]').first();
|
||
const href = await checkoutLink.getAttribute('href');
|
||
|
||
if (!href) {
|
||
throw new Error('No checkout link found on packages page.');
|
||
}
|
||
|
||
await page.goto(href);
|
||
|
||
const nextButton = page.getByRole('button', {
|
||
name: /Weiter zum Zahlungsschritt|Continue to Payment/,
|
||
});
|
||
if (await nextButton.isVisible()) {
|
||
await nextButton.click();
|
||
}
|
||
|
||
await page.waitForSelector('text=/Zahlung|Payment/');
|
||
}
|
||
|
||
declare global {
|
||
interface Window {
|
||
__openedUrls?: Array<{ url: string; target?: string | null; features?: string | null }>;
|
||
__paddleOpenConfig?: { url?: string; items?: Array<{ priceId: string; quantity: number }>; settings?: { displayMode?: string } };
|
||
__paddleEnv?: string;
|
||
__paddleInit?: Record<string, unknown>;
|
||
}
|
||
}
|