> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gr4vy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Airwallex - Card

> Connect to Airwallex to accept credit and debit card payments.

export const connector = {
  displayName: "Airwallex",
  method: "card",
  features: "create_transaction deep_linking delayed_capture digital_wallets network_tokens_default open_loop partial_capture partial_refunds redirect_requires_popup refunds requires_webhook_setup three_d_secure_hosted three_d_secure_pass_through transaction_sync verify_credentials void zero_auth",
  supportedCountries: "AT AU BE BG BR CA CH CN CR CY CZ DE DK EE ES FI FJ FR GB GF GI GP GR HK HR HU ID IE IL IM IN IS IT JP KR KY LI LT LU LV MF MH MO MQ MT MX MY NL NO NZ PH PL PR PT RE RO SC SE SG SI SK TH TW US VG VI VN WS YT",
  supportedCurrencies: "AED ALL AMD AOA ARS AUD AWG AZN BAM BBD BDT BHD BIF BMD BND BOB BRL BSD BTN BWP BZD CAD CHF CLP CNY COP CRC CVE CZK DJF DKK DOP DZD EGP ERN ETB EUR FJD FKP GBP GEL GHS GIP GMD GNF GTQ GYD HKD HNL HUF IDR ILS INR ISK JMD JOD JPY KES KGS KHR KMF KRW KWD KYD KZT LAK LBP LKR LRD LSL LYD MAD MDL MGA MKD MNT MOP MUR MVR MWK MXN MYR NAD NGN NIO NOK NPR NZD OMR PAB PEN PGK PHP PKR PLN PYG QAR RON RSD RWF SAR SBD SCR SEK SGD SHP SLE SRD SVC SZL THB TJS TMT TND TOP TRY TTD TWD TZS UAH UGX USD UYU UZS VND VUV WST XAF XCD XOF XPF ZAR ZMW"
};

