Overview
PIN operations use a secure token-based flow that keeps sensitive data completely isolated from your application. Card details and PIN viewing are delivered as secure images, while PIN setting uses hosted pages. You never handle or store sensitive PIN data.
Never attempt to handle, store, or transmit PIN data directly in your application. Always use the token-based flow.
Security Architecture
Token-Based Flow
Sensitive operations follow a simple pattern:
Generate Token
Request a single-use, time-limited token from the API
Display Secure Content
For viewing: Display secure image. For setting: Use PCI-compliant hosted page
Token Expires
Token is invalidated after use or timeout (~10 minutes)
Benefits
PCI Compliance No sensitive data touches your infrastructure
Zero Liability Reduces security audit scope
Built-in Security Session management and expiration
User Trust Industry-standard secure pages
Viewing Card Details
Display full card information (PAN, CVV, expiry) as a secure image. The card details are never transmitted to or stored by your application.
Step 1: Generate Details Token
JavaScript/Node.js
Python
cURL
const response = await fetch ( 'https://dev.api.baanx.com/v1/card/details/token' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'X-Client-ID' : 'your_client_id' ,
'Authorization' : `Bearer ${ userAccessToken } `
},
body: JSON . stringify ({
customCss: {
cardBackgroundColor: '#000000' ,
cardTextColor: '#FFFFFF' ,
panBackgroundColor: '#EFEFEF' ,
panTextColor: '#000000'
}
})
});
const { token , imageUrl } = await response . json ();
Request Parameters
Optional styling to match your brand. If omitted, default styling is applied. Card background color (hex format)
Text color on card background (hex format). Must contrast with cardBackgroundColor.
PAN number background color (hex format)
PAN number text color (hex format). Must contrast with panBackgroundColor.
Response
{
"token" : "100a99cf-f4d3-4fa1-9be9-2e9828b20ebb" ,
"imageUrl" : "https://cards.baanx.com/details-image?token=100a99cf-f4d3-4fa1-9be9-2e9828b20ebb"
}
Single-use token valid for ~10 minutes
URL that renders card details as a secure image. Use as src attribute of an <img> tag.
Security : Treat imageUrl as highly sensitive. Never log or store it. Use HTTPS only and display in secure contexts.
Step 2: Display Card Image
function showCardDetails ( imageUrl ) {
const img = document . createElement ( 'img' );
img . src = imageUrl ;
img . alt = 'Card Details' ;
img . style . cssText = `
max-width: 100%;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
` ;
document . getElementById ( 'card-details-container' ). appendChild ( img );
}
Complete React Implementation
import { useState } from 'react' ;
export function CardDetailsViewer ({ clientId , accessToken }) {
const [ loading , setLoading ] = useState ( false );
const [ error , setError ] = useState ( null );
const [ imageUrl , setImageUrl ] = useState ( null );
async function viewCardDetails () {
setLoading ( true );
setError ( null );
try {
const response = await fetch ( 'https://dev.api.baanx.com/v1/card/details/token' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'X-Client-ID' : clientId ,
'Authorization' : `Bearer ${ accessToken } `
},
body: JSON . stringify ({
customCss: {
cardBackgroundColor: '#000000' ,
cardTextColor: '#FFFFFF' ,
panBackgroundColor: '#EFEFEF' ,
panTextColor: '#000000'
}
})
});
if ( ! response . ok ) {
throw new Error ( 'Failed to generate details token' );
}
const data = await response . json ();
setImageUrl ( data . imageUrl );
} catch ( err ) {
setError ( err . message );
} finally {
setLoading ( false );
}
}
function hideCardDetails () {
setImageUrl ( null );
}
if ( imageUrl ) {
return (
< div className = "card-details-viewer" >
< div className = "overlay" onClick = { hideCardDetails } />
< div className = "modal" >
< button className = "close-btn" onClick = { hideCardDetails } >
✕
</ button >
< img
src = { imageUrl }
alt = "Card Details"
style = { {
maxWidth: '100%' ,
borderRadius: '12px'
} }
/>
</ div >
</ div >
);
}
return (
< div >
< button
onClick = { viewCardDetails }
disabled = { loading }
className = "btn-view-details"
>
{ loading ? 'Loading...' : '👁️ View Card Details' }
</ button >
{ error && (
< div className = "error-message" > { error } </ div >
) }
</ div >
);
}
Viewing PIN
Display the card PIN as a secure image. The PIN is never transmitted to or stored by your application.
Step 1: Generate PIN Token
JavaScript/Node.js
Python
cURL
const response = await fetch ( 'https://dev.api.baanx.com/v1/card/pin/token' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'X-Client-ID' : 'your_client_id' ,
'Authorization' : `Bearer ${ userAccessToken } `
},
body: JSON . stringify ({
customCss: {
backgroundColor: '#EFEFEF' ,
textColor: '#000000'
}
})
});
const { token , imageUrl } = await response . json ();
Request Parameters
Optional styling to match your brand. If omitted, default styling is applied. Background color of the PIN image (hex format)
Text color for the PIN digits (hex format). Must contrast with backgroundColor.
Response
{
"token" : "100a99cf-f4d3-4fa1-9be9-2e9828b20ebb" ,
"imageUrl" : "https://cards.baanx.com/details-image?token=100a99cf-f4d3-4fa1-9be9-2e9828b20ebb"
}
Single-use token valid for ~10 minutes
URL that renders PIN as a secure image. Use as src attribute of an <img> tag.
Security : Treat imageUrl as highly sensitive. Never log or store it. Display only in authenticated, secure contexts.
Step 2: Display PIN Image
function showPIN ( imageUrl ) {
const img = document . createElement ( 'img' );
img . src = imageUrl ;
img . alt = 'Card PIN' ;
img . style . cssText = `
max-width: 100%;
border-radius: 8px;
` ;
document . getElementById ( 'pin-container' ). appendChild ( img );
}
Complete React Implementation
import { useState } from 'react' ;
export function PINViewer ({ clientId , accessToken }) {
const [ loading , setLoading ] = useState ( false );
const [ error , setError ] = useState ( null );
const [ imageUrl , setImageUrl ] = useState ( null );
async function viewPIN () {
setLoading ( true );
setError ( null );
try {
const response = await fetch ( 'https://dev.api.baanx.com/v1/card/pin/token' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'X-Client-ID' : clientId ,
'Authorization' : `Bearer ${ accessToken } `
},
body: JSON . stringify ({
customCss: {
backgroundColor: '#EFEFEF' ,
textColor: '#000000'
}
})
});
if ( ! response . ok ) {
throw new Error ( 'Failed to generate PIN token' );
}
const data = await response . json ();
setImageUrl ( data . imageUrl );
} catch ( err ) {
setError ( err . message );
} finally {
setLoading ( false );
}
}
function hidePIN () {
setImageUrl ( null );
}
if ( imageUrl ) {
return (
< div className = "pin-viewer" >
< div className = "overlay" onClick = { hidePIN } />
< div className = "modal" >
< button className = "close-btn" onClick = { hidePIN } >
✕
</ button >
< img
src = { imageUrl }
alt = "Card PIN"
style = { {
maxWidth: '100%' ,
borderRadius: '8px'
} }
/>
</ div >
</ div >
);
}
return (
< div >
< button
onClick = { viewPIN }
disabled = { loading }
className = "btn-view-pin"
>
{ loading ? 'Loading...' : '👁️ View PIN' }
</ button >
{ error && (
< div className = "error-message" > { error } </ div >
) }
</ div >
);
}
Setting/Changing PIN
Allow users to create or change their PIN through a secure hosted page.
Step 1: Generate Set PIN Token
JavaScript/Node.js
Python
cURL
const response = await fetch ( 'https://dev.api.baanx.com/v1/card/set-pin/token' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'X-Client-ID' : 'your_client_id' ,
'Authorization' : `Bearer ${ userAccessToken } `
},
body: JSON . stringify ({
isEmbedded: true ,
customCss: {
backgroundColor: '#EFEFEF' ,
textColor: '#000000' ,
backgroundColorPrimary: '#000000' ,
textColorPrimary: '#FFFFFF' ,
buttonBorderRadius: 8 ,
pinBorderRadius: 4
}
})
});
const { token , hostedPageUrl } = await response . json ();
Request Parameters
Same as PIN viewing, with identical customCss options.
Response
{
"token" : "100a99cf-f4d3-4fa1-9be9-2e9828b20ebb" ,
"hostedPageUrl" : "https://cards.baanx.com/pin-direct/set?token=100a99cf-f4d3-4fa1-9be9-2e9828b20ebb"
}
Set PIN hosted page includes built-in validation for PIN requirements (4 digits, no sequential patterns, etc.)
Step 2: Display Set PIN Page
function showSetPIN ( hostedPageUrl ) {
const iframe = document . createElement ( 'iframe' );
iframe . src = hostedPageUrl ;
iframe . style . cssText = `
width: 100%;
height: 450px;
border: none;
border-radius: 8px;
` ;
document . getElementById ( 'pin-container' ). appendChild ( iframe );
window . addEventListener ( 'message' , ( event ) => {
if ( event . origin !== 'https://cards.baanx.com' ) return ;
switch ( event . data ) {
case 'success' :
console . log ( 'PIN set successfully' );
iframe . remove ();
showSuccessMessage ( 'PIN updated successfully' );
break ;
case 'abort' :
console . log ( 'User cancelled PIN change' );
iframe . remove ();
break ;
case 'error' :
console . error ( 'Error setting PIN' );
iframe . remove ();
showErrorMessage ( 'Failed to set PIN' );
break ;
}
});
}
Complete PIN Management Component
import { useState } from 'react' ;
export function PINManagement ({ clientId , accessToken , cardStatus }) {
const [ loading , setLoading ] = useState ( false );
const [ error , setError ] = useState ( null );
const [ activeView , setActiveView ] = useState ( null );
const [ imageUrl , setImageUrl ] = useState ( null );
const [ hostedPageUrl , setHostedPageUrl ] = useState ( null );
async function generateToken ( endpoint ) {
setLoading ( true );
setError ( null );
try {
const customCss = endpoint === 'pin'
? {
backgroundColor: '#f5f5f5' ,
textColor: '#1a1a1a'
}
: {
backgroundColor: '#f5f5f5' ,
textColor: '#1a1a1a' ,
backgroundColorPrimary: '#2563eb' ,
textColorPrimary: '#ffffff' ,
buttonBorderRadius: 8 ,
pinBorderRadius: 4
};
const requestBody = endpoint === 'pin'
? { customCss }
: { isEmbedded: true , customCss };
const response = await fetch ( `https://dev.api.baanx.com/v1/card/ ${ endpoint } /token` , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'X-Client-ID' : clientId ,
'Authorization' : `Bearer ${ accessToken } `
},
body: JSON . stringify ( requestBody )
});
if ( ! response . ok ) {
throw new Error ( 'Failed to generate token' );
}
const data = await response . json ();
if ( endpoint === 'pin' ) {
setImageUrl ( data . imageUrl );
} else {
setHostedPageUrl ( data . hostedPageUrl );
window . addEventListener ( 'message' , handleMessage );
}
setActiveView ( endpoint );
} catch ( err ) {
setError ( err . message );
} finally {
setLoading ( false );
}
}
function handleMessage ( event ) {
if ( event . origin !== 'https://cards.baanx.com' ) return ;
switch ( event . data ) {
case 'success' :
closeView ();
break ;
case 'abort' :
closeView ();
break ;
case 'error' :
setError ( 'Operation failed' );
closeView ();
break ;
}
}
function closeView () {
setActiveView ( null );
setImageUrl ( null );
setHostedPageUrl ( null );
window . removeEventListener ( 'message' , handleMessage );
}
if ( activeView === 'pin' && imageUrl ) {
return (
< div className = "pin-modal" >
< div className = "overlay" onClick = { closeView } />
< div className = "modal-content" >
< button className = "close-btn" onClick = { closeView } > ✕ </ button >
< img
src = { imageUrl }
alt = "Card PIN"
style = { {
maxWidth: '100%' ,
borderRadius: '8px'
} }
/>
</ div >
</ div >
);
}
if ( activeView === 'set-pin' && hostedPageUrl ) {
return (
< div className = "pin-modal" >
< div className = "overlay" onClick = { closeView } />
< div className = "modal-content" >
< button className = "close-btn" onClick = { closeView } > ✕ </ button >
< iframe
src = { hostedPageUrl }
style = { {
width: '100%' ,
height: '450px' ,
border: 'none' ,
borderRadius: '8px'
} }
/>
</ div >
</ div >
);
}
const isFrozen = cardStatus === 'FROZEN' ;
const isBlocked = cardStatus === 'BLOCKED' ;
return (
< div className = "pin-management" >
< h3 > PIN Management </ h3 >
{ error && < div className = "error-message" > { error } </ div > }
< div className = "pin-actions" >
< button
onClick = { () => generateToken ( 'pin' ) }
disabled = { loading || isBlocked }
className = "btn-view-pin"
>
{ loading ? 'Loading...' : '👁️ View PIN' }
</ button >
< button
onClick = { () => generateToken ( 'set-pin' ) }
disabled = { loading || isFrozen || isBlocked }
className = "btn-set-pin"
>
{ loading ? 'Loading...' : '🔐 Change PIN' }
</ button >
</ div >
{ isFrozen && (
< p className = "info-text" >
Unfreeze your card to change PIN
</ p >
) }
{ isBlocked && (
< p className = "error-text" >
Card is blocked - contact support
</ p >
) }
</ div >
);
}
Sequence Diagrams
View Card Details Flow
View PIN Flow
Set PIN Flow
Customization Examples
Dark Theme
// Card details viewing
const darkThemeCardDetails = {
cardBackgroundColor: '#1a1a1a' ,
cardTextColor: '#ffffff' ,
panBackgroundColor: '#2a2a2a' ,
panTextColor: '#ffffff'
};
// PIN viewing
const darkThemePIN = {
backgroundColor: '#1a1a1a' ,
textColor: '#ffffff'
};
// PIN setting (hosted page)
const darkThemeSetPIN = {
backgroundColor: '#1a1a1a' ,
textColor: '#ffffff' ,
backgroundColorPrimary: '#3b82f6' ,
textColorPrimary: '#ffffff' ,
buttonBorderRadius: 8 ,
pinBorderRadius: 4
};
Light Theme
// Card details viewing
const lightThemeCardDetails = {
cardBackgroundColor: '#ffffff' ,
cardTextColor: '#1a1a1a' ,
panBackgroundColor: '#f5f5f5' ,
panTextColor: '#1a1a1a'
};
// PIN viewing
const lightThemePIN = {
backgroundColor: '#ffffff' ,
textColor: '#1a1a1a'
};
// PIN setting (hosted page)
const lightThemeSetPIN = {
backgroundColor: '#ffffff' ,
textColor: '#1a1a1a' ,
backgroundColorPrimary: '#2563eb' ,
textColorPrimary: '#ffffff' ,
buttonBorderRadius: 8 ,
pinBorderRadius: 4
};
Brand Theme
// Card details viewing
const brandThemeCardDetails = {
cardBackgroundColor: '#7c3aed' , // Brand purple
cardTextColor: '#ffffff' ,
panBackgroundColor: '#f5f5f5' ,
panTextColor: '#1a1a1a'
};
// PIN viewing
const brandThemePIN = {
backgroundColor: '#fafafa' ,
textColor: '#7c3aed'
};
// PIN setting (hosted page)
const brandThemeSetPIN = {
backgroundColor: '#fafafa' ,
textColor: '#1a1a1a' ,
backgroundColorPrimary: '#7c3aed' , // Brand purple
textColorPrimary: '#ffffff' ,
buttonBorderRadius: 12 ,
pinBorderRadius: 8
};
Security Best Practices
Token Handling
Never store tokens in localStorage or sessionStorage
Use tokens immediately after generation
Don’t reuse tokens - generate new ones for each operation
Implement token expiration handling
Image URL Security
Image URLs contain sensitive data. Never log, cache, or store them. Use HTTPS only and display only in authenticated contexts.
// ✅ Good: Use imageUrl immediately and clear after viewing
const { imageUrl } = await response . json ();
displayImage ( imageUrl );
// Clean up when user closes
onClose (() => {
removeImageFromDOM ();
// imageUrl is garbage collected
});
// ❌ Bad: Don't store or log
localStorage . setItem ( 'pinImage' , imageUrl ); // NEVER
console . log ( 'Image URL:' , imageUrl ); // NEVER
iframe Security (Set PIN Only)
For PIN setting operations that use hosted pages:
< iframe
src = "{hostedPageUrl}"
sandbox = "allow-scripts allow-same-origin allow-forms"
allow = "clipboard-write"
referrerpolicy = "no-referrer"
style = "border: none;"
/ >
Origin Verification (Set PIN Only)
Only needed for PIN setting hosted pages:
window . addEventListener ( 'message' , ( event ) => {
// Always verify origin
if ( event . origin !== 'https://cards.baanx.com' ) {
console . warn ( 'Rejected message from unauthorized origin:' , event . origin );
return ;
}
// Process message
handleMessage ( event . data );
});
Content Security Policy
< meta http-equiv = "Content-Security-Policy" content = "
frame-src https://cards.baanx.com;
img-src https://cards.baanx.com;
script-src 'self' 'unsafe-inline';
connect-src https://dev.api.baanx.com;
" >
Troubleshooting
Token expires before user views
Cause : Token has ~10 minute lifetimeSolution : Generate token only when user clicks action button// ❌ Bad: Generate token on page load
useEffect (() => {
generateToken (); // May expire before use
}, []);
// ✅ Good: Generate on user action
< button onClick = { generateToken } > View PIN </ button >
Cause : Token expired, already used, or network issueSolution : Implement error handling and retry logicconst img = document . createElement ( 'img' );
img . onerror = () => {
console . error ( 'Failed to load image' );
alert ( 'Unable to display. Please try again.' );
};
img . onload = () => {
console . log ( 'Image loaded successfully' );
};
img . src = imageUrl ;
Image styling doesn't match
Cause : Color contrast issues or invalid hex codesSolution : Ensure valid hex codes and sufficient contrast// ❌ Bad: Same colors for background and text
{
backgroundColor : '#000000' ,
textColor : '#000000' // Can't read!
}
// ✅ Good: High contrast
{
backgroundColor : '#000000' ,
textColor : '#ffffff'
}
PostMessage events not received (Set PIN)
Cause : Origin verification failing or listener not attachedSolution : Verify origin and ensure listener is active (only applies to set-PIN hosted page)window . addEventListener ( 'message' , ( event ) => {
console . log ( 'Received message:' , event . data , 'from:' , event . origin );
if ( event . origin !== 'https://cards.baanx.com' ) {
console . warn ( 'Wrong origin' );
return ;
}
handleMessage ( event . data );
});
Can't set PIN on frozen card
Cause : PIN changes not allowed on frozen cardsSolution : Check card status and disable PIN change button< button
onClick = { changePIN }
disabled = { cardStatus === 'FROZEN' || cardStatus === 'BLOCKED' }
>
Change PIN
</ button >
Next Steps