Skip to main content

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:
1

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.
2

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');
}
3

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');
}
4

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

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

ParameterTypeRequiredDescription
typestringYesCard type. Currently only VIRTUAL is supported

Response

{
  "success": true
}
The order has been accepted and card provisioning has begun. For virtual cards, this process is typically instant.

Checking Card Status

After ordering, poll the card status endpoint to verify activation and retrieve card details.

API Request

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

{
  "id": "000000000050277836",
  "holderName": "JOHN DOE",
  "expiryDate": "2028/01",
  "panLast4": "1234",
  "status": "ACTIVE",
  "type": "VIRTUAL",
  "orderedAt": "2023-03-27 17:07:12.662+03"
}
id
string
Unique card identifier
holderName
string
Cardholder name (uppercase, as printed on card)
expiryDate
string
Card expiry date in YYYY/MM format
panLast4
string
Last 4 digits of card number (for display/identification)
status
enum
Current card status: ACTIVE, FROZEN, or BLOCKED
type
enum
Card type: VIRTUAL, PHYSICAL, or METAL
orderedAt
string
Timestamp when card was ordered

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:
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:
ErrorUser MessageAction
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

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
Cause: User previously ordered a cardSolution: Fetch and display existing card instead of ordering new one
const card = await fetch('/v1/card/status', {
  headers: {
    'X-Client-ID': clientId,
    'Authorization': `Bearer ${accessToken}`
  }
});
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();
}
Cause: Compliance or risk check triggered automatic freezeSolution: Contact support with user ID. User may need to provide additional verification.

Next Steps