export const ConnectorRegions = ({data, kind, name: nameOverride}) => {
  const [query, setQuery] = useState("");
  const [open, setOpen] = useState(false);
  const isCountries = kind === "countries";
  const raw = data && (isCountries ? data.supportedCountries : data.supportedCurrencies);
  const codes = typeof raw === "string" ? raw.split(/\s+/).filter(Boolean) : Array.isArray(raw) ? raw : [];
  const DISPLAY_NAME_OVERRIDES = {
    authorizenet: "Authorize.net",
    cardpointe: "Fiserv CardPointe",
    dlocal: "dLocal",
    shift4i4go: "Shift4 i4go",
    tokenex: "TokenEx"
  };
  const rawName = data && data.displayName || "";
  const name = nameOverride || DISPLAY_NAME_OVERRIDES[rawName.toLowerCase()] || rawName || "This connector";
  const verb = isCountries ? "supports transactions from buyers in" : "supports processing payments in";
  const noun = isCountries ? "countries" : "currencies";
  if (codes.length === 0) return null;
  let displayNames = null;
  try {
    displayNames = new Intl.DisplayNames(["en"], {
      type: isCountries ? "region" : "currency"
    });
  } catch (e) {
    displayNames = null;
  }
  const resolve = code => {
    if (!displayNames) return null;
    try {
      const resolved = displayNames.of(code);
      return resolved && resolved !== code ? resolved : null;
    } catch (e) {
      return null;
    }
  };
  const MAJOR_CURRENCIES = ["USD", "EUR", "GBP", "CAD", "AUD", "JPY", "CHF", "CNY", "SGD", "HKD", "NZD", "SEK", "NOK", "DKK", "MXN", "BRL", "INR"];
  const items = codes.map(code => ({
    code,
    label: resolve(code)
  }));
  if (isCountries) {
    items.sort((a, b) => (a.label || a.code).localeCompare(b.label || b.code));
  } else {
    const rank = code => {
      const i = MAJOR_CURRENCIES.indexOf(code);
      return i === -1 ? MAJOR_CURRENCIES.length : i;
    };
    items.sort((a, b) => rank(a.code) - rank(b.code) || a.code.localeCompare(b.code));
  }
  if (codes.length <= 3) {
    const parts = items.map(it => isCountries || !it.label ? it.label || it.code : `${it.label} (${it.code})`);
    const joined = parts.length === 1 ? parts[0] : parts.length === 2 ? `${parts[0]} and ${parts[1]}` : `${parts.slice(0, -1).join(", ")}, and ${parts[parts.length - 1]}`;
    return <p>
        {name} {verb} {joined}.
      </p>;
  }
  const chipStyle = {
    display: "inline-flex",
    alignItems: "baseline",
    gap: "0.4rem",
    padding: "0.15rem 0.55rem",
    borderRadius: "0.375rem",
    border: "1px solid rgba(128, 128, 128, 0.25)",
    fontSize: "0.875rem",
    lineHeight: 1.5
  };
  const codeStyle = {
    fontFamily: "var(--font-mono, ui-monospace, monospace)",
    fontWeight: 600,
    fontSize: "0.8125rem"
  };
  const controlStyle = {
    color: "inherit",
    background: "transparent",
    border: "1px solid rgba(128, 128, 128, 0.3)",
    borderRadius: "0.5rem",
    fontSize: "0.875rem"
  };
  const renderChip = it => <span key={it.code} style={chipStyle} title={isCountries ? it.code : it.label || it.code}>
      {isCountries ? it.label || it.code : <span style={codeStyle}>{it.code}</span>}
      {!isCountries && it.label ? <span style={{
    opacity: 0.7
  }}>{it.label}</span> : null}
    </span>;
  const PREVIEW = 5;
  const collapsible = items.length > PREVIEW;
  const q = query.trim().toLowerCase();
  const filtered = q ? items.filter(it => it.code.toLowerCase().includes(q) || it.label && it.label.toLowerCase().includes(q)) : items;
  const expanded = open || q !== "";
  const visible = !collapsible ? items : expanded ? filtered : items.slice(0, PREVIEW);
  const toggle = () => {
    const next = !open;
    setOpen(next);
    if (!next) setQuery("");
  };
  return <div>
      <p>
        {name} {verb} the following {codes.length} {noun}:
      </p>

      {collapsible ? <input type="text" value={query} onChange={e => setQuery(e.target.value)} placeholder={`Filter ${noun}…`} aria-label={`Filter ${noun}`} style={{
    ...controlStyle,
    display: "block",
    width: "100%",
    maxWidth: "22rem",
    padding: "0.4rem 0.7rem",
    margin: "0 0 0.75rem"
  }} /> : null}

      <div style={{
    display: "flex",
    flexWrap: "wrap",
    gap: "0.4rem"
  }}>
        {visible.map(renderChip)}
      </div>

      {q && filtered.length === 0 ? <p style={{
    opacity: 0.7,
    marginTop: "0.6rem"
  }}>
          No {noun} match “{query.trim()}”.
        </p> : null}
      {q && filtered.length > 0 ? <p style={{
    opacity: 0.6,
    fontSize: "0.8125rem",
    marginTop: "0.6rem"
  }}>
          Showing {filtered.length} of {items.length}.
        </p> : null}

      {collapsible && !q ? <button type="button" aria-expanded={open} onClick={toggle} style={{
    ...controlStyle,
    display: "inline-flex",
    alignItems: "center",
    gap: "0.4rem",
    padding: "0.35rem 0.75rem",
    marginTop: "0.75rem",
    cursor: "pointer"
  }}>
          <span aria-hidden="true" style={{
    display: "inline-block",
    transform: open ? "rotate(90deg)" : "none",
    transition: "transform 0.15s ease"
  }}>
            ›
          </span>
          {open ? "Show fewer" : `and ${items.length - PREVIEW} more`}
        </button> : null}
    </div>;
};

