Managing Payment Cards
Learn how to create, retrieve, edit, and delete user payment cards.
Create Card
Create a new payment card for a user. The card starts with unrelated status and requires user verification.
Request
POST /rest/v1/card
Content-Type: application/json
Required Scope: cards - Required to manage user's payment cards
Request Body
{
"user_id": 13,
"accounts": [
{
"number": "EVP0123456789012",
"order": 1
}
],
"relation": {
"redirect_back_uri": "https://example.com/card-linked",
"locale": "en"
}
}
View Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
user_id | integer | ✅ Yes | User ID who owns the card |
accounts | array | ⬜ Optional | Card-account relationships |
relation | object | ⬜ Optional | Verification settings |
Accounts Array
| Field | Type | Required | Description |
|---|---|---|---|
number | string | ✅ Yes | Account number |
order | integer | ✅ Yes | Priority order (1 = highest) |
Relation Object
| Field | Type | Required | Description |
|---|---|---|---|
redirect_back_uri | string | ⬜ Optional | Return URL after verification |
locale | string | ⬜ Optional | UI language (ISO 2-letter code) |
Response
{
"id": 1,
"status": "unrelated",
"user_id": 13,
"relation": {
"redirect_uri": "https://wallet.paysera.com/card/verify/123abc"
},
"accounts": [
{
"number": "EVP0123456789012",
"order": 1
}
]
}
View Response Fields
| Field | Type | Description |
|---|---|---|
id | integer | Card ID |
status | string | Card status (unrelated, related, failed) |
user_id | integer | Owner user ID |
relation | object | Verification details (if unrelated) |
card_data | object | Card details (if related) |
accounts | array | Linked accounts |
commission_rule | object | Commission info (if related) |
Verification Flow
Step-by-Step Process
- Create card via API
- Redirect user to
relation.redirect_uri - User enters card details securely
- Wait for verification (poll card status)
- Handle result (
relatedorfailed)
Implementation Example
async function createAndVerifyCard(userId, accountNumber) {
// 1. Create card
const card = await api.createCard({
user_id: userId,
accounts: [{
number: accountNumber,
order: 1
}],
relation: {
redirect_back_uri: 'https://example.com/card-success',
locale: 'en'
}
});
console.log(`Card ${card.id} created`);
console.log(`Redirect user to: ${card.relation.redirect_uri}`);
// 2. Redirect user (in browser)
window.location.href = card.relation.redirect_uri;
// 3. After redirect back, poll for status
return await pollCardStatus(card.id);
}
async function pollCardStatus(cardId, maxAttempts = 20, interval = 3000) {
for (let i = 0; i < maxAttempts; i++) {
const card = await api.getCard(cardId);
console.log(`Attempt ${i + 1}: Status = ${card.status}`);
if (card.status === 'related') {
console.log('✅ Card verified successfully!');
console.log(`Card: ${card.card_data.type} **** ${card.card_data.number}`);
return card;
}
if (card.status === 'failed') {
throw new Error('Card verification failed');
}
// Wait before next check
await new Promise(resolve => setTimeout(resolve, interval));
}
throw new Error('Card verification timeout');
}
Get Card
Retrieve a specific card by ID.
Request
GET /rest/v1/card/{cardId}
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
cardId | integer | ✅ Yes | Card ID |
Response Examples
Response (Unrelated Card)
{
"id": 1,
"status": "unrelated",
"user_id": 13,
"relation": {
"redirect_uri": "https://wallet.paysera.com/card/verify/123abc"
}
}
Response (Related Card)
{
"id": 1,
"status": "related",
"user_id": 13,
"related_at": 1735660800,
"card_data": {
"number": "1234",
"holder": "JOHN SMITH",
"type": "VISA",
"country": "LT",
"expiration": {
"year": 2027,
"month": 12
}
},
"accounts": [
{
"number": "EVP0123456789012",
"order": 1
}
],
"commission_rule": {
"percent": 2,
"fix": 10,
"fix_decimal": "0.10",
"min": 50,
"min_decimal": "0.50",
"max": 500,
"max_decimal": "5.00",
"currency": "EUR"
}
}
Example
async function getCardDetails(cardId) {
const card = await api.getCard(cardId);
console.log(`Card ID: ${card.id}`);
console.log(`Status: ${card.status}`);
if (card.status === 'related') {
console.log(`Type: ${card.card_data.type}`);
console.log(`Number: **** ${card.card_data.number}`);
console.log(`Expires: ${card.card_data.expiration.month}/${card.card_data.expiration.year}`);
console.log(`Holder: ${card.card_data.holder}`);
// Commission info
const comm = card.commission_rule;
console.log(`Commission: ${comm.percent}% + €${comm.fix_decimal}`);
console.log(`Min: €${comm.min_decimal}, Max: €${comm.max_decimal}`);
}
return card;
}
Get Cards
Retrieve all cards for a specific user.
Request
GET /rest/v1/cards?user_id={userId}&limit={limit}&offset={offset}
Query Parameters & Response
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
user_id | integer | ✅ Yes | User ID |
limit | integer | ⬜ Optional | Max results (default: 20, max: 200) |
offset | integer | ⬜ Optional | Skip results (default: 0) |
Response
{
"cards": [
{
"id": 1,
"status": "related",
"user_id": 13,
"card_data": {
"number": "1234",
"type": "VISA"
}
},
{
"id": 2,
"status": "unrelated",
"user_id": 13
}
],
"_metadata": {
"total": 2,
"offset": 0,
"limit": 20
}
}
Example
async function listUserCards(userId) {
const response = await api.getCards({
user_id: userId,
limit: 50
});
console.log(`Total cards: ${response._metadata.total}`);
response.cards.forEach(card => {
const status = card.status === 'related' ? '✅' :
card.status === 'unrelated' ? '⏳' : '❌';
const cardInfo = card.card_data ?
`${card.card_data.type} **** ${card.card_data.number}` :
'Pending verification';
console.log(`${status} Card ${card.id}: ${cardInfo}`);
});
return response.cards;
}
Edit Card
Update card-account relationships. Only available for unrelated and related cards.
Request
PUT /rest/v1/card/{cardId}
Content-Type: application/json
{
"accounts": [
{
"number": "EVP0123456789012",
"order": 1
},
{
"number": "EVP9876543210123",
"order": 2
}
]
}
View Examples
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
accounts | array | ⬜ Optional | Updated card-account links |
Response: Returns updated card object.
Example: Change Default Account
async function setCardDefaultAccount(cardId, newAccountNumber) {
const updated = await api.editCard(cardId, {
accounts: [{
number: newAccountNumber,
order: 1
}]
});
console.log('✅ Default account updated');
return updated;
}
Example: Add Secondary Account
async function addSecondaryAccount(cardId, primaryAccount, secondaryAccount) {
const updated = await api.editCard(cardId, {
accounts: [
{
number: primaryAccount,
order: 1
},
{
number: secondaryAccount,
order: 2
}
]
});
console.log('✅ Secondary account added');
return updated;
}
Delete Card
Remove a card from the system. Available for all statuses.
Request
DELETE /rest/v1/card/{cardId}
Response: HTTP/1.1 204 No Content
Example
async function deleteCard(cardId) {
try {
await api.deleteCard(cardId);
console.log(`✅ Card ${cardId} deleted successfully`);
} catch (error) {
if (error.code === 'not_found') {
console.error('Card not found');
} else {
console.error('Delete failed:', error.message);
}
throw error;
}
}
Advanced Topics
Complete Card Manager Class
Full Implementation
class CardManager {
constructor(api, userId) {
this.api = api;
this.userId = userId;
this.cards = [];
}
async initialize() {
await this.loadCards();
}
async loadCards() {
const response = await this.api.getCards({
user_id: this.userId,
limit: 100
});
this.cards = response.cards;
return this.cards;
}
async addCard(accountNumber, redirectUri) {
const card = await this.api.createCard({
user_id: this.userId,
accounts: [{
number: accountNumber,
order: 1
}],
relation: {
redirect_back_uri: redirectUri,
locale: 'en'
}
});
this.cards.push(card);
return card;
}
async waitForVerification(cardId, timeout = 60000) {
const startTime = Date.now();
const interval = 3000;
while (Date.now() - startTime < timeout) {
const card = await this.api.getCard(cardId);
if (card.status === 'related') {
const index = this.cards.findIndex(c => c.id === cardId);
if (index >= 0) {
this.cards[index] = card;
}
return card;
}
if (card.status === 'failed') {
throw new Error('Card verification failed');
}
await new Promise(resolve => setTimeout(resolve, interval));
}
throw new Error('Card verification timeout');
}
async updateCardPriority(cardId, newOrder) {
const card = this.cards.find(c => c.id === cardId);
if (!card) {
throw new Error('Card not found');
}
const updated = await this.api.editCard(cardId, {
accounts: card.accounts.map(acc => ({
...acc,
order: newOrder
}))
});
const index = this.cards.findIndex(c => c.id === cardId);
this.cards[index] = updated;
return updated;
}
async removeCard(cardId) {
await this.api.deleteCard(cardId);
this.cards = this.cards.filter(c => c.id !== cardId);
}
getRelatedCards() {
return this.cards.filter(c => c.status === 'related');
}
getDefaultCard() {
const related = this.getRelatedCards();
if (related.length === 0) return null;
return related.reduce((prev, curr) => {
const prevOrder = prev.accounts[0]?.order || 999;
const currOrder = curr.accounts[0]?.order || 999;
return currOrder < prevOrder ? curr : prev;
});
}
async setDefaultCard(cardId) {
const related = this.getRelatedCards();
for (const card of related) {
const newOrder = card.id === cardId ? 1 :
(card.accounts[0]?.order || 10) + 1;
if (card.accounts[0]?.order !== newOrder) {
await this.updateCardPriority(card.id, newOrder);
}
}
await this.loadCards();
}
displayCards() {
console.log(`\n=== Cards for User ${this.userId} ===`);
if (this.cards.length === 0) {
console.log('No cards found');
return;
}
this.cards.forEach((card, index) => {
const status = {
'related': '✅',
'unrelated': '⏳',
'failed': '❌'
}[card.status] || '❓';
let cardInfo = `Card #${index + 1} (ID: ${card.id})`;
if (card.card_data) {
cardInfo += ` - ${card.card_data.type} **** ${card.card_data.number}`;
if (card.accounts[0]?.order === 1) {
cardInfo += ' [DEFAULT]';
}
} else {
cardInfo += ' - Pending verification';
}
console.log(`${status} ${cardInfo}`);
});
}
}
// Usage Example
const manager = new CardManager(api, 13);
await manager.initialize();
// Add new card
const newCard = await manager.addCard('EVP0123456789012', 'https://example.com/success');
console.log(`Redirect to: ${newCard.relation.redirect_uri}`);
// Wait for verification
try {
const verified = await manager.waitForVerification(newCard.id);
console.log('✅ Card verified!');
} catch (error) {
console.error('Verification failed:', error.message);
}
// Display all cards
manager.displayCards();
// Set as default
await manager.setDefaultCard(newCard.id);
Best Practices
1. Handle Verification Timeout
async function verifyCardWithTimeout(cardId, maxWaitTime = 300000) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), maxWaitTime)
);
const verification = pollCardStatus(cardId);
try {
return await Promise.race([verification, timeout]);
} catch (error) {
if (error.message === 'Timeout') {
console.log('Verification taking longer than expected');
}
throw error;
}
}
2. Validate Before Operations
async function ensureCardReady(cardId) {
const card = await api.getCard(cardId);
if (card.status !== 'related') {
throw new Error(
`Card is ${card.status}. Only related cards can be used for deposits.`
);
}
return card;
}
3. Cache Card Data
class CardCache {
constructor(api, ttl = 600000) { // 10 minutes
this.api = api;
this.ttl = ttl;
this.cache = new Map();
}
async getCard(cardId) {
const cached = this.cache.get(cardId);
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.data;
}
const card = await this.api.getCard(cardId);
this.cache.set(cardId, {
data: card,
timestamp: Date.now()
});
return card;
}
invalidate(cardId) {
this.cache.delete(cardId);
}
}
Common Issues
Issue: Card Stuck in "unrelated"
Cause: User didn't complete verification
Solution:
const card = await api.getCard(cardId);
if (card.status === 'unrelated') {
console.log('Verification incomplete');
console.log(`Please visit: ${card.relation.redirect_uri}`);
}
Issue: Cannot Edit Card
Cause: Card status is failed
Solution:
try {
await api.editCard(cardId, {...});
} catch (error) {
if (error.code === 'invalid_state') {
console.error('Cannot edit failed cards. Please delete and create new.');
}
}
Issue: Multiple Default Cards
Cause: Incorrect order management
Solution:
async function fixDefaultCards(userId) {
const cards = await api.getCards({ user_id: userId });
const related = cards.cards.filter(c => c.status === 'related');
for (let i = 0; i < related.length; i++) {
await api.editCard(related[i].id, {
accounts: [{
...related[i].accounts[0],
order: i + 1
}]
});
}
}
Next Steps
- Card Deposits - Deposit from cards to accounts
- Overview - Learn about payment cards
- Authentication - Setup OAuth