> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oneclickdz.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Secure Card Delivery

> Encrypt and securely deliver gift card codes to customers

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

<Warning>
  **Security Critical:** Card codes are valuable credentials. Encrypt during storage and transmission.
</Warning>

## API Reference

<Card title="GET /v3/gift-cards/checkOrder/{orderId}" icon="magnifying-glass" href="/en/api-reference/gift-cards/check-order">
  Retrieve fulfilled cards from order
</Card>

## Card Data Structure

Fulfilled orders contain cards with two fields:

```json theme={null}
{
  "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

<CodeGroup>
  ```javascript Node.js theme={null}
  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);
  ```

  ```python Python theme={null}
  from cryptography.hazmat.primitives.ciphers.aead import AESGCM
  import os
  import base64

  # Encryption key (32 bytes)
  ENCRYPTION_KEY = bytes.fromhex(os.getenv('ENCRYPTION_KEY'))

  def encrypt_card_data(text):
      aesgcm = AESGCM(ENCRYPTION_KEY)
      nonce = os.urandom(12)
      
      encrypted = aesgcm.encrypt(nonce, text.encode('utf-8'), None)
      
      return {
          'nonce': base64.b64encode(nonce).decode('utf-8'),
          'encryptedData': base64.b64encode(encrypted).decode('utf-8')
      }

  def decrypt_card_data(nonce_b64, encrypted_data_b64):
      aesgcm = AESGCM(ENCRYPTION_KEY)
      nonce = base64.b64decode(nonce_b64)
      encrypted = base64.b64decode(encrypted_data_b64)
      
      decrypted = aesgcm.decrypt(nonce, encrypted, None)
      
      return decrypted.decode('utf-8')

  # Usage
  encrypted = encrypt_card_data('XXXX-XXXX-XXXX-XXXX')
  decrypted = decrypt_card_data(encrypted['nonce'], encrypted['encryptedData'])
  ```

  ```php PHP theme={null}
  <?php

  // Using OpenSSL for encryption
  define('ENCRYPTION_KEY', getenv('ENCRYPTION_KEY')); // 32 bytes hex
  define('CIPHER_ALGO', 'aes-256-gcm');

  function encryptCardData($text) {
      $key = hex2bin(ENCRYPTION_KEY);
      $iv = random_bytes(16);
      $tag = '';
      
      $encrypted = openssl_encrypt(
          $text,
          CIPHER_ALGO,
          $key,
          OPENSSL_RAW_DATA,
          $iv,
          $tag,
          '',
          16
      );
      
      return [
          'iv' => bin2hex($iv),
          'encryptedData' => bin2hex($encrypted),
          'authTag' => bin2hex($tag)
      ];
  }

  function decryptCardData($ivHex, $encryptedHex, $authTagHex) {
      $key = hex2bin(ENCRYPTION_KEY);
      $iv = hex2bin($ivHex);
      $encrypted = hex2bin($encryptedHex);
      $authTag = hex2bin($authTagHex);
      
      $decrypted = openssl_decrypt(
          $encrypted,
          CIPHER_ALGO,
          $key,
          OPENSSL_RAW_DATA,
          $iv,
          $authTag
      );
      
      if ($decrypted === false) {
          throw new Exception('Decryption failed');
      }
      
      return $decrypted;
  }

  // Usage
  $encrypted = encryptCardData('XXXX-XXXX-XXXX-XXXX');
  $decrypted = decryptCardData($encrypted['iv'], $encrypted['encryptedData'], $encrypted['authTag']);
  ?>
  ```
</CodeGroup>

## Storing Cards Securely

<CodeGroup>
  ```javascript Node.js theme={null}
  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
      )
    }));
  }
  ```

  ```python Python theme={null}
  def store_order_cards(order_id, user_id, cards):
      encrypted_cards = []
      
      for card in cards:
          encrypted_value = encrypt_card_data(card['value'])
          encrypted_serial = encrypt_card_data(card['serial'])
          
          encrypted_cards.append({
              'value': encrypted_value,
              'serial': encrypted_serial,
              'deliveredAt': None
          })
      
      db.orders.update_one(
          {'orderId': order_id},
          {
              '$set': {
                  'userId': user_id,
                  'status': 'FULFILLED',
                  'encryptedCards': encrypted_cards,
                  'fulfilledAt': datetime.now()
              }
          }
      )
      
      print(f"Stored {len(cards)} encrypted cards for order {order_id}")

  def get_order_cards(order_id):
      order = db.orders.find_one({'orderId': order_id})
      
      if not order or 'encryptedCards' not in order:
          raise ValueError('Order not found or cards not available')
      
      return [
          {
              'value': decrypt_card_data(card['value']['nonce'], card['value']['encryptedData']),
              'serial': decrypt_card_data(card['serial']['nonce'], card['serial']['encryptedData'])
          }
          for card in order['encryptedCards']
      ]
  ```

  ```php PHP theme={null}
  <?php

  function storeOrderCards($orderId, $userId, $cards) {
      $encryptedCards = [];
      
      foreach ($cards as $card) {
          $encryptedValue = encryptCardData($card['value']);
          $encryptedSerial = encryptCardData($card['serial']);
          
          $encryptedCards[] = [
              'value' => $encryptedValue,
              'serial' => $encryptedSerial,
              'deliveredAt' => null
          ];
      }
      
      updateOrderInDatabase($orderId, [
          'userId' => $userId,
          'status' => 'FULFILLED',
          'encryptedCards' => $encryptedCards,
          'fulfilledAt' => date('Y-m-d H:i:s')
      ]);
      
      error_log("Stored " . count($cards) . " encrypted cards for order {$orderId}");
  }

  function getOrderCards($orderId) {
      $order = findOrderInDatabase($orderId);
      
      if (!$order || !isset($order['encryptedCards'])) {
          throw new Exception('Order not found or cards not available');
      }
      
      $decryptedCards = [];
      
      foreach ($order['encryptedCards'] as $card) {
          $decryptedCards[] = [
              'value' => decryptCardData(
                  $card['value']['iv'],
                  $card['value']['encryptedData'],
                  $card['value']['authTag']
              ),
              'serial' => decryptCardData(
                  $card['serial']['iv'],
                  $card['serial']['encryptedData'],
                  $card['serial']['authTag']
              )
          ];
      }
      
      return $decryptedCards;
  }
  ?>
  ```
</CodeGroup>

## Delivery Methods

### 1. Email Delivery

```javascript theme={null}
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