export const ConnectorCapabilities = ({data}) => {
  const CAPABILITIES = [{
    keys: ["three_d_secure_hosted"],
    label: "3-D Secure (hosted)",
    description: "Gr4vy-hosted 3DS authentication flow.",
    cardOnly: true
  }, {
    keys: ["three_d_secure_pass_through"],
    label: "3-D Secure (pass-through)",
    description: "Pass through 3DS data authenticated by a third party.",
    cardOnly: true
  }, {
    keys: ["partial_authorization"],
    label: "Partial authorization",
    description: "Support partial approval responses."
  }, {
    keys: ["zero_auth"],
    label: "Zero auth",
    description: "Verify a card without charging it."
  }, {
    keys: ["void"],
    label: "Void",
    description: "Cancel an authorized transaction before capture."
  }, {
    keys: ["direct_capture"],
    label: "Direct capture",
    description: "Capture a payment immediately at authorization.",
    hideWhenUnsupported: true
  }, {
    keys: ["delayed_capture"],
    label: "Delayed capture",
    description: "Authorize a payment and capture it at a later time."
  }, {
    keys: ["partial_capture"],
    label: "Partial capture",
    description: "Capture a portion of the authorized amount."
  }, {
    keys: ["over_capture"],
    label: "Over capture",
    description: "Capture more than the originally authorized amount."
  }, {
    keys: ["refunds"],
    label: "Refunds",
    description: "Refund a captured payment."
  }, {
    keys: ["partial_refunds"],
    label: "Partial refunds",
    description: "Refund a portion of the captured amount."
  }, {
    keys: ["settlement_reporting"],
    label: "Settlement reporting",
    description: "Automatic settlement and reconciliation reporting."
  }, {
    keys: ["create_session"],
    label: "Create session",
    description: "Create a connector session for client-side flows."
  }, {
    keys: ["network_tokens_default", "network_tokens_toggle"],
    label: "Network tokens",
    description: "Network-level tokenization for improved approval rates.",
    cardOnly: true
  }, {
    keys: ["digital_wallets"],
    label: "Digital wallets",
    description: "Apple Pay, Google Pay, and other wallet integrations."
  }, {
    keys: ["payment_method_tokenization", "payment_method_tokenization_toggle"],
    label: "Payment method tokenization",
    description: "Store payment methods outside of transactions."
  }, {
    keys: ["transaction_sync"],
    label: "Transaction sync",
    description: "Synchronize transaction state from the connector."
  }, {
    keys: ["create_token"],
    label: "Tokenization",
    description: "Create a token from card details collected via Secure Fields.",
    hideWhenUnsupported: true
  }, {
    keys: ["delete_token"],
    label: "Delete token",
    description: "Delete a stored token.",
    hideWhenUnsupported: true
  }, {
    keys: ["verify_credentials"],
    label: "Verify credentials",
    description: "Validate the configured credentials against the connector.",
    hideWhenUnsupported: true
  }];
  const raw = data && data.features;
  const enabled = typeof raw === "string" ? new Set(raw.split(/\s+/).filter(Boolean)) : Array.isArray(raw) ? new Set(raw) : new Set(Object.keys(raw || ({})).filter(key => raw[key]));
  const isOn = entry => entry.keys.some(key => enabled.has(key));
  const isNonCard = data && data.method && data.method !== "card";
  const renderGroup = (title, entries, supported) => {
    if (entries.length === 0) return null;
    const mark = supported ? "✓" : "✕";
    const markColor = supported ? "#16a34a" : "#9ca3af";
    return <div style={{
      marginTop: "1rem"
    }}>
        <div style={{
      fontSize: "0.75rem",
      fontWeight: 600,
      letterSpacing: "0.05em",
      textTransform: "uppercase",
      opacity: 0.6,
      marginBottom: "0.25rem"
    }}>
          {title}
        </div>
        {}
        <div role="list">
          {entries.map(entry => <div role="listitem" key={entry.label} style={{
      display: "flex",
      gap: "0.5rem",
      alignItems: "baseline",
      padding: "0.3rem 0",
      opacity: supported ? 1 : 0.7
    }}>
              <span aria-hidden="true" style={{
      color: markColor,
      fontWeight: 700,
      flexShrink: 0
    }}>
                {mark}
              </span>
              <span>
                <strong>{entry.label}</strong>
                {entry.description ? <span style={{
      opacity: 0.85
    }}> — {entry.description}</span> : null}
              </span>
            </div>)}
        </div>
      </div>;
  };
  const visible = isNonCard ? CAPABILITIES.filter(entry => !entry.cardOnly) : CAPABILITIES;
  const supported = visible.filter(isOn);
  const unsupported = visible.filter(entry => !isOn(entry) && !entry.hideWhenUnsupported);
  return <div>
      {renderGroup("Supported", supported, true)}
      {renderGroup("Not supported", unsupported, false)}
    </div>;
};

Airwallex is a global financial platform that provides cross-border payment processing for businesses of all sizes. The platform supports card payments with features like delayed capture, partial capture, partial refunds, 3-D Secure authentication, network tokenization, and digital wallet integration across multiple currencies and countries.

## Setup

Airwallex offers self-service sign-up for production and demo (sandbox) accounts:

