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)
}
]
}
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);
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
// 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']);
?>
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
)
}));
}
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
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;
}
?>
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`);
}
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
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");
}
?>
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 insertion3
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
Overview
Back to integration overview
Track Status
Poll order fulfillment
API Reference
Complete endpoint documentation
Security Best Practices
General security guidelines
Place Orders
Submit validated orders
Load Catalog
Fetch available products

