Airwallex logo

Embedded Balance component

Show a connected account's balances and recent activity in your product with the Embedded Balance component.

Copy for LLMView as Markdown

The Embedded Balance component is a set of pre-built elements. Mount only the elements you need, and arrange them in your own page. The component includes the following:

  • Layouts adapt across desktop and mobile. You still control page composition and branding.
  • Multi-language support to localize the experience.
  • One shared theme applies to every element.
  • Airwallex updates formatting and layout in the hosted elements.

To see the component, go to the Airwallex demo site.

The Embedded Balance component doesn't support Strong Customer Authentication (SCA). If a regulation such as the second Payment Services Directive (PSD2) applies, build SCA in your own product around the component, for example with two-factor authentication.

Before you begin

Ensure you have the following:

Initialize the SDK

Install the package

Install Airwallex.js:

Shell
1npm install @airwallex/[email protected]

Set up the server for authentication

Obtain an access tokenAPI with your Client ID and API key. Then generate a Proof Key for Code Exchange (PKCE) code_verifier and code_challenge with the S256 method in RFC 7636 Section 4. code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))).

  1. Generate a code_verifier. The length must be an integer from 43 to 128.

    JavaScript
    1const dec2hex = (dec) => ('0' + dec.toString(16)).slice(-2);
    2
    3const generateCodeVerifier = () => {
    4 const length = Math.floor(Math.random() * (128 - 43 + 1)) + 43;
    5 const array = new Uint32Array(Math.ceil(length / 2));
    6 window.crypto.getRandomValues(array);
    7 return Array.from(array, dec2hex).join('');
    8};
    9
    10const codeVerifier = generateCodeVerifier();
  2. Install js-base64, then create the code_challenge from the verifier.

    JavaScript
    1import { Base64 } from 'js-base64';
    2
    3const generateCodeChallenge = async (verifier) => {
    4 const hashed = await window.crypto.subtle.digest(
    5 'SHA-256',
    6 new TextEncoder().encode(verifier),
    7 );
    8 return Base64.fromUint8Array(new Uint8Array(hashed), true);
    9};
    10
    11const codeChallenge = await generateCodeChallenge(codeVerifier);
  3. On your server, call Authorize a connected accountAPI. Pass the connected account's open ID in the x-on-behalf-of header. The open ID is the connected account ID, in the format acct_xxxxxx. For the Embedded Balance component, scope must include both r:awx_action:financial_transactions_view and r:awx_action:balances_view. On the scope name mapping, those are the previous names for financial_transaction:read and balance:read.

    Shell
    1curl -X POST https://api.sandbox.airwallex.com/api/v1/authentication/authorize \
    2 -H 'Content-Type: application/json' \
    3 -H 'Authorization: Bearer {{ACCESS_TOKEN}}' \
    4 -H 'x-on-behalf-of: {{CONNECTED_ACCOUNT_OPEN_ID}}' \
    5 -d '{
    6 "code_challenge": "{{CODE_CHALLENGE}}",
    7 "scope": [
    8 "r:awx_action:financial_transactions_view",
    9 "r:awx_action:balances_view"
    10 ]
    11 }'

    The response includes an authorization_code. Return it to your client as authCode.

Call init

Call init with the authorization code. Use 'sandbox' while you test, and set env to 'prod' when you go live.

JavaScript
1import { init } from '@airwallex/components-sdk';
2
3const sdk = await init({
4 env: 'sandbox', // Set to 'prod' when you go live
5 authCode,
6 codeVerifier,
7 clientId,
8 locale: 'en', // 'zh' for Chinese
9});

Add balance elements to your page

This example mounts the overview. Each other element needs its own container and its own mount call.

  1. Add an empty container for the overview.

    HTML
    1<div id="balance-overview-container"></div>
  2. Create balanceOverview and mount it. The overview renders in #balance-overview-container. Mount each element once.

    JavaScript
    1const { createElement } = sdk;
    2
    3const overview = await createElement('balanceOverview', {
    4 currencies: ['USD', 'EUR', 'GBP'],
    5});
    6overview.mount('balance-overview-container');
  3. Create any other element you want to display, then call mount with that element's container id.

    JavaScript
    1const list = await createElement('balanceList', {
    2 currencies: ['USD', 'EUR', 'GBP'],
    3});
    4const detail = await createElement('balanceDetail', { currency: 'USD' });
    5const info = await createElement('balanceInfo', { currency: 'USD' });
    6const chart = await createElement('balanceHistoryChart', { currency: 'USD' });
    7const scheduledtxn = await createElement('balanceScheduledTransactions', { currency: 'USD' });
    8const activities = await createElement('balanceActivities', { currency: 'USD' });
    9const activityDetails = await createElement('balanceActivityDetails', { transactionId: 'txn_123' });

