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
196 changes: 196 additions & 0 deletions src/components/TestCards/TestCards.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
---
import { getIconURL } from "@sumup-oss/icons";
import { testCards } from "../../lib/testCards";

const successCards = testCards.filter((card) => !card.error && !card.challenge);
const challengeCards = testCards.filter((card) => card.challenge);
const errorCards = testCards.filter((card) => card.error);

const sections = [
{ title: "Successful Transactions", cards: successCards },
{ title: "3DS Challenge Flows", cards: challengeCards },
{ title: "Error Cases", cards: errorCards },
];
---

<div class="test-cards">
{
sections.map(({ title, cards }) => (
<div class="test-cards-section">
<h2 class="test-cards-section-title">{title}</h2>
<div class="test-cards-list">
{cards.map((card) => (
<div class="test-card">
<div class="test-card-header">
<img
src={getIconURL(card.brand, "24")}
alt={card.brand}
class="test-card-brand-icon"
/>
<span class="test-card-name">{card.name}</span>
{card.returnsMethodData && (
<span class="test-card-badge">Method Data</span>
)}
</div>
<div class="test-card-number-wrapper">
<code class="test-card-number">{card.number}</code>
<button
class="test-card-copy"
data-number={card.number.replace(/\s/g, "")}
aria-label="Copy card number"
>
📋
</button>
</div>
<p class="test-card-description">{card.description}</p>
</div>
))}
</div>
</div>
))
}
</div>

<style>
.test-cards {
display: flex;
flex-direction: column;
gap: 3rem;
margin: 2rem 0;
}

.test-cards-section {
display: flex;
flex-direction: column;
gap: 1.5rem;
}

.test-cards-section-title {
font-size: 1.75rem;
font-weight: 600;
margin: 0;
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--sl-color-gray-5);
}

.test-cards-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem;
}

.test-card {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 1rem;
border: 1px solid var(--sl-color-gray-5);
border-radius: 0.5rem;
background-color: var(--sl-color-bg-nav);
transition: border-color 0.2s ease;
}

.test-card:hover {
border-color: var(--sl-color-text-accent);
}

.test-card-header {
display: flex;
align-items: center;
gap: 0.5rem;
}

.test-card-brand-icon {
width: 1.5rem;
height: 1.5rem;
object-fit: contain;
flex-shrink: 0;
}

.test-card-name {
font-weight: 600;
font-size: 0.9rem;
color: var(--sl-color-white);
flex: 1;
}

.test-card-badge {
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
background-color: var(--sl-color-gray-6);
color: var(--sl-color-gray-2);
white-space: nowrap;
}

.test-card-number-wrapper {
position: relative;
display: flex;
align-items: center;
gap: 0.5rem;
}

.test-card-number {
font-family: var(--sl-font-mono);
font-size: 1rem;
font-weight: 600;
padding: 0.5rem;
background-color: var(--sl-color-gray-7);
border-radius: 0.25rem;
letter-spacing: 0.05em;
user-select: all;
flex: 1;
}

.test-card-copy {
position: absolute;
right: 0.5rem;
padding: 0.25rem 0.5rem;
background-color: var(--sl-color-gray-6);
border: 1px solid var(--sl-color-gray-5);
border-radius: 0.25rem;
cursor: pointer;
font-size: 0.875rem;
opacity: 0;
transition: opacity 0.2s ease;
}

.test-card-number-wrapper:hover .test-card-copy {
opacity: 1;
}

.test-card-copy:hover {
background-color: var(--sl-color-gray-5);
}

.test-card-description {
font-size: 0.875rem;
color: var(--sl-color-gray-2);
margin: 0;
line-height: 1.5;
}
</style>

<script>
document.addEventListener("DOMContentLoaded", () => {
const copyButtons = document.querySelectorAll(".test-card-copy");

copyButtons.forEach((button) => {
button.addEventListener("click", async () => {
const number = button.getAttribute("data-number");
if (!number) return;

try {
await navigator.clipboard.writeText(number);
const originalText = button.textContent;
button.textContent = "✓";
setTimeout(() => {
button.textContent = originalText;
}, 2000);
} catch (err) {
console.error("Failed to copy:", err);
}
});
});
});
</script>
1 change: 1 addition & 0 deletions src/components/TestCards/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as TestCards } from "./TestCards.astro";
83 changes: 83 additions & 0 deletions src/content/docs/online-payments/testing.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
title: Testing
description: Learn how to test online payments using sandbox accounts and test cards with different 3D Secure flows.
sidebar:
order: 6
---

import { Aside, Steps } from '@astrojs/starlight/components';
import { TestCards } from '@components/TestCards';

Testing your online payments integration is crucial before going live. SumUp provides sandbox test accounts and a comprehensive set of test cards to simulate various payment scenarios, including different 3D Secure authentication flows.

## Setting up a Test Account

Before you can test online payments, you need to create a test account in the SumUp Dashboard.

<Steps>

1. Log in to your SumUp account.

2. Open the drop-down menu between Support and your user panel.

3. Select **Test Account**. Your merchant account is now switched to test mode.

</Steps>

<Aside type="note">
Test accounts **do not** process transactions with real funds. All transactions are simulated for testing purposes only.
</Aside>

## Test Card Details

When testing with card payments, you can use the following common details for all test cards:

- **CVV**: Any 3 digits (e.g., `123`)
- **Expiry Date**: Any future date (e.g., `12/25`)
- **Cardholder Name**: Any name

## Testing Failed Transactions

To test failed transactions with your test account, create a checkout with an amount of `11` in any currency. This will always result in a failed transaction, allowing you to test your error handling logic.

<Aside type="tip">
Example: A checkout amount of `11.00 EUR` or `11.00 USD` will always fail.
</Aside>

## Test Cards by Card Scheme

Use the test cards below to simulate different payment scenarios and 3D Secure flows. Each card is designed to trigger specific authentication behaviors:

- **Frictionless**: Authentication completes without user interaction
- **Challenge**: Requires user authentication (e.g., entering a code or using biometrics)
- **Error**: Simulates various error conditions

<TestCards />

## Understanding 3D Secure Test Responses

When testing with these cards, you'll encounter different 3D Secure transaction statuses:

- **TransactionStatus=Y**: Authentication successful
- **TransactionStatus=A**: Authentication attempted (issuer or cardholder not participating)
- **TransactionStatus=N**: Authentication failed
- **TransactionStatus=U**: Technical error during authentication

The **ECI (Electronic Commerce Indicator)** values vary by card scheme:
- **ECI=05**: Full authentication (Visa, Amex, Discover, JCB)
- **ECI=02**: Full authentication (Mastercard, Maestro)
- **ECI=06**: Attempted authentication (Visa, Amex, Discover, JCB)
- **ECI=01**: Attempted authentication (Mastercard, Maestro)

## Next Steps

Once you've thoroughly tested your integration with sandbox accounts and test cards:

1. Switch back to your live account in the Dashboard
2. Ensure your production credentials are properly configured
3. Process a small real transaction to verify everything works as expected
4. Monitor your first transactions closely to ensure proper payment processing

<Aside type="caution">
Remember to switch from your test account to your live account before accepting real payments from customers.
</Aside>
Loading