* [Airwallex sign-up](https://www.airwallex.com/signup)
* [Airwallex demo environment](https://demo.airwallex.com/)

## Credentials

When setting up Airwallex in the dashboard, configure the following credentials. The Client ID and API Key can be found in the Airwallex web app under **Developer** -> **API keys**, and the Webhook Secret is generated under **Developer** -> **Webhooks**.

* **Client ID** - The unique identifier for your Airwallex account.
* **API Key** - A secret API key used to authenticate requests. Treat this value as a password.
* **Webhook Secret** (optional) - The signing secret used to validate webhook payloads. Generate this when creating a webhook endpoint in the Airwallex web app.

Airwallex recommends creating a [scoped API key](https://www.airwallex.com/docs/developer-tools/api/manage-api-keys#scoped-api-keys) limited to the payment resources required by this integration rather than reusing an administrator key across multiple systems.

## Connector configuration

After setting up your Airwallex connector in the dashboard, configure how transactions are routed to it. Choose one of the following options:

* **Using Flow** - Configure Airwallex as the target connector in [Flow](/guides/dashboard/flow/overview) to automatically route card transactions to this connector
* **Using the API** - Explicitly set the `payment_service_id` parameter to the Airwallex connector ID when creating transactions. This overrides any Flow routing rules.

The connector ID can be found in the dashboard under **Connections** -> **Configured connections**.

## Capabilities

<ConnectorCapabilities data={connector} />

## Supported countries

<ConnectorRegions data={connector} kind="countries" />

## Supported currencies

<ConnectorRegions data={connector} kind="currencies" />

## Integration

To accept card payments with Airwallex, use one of Gr4vy's client-side integration methods to securely collect card details. Due to PCI compliance requirements, card data should never be sent directly to your servers.

You can integrate using:

* **[Embed](/guides/payments/embed/quick-start)** - A pre-built, customizable payment form that handles the complete payment flow
* **[Secure Fields](/guides/payments/secure-fields/quick-start)** - Embed card input fields for building custom payment forms while maintaining PCI compliance
* **[Mobile SDKs](/guides/get-started#sdks-and-plugins)** - Native SDKs for iOS, Android, React Native, and other platforms

These methods handle card data collection and tokenization. Once the card details are collected and tokenized, create a transaction through the Gr4vy API, which automatically routes the payment to your configured Airwallex connection based on your Flow rules or explicit `payment_service_id` parameter.

### Sending billing information

Airwallex strongly recommends that merchants pass enhanced risk data fields to improve the performance of the risk engine. For more details on how Airwallex leverages a wide range of signals to safeguard transactions, see [Enhance fraud protection with comprehensive payment data](https://www.airwallex.com/docs/payments__native-api__enhance-fraud-protection-with-comprehensive-payment-data).

Billing details on the `buyer` object can be sent using either an [inline guest buyer](/reference/transactions/new-transaction#body-buyer-one-of-0) or a [stored buyer reference](/reference/transactions/new-transaction#body-buyer-id) when [creating a transaction](/reference/transactions/new-transaction).

### Device fingerprinting

A key integration requirement for all Gr4vy merchants using Airwallex is to load the Airwallex Risk JavaScript on the checkout page and forward the resulting device data to Airwallex via Gr4vy's API.

1. **Load the Airwallex Risk JavaScript and collect device data.** Follow Step 1 and Step 2 in the [Airwallex device fingerprinting integration guide](https://www.airwallex.com/docs/payments__native-api__device-fingerprinting) to load the script on your checkout page and collect device data. The device data is stored in the order session ID via `data-order-session-id="ORDER-SESSION-ID"`.
2. **Pass the collected device data to Airwallex** when [creating a transaction](/reference/transactions/new-transaction) using the [`anti_fraud_fingerprint`](/reference/transactions/new-transaction#body-anti-fraud-fingerprint) field.

### External 3-D Secure and risk engines

To enable an external 3-D Secure provider or external risk engine for Airwallex — including Gr4vy-provided 3DS or risk tools — contact your Airwallex Account Manager for support.

## Webhooks

Airwallex uses webhooks to deliver asynchronous status updates for payment intents and refunds. Register a notification URL and select the events to receive in the Airwallex web app under **Developer** -> **Webhooks** to keep transaction and refund statuses in sync.

The generated signing secret is optional for connector setup, but Gr4vy recommends adding it to the **Webhook Secret** field on your connector so Gr4vy can validate webhook signatures.

## Testing

For testing in the Airwallex demo environment, you can use standard test card numbers. Airwallex supports industry-standard test cards for simulating different transaction scenarios:

**Successful transactions:**

* Visa: `4111 1111 1111 1111`
* Mastercard: `5555 5555 5555 4444`
* American Express: `3782 822463 10005`

Use any future expiry date and any 3-digit CVV (4 digits for American Express).

For more test scenarios and additional card numbers, refer to the [Airwallex testing documentation](https://www.airwallex.com/docs/payments__native-api__test-card-numbers) or contact your Airwallex representative.
