Advanced Integration Scenarios
Complex integration patterns and real-world use cases for production deployments.
This guide covers advanced scenarios beyond basic API integration.
1. Multi-Bank Account Aggregation​
Aggregate accounts from multiple banks for a single user.
Multi-Bank Flow​
Implementation Strategy​
- Create separate consents per bank
- Aggregate data from multiple sources
- Normalize different data structures
- Handle varying consent durations
- Merge transactions chronologically
Code Example​
- JavaScript
- Python
class MultiBankAggregator {
constructor(apiClient) {
this.apiClient = apiClient;
this.banks = new Map();
}
async addBank(bankId, userId) {
// Create consent for specific bank
const consent = await this.apiClient.post('/v1/consents', {
access: {
allPsd2: 'allAccounts'
},
recurringIndicator: true,
validUntil: this.calculateValidUntil(90),
frequencyPerDay: 4
});
// Store consent mapping
this.banks.set(bankId, {
consentId: consent.consentId,
userId: userId,
bankId: bankId,
authUrl: consent._links.scaRedirect.href,
status: 'pending_authorization'
});
return {
bankId,
consentId: consent.consentId,
authUrl: consent._links.scaRedirect.href
};
}
async getAggregatedAccounts(userId) {
const allAccounts = [];
// Get consents for this user
const userConsents = Array.from(this.banks.values())
.filter(bank => bank.userId === userId && bank.status === 'valid');
// Fetch accounts from all banks in parallel
const accountPromises = userConsents.map(async (bank) => {
try {
const accounts = await this.apiClient.get('/v1/accounts', {
headers: { 'Consent-ID': bank.consentId }
});
// Add bank identifier to each account
return accounts.accounts.map(account => ({
...account,
bankId: bank.bankId,
bankName: this.getBankName(bank.bankId),
consentId: bank.consentId
}));
} catch (error) {
console.error(`Failed to fetch accounts from ${bank.bankId}:`, error);
return [];
}
});
const results = await Promise.all(accountPromises);
// Flatten and return
return results.flat();
}
async getAggregatedBalances(userId) {
const accounts = await this.getAggregatedAccounts(userId);
// Fetch balances for all accounts in parallel
const balancePromises = accounts.map(async (account) => {
try {
const balances = await this.apiClient.get(
`/v1/accounts/${account.resourceId}/balances`,
{
headers: { 'Consent-ID': account.consentId }
}
);
return {
accountId: account.resourceId,
iban: account.iban,
bankName: account.bankName,
balances: balances.balances
};
} catch (error) {
console.error(`Failed to fetch balance for ${account.iban}:`, error);
return null;
}
});
const results = await Promise.all(balancePromises);
return results.filter(result => result !== null);
}
async getAggregatedTransactions(userId, dateFrom, dateTo) {
const accounts = await this.getAggregatedAccounts(userId);
// Fetch transactions from all accounts
const transactionPromises = accounts.map(async (account) => {
try {
const transactions = await this.apiClient.get(
`/v1/accounts/${account.resourceId}/transactions`,
{
headers: { 'Consent-ID': account.consentId },
params: { dateFrom, dateTo }
}
);
// Add account context to each transaction
return transactions.transactions.booked.map(tx => ({
...tx,
accountId: account.resourceId,
iban: account.iban,
bankName: account.bankName
}));
} catch (error) {
console.error(`Failed to fetch transactions for ${account.iban}:`, error);
return [];
}
});
const results = await Promise.all(transactionPromises);
// Merge and sort by date
const allTransactions = results.flat();
allTransactions.sort((a, b) =>
new Date(b.bookingDate) - new Date(a.bookingDate)
);
return allTransactions;
}
calculateValidUntil(days) {
const date = new Date();
date.setDate(date.getDate() + days);
return date.toISOString().split('T')[0];
}
getBankName(bankId) {
const bankNames = {
'swedbank': 'Swedbank',
'seb': 'SEB',
'luminor': 'Luminor',
'revolut': 'Revolut'
};
return bankNames[bankId] || bankId;
}
}
// Usage
const aggregator = new MultiBankAggregator(apiClient);
// User adds Swedbank
const swedbank = await aggregator.addBank('swedbank', 'user-123');
console.log('Authorize Swedbank:', swedbank.authUrl);
// User adds SEB
const seb = await aggregator.addBank('seb', 'user-123');
console.log('Authorize SEB:', seb.authUrl);
// After authorization, get aggregated data
const accounts = await aggregator.getAggregatedAccounts('user-123');
const balances = await aggregator.getAggregatedBalances('user-123');
const transactions = await aggregator.getAggregatedTransactions(
'user-123',
'2026-01-01',
'2026-02-10'
);
console.log(`Total accounts: ${accounts.length}`);
console.log(`Total balances: ${balances.length}`);
console.log(`Total transactions: ${transactions.length}`);
from datetime import datetime, timedelta
from typing import List, Dict
import asyncio
class MultiBankAggregator:
def __init__(self, api_client):
self.api_client = api_client
self.banks = {}
async def add_bank(self, bank_id: str, user_id: str):
"""Create consent for specific bank"""
valid_until = (datetime.now() + timedelta(days=90)).date().isoformat()
consent = await self.api_client.post('/v1/consents', json={
'access': {
'allPsd2': 'allAccounts'
},
'recurringIndicator': True,
'validUntil': valid_until,
'frequencyPerDay': 4
})
# Store consent mapping
consent_key = f'{user_id}:{bank_id}'
self.banks[consent_key] = {
'consentId': consent['consentId'],
'userId': user_id,
'bankId': bank_id,
'authUrl': consent['_links']['scaRedirect']['href'],
'status': 'pending_authorization'
}
return {
'bankId': bank_id,
'consentId': consent['consentId'],
'authUrl': consent['_links']['scaRedirect']['href']
}
async def get_aggregated_accounts(self, user_id: str) -> List[Dict]:
"""Get accounts from all banks for user"""
# Get valid consents for this user
user_consents = [
bank for bank in self.banks.values()
if bank['userId'] == user_id and bank['status'] == 'valid'
]
# Fetch accounts from all banks in parallel
tasks = [
self._fetch_accounts_from_bank(bank)
for bank in user_consents
]
results = await asyncio.gather(*tasks, return_exceptions=True)
# Flatten results
all_accounts = []
for accounts in results:
if isinstance(accounts, Exception):
print(f'Error fetching accounts: {accounts}')
continue
all_accounts.extend(accounts)
return all_accounts
async def _fetch_accounts_from_bank(self, bank: Dict) -> List[Dict]:
"""Fetch accounts from single bank"""
try:
response = await self.api_client.get(
'/v1/accounts',
headers={'Consent-ID': bank['consentId']}
)
# Add bank context
accounts = response['accounts']
for account in accounts:
account['bankId'] = bank['bankId']
account['bankName'] = self.get_bank_name(bank['bankId'])
account['consentId'] = bank['consentId']
return accounts
except Exception as e:
print(f"Failed to fetch accounts from {bank['bankId']}: {e}")
return []
async def get_aggregated_transactions(
self,
user_id: str,
date_from: str,
date_to: str
) -> List[Dict]:
"""Get transactions from all accounts"""
accounts = await self.get_aggregated_accounts(user_id)
# Fetch transactions from all accounts
tasks = [
self._fetch_transactions_from_account(account, date_from, date_to)
for account in accounts
]
results = await asyncio.gather(*tasks, return_exceptions=True)
# Merge and sort
all_transactions = []
for transactions in results:
if isinstance(transactions, Exception):
print(f'Error fetching transactions: {transactions}')
continue
all_transactions.extend(transactions)
# Sort by booking date (newest first)
all_transactions.sort(
key=lambda tx: tx['bookingDate'],
reverse=True
)
return all_transactions
async def _fetch_transactions_from_account(
self,
account: Dict,
date_from: str,
date_to: str
) -> List[Dict]:
"""Fetch transactions from single account"""
try:
response = await self.api_client.get(
f"/v1/accounts/{account['resourceId']}/transactions",
headers={'Consent-ID': account['consentId']},
params={'dateFrom': date_from, 'dateTo': date_to}
)
transactions = response['transactions']['booked']
# Add account context
for tx in transactions:
tx['accountId'] = account['resourceId']
tx['iban'] = account['iban']
tx['bankName'] = account['bankName']
return transactions
except Exception as e:
print(f"Failed to fetch transactions for {account['iban']}: {e}")
return []
@staticmethod
def get_bank_name(bank_id: str) -> str:
bank_names = {
'swedbank': 'Swedbank',
'seb': 'SEB',
'luminor': 'Luminor',
'revolut': 'Revolut'
}
return bank_names.get(bank_id, bank_id)
# Usage
aggregator = MultiBankAggregator(api_client)
# User adds banks
swedbank = await aggregator.add_bank('swedbank', 'user-123')
print(f'Authorize Swedbank: {swedbank["authUrl"]}')
seb = await aggregator.add_bank('seb', 'user-123')
print(f'Authorize SEB: {seb["authUrl"]}')
# Get aggregated data
accounts = await aggregator.get_aggregated_accounts('user-123')
transactions = await aggregator.get_aggregated_transactions(
'user-123',
'2026-01-01',
'2026-02-10'
)
print(f'Total accounts: {len(accounts)}')
print(f'Total transactions: {len(transactions)}')
Error Handling​
Handle partial failures gracefully:
async function getAggregatedDataWithFallback(userId) {
const results = {
accounts: [],
errors: []
};
const consents = getValidConsents(userId);
for (const consent of consents) {
try {
const accounts = await fetchAccounts(consent.consentId);
results.accounts.push(...accounts);
} catch (error) {
// Continue with other banks even if one fails
results.errors.push({
bankId: consent.bankId,
error: error.message
});
console.error(`Failed to fetch from ${consent.bankId}:`, error);
}
}
return results;
}
2. Automatic Consent Renewal​
Proactively renew consents before expiration.
Strategy​
- Track consent expiration dates
- Trigger renewal 7 days before expiry
- Notify users proactively
- Implement background renewal job
Implementation​
class ConsentRenewalManager {
constructor(apiClient, db) {
this.apiClient = apiClient;
this.db = db;
}
async checkExpiringConsents() {
const sevenDaysFromNow = new Date();
sevenDaysFromNow.setDate(sevenDaysFromNow.getDate() + 7);
// Find consents expiring soon
const expiringConsents = await this.db.query(`
SELECT * FROM consents
WHERE valid_until <= $1
AND status = 'valid'
AND renewal_notified = FALSE
`, [sevenDaysFromNow]);
for (const consent of expiringConsents) {
await this.triggerRenewal(consent);
}
}
async triggerRenewal(oldConsent) {
try {
// Create new consent with same scope
const newConsent = await this.apiClient.post('/v1/consents', {
access: oldConsent.access,
recurringIndicator: true,
validUntil: this.calculateValidUntil(90),
frequencyPerDay: 4
});
// Notify user
await this.notifyUser(oldConsent.userId, {
type: 'consent_renewal_required',
oldConsentId: oldConsent.id,
newConsentId: newConsent.consentId,
authUrl: newConsent._links.scaRedirect.href,
expiresAt: oldConsent.valid_until
});
// Mark as notified
await this.db.query(`
UPDATE consents
SET renewal_notified = TRUE,
renewal_consent_id = $1
WHERE id = $2
`, [newConsent.consentId, oldConsent.id]);
console.log(`Renewal triggered for consent ${oldConsent.id}`);
} catch (error) {
console.error(`Failed to renew consent ${oldConsent.id}:`, error);
}
}
async scheduleRenewalCheck() {
// Run daily at 9 AM
const schedule = require('node-schedule');
schedule.scheduleJob('0 9 * * *', async () => {
console.log('Running consent renewal check...');
await this.checkExpiringConsents();
});
}
calculateValidUntil(days) {
const date = new Date();
date.setDate(date.getDate() + days);
return date.toISOString().split('T')[0];
}
async notifyUser(userId, notification) {
// Send email, push notification, in-app message
await this.sendEmail(userId, {
subject: 'Please renew your bank connection',
template: 'consent_renewal',
data: notification
});
}
}
// Usage
const renewalManager = new ConsentRenewalManager(apiClient, db);
// Start background job
renewalManager.scheduleRenewalCheck();
// Manual check
await renewalManager.checkExpiringConsents();
3. Payment Status Polling (Without Webhooks)​
Efficiently poll payment status when webhooks aren't available.
Smart Polling Flow​
Smart Polling Strategy​
This is a suggested polling pattern that balances responsiveness with API efficiency. Adjust intervals based on your use case and rate limit constraints (4 req/day for AIS without user presence).
Suggested Intervals:
- Initial period: Frequent checks (e.g., every few seconds)
- Middle period: Reduced frequency (e.g., every 30 seconds)
- Extended period: Infrequent checks (e.g., every few minutes)
- Stop after: Payment reaches final status or timeout (e.g., 24 hours)
Implementation​
class PaymentStatusPoller {
constructor(apiClient) {
this.apiClient = apiClient;
this.pollers = new Map();
}
async startPolling(paymentId, onStatusChange) {
// Prevent duplicate pollers
if (this.pollers.has(paymentId)) {
console.log(`Already polling ${paymentId}`);
return;
}
const poller = {
paymentId,
startTime: Date.now(),
lastStatus: null,
attempts: 0,
onStatusChange
};
this.pollers.set(paymentId, poller);
await this.poll(paymentId);
}
async poll(paymentId) {
const poller = this.pollers.get(paymentId);
if (!poller) return;
try {
// Fetch current status
const payment = await this.apiClient.get(`/v1/payments/${paymentId}`);
const currentStatus = payment.transactionStatus;
poller.attempts++;
// Check if status changed
if (currentStatus !== poller.lastStatus) {
poller.lastStatus = currentStatus;
await poller.onStatusChange(payment);
}
// Stop polling if terminal state reached
if (this.isTerminalState(currentStatus)) {
console.log(`Payment ${paymentId} reached terminal state: ${currentStatus}`);
this.stopPolling(paymentId);
return;
}
// Stop after 24 hours
const elapsed = Date.now() - poller.startTime;
if (elapsed > 24 * 60 * 60 * 1000) {
console.log(`Payment ${paymentId} polling timeout (24h)`);
this.stopPolling(paymentId);
return;
}
// Calculate next poll interval
const delay = this.calculateDelay(elapsed);
// Schedule next poll
setTimeout(() => this.poll(paymentId), delay);
} catch (error) {
console.error(`Error polling payment ${paymentId}:`, error);
// Retry with exponential backoff
const delay = Math.min(poller.attempts * 1000, 60000);
setTimeout(() => this.poll(paymentId), delay);
}
}
calculateDelay(elapsedMs) {
const minutes = elapsedMs / (60 * 1000);
if (minutes < 1) {
return 5000; // 5 seconds for first minute
} else if (minutes < 5) {
return 30000; // 30 seconds for next 5 minutes
} else {
return 300000; // 5 minutes after that
}
}
isTerminalState(status) {
return ['ACSC', 'RJCT', 'CANC'].includes(status);
}
stopPolling(paymentId) {
this.pollers.delete(paymentId);
}
}
// Usage
const poller = new PaymentStatusPoller(apiClient);
// Start polling for payment
await poller.startPolling('pmt-123', async (payment) => {
console.log(`Payment status changed: ${payment.transactionStatus}`);
// Update database
await db.query(
'UPDATE payments SET status = $1 WHERE payment_id = $2',
[payment.transactionStatus, payment.paymentId]
);
// Notify user
if (payment.transactionStatus === 'ACSC') {
await notifyUser(payment.debtorAccount, 'Payment completed successfully');
} else if (payment.transactionStatus === 'RJCT') {
await notifyUser(payment.debtorAccount, 'Payment was rejected');
}
});
4. Multi-Currency Account Management​
Handle accounts in different currencies.
Considerations​
- Exchange rate handling
- Currency conversion
- Display formatting
- Transaction categorization per currency
Implementation​
class MultiCurrencyManager {
constructor(apiClient) {
this.apiClient = apiClient;
this.exchangeRates = new Map();
}
async getAggregatedBalance(accounts) {
const baseCurrency = 'EUR';
let totalInBaseCurrency = 0;
const balances = [];
for (const account of accounts) {
const accountBalance = await this.apiClient.get(
`/v1/accounts/${account.resourceId}/balances`
);
const balance = this.extractBalance(accountBalance.balances);
// Convert to base currency
const rate = await this.getExchangeRate(account.currency, baseCurrency);
const convertedAmount = balance.amount * rate;
totalInBaseCurrency += convertedAmount;
balances.push({
iban: account.iban,
currency: account.currency,
amount: balance.amount,
convertedAmount: convertedAmount,
baseCurrency: baseCurrency,
exchangeRate: rate
});
}
return {
totalInBaseCurrency,
baseCurrency,
balances
};
}
extractBalance(balances) {
// Prefer interimAvailable, fallback to closingBooked
const balance = balances.find(b => b.balanceType === 'interimAvailable') ||
balances.find(b => b.balanceType === 'closingBooked');
return {
amount: parseFloat(balance.balanceAmount.amount),
currency: balance.balanceAmount.currency
};
}
async getExchangeRate(fromCurrency, toCurrency) {
if (fromCurrency === toCurrency) return 1.0;
const cacheKey = `${fromCurrency}-${toCurrency}`;
// Check cache (1 hour TTL)
if (this.exchangeRates.has(cacheKey)) {
const cached = this.exchangeRates.get(cacheKey);
if (Date.now() - cached.timestamp < 3600000) {
return cached.rate;
}
}
// Fetch from exchange rate API
const rate = await this.fetchExchangeRate(fromCurrency, toCurrency);
this.exchangeRates.set(cacheKey, {
rate,
timestamp: Date.now()
});
return rate;
}
async fetchExchangeRate(from, to) {
// Use external API (e.g., ECB, fixer.io)
// Simplified example
const response = await fetch(
`https://api.exchangerate.host/latest?base=${from}&symbols=${to}`
);
const data = await response.json();
return data.rates[to];
}
formatCurrency(amount, currency) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency
}).format(amount);
}
}
// Usage
const currencyManager = new MultiCurrencyManager(apiClient);
const accounts = [
{ resourceId: 'acc-1', iban: 'LT...', currency: 'EUR' },
{ resourceId: 'acc-2', iban: 'LT...', currency: 'USD' },
{ resourceId: 'acc-3', iban: 'LT...', currency: 'GBP' }
];
const aggregated = await currencyManager.getAggregatedBalance(accounts);
console.log(`Total (EUR): ${currencyManager.formatCurrency(aggregated.totalInBaseCurrency, 'EUR')}`);
aggregated.balances.forEach(balance => {
console.log(`${balance.iban}: ${currencyManager.formatCurrency(balance.amount, balance.currency)} = ${currencyManager.formatCurrency(balance.convertedAmount, 'EUR')} (rate: ${balance.exchangeRate})`);
});
5. Bulk Payment Processing​
Process multiple payments efficiently.
Queue-Based Implementation​
const Queue = require('bull');
class BulkPaymentProcessor {
constructor(apiClient) {
this.apiClient = apiClient;
this.paymentQueue = new Queue('payments', {
redis: { port: 6379, host: '127.0.0.1' }
});
this.setupProcessor();
}
setupProcessor() {
// Process payments one at a time (rate limiting)
this.paymentQueue.process(1, async (job) => {
const { payment, userId } = job.data;
try {
// Initiate payment
const result = await this.apiClient.post('/v1/payments', payment);
// Update progress
await job.progress(100);
return {
success: true,
paymentId: result.paymentId,
status: result.transactionStatus
};
} catch (error) {
// Handle payment failure
console.error(`Payment failed:`, error);
throw error; // Will trigger retry
}
});
// Listen for completion
this.paymentQueue.on('completed', (job, result) => {
console.log(`Payment ${result.paymentId} completed`);
});
this.paymentQueue.on('failed', (job, error) => {
console.error(`Payment failed:`, error);
});
}
async addBulkPayments(payments, userId) {
const jobs = [];
for (const payment of payments) {
const job = await this.paymentQueue.add(
{ payment, userId },
{
attempts: 3,
backoff: {
type: 'exponential',
delay: 2000
},
removeOnComplete: false
}
);
jobs.push(job);
}
return jobs;
}
async getBulkStatus(jobIds) {
const statuses = [];
for (const jobId of jobIds) {
const job = await this.paymentQueue.getJob(jobId);
statuses.push({
jobId,
state: await job.getState(),
progress: job.progress(),
result: job.returnvalue,
failedReason: job.failedReason
});
}
return statuses;
}
}
// Usage
const processor = new BulkPaymentProcessor(apiClient);
// Add 100 payments
const payments = [
{
instructedAmount: { amount: '100.00', currency: 'EUR' },
creditorAccount: { iban: 'LT...' },
creditorName: 'Employee 1'
},
// ... 99 more
];
const jobs = await processor.addBulkPayments(payments, 'user-123');
console.log(`Queued ${jobs.length} payments`);
// Check status
const statuses = await processor.getBulkStatus(jobs.map(j => j.id));
console.log(`Completed: ${statuses.filter(s => s.state === 'completed').length}`);
6. Consent Lifecycle Management​
Track complete consent lifecycle.
State Machine​
Implementation​
class ConsentLifecycleManager {
constructor(apiClient, db) {
this.apiClient = apiClient;
this.db = db;
}
async createConsent(userId, access) {
// Create consent
const consent = await this.apiClient.post('/v1/consents', {
access,
recurringIndicator: true,
validUntil: this.calculateValidUntil(90),
frequencyPerDay: 4
});
// Store in database
await this.db.query(`
INSERT INTO consents (
consent_id, user_id, status, access,
valid_until, created_at
) VALUES ($1, $2, $3, $4, $5, NOW())
`, [
consent.consentId,
userId,
'pending_authorization',
JSON.stringify(access),
consent.validUntil
]);
return consent;
}
async handleAuthorization(consentId) {
// Update status after user authorizes
await this.db.query(`
UPDATE consents
SET status = 'valid',
authorized_at = NOW()
WHERE consent_id = $1
`, [consentId]);
console.log(`Consent ${consentId} authorized`);
}
async handleExpiration(consentId) {
// Consent expired
await this.db.query(`
UPDATE consents
SET status = 'expired',
expired_at = NOW()
WHERE consent_id = $1
`, [consentId]);
// Trigger renewal
const consent = await this.getConsent(consentId);
await this.triggerRenewal(consent);
}
async handleRevocation(consentId) {
// User revoked consent
await this.db.query(`
UPDATE consents
SET status = 'revoked',
revoked_at = NOW()
WHERE consent_id = $1
`, [consentId]);
// Stop all scheduled operations
await this.cancelScheduledOperations(consentId);
}
async monitorConsentHealth() {
// Check for expired consents
const expired = await this.db.query(`
SELECT * FROM consents
WHERE status = 'valid'
AND valid_until < NOW()
`);
for (const consent of expired) {
await this.handleExpiration(consent.consent_id);
}
}
calculateValidUntil(days) {
const date = new Date();
date.setDate(date.getDate() + days);
return date.toISOString().split('T')[0];
}
}
7. Transaction Categorization & Analysis​
Categorize and analyze transactions.
Implementation​
class TransactionAnalyzer {
constructor() {
this.categories = {
'Groceries': ['maxima', 'rimi', 'iki', 'lidl'],
'Transport': ['bolt', 'wolt', 'uber', 'ryanair'],
'Utilities': ['telia', 'teo', 'eso', 'ignitis'],
'Entertainment': ['netflix', 'spotify', 'steam'],
'Restaurants': ['mcdonalds', 'kfc', 'pizza']
};
}
categorize(transactions) {
return transactions.map(tx => ({
...tx,
category: this.detectCategory(tx)
}));
}
detectCategory(transaction) {
const merchant = (
transaction.creditorName ||
transaction.remittanceInformationUnstructured ||
''
).toLowerCase();
for (const [category, keywords] of Object.entries(this.categories)) {
if (keywords.some(keyword => merchant.includes(keyword))) {
return category;
}
}
return 'Other';
}
analyze(transactions) {
const categorized = this.categorize(transactions);
// Group by category
const byCategory = {};
for (const tx of categorized) {
const amount = parseFloat(tx.transactionAmount.amount);
if (!byCategory[tx.category]) {
byCategory[tx.category] = {
count: 0,
total: 0,
transactions: []
};
}
byCategory[tx.category].count++;
byCategory[tx.category].total += Math.abs(amount);
byCategory[tx.category].transactions.push(tx);
}
return byCategory;
}
}
// Usage
const analyzer = new TransactionAnalyzer();
const transactions = await apiClient.get('/v1/accounts/123/transactions');
const analysis = analyzer.analyze(transactions.transactions.booked);
console.log('Spending by category:');
for (const [category, data] of Object.entries(analysis)) {
console.log(`${category}: €${data.total.toFixed(2)} (${data.count} transactions)`);
}
8. SCA (Strong Customer Authentication) Handling​
Handle different SCA flows.
Redirect Flow​
async function handleRedirectFlow(paymentData) {
// 1. Initiate payment
const payment = await apiClient.post('/v1/payments', paymentData);
// 2. Redirect user to bank for SCA
const scaUrl = payment._links.scaRedirect.href;
window.location.href = scaUrl;
// 3. User completes SCA at bank
// 4. Bank redirects back to your redirect_url
// 5. Check payment status
}
// On redirect callback
app.get('/payment-callback', async (req, res) => {
const paymentId = req.query.paymentId;
// Check status
const payment = await apiClient.get(`/v1/payments/${paymentId}`);
if (payment.transactionStatus === 'ACSC') {
res.send('Payment successful!');
} else {
res.send('Payment failed');
}
});
Related Documentation​
- 📖 Error Handling
- 📖 Performance Optimization
- 📖 Examples
Support​
Need help with complex integrations?
Contact: tech_support@paysera.com