Overview
The card ordering process enables you to provision virtual cards for verified users instantly. Virtual cards are activated immediately upon order completion, allowing users to start making purchases right away.
Currently, only virtual cards are supported. Physical card support is coming soon.
Prerequisites
Before ordering a card for a user, verify these requirements:
Completed Registration with Consent
User must have completed registration including acceptance of terms of service and consent for data processing. Card issuance requires prior consent acceptance during the registration flow. See User Registration and Consent Management for implementation details.
User Verification Status
User must have completed KYC and have VERIFIED status. const profile = await fetch ( '/v1/user/profile' , {
headers: {
'X-Client-ID' : clientId ,
'Authorization' : `Bearer ${ accessToken } `
}
});
const userData = await profile . json ();
if ( userData . status !== 'VERIFIED' ) {
throw new Error ( 'User must complete KYC verification first' );
}
No Existing Card
Users can only have one active card at a time. Check if user already has a card. const cardStatus = await fetch ( '/v1/card/status' , {
headers: {
'X-Client-ID' : clientId ,
'Authorization' : `Bearer ${ accessToken } `
}
});
if ( cardStatus . ok ) {
throw new Error ( 'User already has a card' );
}
Delegated Wallet
User should have at least one delegated external wallet for funding card transactions. const wallets = await fetch ( '/v1/wallet/external' , {
headers: {
'X-Client-ID' : clientId ,
'Authorization' : `Bearer ${ accessToken } `
}
});
const walletData = await wallets . json ();
if ( walletData . length === 0 ) {
console . warn ( 'User has no delegated wallets' );
}
Attempting to order a card without meeting these prerequisites will result in a 400 or 403 error.
Ordering a Virtual Card
API Request
JavaScript/Node.js
Python
cURL
const response = await fetch ( 'https://dev.api.baanx.com/v1/card/order' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'X-Client-ID' : 'your_client_id' ,
'Authorization' : `Bearer ${ userAccessToken } `
},
body: JSON . stringify ({
type: 'VIRTUAL'
})
});
const result = await response . json ();
if ( result . success ) {
console . log ( 'Card order accepted and processing' );
}
Request Parameters
Parameter Type Required Description typestring Yes Card type. Currently only VIRTUAL is supported
Response
Success (200)
Error (400)
Error (403)
Error (422)
The order has been accepted and card provisioning has begun. For virtual cards, this process is typically instant. {
"message" : "User already has a card" ,
"code" : "CARD_EXISTS"
}
User already has an active card. Fetch the existing card status instead. {
"message" : "User is not verified" ,
"code" : "USER_NOT_VERIFIED"
}
User hasn’t completed KYC verification. Direct them to complete verification first. {
"detail" : [
{
"loc" : [ "body" , "type" ],
"msg" : "value is not a valid enumeration member" ,
"type" : "type_error.enum"
}
]
}
Invalid card type specified. Use VIRTUAL (currently the only supported value).
Checking Card Status
After ordering, poll the card status endpoint to verify activation and retrieve card details.
API Request
JavaScript/Node.js
Python
cURL
async function waitForCardActivation ( clientId , accessToken , maxAttempts = 10 ) {
for ( let i = 0 ; i < maxAttempts ; i ++ ) {
const response = await fetch ( 'https://dev.api.baanx.com/v1/card/status' , {
headers: {
'X-Client-ID' : clientId ,
'Authorization' : `Bearer ${ accessToken } `
}
});
if ( response . ok ) {
const card = await response . json ();
if ( card . status === 'ACTIVE' ) {
return card ;
}
console . log ( `Card status: ${ card . status } , waiting...` );
} else if ( response . status === 404 ) {
console . log ( 'Card not ready yet...' );
} else {
throw new Error ( `Error checking card status: ${ response . status } ` );
}
// Wait 2 seconds before next check
await new Promise ( resolve => setTimeout ( resolve , 2000 ));
}
throw new Error ( 'Card activation timeout' );
}
// Usage
try {
const card = await waitForCardActivation ( clientId , accessToken );
console . log ( 'Card activated:' , card );
} catch ( error ) {
console . error ( 'Card activation failed:' , error );
}
Response
Success (200)
Error (404)
{
"id" : "000000000050277836" ,
"holderName" : "JOHN DOE" ,
"expiryDate" : "2028/01" ,
"panLast4" : "1234" ,
"status" : "ACTIVE" ,
"type" : "VIRTUAL" ,
"orderedAt" : "2023-03-27 17:07:12.662+03"
}
Cardholder name (uppercase, as printed on card)
Card expiry date in YYYY/MM format
Last 4 digits of card number (for display/identification)
Current card status: ACTIVE, FROZEN, or BLOCKED
Card type: VIRTUAL, PHYSICAL, or METAL
Timestamp when card was ordered
{
"message" : "Card not found"
}
User doesn’t have a card yet. This is expected immediately after ordering - continue polling.
Card Activation Timeline
Virtual cards are typically activated within seconds of ordering. However, processing may take up to 2 minutes during high load.
Complete Ordering Flow
Here’s a complete implementation showing the recommended card ordering flow:
React Component
Flask Backend
import { useState } from 'react' ;
export function CardOrderingFlow ({ clientId , accessToken }) {
const [ loading , setLoading ] = useState ( false );
const [ error , setError ] = useState ( null );
const [ card , setCard ] = useState ( null );
async function checkPrerequisites () {
// Check user verification
const profile = await fetch ( '/v1/user/profile' , {
headers: {
'X-Client-ID' : clientId ,
'Authorization' : `Bearer ${ accessToken } `
}
});
const userData = await profile . json ();
if ( userData . status !== 'VERIFIED' ) {
throw new Error ( 'Please complete identity verification first' );
}
// Check for existing card
const statusResponse = await fetch ( '/v1/card/status' , {
headers: {
'X-Client-ID' : clientId ,
'Authorization' : `Bearer ${ accessToken } `
}
});
if ( statusResponse . ok ) {
const existingCard = await statusResponse . json ();
throw new Error ( `You already have a ${ existingCard . type } card` );
}
// Check for delegated wallets
const walletsResponse = await fetch ( '/v1/wallet/external' , {
headers: {
'X-Client-ID' : clientId ,
'Authorization' : `Bearer ${ accessToken } `
}
});
const wallets = await walletsResponse . json ();
if ( wallets . length === 0 ) {
console . warn ( 'No delegated wallets - user may need to add funding source' );
}
}
async function orderCard () {
try {
setLoading ( true );
setError ( null );
// Verify prerequisites
await checkPrerequisites ();
// Order card
const orderResponse = await fetch ( '/v1/card/order' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'X-Client-ID' : clientId ,
'Authorization' : `Bearer ${ accessToken } `
},
body: JSON . stringify ({ type: 'VIRTUAL' })
});
if ( ! orderResponse . ok ) {
const error = await orderResponse . json ();
throw new Error ( error . message || 'Failed to order card' );
}
// Poll for activation
const activatedCard = await pollCardStatus ();
setCard ( activatedCard );
} catch ( err ) {
setError ( err . message );
} finally {
setLoading ( false );
}
}
async function pollCardStatus ( maxAttempts = 30 ) {
for ( let i = 0 ; i < maxAttempts ; i ++ ) {
const response = await fetch ( '/v1/card/status' , {
headers: {
'X-Client-ID' : clientId ,
'Authorization' : `Bearer ${ accessToken } `
}
});
if ( response . ok ) {
const cardData = await response . json ();
if ( cardData . status === 'ACTIVE' ) {
return cardData ;
}
}
await new Promise ( resolve => setTimeout ( resolve , 2000 ));
}
throw new Error ( 'Card activation timeout - please try again later' );
}
if ( card ) {
return (
< div className = "success" >
< h2 > Card Activated Successfully! </ h2 >
< p > Card ending in { card . panLast4 } </ p >
< p > Expires: { card . expiryDate } </ p >
< button onClick = { () => { /* Navigate to card details */ } } >
View Card Details
</ button >
</ div >
);
}
return (
< div className = "card-ordering" >
< h2 > Order Virtual Card </ h2 >
< p > Get instant access to a virtual card for online purchases </ p >
{ error && (
< div className = "error" >
{ error }
</ div >
) }
< button
onClick = { orderCard }
disabled = { loading }
>
{ loading ? 'Processing...' : 'Order Card' }
</ button >
{ loading && (
< div className = "progress" >
< p > Activating your card... </ p >
< p > This usually takes just a few seconds </ p >
</ div >
) }
</ div >
);
}
User Experience Best Practices
Loading States
Show clear progress indicators during the ordering and activation process:
const steps = [
{ label: 'Verifying eligibility' , status: 'complete' },
{ label: 'Ordering card' , status: 'complete' },
{ label: 'Activating card' , status: 'loading' },
{ label: 'Card ready' , status: 'pending' }
];
Error Messaging
Provide actionable error messages:
Error User Message Action Not verified ”Complete identity verification to order your card” Link to KYC flow Already has card ”You already have an active card” Show existing card No wallets ”Connect a wallet to fund your card” Link to wallet delegation Network error ”Connection error - please try again” Retry button
Success Confirmation
After successful activation, show:
Card last 4 digits
Expiry date
Next steps (view details, set PIN)
Clear call-to-action buttons
Troubleshooting
Card order succeeds but status remains 404
Cause : Card provisioning is still in progressSolution : Continue polling for up to 2 minutes. Virtual cards rarely take longer than 30 seconds to activate.// Increase polling duration
await pollCardStatus ( maxAttempts : 60 ); // 2 minutes
400 Error: User already has a card
Cause : User previously ordered a cardSolution : Fetch and display existing card instead of ordering new oneconst card = await fetch ( '/v1/card/status' , {
headers: {
'X-Client-ID' : clientId ,
'Authorization' : `Bearer ${ accessToken } `
}
});
403 Error: User not verified
Cause : User hasn’t completed KYCSolution : Direct user to complete identity verification first// Check verification status
const profile = await fetch ( '/v1/user/profile' );
if ( profile . status !== 'VERIFIED' ) {
redirectToKYC ();
}
Card activates but shows FROZEN status
Cause : Compliance or risk check triggered automatic freezeSolution : Contact support with user ID. User may need to provide additional verification.
Next Steps