Skip to main content

Overview

Card lifecycle management gives users control over their card’s operational state. The primary operations are freezing (temporarily disabling) and unfreezing (reactivating) cards for security purposes.
Freezing a card is a temporary, reversible action. Users can freeze and unfreeze their cards as many times as needed.

Card States

Understanding card states is essential for proper lifecycle management:

Active

Card is operational and can process transactions

Frozen

Card is temporarily disabled by user

Blocked

Card is permanently disabled by system

State Transitions

State Details

Description: Normal operational state. Card can be used for all transactions.Allowed Operations:
  • Make purchases (online, in-store)
  • View card details
  • View/set PIN
  • Check transaction history
  • Freeze card
User Actions:
  • ✅ Use card for purchases
  • ✅ Freeze card if needed
  • ✅ View card details and PIN
  • ✅ Change PIN
Description: Temporarily disabled by user for security. Reversible.Allowed Operations:
  • View card details (read-only)
  • Check transaction history
  • Unfreeze card
Blocked Operations:
  • ❌ Make purchases (all declined)
  • ❌ Change PIN
  • ❌ ATM withdrawals
Common Use Cases:
  • Lost wallet (not sure if card is missing)
  • Traveling and not using card
  • Suspicious activity detected
  • Temporary security measure
User Actions:
  • ✅ Unfreeze when ready to use again
  • ❌ Cannot make purchases while frozen
Description: Permanently disabled by system. Not reversible.Allowed Operations:
  • View transaction history
  • Request replacement card
Blocked Operations:
  • ❌ Make purchases
  • ❌ Unfreeze card
  • ❌ Change PIN
  • ❌ All card operations
Common Causes:
  • Fraud detection triggered
  • Multiple failed PIN attempts
  • Compliance/KYC issues
  • Card reported as lost/stolen (permanent)
  • Regulatory requirement
Resolution:
  • Contact support to understand reason
  • Resolve underlying issue (if applicable)
  • Request replacement card

Freezing a Card

Temporarily disable a card to prevent unauthorized transactions.

When to Freeze

Wallet misplaced (not confirmed lost)
Suspicious transaction notification
Traveling and not using card
Taking a break from spending
Device with saved card details lost

API Request

const response = await fetch('https://dev.api.baanx.com/v1/card/freeze', {
  method: 'POST',
  headers: {
    'X-Client-ID': 'your_client_id',
    'Authorization': `Bearer ${userAccessToken}`
  }
});

const result = await response.json();

if (result.success) {
  console.log('Card frozen successfully');
  // Update UI to show frozen state
}

Response

{
  "success": true
}
Card has been frozen. All transaction attempts will now be declined until the card is unfrozen.

Implementation Example

import { useState } from 'react';

