# Troubleshooting

Diagnose and resolve common Aeropay setup, verification, bank-linking, and transaction issues.

## Error handling

Aeropay failures are reported through different paths:

| Failure type | Where to handle it |
|  --- | --- |
| Invalid component or SDK configuration | Catch the exception thrown by `pxpSdk.create()`. |
| Recognised shopper, user, OTP provider, bank account, or Aerosync error | Handle `onError`. |
| Unexpected Aeropay result shape | The component can show generic inline feedback without calling `onError`. |
| Transaction submission failure | Handle `onSubmitError`. |
| Shopper uses the popup close button | Handle `onCancel`. Cancellation isn't an error. |
| Inline field or OTP format error | The component displays the message and prevents progression. |


Use all three error paths in your integration:

```typescript
try {
  const aeropayButton = pxpSdk.create('aeropay-button', {
    onCancel: () => {
      resetCheckoutState();
    },
    onPreAuthorisation: () => true,
    onPostAuthorisation: (result) => {
      const submitResult = result as {
        merchantTransactionId: string;
        systemTransactionId: string;
      };
      verifyPaymentOnBackend({
        merchantTransactionId: submitResult.merchantTransactionId,
        systemTransactionId: submitResult.systemTransactionId
      });
    },
    onSubmitError: (error) => {
      console.error('Aeropay submission failed', error);
      showPaymentError('Unable to complete the transaction.');
    },
    onError: (error) => {
      console.error('Aeropay flow failed', {
        errorCode: error.ErrorCode,
        message: error.message
      });
      showPaymentError('Unable to continue with Aeropay.');
    }
  });

  aeropayButton.mount('aeropay-button-container');
} catch (error) {
  console.error('Aeropay configuration failed', error);
  showAlternativePaymentMethods();
}
```

Don't show raw provider or SDK messages to shoppers. Map failures to approved user-facing messages and keep technical details in restricted logs.

## Error code reference

### Setup and configuration

These SDK errors occur when creating or mounting the component:

| Error code | Meaning | Check |
|  --- | --- | --- |
| `SDK0106` | Unsupported Aeropay intent. | Use `Authorisation`, `Purchase`, `EstimatedAuthorisation`, or `Payout`. |
| `SDK0113` | Aeropay funding configuration is missing. | Confirm that the session contains non-empty `externalMerchantId` and `configurationId`. |
| `SDK0114` | Unsupported entry type. | Set `transactionData.entryType` to `Ecom`. |
| `SDK0115` | Aeropay intent is missing. | Set `transactionData.intent.aeropay`. |
| `SDK0116` | Unsupported currency. | Set `transactionData.currency` to `USD`. |
| `SDK0201` | Mount container wasn't found. | Add the container to the DOM before calling `mount()` and check its ID. |


### Aeropay flow

These errors can occur after the shopper starts the Aeropay flow:

| Error code | Meaning | Check |
|  --- | --- | --- |
| `SDK1300` | Shopper data is invalid. | Inspect `error.details` and correct values returned by `onGetShopper`. |
| `SDK1301` | Aeropay user creation failed. | Check consumer values, provider details, credentials, and network status. |
| `SDK1302` | User verification or aggregator-credential lookup failed. | Identify the active screen and inspect restricted error details. This code is used by both operations. |
| `SDK1303` | Aerosync returned an invalid payload. | Retry bank linking and inspect the widget result without logging bank data. |
| `SDK1304` | Aerosync failed. | Check network access, environment configuration, and the provider response. |
| `SDK1305` | Linking the bank account failed. | Retry linking and check PXP or Aeropay service status. |
| `SDK1306` | Retrieving bank accounts failed. | Check the user, provider response, session, and network. |
| `SDK1307` | The Aeropay user isn't active. | Remove the stored ID and offer the new-shopper flow. |
| `SDK1308` | Retrieving the Aeropay user failed. | Check the stored ID, session, network, and provider availability. |
| `SDK1309` | The Aeropay user wasn't found. | Remove the stored ID and offer the new-shopper flow. |
| `SDK1310` | A truthy non-string `userId` was supplied at runtime. | Pass a string. Falsy values, including an empty string, don't produce this error and start the new-shopper flow. |
| `SDK1311` | Aeropay transaction failure recorded for analytics. | Don't expect this code in the normal `onSubmitError` callback. An ordinary failed transaction response passes a `FailedSubmitResult` to that callback. |


