Airwallex logo
Airwallex logo

Secure iframes

As an Airwallex customer, you are required to be PCI compliant to retrieve sensitive details of individual cards or cards issued to connected accounts (if you have a Scale implementation) using Get sensitive card details API.

The PAN delegation feature allows you to retrieve sensitive details using a secure token in a PCI-compliant manner regardless of your PCI compliance status.

Before you begin

  • Contact your Airwallex Account Manager to enable Issuing APIs, Cards, and PAN delegation for your Airwallex account.
  • Obtain your access token API by authenticating to Airwallex using your unique Client ID and API key. You will need the access token to make API calls.

Step 1: Retrieve a token

Use Create a PAN token API with the card_id parameter (the ID of the individual card or the card issued to the connected account). Note that as a platform account, must specify the connected account’s open ID (starting with the prefix acct_) in the x-on-behalf-of header as you are acting on behalf of the connected account.

A successful response will return a token and its expiration date. The token expires after a minute from when it’s issued. The iframe in the next step will need to be loaded within this time.

Example request

Shell
1curl --request POST \
2--url 'https://api-demo.airwallex.com/api/v1/issuing/pantokens/create'
3--header 'Authorization: Bearer <your_bearer_token>'
4--data '{
5 "card_id": "75c89b87-eb15-453a-85d7-621c104f707d"
6}

Example response

JSON
1{
2 "expires_at": "2021-03-22T00:29:34.558+0000",
3 "token": "1HxHDfKJNnITGTULgnNoAADkgE1q+tMecRMVnk0w5LbpuqXeYdytS/EorRNvJZAFftjQCse6+0UPJNe+dzDwfZv+lxuLt06Blc59BSZkwRx1kIMwtkvPmam7PZNf8ZQvOEfTv6+5Cei/jkjUpivhRA4THHc2hX2gpDSIr/mljHhKXMkKxQU+F7USo2x52cNslYhQVl04U5PYdUbnygJnFtmE+Fd3ENy+HHfkErCCTOTcVzwRRA=="
4}

Step 2: Prepare a hash

After retrieving the token, prepare a hash for the iframe. The hash allows you to inject the initial values and options for the iframe. Currently, its typing is as below:

1const hash = {
2 token: "The token fetched via api for the card",
3 rules: {
4 ".details": { CSSType },
5 ".pin": { CSSType }
6 },
7 langKey: "zh"
8 };
9
10 const hashURI = encodeURIComponent(JSON.stringify(hash));

The hash accepts three fields described in the following sections.

token [Required]

1const res = await get('https://api.airwallex.com/api/v1/issuing/pantokens/create', {
2 card_id
3});
4
5const hash = {
6 token: res.token
7};

rules [Optional]

To provide a consistent, seamless user experience when displaying the card details or card PIN, you can provide rules to style the iframe. Rules are a map of selectors and CSS properties. Both the selectors and the CSS properties are white listed.

Here's an example of hash with style configuration.

1const hash = {
2 token: YOUR_TOKEN,
3 rules: {
4 '.pin': {
5 fontSize: '40px',
6 fontWeight: '800',
7 letterSpacing: '2px'
8 }
9 }
10}
11
12const hashURI = encodeURIComponent(JSON.stringify(hash));

The available CSS properties are listed below, for a list of available selectors, see Style iframe.

1type CSSProperties =
2 | '-moz-osx-font-smoothing'
3 | '-webkit-font-smoothing'
4 | '-webkit-text-fill-color'
5 | 'backgroundColor'
6 | 'border'
7 | 'borderBottom'
8 | 'borderColor'
9 | 'borderLeft'
10 | 'borderRadius'
11 | 'borderRight'
12 | 'borderStyle'
13 | 'borderTop'
14 | 'borderWidth'
15 | 'boxShadow'
16 | 'color'
17 | 'font'
18 | 'fontFamily'
19 | 'fontFeatureSettings'
20 | 'fontSize'
21 | 'fontStyle'
22 | 'fontVariant'
23 | 'fontWeight'
24 | 'letterSpacing'
25 | 'lineHeight'
26 | 'margin'
27 | 'marginBottom'
28 | 'marginLeft'
29 | 'marginRight'
30 | 'marginTop'
31 | 'outline'
32 | 'outlineColor'
33 | 'outlineOffset'
34 | 'outlineStyle'
35 | 'outlineWidth'
36 | 'padding'
37 | 'paddingBottom'
38 | 'paddingLeft'
39 | 'paddingRight'
40 | 'paddingTop'
41 | 'textAlign'
42 | 'textDecoration'
43 | 'textIndent'
44 | 'textJustify'
45 | 'textShadow'
46 | 'textTransform'
47 | 'transition'
48 | 'wordBreak'
49 | 'wordSpacing'
50 | 'wordWrap';

langKey [Optional]

You can use langKey to define the language of the iframe. When not provided or if the specified language is unavailable, it defaults to “en”. The list of valid langKey values include:

ValueLanguage
enEnglish
zhChinese (Simplified)
zh-HantChinese (Traditional)
1const hash = {
2...
3langKey: 'en',
4};
5

Step 3: Embed iframe on your page

You can embed two types of iframes on your page:

  • The card details iframe displays the card number, expiry and cvv.
  • The card PIN iframe displays the card PIN

Display card details iframe

The card details iframe can be displayed using the following endpoint. https://airwallex.com/issuing/pci/v2/:cardId/details#:hash

