Skip to main content

Overview

Once orders are fulfilled, retrieve card codes and serials from the API and deliver them securely to customers. Never store or transmit card codes in plain text.
Security Critical: Card codes are valuable credentials. Encrypt during storage and transmission.

API Reference

GET /v3/gift-cards/checkOrder/{orderId}

Retrieve fulfilled cards from order

Card Data Structure

Fulfilled orders contain cards with two fields:
{
  "cards": [
    {
      "value": "XXXX-XXXX-XXXX-XXXX",  // Primary credential (PIN/code)
      "serial": "123456789"             // Secondary credential (serial number)
    }
  ]
}
Both value and serial are sensitive and must be protected.

Encryption Helpers

const crypto = require('crypto');

// Encryption configuration
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY; // Must be 32 bytes
const ALGORITHM = 'aes-256-gcm';

function encryptCardData(text) {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv(
    ALGORITHM,
    Buffer.from(ENCRYPTION_KEY, 'hex'),
    iv
  );
  
  let encrypted = cipher.update(text, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  
  const authTag = cipher.getAuthTag();
  
  return {
    iv: iv.toString('hex'),
    encryptedData: encrypted,
    authTag: authTag.toString('hex')
  };
}

function decryptCardData(iv, encryptedData, authTag) {
  const decipher = crypto.createDecipheriv(
    ALGORITHM,
    Buffer.from(ENCRYPTION_KEY, 'hex'),
    Buffer.from(iv, 'hex')
  );
  
  decipher.setAuthTag(Buffer.from(authTag, 'hex'));
  
  let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
  decrypted += decipher.final('utf8');
  
  return decrypted;
}

// Usage
const encrypted = encryptCardData('XXXX-XXXX-XXXX-XXXX');
const decrypted = decryptCardData(encrypted.iv, encrypted.encryptedData, encrypted.authTag);

Storing Cards Securely

async function storeOrderCards(orderId, userId, cards) {
  const encryptedCards = cards.map(card => {
    const encryptedValue = encryptCardData(card.value);
    const encryptedSerial = encryptCardData(card.serial);
    
    return {
      value: encryptedValue,
      serial: encryptedSerial,
      deliveredAt: null
    };
  });
  
  await db.orders.updateOne(
    { orderId },
    {
      $set: {
        userId,
        status: 'FULFILLED',
        encryptedCards,
        fulfilledAt: new Date()
      }
    }
  );
  
  console.log(`Stored ${cards.length} encrypted cards for order ${orderId}`);
}

// Retrieve and decrypt
async function getOrderCards(orderId) {
  const order = await db.orders.findOne({ orderId });
  
  if (!order || !order.encryptedCards) {
    throw new Error('Order not found or cards not available');
  }
  
  return order.encryptedCards.map(card => ({
    value: decryptCardData(
      card.value.iv,
      card.value.encryptedData,
      card.value.authTag
    ),
    serial: decryptCardData(
      card.serial.iv,
      card.serial.encryptedData,
      card.serial.authTag
    )
  }));
}

Delivery Methods

1. Email Delivery

async function deliverCardsViaEmail(userId, orderId, cards) {
  const user = await db.users.findOne({ _id: userId });
  
  // Build email content with cards
  const cardList = cards.map((card, index) => `
    Card ${index + 1}:
    Code: ${card.value}
    Serial: ${card.serial}
  `).join('\n\n');
  
  await sendEmail({
    to: user.email,
    subject: 'Your Gift Card Order',
    html: `
      <h2>Your gift cards are ready!</h2>
      <p>Order ID: ${orderId}</p>
      <pre>${cardList}</pre>
      <p><strong>Important:</strong> Keep these codes secure and don't share them.</p>
    `
  });
  
  // Mark as delivered
  await db.orders.updateOne(
    { orderId },
    { $set: { 'encryptedCards.$[].deliveredAt': new Date() } }
  );
  
  console.log(`Delivered ${cards.length} cards to ${user.email}`);
}

2. In-App Display

// API endpoint to display cards in-app
app.get('/api/orders/:orderId/cards', authenticateUser, async (req, res) => {
  const { orderId } = req.params;
  const userId = req.user.id;
  
  // Verify ownership
  const order = await db.orders.findOne({ orderId, userId });
  
  if (!order) {
    return res.status(404).json({ error: 'Order not found' });
  }
  
  if (order.status !== 'FULFILLED') {
    return res.status(400).json({ error: 'Order not fulfilled yet' });
  }
  
  // Decrypt and return cards
  const cards = await getOrderCards(orderId);
  
  res.json({
    orderId,
    cards: cards.map((card, index) => ({
      cardNumber: index + 1,
      code: card.value,
      serial: card.serial
    }))
  });
});

3. Secure File Download

async function generateSecureCardFile(orderId, cards) {
  const content = cards.map((card, index) => `
Gift Card ${index + 1}
Code: ${card.value}
Serial: ${card.serial}
--------------------
  `).join('\n');
  
  // Encrypt the file content
  const encrypted = encryptCardData(content);
  
  // Store temporarily with unique token
  const token = crypto.randomBytes(32).toString('hex');
  await db.downloadTokens.insertOne({
    token,
    orderId,
    encrypted,
    expiresAt: new Date(Date.now() + 5 * 60 * 1000) // 5 minutes
  });
  
  return token;
}

// Download endpoint
app.get('/api/download/:token', async (req, res) => {
  const { token } = req.params;
  
  const download = await db.downloadTokens.findOne({ token });
  
  if (!download || download.expiresAt < new Date()) {
    return res.status(404).json({ error: 'Download link expired' });
  }
  
  const content = decryptCardData(
    download.encrypted.iv,
    download.encrypted.encryptedData,
    download.encrypted.authTag
  );
  
  res.setHeader('Content-Type', 'text/plain');
  res.setHeader('Content-Disposition', `attachment; filename="cards-${download.orderId}.txt"`);
  res.send(content);
  
  // Delete token after use
  await db.downloadTokens.deleteOne({ token });
});

Audit Logging

Log delivery events without exposing card data:
async function logCardDelivery(orderId, userId, method, cardCount) {
  await db.auditLogs.insertOne({
    event: 'CARDS_DELIVERED',
    orderId,
    userId,
    method, // 'email', 'in-app', 'download'
    cardCount,
    timestamp: new Date(),
    ipAddress: req.ip,
    userAgent: req.headers['user-agent']
  });
  
  console.log(`[AUDIT] Delivered ${cardCount} cards for order ${orderId} via ${method}`);
}

// ❌ NEVER log actual card codes
// console.log('Card code:', card.value); // DON'T DO THIS

Best Practices

Encrypt at Rest

Always encrypt cards before storing in database

Use HTTPS Only

Never transmit cards over unencrypted connections

Limit Access

Verify user ownership before showing cards

No Plain Text Logs

Never log card codes in plain text

Secure Deletion

Securely delete cards after customer retrieval if policy requires

Audit Trail

Log delivery events without sensitive data

Complete Delivery Flow

async function completeOrderDelivery(orderId, userId) {
  // 1. Verify order is fulfilled
  const orderStatus = await checkOrderStatus(orderId);
  
  if (orderStatus.status !== 'FULFILLED' && orderStatus.status !== 'PARTIALLY_FILLED') {
    throw new Error('Order not ready for delivery');
  }
  
  // 2. Store encrypted cards
  await storeOrderCards(orderId, userId, orderStatus.cards);
  
  // 3. Deliver to customer
  await deliverCardsViaEmail(userId, orderId, orderStatus.cards);
  
  // 4. Log delivery
  await logCardDelivery(orderId, userId, 'email', orderStatus.cards.length);
  
  console.log(`✅ Order ${orderId} delivery complete`);
}

Security Checklist

1

Generate Encryption Key

Create a secure 32-byte encryption key and store in environment variables
2

Encrypt Before Storing

Always encrypt card value and serial before database insertion
3

Verify User Ownership

Check user ID matches order before displaying cards
4

Use HTTPS

Ensure all delivery methods use encrypted transport
5

Audit Logging

Log delivery events without including actual card codes
6

Secure Deletion

Implement card deletion policy if required by regulations

Next Steps