Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions tests/workshop/workshop-advanced-flow.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ test('Workshop - Advanced Flow Payment', async ({ page }) => {
// Wait for load event
await page.waitForLoadState('load');

// Fill card details
await utilities.fillDropinCardDetails(page);
// Fill card details (Adyen.Web v6 Drop-in)
await utilities.fillDropinCardDetailsV6(page);

// Click "Pay" button
const payButton = page.locator('.adyen-checkout__button__text >> visible=true');
Expand Down
135 changes: 135 additions & 0 deletions tests/workshop/workshop-preauth-flow.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// @ts-check
const { test, expect } = require('@playwright/test');
const utilities = require('../utilities');
const { v4: uuidv4 } = require('uuid');

/**
* Workshop - Pre-Authorisation Flow
*
* Flow: /api/preauthorisation -> /api/modify-amount -> /api/capture -> /api/refund
*
* 1. Navigate to the preauthorisation checkout page
* 2. Select "Credit or debit card" and fill card details
* 3. Submit the pre-auth payment and capture the real pspReference
* 4. Verify success result page
* 5. Adjust the authorisation amount to 66 USD via /api/modify-amount
* 6. Capture the payment via /api/capture
* 7. Refund the payment via /api/refund
* 8. Simulate webhooks (AUTHORISATION, CAPTURE, REFUND)
*/
test('Workshop - PreAuth: Authorise, Adjust, Capture, Refund', async ({ page, request }) => {
// Step 1: Navigate to the preauthorisation checkout
await page.goto('/checkout?type=preauthorisation');

// Step 2: Wait for Drop-in to load and select "Credit or debit card"
await page.waitForLoadState('load');
await expect(page.locator('text="Credit or debit card"')).toBeVisible();

const radioButton = await page.getByRole('radio', { name: 'Credit or debit card' });
await radioButton.click();
await page.waitForLoadState('load');

// Step 3: Fill card details and submit, capturing the real pspReference (Adyen.Web v6 Drop-in)
await utilities.fillDropinCardDetailsV6(page);

// Intercept the API response to capture pspReference before page navigates
let pspReference;
await page.route('**/api/preauthorisation', async (route) => {
const response = await route.fetch();
const body = await response.json();
pspReference = body.pspReference;
console.log("PreAuth pspReference:", pspReference);
await route.fulfill({ response, json: body });
});

const payButton = page.locator('.adyen-checkout__button__text >> visible=true');
await expect(payButton).toBeVisible();
await payButton.click();

// Step 4: Verify success - the pre-auth payment should be authorised
await expect(page.locator('text="Return Home"')).toBeVisible();

// Step 5: Adjust the authorisation amount to 66 USD using real pspReference
const modifyResponse = await request.post('/api/modify-amount', {
data: {
"pspReference": pspReference,
"amount": 6600
}
});
expect(modifyResponse.status()).toEqual(200);
console.log("Modify amount response:", JSON.stringify(await modifyResponse.json()));

// Step 6: Capture the payment for 66 USD
const captureResponse = await request.post('/api/capture', {
data: {
"pspReference": pspReference,
"amount": 6600
}
});
expect(captureResponse.status()).toEqual(200);
console.log("Capture response:", JSON.stringify(await captureResponse.json()));

// Step 7: Refund the captured payment
const refundResponse = await request.post('/api/refund', {
data: {
"pspReference": pspReference,
"amount": 6600
}
});
expect(refundResponse.status()).toEqual(200);
console.log("Refund response:", JSON.stringify(await refundResponse.json()));

// Step 8: Simulate webhooks to verify webhook handling
var authNotification = {
"eventCode": "AUTHORISATION",
"success": "true",
"eventDate": "2026-06-12T12:00:00+01:00",
"merchantAccountCode": "YOUR_MERCHANT_ACCOUNT",
"pspReference": pspReference,
"merchantReference": "preauth-test",
"amount": { "value": 1000, "currency": "USD" }
};
var hmacSignature = await utilities.calculateHmacSignature(authNotification);
authNotification["additionalData"] = { "hmacSignature": "" + hmacSignature + "" };

var webhookResponse = await request.post('/webhooks', {
data: { "live": "false", "notificationItems": [{ "NotificationRequestItem": authNotification }] }
});
expect([200, 202]).toContain(webhookResponse.status());

var captureNotification = {
"eventCode": "CAPTURE",
"success": "true",
"eventDate": "2026-06-12T12:02:00+01:00",
"merchantAccountCode": "YOUR_MERCHANT_ACCOUNT",
"pspReference": uuidv4().replace(/-/g, '').substring(0, 16).toUpperCase(),
"originalReference": pspReference,
"merchantReference": "capture-test",
"amount": { "value": 6600, "currency": "USD" }
};
hmacSignature = await utilities.calculateHmacSignature(captureNotification);
captureNotification["additionalData"] = { "hmacSignature": "" + hmacSignature + "" };

webhookResponse = await request.post('/webhooks', {
data: { "live": "false", "notificationItems": [{ "NotificationRequestItem": captureNotification }] }
});
expect([200, 202]).toContain(webhookResponse.status());

var refundNotification = {
"eventCode": "REFUND",
"success": "true",
"eventDate": "2026-06-12T12:03:00+01:00",
"merchantAccountCode": "YOUR_MERCHANT_ACCOUNT",
"pspReference": uuidv4().replace(/-/g, '').substring(0, 16).toUpperCase(),
"originalReference": pspReference,
"merchantReference": "refund-test",
"amount": { "value": 6600, "currency": "USD" }
};
hmacSignature = await utilities.calculateHmacSignature(refundNotification);
refundNotification["additionalData"] = { "hmacSignature": "" + hmacSignature + "" };

webhookResponse = await request.post('/webhooks', {
data: { "live": "false", "notificationItems": [{ "NotificationRequestItem": refundNotification }] }
});
expect([200, 202]).toContain(webhookResponse.status());
});
141 changes: 141 additions & 0 deletions tests/workshop/workshop-tokenization-flow.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// @ts-check
const { test, expect } = require('@playwright/test');
const utilities = require('../utilities');
const { v4: uuidv4 } = require('uuid');

/**
* Workshop - Tokenization Flows (README_TOKENIZATION.md)
*
* Tests all tokenization flows:
* Flow 1: /api/subscription-create -> /api/subscription-payment
* Flow 2: /api/subscription-create -> /api/subscription-cancel
* Flow 3: /api/subscription-create -> /api/subscription-payment -> /api/subscription-cancel
* Flow 4: /api/subscription-create -> /api/subscription-payment -> /api/subscription-cancel -> /api/subscription-payment (should fail)
*
* Webhooks handled: AUTHORISATION, RECURRING_CONTRACT
*/

// Helper: perform zero-auth via Drop-in UI and return token info
async function createToken(page) {
await page.goto('/checkout?type=tokenization');
await page.waitForLoadState('load');
await expect(page.locator('text="Credit or debit card"')).toBeVisible();

const radioButton = await page.getByRole('radio', { name: 'Credit or debit card' });
await radioButton.click();
await page.waitForLoadState('load');

await utilities.fillDropinCardDetailsV6(page);

let pspReference;
let recurringDetailRef;
await page.route('**/api/subscription-create', async (route) => {
const response = await route.fetch();
const body = await response.json();
pspReference = body.pspReference;
recurringDetailRef = body.additionalData
? body.additionalData['recurring.recurringDetailReference']
: null;
console.log("Zero-auth pspReference:", pspReference);
console.log("recurringDetailReference:", recurringDetailRef);
await route.fulfill({ response, json: body });
});

const payButton = page.locator('.adyen-checkout__button__text >> visible=true');
await expect(payButton).toBeVisible();
await payButton.click();

await expect(page.locator('text="Return Home"')).toBeVisible();

return { pspReference, recurringDetailRef };
}

// Helper: simulate RECURRING_CONTRACT webhook to store the token on the server
async function simulateRecurringWebhook(request, pspReference, token) {
var notificationRequestItem = {
"eventCode": "RECURRING_CONTRACT",
"success": "true",
"eventDate": "2026-06-12T12:00:00+01:00",
"merchantAccountCode": "YOUR_MERCHANT_ACCOUNT",
"pspReference": pspReference,
"merchantReference": "zero-auth-test",
"amount": { "value": 0, "currency": "EUR" },
"additionalData": {
"recurring.recurringDetailReference": token,
"recurring.shopperReference": "test-shopper"
}
};
const hmacSignature = await utilities.calculateHmacSignature(notificationRequestItem);
notificationRequestItem["additionalData"]["hmacSignature"] = "" + hmacSignature + "";

const resp = await request.post('/webhooks', {
data: { "live": "false", "notificationItems": [{ "NotificationRequestItem": notificationRequestItem }] }
});
expect([200, 202]).toContain(resp.status());
}

// Flow 1: subscription-create -> subscription-payment
test('Tokenization Flow 1: Create Token → Make Payment', async ({ page, request }) => {
const { pspReference, recurringDetailRef } = await createToken(page);
const token = recurringDetailRef || ('TOKEN_' + uuidv4().substring(0, 8));

await simulateRecurringWebhook(request, pspReference, token);

const payment = await request.post('/api/subscription-payment');
expect(payment.status()).toEqual(200);
const result = await payment.json();
console.log("Flow 1 - Payment result:", result.resultCode);
expect(result.resultCode).toBeDefined();
});

// Flow 2: subscription-create -> subscription-cancel
test('Tokenization Flow 2: Create Token → Cancel Subscription', async ({ page, request }) => {
const { pspReference, recurringDetailRef } = await createToken(page);
const token = recurringDetailRef || ('TOKEN_' + uuidv4().substring(0, 8));

await simulateRecurringWebhook(request, pspReference, token);

const cancel = await request.post('/api/subscription-cancel');
expect(cancel.status()).toEqual(200);
const result = await cancel.text();
console.log("Flow 2 - Cancel result:", result);
});

// Flow 3: subscription-create -> subscription-payment -> subscription-cancel
test('Tokenization Flow 3: Create Token → Payment → Cancel', async ({ page, request }) => {
const { pspReference, recurringDetailRef } = await createToken(page);
const token = recurringDetailRef || ('TOKEN_' + uuidv4().substring(0, 8));

await simulateRecurringWebhook(request, pspReference, token);

const payment = await request.post('/api/subscription-payment');
expect(payment.status()).toEqual(200);
const payResult = await payment.json();
console.log("Flow 3 - Payment result:", payResult.resultCode);
expect(payResult.resultCode).toBeDefined();

const cancel = await request.post('/api/subscription-cancel');
expect(cancel.status()).toEqual(200);
console.log("Flow 3 - Cancel result:", await cancel.text());
});

// Flow 4: subscription-create -> subscription-payment -> subscription-cancel -> subscription-payment (should fail)
test('Tokenization Flow 4: Create Token → Payment → Cancel → Payment (should fail)', async ({ page, request }) => {
const { pspReference, recurringDetailRef } = await createToken(page);
const token = recurringDetailRef || ('TOKEN_' + uuidv4().substring(0, 8));

await simulateRecurringWebhook(request, pspReference, token);

const payment1 = await request.post('/api/subscription-payment');
expect(payment1.status()).toEqual(200);
console.log("Flow 4 - First payment:", (await payment1.json()).resultCode);

const cancel = await request.post('/api/subscription-cancel');
expect(cancel.status()).toEqual(200);
console.log("Flow 4 - Cancel:", await cancel.text());

// After cancel, subscription-payment should fail (400 = no token available)
const payment2 = await request.post('/api/subscription-payment');
expect(payment2.status()).toEqual(400);
console.log("Flow 4 - Second payment after cancel returned 400 as expected");
});