The component creates `SDK1311` to emit `ComponentError` analytics after an ordinary failed transaction response. It doesn't pass that exception to `onSubmitError`.

Network failures can also produce `SDK0500`. Determine whether the failure occurred before transaction submission or during submission so you can handle it through the correct callback.

## Collect safe diagnostic information

Record identifiers and configuration presence without logging credentials or personal data:

```typescript
function logAeropayDiagnostics({
  session,
  transactionData,
  environment
}) {
  const aeropay = session.allowedFundingTypes?.payByBanks?.aeropay;

  console.log('Aeropay diagnostics', {
    environment,
    sessionId: session.sessionId,
    hasExternalMerchantId: Boolean(aeropay?.externalMerchantId),
    hasConfigurationId: Boolean(aeropay?.configurationId),
    currency: transactionData.currency,
    entryType: transactionData.entryType,
    intent: transactionData.intent?.aeropay,
    merchantTransactionId: transactionData.merchantTransactionId
  });
}
```

Don't log `hmacKey`, `encryptionKey`, PXP tokens, Aeropay credentials, shopper details, OTPs, user IDs, or bank account metadata.

If a PXP token or Aeropay secret may have been exposed:

1. Revoke or replace the affected credential.
2. Update the backend secret store and redeploy the affected service.
3. Review logs, analytics, screenshots, and support records for further exposure.
4. Retest session creation and the Aeropay flow with the replacement credential.


## Portal and session setup

### Aeropay isn't available in site services

The likely cause is that Aeropay isn't enabled at merchant group level or your account isn't entitled to use the service.

Resolve the issue:

1. Go to **Merchant setup > Merchant groups** in the Unity Portal.
2. Select the merchant group and open the **Services** tab.
3. Add or enable *Aeropay service*.
4. Return to the site and configure the service.


See [Aeropay onboarding](/guides/checkout/components/web/aeropay/onboarding) for the complete steps.

### Aeropay is missing from the session

The session doesn't contain `allowedFundingTypes.payByBanks.aeropay`.

Check that:

1. The session request uses the correct merchant and site.
2. Aeropay is active for that site.
3. Merchant ID, API key, API secret, and configuration ID are saved.
4. The session was created after the portal configuration was saved.
5. `transactionMethod.intent.aeropay` contains a supported intent.


Create a new session after correcting the service.

### `SDK0113` during component creation

Inspect the session response:

```typescript
const aeropay =
  sessionResult.session.allowedFundingTypes?.payByBanks?.aeropay;

console.log({
  hasExternalMerchantId: Boolean(aeropay?.externalMerchantId),
  hasConfigurationId: Boolean(aeropay?.configurationId)
});
```

If `externalMerchantId` is missing, check the Aeropay Merchant ID in the site configuration. If `configurationId` is missing, check the Aerosync Configuration ID.

## Button and popup issues

### Button doesn't render

Check the following causes:

* `pxpSdk.create()` threw an exception that wasn't caught.
* The mount container doesn't exist yet.
* The container ID passed to `mount()` doesn't match the DOM.
* The component is disabled or hidden by application state.
* Custom CSS hides the container or button.
* A route change unmounted the component.


Mount only after the container exists:

```html
<div id="aeropay-button-container"></div>
```

```typescript
const aeropayButton = pxpSdk.create('aeropay-button', {
  onPreAuthorisation: () => true
});

aeropayButton.mount('aeropay-button-container');
```

If the container is missing, `mount()` throws `SDK0201`.

### Clicking the button does nothing

Check whether:

* The button is disabled.
* A popup is already open.
* A previous start operation is still loading.
* `onGetShopper` is waiting indefinitely.
* Invalid shopper data caused `SDK1300`.
* A returning-user lookup is still running or failed.


Add `onClick` and `onError` logging, then check the browser network panel for stalled requests.