export function CardFreezeButton({ clientId, accessToken, currentStatus }) {
  const [loading, setLoading] = useState(false);
  const [status, setStatus] = useState(currentStatus);
  const [error, setError] = useState(null);

  async function freezeCard() {
    setLoading(true);
    setError(null);

    try {
      const response = await fetch('/v1/card/freeze', {
        method: 'POST',
        headers: {
          'X-Client-ID': clientId,
          'Authorization': `Bearer ${accessToken}`
        }
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.message || 'Failed to freeze card');
      }

      const result = await response.json();

      if (result.success) {
        setStatus('FROZEN');
        // Optionally show success notification
      }
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }

  if (status === 'FROZEN') {
    return (
      <div className="card-frozen">
        <span>🥶 Card is frozen</span>
      </div>
    );
  }

  return (
    <div>
      <button
        onClick={freezeCard}
        disabled={loading || status !== 'ACTIVE'}
        className="btn-freeze"
      >
        {loading ? 'Freezing...' : 'Freeze Card'}
      </button>

      {error && (
        <div className="error-message">
          {error}
        </div>
      )}
    </div>
  );
}

Unfreezing a Card

Reactivate a frozen card to resume normal operations.

When to Unfreeze

Wallet found
Suspicious activity resolved
Ready to use card again
Returned from travel

API Request

const response = await fetch('https://dev.api.baanx.com/v1/card/unfreeze', {
  method: 'POST',
  headers: {
    'X-Client-ID': 'your_client_id',
    'Authorization': `Bearer ${userAccessToken}`
  }
});

const result = await response.json();

if (result.success) {
  console.log('Card unfrozen - ready to use');
}

Response

{
  "success": true
}
Card has been unfrozen and is now active. Transactions will be authorized normally.

Implementation Example

import { useState } from 'react';

export function CardUnfreezeButton({ clientId, accessToken, currentStatus }) {
  const [loading, setLoading] = useState(false);
  const [status, setStatus] = useState(currentStatus);
  const [error, setError] = useState(null);

  async function unfreezeCard() {
    setLoading(true);
    setError(null);

    try {
      const response = await fetch('/v1/card/unfreeze', {
        method: 'POST',
        headers: {
          'X-Client-ID': clientId,
          'Authorization': `Bearer ${accessToken}`
        }
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.message || 'Failed to unfreeze card');
      }

      const result = await response.json();

      if (result.success) {
        setStatus('ACTIVE');
        // Show success notification
      }
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }

  if (status === 'ACTIVE') {
    return (
      <div className="card-active">
        <span>✅ Card is active</span>
      </div>
    );
  }

  if (status === 'BLOCKED') {
    return (
      <div className="card-blocked">
        <span>🚫 Card is blocked - contact support</span>
      </div>
    );
  }

  return (
    <div>
      <button
        onClick={unfreezeCard}
        disabled={loading || status !== 'FROZEN'}
        className="btn-unfreeze"
      >
        {loading ? 'Unfreezing...' : 'Unfreeze Card'}
      </button>

      {error && (
        <div className="error-message">
          {error}
        </div>
      )}
    </div>
  );
}

Complete Card Control Component

Here’s a comprehensive component that handles all card states:
import { useState, useEffect } from 'react';

export function CardControl({ clientId, accessToken }) {
  const [card, setCard] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchCardStatus();
  }, []);

  async function fetchCardStatus() {
    try {
      const response = await fetch('/v1/card/status', {
        headers: {
          'X-Client-ID': clientId,
          'Authorization': `Bearer ${accessToken}`
        }
      });

      if (response.ok) {
        const cardData = await response.json();
        setCard(cardData);
      }
    } catch (err) {
      console.error('Failed to fetch card status:', err);
    }
  }

  async function toggleFreeze() {
    setLoading(true);
    setError(null);

    try {
      const endpoint = card.status === 'ACTIVE' ? 'freeze' : 'unfreeze';

      const response = await fetch(`/v1/card/${endpoint}`, {
        method: 'POST',
        headers: {
          'X-Client-ID': clientId,
          'Authorization': `Bearer ${accessToken}`
        }
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.message);
      }

      // Refresh card status
      await fetchCardStatus();

    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }

  if (!card) {
    return <div>Loading card details...</div>;
  }

  return (
    <div className="card-control">
      <div className="card-info">
        <h3>Card ending in {card.panLast4}</h3>
        <CardStatusBadge status={card.status} />
      </div>

      {card.status === 'BLOCKED' ? (
        <div className="blocked-message">
          <p>⚠️ This card has been blocked and cannot be used.</p>
          <p>Please contact support for assistance.</p>
          <button>Contact Support</button>
        </div>
      ) : (
        <div className="card-actions">
          <button
            onClick={toggleFreeze}
            disabled={loading}
            className={card.status === 'ACTIVE' ? 'btn-freeze' : 'btn-unfreeze'}
          >
            {loading
              ? 'Processing...'
              : card.status === 'ACTIVE'
              ? '❄️ Freeze Card'
              : '✅ Unfreeze Card'
            }
          </button>

          <p className="help-text">
            {card.status === 'ACTIVE'
              ? 'Temporarily disable your card for security'
              : 'Reactivate your card to resume using it'
            }
          </p>
        </div>
      )}

      {error && (
        <div className="error-message">
          {error}
        </div>
      )}
    </div>
  );
}

function CardStatusBadge({ status }) {
  const statusConfig = {
    ACTIVE: { icon: '✅', color: 'green', text: 'Active' },
    FROZEN: { icon: '❄️', color: 'blue', text: 'Frozen' },
    BLOCKED: { icon: '🚫', color: 'red', text: 'Blocked' }
  };

  const config = statusConfig[status] || statusConfig.ACTIVE;

  return (
    <span className={`status-badge status-${config.color}`}>
      {config.icon} {config.text}
    </span>
  );
}

Security Best Practices

Confirmation Dialogs

Always confirm destructive or significant actions:
async function freezeCard() {
  const confirmed = await showConfirmDialog({
    title: 'Freeze Card?',
    message: 'Your card will be temporarily disabled. You can unfreeze it anytime.',
    confirmText: 'Freeze Card',
    cancelText: 'Cancel'
  });

  if (!confirmed) return;

  // Proceed with freeze
}

Audit Trail

Log all state changes for security and support:
async function logCardStateChange(action, cardId, userId) {
  await fetch('/api/audit/log', {
    method: 'POST',
    body: JSON.stringify({
      event: `card_${action}`,
      cardId,
      userId,
      timestamp: new Date().toISOString(),
      userAgent: navigator.userAgent
    })
  });
}

Notification

Notify users of all card state changes:
async function notifyCardStateChange(action, userEmail) {
  await fetch('/api/notifications/send', {
    method: 'POST',
    body: JSON.stringify({
      to: userEmail,
      template: `card_${action}`,
      data: {
        timestamp: new Date().toISOString(),
        action: action === 'freeze' ? 'frozen' : 'unfrozen'
      }
    })
  });
}

Freeze vs Block: When to Use Each

User initiates freeze when:
  • Wallet temporarily misplaced
  • Suspicious activity noticed
  • Traveling without plans to use card
  • Taking break from spending
  • Device with saved card lost
Characteristics:
  • ✅ Reversible by user anytime
  • ✅ No support contact needed
  • ✅ Instant activation/deactivation
  • ✅ Can freeze/unfreeze repeatedly

User Experience Guidelines

Status Visibility

Always display current card status prominently:
┌─────────────────────────────────────┐
│  Virtual Card                       │
│  •••• •••• •••• 1234                │
│                                     │
│  Status: [❄️ FROZEN]                │
│                                     │
│  Last 4 transactions declined       │
│  [Unfreeze Card]                    │
└─────────────────────────────────────┘

Action Feedback

Provide immediate visual feedback:
  • Freeze: Show snowflake icon, blue color, disable purchase buttons
  • Unfreeze: Show checkmark, green color, enable purchase buttons
  • Blocked: Show stop icon, red color, display support contact

State Persistence

Remember user’s last action to prevent confusion:
// Store last action in local storage
localStorage.setItem('lastCardAction', JSON.stringify({
  action: 'freeze',
  timestamp: Date.now(),
  cardId: card.id
}));

Troubleshooting

Cause: Processing delay or cached card stateSolution:
  • Wait 5-10 seconds for state propagation
  • Verify status with GET /v1/card/status
  • If issue persists after 1 minute, contact support
Cause: Card may be blocked instead of frozenSolution:
const card = await fetch('/v1/card/status');
if (card.status === 'BLOCKED') {
  // Show blocked message and support contact
  showBlockedCardMessage();
}
Cause: No confirmation dialog implementedSolution: Always show confirmation before state changes
if (action === 'freeze') {
  const confirmed = confirm(
    'Freeze card? All transactions will be declined until you unfreeze it.'
  );
  if (!confirmed) return;
}
Cause: UI not updated after freezeSolution: Refresh card status after state change
await fetch('/v1/card/freeze', { method: 'POST' });
// Refresh status
const updatedCard = await fetch('/v1/card/status');
setCard(updatedCard);

Rate Limiting

Freeze/unfreeze operations are rate limited to prevent abuse:
  • Maximum 10 freeze/unfreeze operations per hour
  • Maximum 50 operations per day
  • Exceeding limits results in 429 Too Many Requests
Handle rate limiting gracefully:
async function freezeCard() {
  try {
    const response = await fetch('/v1/card/freeze', { method: 'POST' });

    if (response.status === 429) {
      const retryAfter = response.headers.get('Retry-After');
      throw new Error(
        `Too many requests. Please try again in ${retryAfter} seconds.`
      );
    }
  } catch (err) {
    showError(err.message);
  }
}

Next Steps