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
Generate Encryption Key
Create a secure 32-byte encryption key and store in environment variables
Encrypt Before Storing
Always encrypt card value and serial before database insertion
Verify User Ownership
Check user ID matches order before displaying cards
Use HTTPS
Ensure all delivery methods use encrypted transport
Audit Logging
Log delivery events without including actual card codes
Secure Deletion
Implement card deletion policy if required by regulations
Next Steps