import { test, expect } from '@playwright/test';
// Mock data removed - was unused
const mockLedgerEntries = [
{
id: 'ledger-1',
orgId: 'test-org-1',
eventId: 'test-event-1',
orderId: 'test-order-1',
type: 'sale',
amountCents: 15000,
currency: 'USD',
createdAt: new Date().toISOString(),
stripe: {
balanceTxnId: 'txn_mock_1',
chargeId: 'ch_mock_1',
accountId: 'acct_mock_1',
},
},
{
id: 'ledger-2',
orgId: 'test-org-1',
eventId: 'test-event-1',
orderId: 'test-order-1',
type: 'platform_fee',
amountCents: 450,
currency: 'USD',
createdAt: new Date().toISOString(),
stripe: {
balanceTxnId: 'txn_mock_1',
chargeId: 'ch_mock_1',
accountId: 'acct_mock_1',
},
},
{
id: 'ledger-3',
orgId: 'test-org-1',
eventId: 'test-event-1',
orderId: 'test-order-1',
type: 'fee',
amountCents: -465,
currency: 'USD',
createdAt: new Date().toISOString(),
stripe: {
balanceTxnId: 'txn_mock_1',
chargeId: 'ch_mock_1',
accountId: 'acct_mock_1',
},
},
];
test.describe('Refunds and Disputes System', () => {
test.beforeEach(async ({ page }) => {
// Mock network requests for refunds API
await page.route('**/createRefund', async (route) => {
const request = await route.request().postDataJSON();
// Simulate validation errors
if (!request.orderId) {
await route.fulfill({
status: 400,
contentType: 'application/json',
body: JSON.stringify({ error: 'orderId is required' })
});
return;
}
if (request.amountCents && request.amountCents > 15000) {
await route.fulfill({
status: 400,
contentType: 'application/json',
body: JSON.stringify({
error: 'Invalid refund amount: exceeds order total',
details: `Refund amount ${request.amountCents} exceeds order total 15000`
})
});
return;
}
// Simulate successful refund
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
refundId: 'refund_mock_123',
stripeRefundId: 're_mock_123',
amountCents: request.amountCents || 15000,
status: 'succeeded'
})
});
});
// Mock get order refunds API
await page.route('**/getOrderRefunds', async (route) => {
const request = await route.request().postDataJSON();
if (request.orderId === 'test-order-1') {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
refunds: [
{
id: 'refund_mock_123',
amountCents: 7500,
status: 'succeeded',
createdAt: new Date().toISOString(),
reason: 'Customer request'
}
]
})
});
} else {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ refunds: [] })
});
}
});
// Mock reconciliation API
await page.route('**/getReconciliationData', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
summary: {
grossSales: 15000,
refunds: 0,
stripeFees: 465,
platformFees: 450,
disputeFees: 0,
netToOrganizer: 14085,
totalTransactions: 1,
period: {
start: '2024-01-01',
end: '2024-12-31'
}
},
entries: mockLedgerEntries,
total: mockLedgerEntries.length
})
});
});
// Mock events API for reconciliation
await page.route('**/getReconciliationEvents', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
events: [
{
id: 'test-event-1',
name: 'Test Concert 2024',
startAt: '2024-08-01T19:00:00Z'
}
]
})
});
});
// Mock dispute API
await page.route('**/getOrderDisputes', async (route) => {
const request = await route.request().postDataJSON();
if (request.orderId === 'disputed-order') {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
orderId: 'disputed-order',
dispute: {
disputeId: 'dp_mock_789',
status: 'warning_needs_response',
reason: 'fraudulent',
createdAt: new Date().toISOString()
}
})
});
} else {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
orderId: request.orderId,
dispute: null
})
});
}
});
// Mock Firebase authentication
await page.addInitScript(() => {
// @ts-ignore
window.mockUser = {
email: 'admin@example.com',
organization: {
id: 'test-org-1',
name: 'Test Organization'
},
role: 'admin'
};
});
});
test('should display orders table with refund actions', async ({ page }) => {
// Mock the orders management page
await page.setContent(`
Orders Management
Event Orders
Customer: test@example.com
Tickets: 2
`);
// Verify initial page content
await expect(page.locator('h1')).toContainText('Event Orders');
await expect(page.locator('.order-card')).toBeVisible();
await expect(page.locator('.status-badge.paid')).toContainText('Paid');
await expect(page.locator('.amount')).toContainText('$150.00');
// Click refund button to open modal
await page.click('#refund-btn');
await expect(page.locator('#refund-modal')).toBeVisible();
await expect(page.locator('#refund-modal h2')).toContainText('Create Refund');
// Test full refund (default)
await expect(page.locator('input[value="full"]')).toBeChecked();
await expect(page.locator('#refund-total')).toContainText('$150.00');
// Create full refund
await page.click('#confirm-refund');
// Verify success message
await expect(page.locator('#refund-result')).toBeVisible();
await expect(page.locator('#refund-result')).toContainText('Refund created successfully');
await expect(page.locator('#refund-result')).toContainText('refund_mock_123');
});
test('should handle partial refund with custom amount', async ({ page }) => {
await page.setContent(`
`);
// Select partial refund type
await page.click('input[value="partial"]');
await expect(page.locator('#custom-amount')).toBeVisible();
// Enter custom amount
await page.fill('#refund-amount', '75.50');
await expect(page.locator('#refund-total')).toContainText('$75.50');
// Create partial refund
await page.click('#confirm-refund');
// Verify success
await expect(page.locator('#refund-result')).toContainText('Success: $75.50 refunded');
});
test('should handle ticket-specific refunds', async ({ page }) => {
await page.setContent(`
Select Tickets:
Refund Amount: $150.00
`);
// Select tickets refund type
await page.click('input[value="tickets"]');
await expect(page.locator('#ticket-selection')).toBeVisible();
// Select first ticket only
await page.click('input[data-ticket-id="ticket-1"]');
await expect(page.locator('#refund-total')).toContainText('$75.00');
// Create single ticket refund
await page.click('#confirm-refund');
await expect(page.locator('#refund-result')).toContainText('Ticket refund successful');
});
test('should validate refund amounts and show errors', async ({ page }) => {
await page.setContent(`
Refund Amount: $0.00
`);
// Test excessive refund amount
await page.fill('#refund-amount', '200.00');
await page.click('#confirm-refund');
await expect(page.locator('#refund-result')).toContainText('Error: Invalid refund amount: exceeds order total');
});
test('should display reconciliation report with correct calculations', async ({ page }) => {
await page.setContent(`
`);
// Verify initial page content
await expect(page.locator('h1')).toContainText('Reconciliation Report');
// Load reconciliation data
await page.click('#load-data');
// Wait for loading to complete
await expect(page.locator('#loading')).toBeVisible();
await expect(page.locator('#summary-cards')).toBeVisible();
// Verify summary calculations
await expect(page.locator('#gross-sales')).toContainText('$150.00');
await expect(page.locator('#stripe-fees')).toContainText('$4.65');
await expect(page.locator('#platform-fees')).toContainText('$4.50');
await expect(page.locator('#net-amount')).toContainText('$140.85');
// Verify transactions table
await expect(page.locator('#transactions-table')).toBeVisible();
await expect(page.locator('#transactions-body tr')).toHaveCount(3); // 3 ledger entries
// Verify export button appears
await expect(page.locator('#export-csv')).toBeVisible();
});
test('should handle dispute status display', async ({ page }) => {
await page.setContent(`
Dispute
VIP Access - $100.00
`);
// Load dispute information
await page.click('#load-dispute-info');
// Verify dispute alert appears
await expect(page.locator('#dispute-alert')).toBeVisible();
await expect(page.locator('#dispute-details')).toContainText('Status: warning_needs_response');
await expect(page.locator('#dispute-details')).toContainText('Reason: fraudulent');
// Verify ticket shows dispute status
await expect(page.locator('.ticket-status.locked-dispute')).toContainText('Dispute');
});
test('should validate permission requirements for refunds', async ({ page }) => {
// Mock unauthorized request
await page.route('**/createRefund', async (route) => {
await route.fulfill({
status: 403,
contentType: 'application/json',
body: JSON.stringify({ error: 'Insufficient permissions' })
});
});
await page.setContent(`
`);
await page.click('#create-refund');
await expect(page.locator('#result')).toContainText('Permission denied: Insufficient permissions');
});
});
test.describe('Idempotency and Concurrency', () => {
test('should handle duplicate refund requests gracefully', async ({ page }) => {
let requestCount = 0;
await page.route('**/createRefund', async (route) => {
requestCount++;
if (requestCount === 1) {
// First request - create refund
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
refundId: 'refund_mock_123',
status: 'succeeded',
amountCents: 5000
})
});
} else {
// Subsequent requests - return existing refund
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
refundId: 'refund_mock_123',
status: 'succeeded',
message: 'Refund already exists'
})
});
}
});
await page.setContent(`
`);
// Click multiple times rapidly
await page.click('#create-refund');
await page.click('#create-refund');
await page.click('#create-refund');
// Verify idempotency handling
const results = page.locator('#results div');
await expect(results).toHaveCount(3);
await expect(results.nth(0)).toContainText('Click 1: Created - refund_mock_123');
await expect(results.nth(1)).toContainText('Click 2: Refund already exists - refund_mock_123');
await expect(results.nth(2)).toContainText('Click 3: Refund already exists - refund_mock_123');
});
});