```javascript theme={null}
// 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

```javascript theme={null}
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:

```javascript theme={null}
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

<CardGroup cols={2}>
  <Card title="Encrypt at Rest" icon="lock">
    Always encrypt cards before storing in database
  </Card>

  <Card title="Use HTTPS Only" icon="shield-halved">
    Never transmit cards over unencrypted connections
  </Card>

  <Card title="Limit Access" icon="user-lock">
    Verify user ownership before showing cards
  </Card>

  <Card title="No Plain Text Logs" icon="file-slash">
    Never log card codes in plain text
  </Card>

  <Card title="Secure Deletion" icon="trash">
    Securely delete cards after customer retrieval if policy requires
  </Card>

  <Card title="Audit Trail" icon="clipboard-list">
    Log delivery events without sensitive data
  </Card>
</CardGroup>

## Complete Delivery Flow

<CodeGroup>
  ```javascript Node.js theme={null}
  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`);
  }
  ```

  ```python Python theme={null}
  def complete_order_delivery(order_id, user_id):
      # 1. Verify order is fulfilled
      order_status = check_order_status(order_id)
      
      if order_status['status'] not in ['FULFILLED', 'PARTIALLY_FILLED']:
          raise ValueError('Order not ready for delivery')
      
      # 2. Store encrypted cards
      store_order_cards(order_id, user_id, order_status['cards'])
      
      # 3. Deliver to customer
      deliver_cards_via_email(user_id, order_id, order_status['cards'])
      
      # 4. Log delivery
      log_card_delivery(order_id, user_id, 'email', len(order_status['cards']))
      
      print(f"✅ Order {order_id} delivery complete")
  ```

  ```php PHP theme={null}
  <?php

  function completeOrderDelivery($orderId, $userId, $apiKey) {
      // 1. Verify order is fulfilled
      $orderStatus = checkOrderStatus($orderId, $apiKey);
      
      if (!in_array($orderStatus['status'], ['FULFILLED', 'PARTIALLY_FILLED'])) {
          throw new Exception('Order not ready for delivery');
      }
      
      // 2. Store encrypted cards
      storeOrderCards($orderId, $userId, $orderStatus['cards']);
      
      // 3. Deliver to customer
      deliverCardsViaEmail($userId, $orderId, $orderStatus['cards']);
      
      // 4. Log delivery
      logCardDelivery($orderId, $userId, 'email', count($orderStatus['cards']));
      
      error_log("✅ Order {$orderId} delivery complete");
  }
  ?>
  ```
</CodeGroup>

## Security Checklist

<Steps>
  <Step title="Generate Encryption Key">
    Create a secure 32-byte encryption key and store in environment variables
  </Step>

  <Step title="Encrypt Before Storing">
    Always encrypt card `value` and `serial` before database insertion
  </Step>

  <Step title="Verify User Ownership">
    Check user ID matches order before displaying cards
  </Step>

  <Step title="Use HTTPS">
    Ensure all delivery methods use encrypted transport
  </Step>

  <Step title="Audit Logging">
    Log delivery events without including actual card codes
  </Step>

  <Step title="Secure Deletion">
    Implement card deletion policy if required by regulations
  </Step>
</Steps>

## Next Steps

<CardGroup cols={3}>
  <Card title="Overview" icon="book-open" href="/en/gift-card-guides/overview">
    Back to integration overview
  </Card>

  <Card title="Track Status" icon="chart-line" href="/en/gift-card-guides/4-status-tracking">
    Poll order fulfillment
  </Card>

  <Card title="API Reference" icon="book" href="/en/api-reference/gift-cards/check-order">
    Complete endpoint documentation
  </Card>

  <Card title="Security Best Practices" icon="shield" href="/en/security-best-practices">
    General security guidelines
  </Card>

  <Card title="Place Orders" icon="cart-shopping" href="/en/gift-card-guides/3-placing-orders">
    Submit validated orders
  </Card>

  <Card title="Load Catalog" icon="list" href="/en/gift-card-guides/1-loading-catalog">
    Fetch available products
  </Card>
</CardGroup>
