</>

Technology

Playwright

Difficulty

Advanced

Interview Question

How would you approach testing a payment gateway that redirects to a third-party site?

Answer

Scenario: Testing Third-Party Payment Gateway Redirects

The Challenge

Payment gateways like Stripe, PayPal, Razorpay redirect users to a third-party domain. Your test needs to handle:

  1. The redirect away from your app
  2. Filling payment details on the external page
  3. The redirect back to your confirmation page

Approach 1 — Mock the Payment Gateway (Recommended for CI)

TypeScript
import { test, expect } from '@playwright/test';

test('checkout completes with mocked payment', async ({ page }) => {
  // Intercept the payment initiation API
  await page.route('/api/checkout/initiate', route =>
    route.fulfill({
      status: 200,
      json: {
        paymentId: 'test-payment-123',
        redirectUrl: 'http://localhost:3000/payment-mock',
      },
    })
  );

  // Intercept payment verification
  await page.route('/api/checkout/verify', route =>
    route.fulfill({
      status: 200,
      json: { status: 'success', orderId: 'ORD-2024-001' },
    })
  );

  await page.goto('/checkout');
  await page.getByRole('button', { name: 'Pay $99.99' }).click();

  // Simulate redirect back after payment
  await page.goto('http://localhost:3000/payment-success?payment_id=test-payment-123&status=success');

  await expect(page).toHaveURL(/order-confirmed/);
  await expect(page.getByRole('heading')).toContainText('Order Confirmed');
});

Approach 2 — Stripe Sandbox with Test Cards

TypeScript
test('Stripe payment with test card', async ({ page }) => {
  await page.goto('/checkout');
  await page.getByRole('button', { name: 'Proceed to Payment' }).click();

  // Handle Stripe redirect (opens Stripe-hosted checkout)
  const [stripePage] = await Promise.all([
    page.context().waitForEvent('page'),
    page.getByRole('link', { name: 'Pay with Stripe' }).click(),
  ]);

  await stripePage.waitForLoadState('domcontentloaded');

  // Fill Stripe test card details
  const cardFrame = stripePage.frameLocator('[name="card-number-element"]');
  await cardFrame.locator('input').fill('4242424242424242');

  const expiryFrame = stripePage.frameLocator('[name="card-expiry-element"]');
  await expiryFrame.locator('input').fill('1226');

  const cvcFrame = stripePage.frameLocator('[name="card-cvc-element"]');
  await cvcFrame.locator('input').fill('123');

  await stripePage.getByRole('button', { name: 'Pay' }).click();

  // Wait for redirect back to your app
  await page.waitForURL('**/order-confirmed');
  await expect(page.getByRole('heading')).toContainText('Order Confirmed');
});

Approach 3 — Verify Payment Initiation (Without Gateway)

TypeScript
test('checkout initiates payment with correct data', async ({ page }) => {
  // Capture the payment initiation request
  const [request] = await Promise.all([
    page.waitForRequest(req =>
      req.url().includes('/api/checkout/initiate') && req.method() === 'POST'
    ),
    page.getByRole('button', { name: 'Pay $99.99' }).click(),
  ]);

  // Verify correct amount sent to gateway
  const body = JSON.parse(request.postData()!);
  expect(body.amount).toBe(9999);       // In cents
  expect(body.currency).toBe('USD');
  expect(body.orderId).toBeTruthy();
});

Approach 4 — Block Gateway and Test Callback URL

TypeScript
test('payment failure handling', async ({ page }) => {
  // Block gateway and directly test the callback
  await page.route('**/payment-gateway.com/**', route => route.abort());

  await page.goto('/checkout');
  await page.getByRole('button', { name: 'Pay' }).click();

  // Simulate a failed payment callback
  await page.goto('/payment-failed?error=card_declined&order_id=ORD-001');

  await expect(page.getByRole('heading')).toContainText('Payment Failed');
  await expect(page.getByText('Card was declined')).toBeVisible();
  await expect(page.getByRole('button', { name: 'Try Again' })).toBeVisible();
});

Best Practice for Payment Testing

  1. Use provider test modes — Stripe/PayPal/Razorpay all have sandbox environments
  2. Mock for unit/regression — fast, reliable, no network dependency
  3. Sandbox E2E — test the full flow in staging with sandbox credentials
  4. Never test with real cards — even in staging
  5. Store gateway keys in CI secrets — never in code

Follow AutomateQA

Related Topics