1const res = await get('https://api.airwallex.com/v1/issuing/pantokens/create', {
2 card_id
3});
4
5const hash = {
6 token: res.token
7};
8
9const hashURI = encodeURIComponent(JSON.stringify(hash));
10
11return
12 <iframe src={`https://airwallex.com/issuing/pci/v2/$\{cardId\}/details#$\{hashURI\}`}/>

Display card PIN iframe

The card PIN iframe can be displayed using the following endpoint. https://airwallex.com/issuing/pci/v2/:cardId/pin/#:hash

1const hashURI = encodeURIComponent(JSON.stringify(hash));
2
3return
4 <iframe src={`https://airwallex.com/issuing/pci/v2/$\{cardId\}/pin#$\{hashURI\}`}/>

The PIN iframe does not accept any langKey as it only displays numbers.

Step 4: Style iframe

You can style the iframe by providing rules when preparing the hash.

Style card details iframe

The available selectors for the card details iframe are listed below:

1.details
2.details__row
3.details__row--card-number
4.details__row--expiry-date
5.details__row--security-code
6.details__content
7.details__label
8.details__value
9.details__tooltip
10.details__button
11.details__button:hover
12.details__button:active
13.details__button:focus
14.details__button svg

Example code

1const hash = {
2 token: token,
3 rules: {
4 '.details': {
5 backgroundColor: '#2a2a2a',
6 color: 'white',
7 borderRadius: '20px',
8 fontFamily: 'Arial'
9
10 },
11 '.details__row': {
12 display: 'flex',
13 justifyContent: 'space-between',
14 padding: '20px'
15 },
16 '.details__label': {
17 width: '100px',
18 fontWeight: 'bold'
19 },
20 '.details__content': { display: 'flex' },
21 '.details__button svg': { color: 'white' }
22 }
23};

Styled card details iframe

The example code above creates the styling changes as shown in the visual.

Before styling vs after styling

Before vs After card iframe styling

Style card PIN iframe

The available selectors for the card PIN iframe are:

1.pin
2.pin__value

Example code, PIN iframe

1const hash = {
2 token: token,
3 rules: {
4 '.pin': {
5 backgroundColor: '#2a2a2a',
6 color: 'white',
7 borderRadius: '12px',
8 fontFamily: 'Arial',
9 padding: '20px'
10 },
11 '.pin_value': {
12 fontSize: '24px',
13 fontWeight: 'bold',
14 letterSpacing: '4px'
15 }
16 }
17};

Step 5: Handle iframe event lifecycle

The iframe uses the postMessage API to inform the parent page about its lifecycle including load status and/or errors. Possible event types for the card details iframe are listed below.

1// iframe has completed its first render
2{
3 type: `$\{cardId\}:details:mounted`
4}
1// iframe is loading details
2{
3 type: `$\{cardId\}:details:loading`
4}
1// iframe has loaded details
2{
3 type: `$\{cardId\}:details:loaded`
4}
1// iframe has encountered an error while parsing hash or loading
2{
3 type: `$\{cardId\}:details:error`;
4 error: 'cannot_retrieve_details' | 'unable_to_parse_hash' | 'unknown_hash_format' | 'invalid_hash_format';
5}
1// iframe has resized and is returning its new height
2{
3 type: `$\{cardId\}:details:resize`;
4 height: number;
5};

The card PIN iframe has the same events except details is replaced with pin.

1// Card details iframe event
2{
3 type: `$\{cardId\}:details:error`;
4 error: 'cannot_retrieve_details' | 'unable_to_parse_hash' | 'unknown_hash_format' | 'invalid_hash_format';
5}
1// Card PIN iframe event
2{
3 type: `$\{cardId\}:pin:error`;
4 error: 'cannot_retrieve_pin' | 'unable_to_parse_hash' | 'unknown_hash_format' | 'invalid_hash_format';
5}

Full example code (React)

1export const IframeDetails = ({ cardId }) => {
2 const [error, setError] = useState(false);
3 const [loaded, setLoaded] = useState(false);
4 const [height, setHeight] = useState(0);
5
6 function handleMessage(event) {
7 if (!event.origin.includes('airwallex.com')) return;
8 if (event?.data.type === `$\{cardId\}:details:loaded`) setLoaded(true);
9 if (event?.data.type === `$\{cardId\}:details:error`) setError(true);
10 if (event?.data.type === `$\{cardId\}:details:resize`) setHeight(event?.data.height);
11 }
12
13 useEffect(() => {
14 window.addEventListener('message', handleMessage);
15 return () => window.removeEventListener('message', handleMessage);
16 }, []);
17
18 const hash = {
19 token: getAuthToken(),
20 langKey: 'en',
21 rules: {
22 '.details': {
23 font: '14px Arial, sans-serif',
24 margin: '0',
25 },
26 },
27 };
28 const hashURI = encodeURIComponent(JSON.stringify(hash));
29
30 return (
31 <>
32 {!loaded && <p>loading...</p>}
33 {error && <p>Oops something went wrong</p>}
34 <iframe
35 style={{ height, display: loaded ? 'block' : 'none' }}
36 frameBorder="0"
37 src={`https://airwallex.com/issuing/pci/v2/$\{cardId\}/details#$\{hashURI\}`}
38 />
39 </>
40 );
41}
Was this page helpful?