The following elements are available:

ElementDescriptionOptions
balanceOverviewTotal balance with a multi-currency overview.currencies?: string[]
balanceListList of balances across currencies.currencies?: string[]
balanceDetailSingle-currency balance detail.currency: string
balanceInfoPending, available, and account balances for a currency.currency: string
balanceHistoryChart30-day or 90-day balance history chart.currency: string
balanceActivitiesBalance events that change the balance amount.currency?: string
balanceActivityDetailsDetail view for one activity or transaction.transactionId: string
balanceScheduledTransactionsTransactions scheduled for future settlement.currency: string

Optional: Customize the theme

Every balance element accepts the same theme options through appearance (mode and color variables) and theme.typography (for example, fontFamily).

  1. Define one themeOptions object.

    JavaScript
    1const themeOptions = {
    2 appearance: {
    3 mode: 'light',
    4 variables: {
    5 colorBrand: '#4c4a51',
    6 colorText: '#14171A',
    7 colorBackground: '#ffa3a3',
    8 colorError: '#D91807',
    9 colorSuccess: '#008044',
    10 },
    11 radius: { s: '2px', m: '4px', l: '6px' },
    12 shadows: {
    13 s: '0 2px 12px rgb(20 23 26 / 6%)',
    14 m: '0 4px 24px rgb(20 23 26 / 12%)',
    15 l: '0 4px 32px rgb(20 23 26 / 16%)',
    16 },
    17 space: { s: '16px', m: '24px', l: '32px' },
    18 },
    19 theme: { typography: { fontFamily: '"SF Mono", Menlo, Monaco, monospace' } },
    20};
  2. Pass themeOptions into each createElement call.

    JavaScript
    1const overview = await createElement('balanceOverview', {
    2 currencies: ['USD', 'EUR', 'GBP'],
    3 ...themeOptions,
    4});

Handle element events

Listen with element.on(eventCode, handler). Each element emits the following events:

EventPayloadElementsWhen it fires
readyNoneAllThe balance element is ready.
error{ code?: string; message: string }AllAn error occurs in the element.
balanceSelected{ currency: string }balanceOverview, balanceListThe user selects a balance row.
activitySelected{ transactionId: string }balanceActivitiesThe user selects an activity row.

Use balanceSelected to choose the currency for a single-currency element. Use activitySelected to pass transactionId into balanceActivityDetails.

  1. Add a listener for the events you handle.

    JavaScript
    1overview.on('ready', () => {
    2 // The element has rendered.
    3});
    4overview.on('error', ({ message }) => console.error(message));
    5
    6overview.on('balanceSelected', ({ currency }) => {
    7 console.log('Selected currency:', currency);
    8});
    9
    10activities.on('activitySelected', ({ transactionId }) => {
    11 console.log('Selected transaction:', transactionId);
    12});

Refresh an expired session

The SDK session can expire while an element is mounted. The element then emits error with code: 'TOKEN_EXPIRED'.

  1. Fetch a new authorization code from your server, using the same authorize and PKCE flow as the Initialize the SDK section of this document.

  2. Call init() again with that value as authCode. You don't need to remount the element.

    JavaScript
    1element.on('error', async (err) => {
    2 if (err.code === 'TOKEN_EXPIRED') {
    3 // Fetch a new authorization code with the authorize call and PKCE flow above.
    4 await init({
    5 env: 'sandbox',
    6 authCode,
    7 codeVerifier,
    8 clientId,
    9 locale: 'en',
    10 });
    11 }
    12});

Test your integration

To verify the component in the sandbox environment, complete these steps:

  1. Call init with env set to 'sandbox' and an authorization code for a connected account that has a balance.
  2. Mount balanceOverview and confirm the balances render in the container.
  3. Select a balance row and an activity row. Confirm your handlers receive currency and transactionId.
  4. After the element emits TOKEN_EXPIRED, call init() with a new authorization code and confirm the element renders again without a remount.

Next steps

Now that the component is mounted:

Was this page helpful?