### Popup closes unexpectedly

The popup close button calls `onCancel`. Pressing Escape currently hides the popup without calling `onCancel`. In either case, restore the checkout so the shopper can continue or restart the flow.

If neither close action explains the outcome, check for:

* A route change or component unmount.
* Application code removing the container.
* An unhandled exception.
* Session or network failure.


## Shopper data issues

### `SDK1300` before the popup opens

The SDK validates non-empty fields returned by `onGetShopper`. Inspect `error.details`:

```typescript
onError: (error) => {
  if (error.ErrorCode === 'SDK1300') {
    console.error('Invalid shopper fields', error.details);
    showPaymentError('Check your contact details and try again.');
  }
}
```

Apply these formats:

| Field | Required format |
|  --- | --- |
| `firstName` | Unicode letters and spaces, up to 100 characters |
| `lastName` | Unicode letters and spaces, up to 100 characters |
| `email` | A valid address containing `@` and a domain dot, up to 128 characters |
| `phoneNumber` | `+1` followed by exactly 10 digits |


Empty or omitted values are collected in the popup. Invalid non-empty values stop the popup from opening.

### Prefilled fields can't be edited

This is the default behaviour. Add the fields to `consumerDataCollectionConfig.editableFields`:

```typescript
consumerDataCollectionConfig: {
  editableFields: ['email', 'phoneNumber']
}
```

### Consumer data screen appears unexpectedly

If you set `skipConsumerDataCollection: true`, check that:

* `onGetShopper` returns all four required fields.
* Every field is non-empty and valid.
* `consumerDataCollectionConfig.editableFields` is empty or omitted.


Missing or empty values, or a non-empty `editableFields` configuration, cause the component to show the consumer data collection screen. Invalid non-empty values supplied by `onGetShopper` invoke `onError` with `SDK1300` and prevent the popup from opening.

### Valid customer name is rejected

The current validator accepts Unicode letters and spaces only. Apostrophes, hyphens, digits, and other punctuation fail validation.

If legitimate customer names can't pass the field, capture the example without personal data and contact PXP support.

## OTP issues

### OTP isn't accepted

The UI removes non-digit characters and enables **Verify code** only after all six-digit cells contain a value. If the shopper can't continue, check for an incomplete code. Empty or malformed-input messages are defensive validation and aren't normally shown through standard typing, pasting, or button use.

If a six-digit code fails:

1. Confirm that the shopper is using the most recent code.
2. Check whether the code expired.
3. Check `onError` for `SDK1302`.
4. Inspect the network request and provider status.
5. Let the shopper resend after the countdown.


### Resend is disabled

The resend action remains disabled during its 59-second countdown and while a resend request is in progress.

If both resend and verification remain disabled, Aeropay may have reported the maximum number of attempts (`providerResponseCode` `AP112`). Close the flow and follow your approved recovery process.

### Verification fails with `SDK1302`

`SDK1302` is used for both OTP verification and aggregator-credential lookup. Use the active screen to identify the operation:

* On the OTP screen, check the code, attempt limit, and verification response.
* On the bank-linking screen, check the Aerosync credential request and configuration ID.


Don't infer the failing operation from the error code alone.

## Returning-shopper issues

### Bank selection doesn't open

If you supplied `userId`, check `onError`:

| Error  | Recovery |
|  --- | --- |
| `SDK1307` | User isn't active. Remove the stored ID and start a new-shopper flow. |
| `SDK1308` | Lookup failed. Check network and provider status before deciding whether to retry. |
| `SDK1309` | User wasn't found. Remove the stored ID and start a new-shopper flow. |
| `SDK1310` | Validate that a truthy `userId` is a string from the correct customer record. Falsy values start the new-shopper flow rather than producing `SDK1310`. |


If `userId` is falsy, expect consumer data collection instead of direct bank selection.

Don't automatically fall back to another customer's ID or accept an ID supplied by the browser.

## Bank selection and Aerosync issues

### Bank list is empty

An empty list can mean that the shopper has no linked accounts or that retrieval failed.

Check whether:

* The UI shows the expected empty state without `onError`.
* `onError` received `SDK1306`.
* The Aeropay user is active.
* The request used the expected UAT or production environment.
* Aeropay or PXP reported an outage.


For a payment, let the shopper link an account. For a payout, bank linking is hidden unless `allowLinkBankOnPayout` is `true`.

### Aerosync doesn't open

Check that:

* `configurationId` is present in the session.
* Aggregator credentials were retrieved successfully.
* Browser content-security or network controls don't block required provider resources.
* Another link-bank operation isn't already loading.
* The component hasn't been unmounted.


`SDK1302` can indicate failed aggregator credentials. `SDK1304` indicates an Aerosync widget failure.

### Linked account doesn't appear

The widget can complete before PXP finishes linking and refreshing the bank list.

Wait for the loading state to finish. If the account still doesn't appear, check for:

* `SDK1303` for an invalid widget result.
* `SDK1305` for a link-account failure.
* `SDK1306` when refreshing bank accounts.
* A connection ID missing from the provider result.


Don't ask the shopper to repeat linking until you know whether the first attempt completed.

## Transaction issues

### Confirming the bank doesn't submit a transaction

The most common cause is `onPreAuthorisation`.

The SDK submits only when the callback returns a truthy value:

```typescript
onPreAuthorisation: async () => {
  const result = await validateOrderOnBackend();
  return result.approved;
}
```

Check that:

* The callback is configured.
* Every code path returns `true` or `false`.
* An awaited backend request resolves.
* Validation intentionally approves the current transaction.
* The callback doesn't throw.


If the callback is omitted or returns `undefined`, the SDK stops submission.

### `onSubmitError` runs

Ordinary failed transaction responses pass a `FailedSubmitResult` to `onSubmitError`. They don't pass the analytics-only `SDK1311` exception. Inspect the payload shape before reading fields:

```typescript
onSubmitError: (error) => {
  if (
    typeof error === 'object' &&
    error !== null &&
    'errorCode' in error
  ) {
    console.error('PXP submission failure', {
      errorCode: error.errorCode,
      correlationId: error.correlationId,
      httpStatusCode: error.httpStatusCode
    });
  } else {
    console.error('SDK or network submission failure', error);
  }

  showPaymentError('Unable to complete the transaction.');
}
```

For failed PXP responses, record `errorCode`, `correlationId`, and `httpStatusCode`. For network failures, check connectivity, session validity, and whether the request reached PXP.

Before retrying, retrieve the transaction from your backend using the existing `merchantTransactionId`. This prevents duplicate payments or payouts when the browser didn't receive the original response.

### `onPostAuthorisation` runs but the order isn't confirmed

This normally means backend verification failed, is still pending, or found a mismatched transaction.

Check that your backend:

1. Uses the transaction identifiers from the callback only to retrieve the authoritative result.
2. Uses valid PXP API credentials.
3. Compares merchant, amount, currency, intent, and transaction state.
4. Correlates the transaction with the order or payout stored on your backend.
5. Returns a clear pending or failed state to the frontend.


Don't bypass backend verification to resolve the issue.

### Payment or payout may have been submitted twice

Stop automatic retries and retrieve every transaction associated with the merchant transaction identifier.

Check for:

* Repeated button clicks.
* Multiple mounted components.
* A retry after a network timeout without checking transaction status.
* Different `merchantTransactionId` values for the same order or payout.
* Backend endpoints that don't enforce idempotency or duplicate protection.


Resolve the transaction state before allowing another attempt.

## Analytics issues

### Expected event isn't emitted

Check event-specific conditions:

* `PreAuthorisation` requires `onPreAuthorisation`.
* `PostAuthorisation` requires `onPostAuthorisation` and successful submission.
* Aerosync events occur only during bank linking.
* User verification success doesn't have a dedicated SDK analytics event. Track it from `onUserVerificationSuccess`.
* Using the popup close button appears as `ComponentInteraction` with `interactionType: "Close"` and calls `onCancel`. Pressing Escape currently hides the popup without this callback.


Log `event.eventName` temporarily to inspect the sequence. Remove verbose logging before production.