# Flutter SDK Reference

> For the complete documentation index, see [llms.txt](https://docs.banxa.com/llms.txt). Append `.md` to any page URL for its markdown version.


The Banxa Flutter SDK (`banxa_payments_flutter`) is a Dart interface to Banxa Hosted Checkout for Flutter apps. You configure it once and start a payment with a single call. The SDK creates the order, presents the native payment sheet when the order supports it, and otherwise hands you a WebView widget to render for hosted checkout. Outcomes from both routes arrive on one stream.

For the end-to-end walkthrough, see the [Flutter SDK Integration Guide](/products/hosted-checkout/docs/sdk-integration/flutter-sdk-guide).

## Requirements

| Requirement | Version |
|  --- | --- |
| Dart | `>=3.12.2 <4.0.0` |
| Flutter | `>=3.44.9` |
| iOS | 15.0 and above |
| Android | minSdk 24 and above |


The plugin ships iOS and Android implementations only. It is TLS only (`https`) and does not certificate-pin.

## Installation

```yaml
dependencies:
  banxa_payments_flutter: 0.1.0
```

```sh
flutter pub add banxa_payments_flutter:0.1.0
```

Pin the exact version
Pin `0.1.0`. Do not use a caret range: this is a preview release and Android native checkout is still on a Primer Checkout beta pin (`3.0.0-beta.2`; iOS is on `2.49.0`).

Do not depend on `banxa_payments_flutter_ios`, `banxa_payments_flutter_android`, or `banxa_payments_flutter_platform_interface` directly. Only the umbrella package is a supported dependency.

### iOS deployment target

Set the iOS deployment target to 15.0 in Xcode, then build once with Flutter before opening the iOS project:

```sh
flutter build ios --config-only
```

Skipping this can leave Xcode showing a 13.0 minimum, and plugin resolution fails.

Primer adds native binary size. Measure with `flutter build appbundle --analyze-size` and an iOS archive if binary size matters for your app.

### Permissions

The plugin merges `INTERNET` on Android. Checkout may open the camera or photo library for KYC document capture, and those permissions must be declared in the **host app**.

| Platform | Declare |
|  --- | --- |
| iOS (`Info.plist`) | `NSCameraUsageDescription`, `NSPhotoLibraryUsageDescription`, and `NSMicrophoneUsageDescription` if you enable video KYC |
| Android (`AndroidManifest.xml`) | `android.permission.CAMERA` |


Missing iOS usage strings crash the app when capture starts.

## Configuration

Call `configure` once per process, before any other SDK method. Re-calling it replaces the HTTP client and rebinds checkout listeners.

```dart
import 'package:banxa_payments_flutter/banxa_payments_flutter.dart';

await BanxaPayments.configure(
  const BanxaConfig(
    apiKey: 'YOUR_API_KEY',
    partnerId: 'your-partner-id',
    environment: BanxaEnvironment.sandbox,
  ),
);
```

### BanxaConfig

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `apiKey` | `String` | Yes | Your v2 API key from the Partner Dashboard. |
| `partnerId` | `String` | Yes | Your partner identifier. |
| `environment` | `BanxaEnvironment` | No | Defaults to `BanxaEnvironment.sandbox`. |
| `applePayMerchantIdentifier` | `String?` | No | Apple Pay merchant identifier, for example `merchant.com.example`. Primer refuses Apple Pay without it. |
| `applePayMerchantName` | `String?` | No | Fallback name for the Apple Pay sheet, used only when the Primer client session does not carry a merchant name. |


`configure` also takes an optional `httpClient`:

```dart
await BanxaPayments.configure(config, httpClient: myClient);
```

All partner-api traffic is issued from Dart, so it appears in the DevTools network view and can be intercepted, logged, or mocked with your own `http.Client`.

A blank `apiKey` or `partnerId` throws `MissingCredentialsException`.

### Environments

| `BanxaEnvironment` | Host |
|  --- | --- |
| `sandbox` | `https://api.banxa-sandbox.com` |
| `preprod` | `https://api.banxa-preprod.com` |
| `production` | `https://api.banxa.com` |


Requests go to `{host}/{partnerId}/v2` with `x-api-key` and `Content-Type: application/json`. Sandbox and production credentials are separate and are not interchangeable.

## Method summary

| Method | Endpoint | Returns |
|  --- | --- | --- |
| `configure(config, {httpClient})` | — | `Future<void>` |
| `checkoutEvents` | — | `Stream<BanxaCheckoutEvent>` |
| `fetchCountries()` | `GET /countries` | `Future<List<Country>>` |
| `fetchFiats({required orderType})` | `GET /fiats/{buy|sell}` | `Future<List<Fiat>>` |
| `fetchCrypto({required orderType})` | `GET /crypto/{buy|sell}` | `Future<List<Crypto>>` |
| `fetchPaymentMethods({required orderType, fiat})` | `GET /payment-methods/{buy|sell}` | `Future<List<PaymentMethod>>` |
| `fetchQuotes({required orderType, required request})` | `GET /quotes/{buy|sell}` | `Future<List<Quote>>` |
| `checkEligibility(request)` | `POST /eligibility` | `Future<EligibilityResponse>` |
| `createOrder(request)` | `POST /buy` or `POST /sell` | `Future<CreateOrderResponse>` |
| `startPayment(request)` | `POST /buy` or `POST /sell`, then checkout | `Future<BanxaCheckoutLaunch>` |
| `updatePrimerSession({required primerToken, required savedCard, additionalFields})` | `POST /primer/session` | `Future<void>` |


Every method is static on `BanxaPayments`. Calling any of them before `configure` throws `SdkNotConfiguredException`.

## startPayment

```dart
final launch = await BanxaPayments.startPayment(
  const CreateOrderRequest(
    orderType: OrderType.buy,
    crypto: 'ETH',
    fiat: 'EUR',
    fiatAmount: '40',
    walletAddress: '0x0000000000000000000000000000000000000000',
    email: 'user@example.com',
    redirectUrl: 'https://example.com/redirect',
    paymentMethodId: 'debit-credit-card',
    blockchain: 'ETH',
  ),
);

switch (launch) {
  case BanxaPrimerCheckoutLaunched():
    // Native sheet is on screen. Wait for checkoutEvents.
  case BanxaHostedCheckoutRequired():
    Navigator.of(context).push(
      MaterialPageRoute<void>(
        builder: (_) => Scaffold(
          appBar: AppBar(title: const Text('Checkout')),
          body: BanxaHostedCheckoutView(checkout: launch),
        ),
      ),
    );
}
```

`startPayment` creates the order, then presents the native payment sheet when the order carries a `nativeToken` and the requested method can run on the device. Otherwise it returns `BanxaHostedCheckoutRequired`.

### BanxaCheckoutLaunch

`BanxaCheckoutLaunch` is a sealed class. Both variants expose the created `order` as a `CreateOrderResponse`.

| Type | Meaning | Extra fields |
|  --- | --- | --- |
| `BanxaPrimerCheckoutLaunched` | The native payment sheet has been presented. | — |
| `BanxaHostedCheckoutRequired` | Checkout must run in a WebView. | `checkoutUrl`, `redirectUrl` |


`checkoutUrl` is always `https` on a Banxa-owned host.

On iOS, Primer presents a single payment method (`showPaymentMethod`). On Android, Primer Checkout 3.x presents the methods from the client session; the requested Banxa method is still recorded natively.

`startPayment` throws `NativeCheckoutNotEligibleException` when there is neither a usable native route nor a valid Banxa `https` checkout URL.

Do not create orders in advance
Banxa checkout URLs must be loaded within about one minute of order creation. `startPayment` creates and presents in a single call. `createOrder` exists for creating an order without presenting checkout, but there is no supported pattern for creating an order early and presenting it later.

`startPayment` does not call `POST /eligibility`. Use `checkEligibility` explicitly if you want to gate checkout yourself.

## Checkout events

`BanxaPayments.checkoutEvents` is a `Stream<BanxaCheckoutEvent>` carrying outcomes from both the native payment sheet and `BanxaHostedCheckoutView`. Subscribe before calling `startPayment`.

```dart
BanxaPayments.checkoutEvents.listen((event) {
  switch (event) {
    case BanxaCheckoutCompleted(:final paymentId, :final orderId, :final status):
      // Success.
    case BanxaCheckoutFailed(:final message):
      // Error.
    case BanxaCheckoutDismissed():
      // Sheet closed.
  }
});
```

| Event | Fires when | Fields |
|  --- | --- | --- |
| `BanxaCheckoutCompleted` | The customer completed payment. | `paymentId`, `orderId`, `status`, all `String?` |
| `BanxaCheckoutFailed` | Any checkout failure. | `message`, a short reason, never a URL |
| `BanxaCheckoutDismissed` | The customer closed checkout, or the hosted WebView was disposed before a terminal URL. | none |


`status` may be null on Android. Primer may emit `dismissed` after `completed` or `failed`, so treat the events as distinct rather than mutually exclusive.

Completion identifiers are read from Primer or from hosted-checkout query parameters (`paymentId`, `orderId`, `status`, plus `payment_id` and `order_id` aliases). Raw URLs are never surfaced to your app.

Checkout events are a UI signal, not the authoritative order state. Confirm the final state from [webhooks](/products/hosted-checkout/docs/transaction-lifecycle/webhooks) or [Order Lookup](/products/hosted-checkout/docs/transaction-lifecycle/order-lookup) before you credit a customer.

## BanxaHostedCheckoutView

```dart
BanxaHostedCheckoutView(checkout: launch)
```

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `checkout` | `BanxaHostedCheckoutRequired` | Yes | The launch result from `startPayment`. |


Behaviour to design around:

- The widget reports on `checkoutEvents` and never pops itself. Dismiss the route yourself when a terminal event arrives.
- Disposing it before a terminal URL emits `BanxaCheckoutDismissed`.
- A first page that has not loaded within 30 seconds emits `BanxaCheckoutFailed`.
- The first URL must be `https` on a Banxa-owned host: `banxa.com`, `banxa-sandbox.com`, or `banxa-preprod.com`, including subdomains. An initial URL outside that set emits `BanxaCheckoutFailed`.
- Later navigations may leave Banxa for bank or wallet pages during 3DS, but must stay `https`. Non-`https` navigations are blocked.
- JavaScript is enabled for the hosted UI. There is no JavaScript bridge into your app.


## Models

### CreateOrderRequest

| Field | Type | Required | Description |
|  --- | --- | --- | --- |
| `orderType` | `OrderType` | Yes | `OrderType.buy` or `OrderType.sell`. Selects `POST /buy` or `POST /sell`. |
| `crypto` | `String` | Yes | Crypto asset symbol, for example `ETH`. |
| `fiat` | `String` | Yes | Fiat currency code, for example `EUR`. |
| `fiatAmount` | `String` | Yes | Fiat amount as a string. |
| `walletAddress` | `String` | Yes | Destination wallet address. |
| `email` | `String` | Yes | Customer email address. |
| `redirectUrl` | `String` | Yes | URL the customer returns to after hosted checkout. |
| `id` | `String?` | No | Your order id. |
| `paymentMethodId` | `String?` | No | Banxa payment method slug, for example `apple-pay`. Required in practice: native checkout is skipped without it. |
| `blockchain` | `String?` | No | Explicit blockchain network. |
| `cryptoAmount` | `String?` | No | Crypto amount when ordering by crypto value. |
| `walletAddressTag` | `String?` | No | Tag or memo for chains that require it. |
| `subPartnerId` | `String?` | No | Sub-partner identifier. |
| `metadata` | `String?` | No | Opaque metadata string. |
| `externalCustomerId` | `String?` | No | Your stable customer identifier. Banxa uses it to recognise returning customers. |
| `externalOrderId` | `String?` | No | Your order reference. |
| `discountCode` | `String?` | No | Promotion or discount code. |


`crypto`, `fiat`, `fiatAmount`, `walletAddress`, `email`, and `redirectUrl` are validated locally before the request is sent. A blank value throws `ValidationException` with a `FieldError` per missing field.

A missing memo can permanently lose funds
XRP, XLM, EOS, and ATOM require a memo or tag. Pass it as `walletAddressTag`. An on-ramp to one of these chains without the memo can be unrecoverable.

### CreateOrderResponse

| Field | Type | Notes |
|  --- | --- | --- |
| `id` | `String` | Banxa order id. |
| `checkoutUrl` | `String?` | Hosted checkout URL. Drives the `BanxaHostedCheckoutRequired` route. |
| `nativeToken` | `String?` | Primer client token. Present when native checkout is available. |
| `status` | `String?` | Order status at creation. |
| `paymentMethodId` | `String?` | Catalog slug. Numeric wire values are stringified. |
| `paymentMethodType` | `String?` | Payment method type. |
| `orderType` | `String?` | `buy` or `sell`. |
| `fiat`, `fiatAmount`, `crypto`, `cryptoAmount`, `blockchain` | `String?` | Echoed order amounts and assets. |
| `walletAddress`, `walletAddressTag` | `String?` | Echoed destination. |
| `externalCustomerId`, `externalOrderId` | `String?` | Echoed partner references. |
| `createdAt`, `updatedAt` | `String?` | Timestamps. |


### EligibilityResponse

| Field | Type | Notes |
|  --- | --- | --- |
| `id` | `String?` | Eligibility check identifier. |
| `paymentReady` | `bool?` | Whether the customer can pay now. |
| `kycRequirements` | `List<String>` | Outstanding KYC requirement identifiers. |
| `message` | `String?` | Human-readable detail. |


## Catalog and quote helpers

```dart
final countries = await BanxaPayments.fetchCountries();
final fiats = await BanxaPayments.fetchFiats(orderType: OrderType.buy);
final cryptos = await BanxaPayments.fetchCrypto(orderType: OrderType.buy);
final methods = await BanxaPayments.fetchPaymentMethods(
  orderType: OrderType.buy,
  fiat: 'USD',
);
```

### Catalog types

| Type | Fields |
|  --- | --- |
| `Country` | `id`, `description`, `states: List<CountryState>` |
| `CountryState` | `id`, `description` |
| `Fiat` | `id`, `description?`, `symbol?`, `supportedPaymentMethods: List<FiatPaymentMethod>` |
| `FiatPaymentMethod` | `id`, `name?`, `minimum?`, `maximum?` |
| `Crypto` | `id`, `description?`, `blockchains: List<Blockchain>` |
| `Blockchain` | `id`, `description?`, `isDefaultBlockchain?`, `address?`, `network?`, `minimum?`, `unsupportedCountries: Map<String, List<String>>` |
| `PaymentMethod` | `id`, `name?`, `description?`, `supportedFiats: List<String>` |


In `unsupportedCountries`, the key is a country code and the value is the restricted state codes. An empty list means the whole country is restricted.

### Quotes

```dart
final quotes = await BanxaPayments.fetchQuotes(
  orderType: OrderType.buy,
  request: const QuoteRequest(
    paymentMethodId: 'debit-credit-card',
    crypto: 'ETH',
    blockchain: 'ETH',
    fiat: 'AUD',
    fiatAmount: '200',
  ),
);
```

| `QuoteRequest` field | Type | Required | Notes |
|  --- | --- | --- | --- |
| `paymentMethodId` | `String` | Yes | Payment method slug. |
| `crypto` | `String` | Yes | Crypto asset symbol. |
| `blockchain` | `String` | Yes | Blockchain network. |
| `fiat` | `String` | Yes | Fiat currency code. |
| `fiatAmount` | `String?` | Conditional | Provide this or `cryptoAmount`. |
| `cryptoAmount` | `String?` | Conditional | Provide this or `fiatAmount`. |
| `externalCustomerId` | `String?` | No | Your customer identifier. |
| `ipAddress` | `String?` | No | Customer IP for regional pricing. |
| `discountCode` | `String?` | No | Promotion or discount code. |


| `Quote` field | Type | Notes |
|  --- | --- | --- |
| `paymentMethodId` | `String?` | Payment method the quote applies to. |
| `cryptoAmount` | `String?` | Crypto the customer receives. |
| `fiatAmount` | `String?` | Fiat the customer spends. |
| `processingFee` | `String?` | Banxa processing fee in fiat. |
| `networkFee` | `String?` | Blockchain network fee in fiat. |
| `discount` | `QuoteDiscount?` | Present when a discount code was applied. Carries `discountCode` and `originalQuote: QuoteOriginalAmounts?` with `originalFiatAmount`, `originalCryptoAmount`, `originalProcessingFee`, `originalNetworkFee`. |


`fetchQuotes` returns a list because Banxa returns multiple entries when discount codes apply. Quotes carry no quote id and are not accepted by `startPayment`. Do not cache them: call immediately before showing a price.

## Error handling

Every SDK failure is a subclass of the sealed `BanxaPaymentsException`, which exposes a `message`. Catch the sealed type and switch on the subclass.

| Exception | Cause |
|  --- | --- |
| `SdkNotConfiguredException` | A method was called before `BanxaPayments.configure`. |
| `MissingCredentialsException` | `apiKey` or `partnerId` was blank. Exposes `fields`. |
| `UnauthorizedException` | HTTP 401. Check the key matches the environment. |
| `ValidationException` | HTTP 400 or 422, or local field validation. Exposes `errors: List<FieldError>`, each with `field` and `messages`. |
| `ServerException` | Any other non-success HTTP status. Exposes `statusCode`. |
| `NetworkException` | Transport failure, including TLS errors and timeouts. |
| `CheckoutFailedException` | Native Primer presentation failed. |
| `NativeCheckoutNotEligibleException` | The order has no usable native Primer path and no valid hosted `checkoutUrl`. |
| `UnknownException` | Unexpected local failure, for example a JSON decode error. |


```dart
try {
  await BanxaPayments.startPayment(request);
} on ValidationException catch (e) {
  for (final error in e.errors) {
    debugPrint('${error.field}: ${error.messages.join(', ')}');
  }
} on BanxaPaymentsException catch (e) {
  debugPrint(e.message);
}
```

Show user-facing messages only from validated error fields. Do not expose raw error strings that may include internal detail. See [Error Codes](/products/hosted-checkout/docs/reference/error-codes).

## Not available in the SDK

| Capability | Use instead |
|  --- | --- |
| Order lookup and reconciliation | [Order Lookup](/products/hosted-checkout/docs/transaction-lifecycle/order-lookup) and [Webhooks](/products/hosted-checkout/docs/transaction-lifecycle/webhooks) |
| Full order list across all customers | `GET /{partnerRef}/v2/orders` via [API integration](/products/hosted-checkout/docs/api-integration/api-integration-overview) |
| KYC data sharing (Sumsub token share) | [API integration](/products/hosted-checkout/docs/api-integration/api-integration-overview). Uses HMAC auth, which the SDK does not use |
| Locked pricing (`quoteId`) | Not supported. Quotes are indicative |


## Payment methods and supported fiats

Availability varies by partner, region, and order type. Treat `fetchPaymentMethods` as the source of truth. The table below is a static reference.

| Payment method id | Supported fiats |
|  --- | --- |
| `debit-credit-card` | `AED`, `ARS`, `AUD`, `BRL`, `CAD`, `CHF`, `CZK`, `DKK`, `EUR`, `GBP`, `HKD`, `IDR`, `INR`, `JPY`, `KRW`, `MXN`, `MYR`, `NGN`, `NOK`, `NZD`, `PHP`, `PLN`, `QAR`, `RUB`, `SAR`, `SEK`, `SGD`, `THB`, `TRY`, `TWD`, `USD`, `VND`, `ZAR` |
| `apple-pay` | `AUD`, `EUR`, `GBP`, `USD` |
| `google-pay` | `AUD`, `EUR`, `USD` |
| `payid-bank-transfer` | `AUD` |
| `pix` | `BRL` |
| `zar-bank-transfer` | `ZAR` |
| `pse` | `COP` |
| `khipu` | `CLP` |
| `interac-bank-transfer` | `CAD` |
| `klarna-paynow` | `EUR` |
| `ideal-bank-transfer` | `AUD`, `EUR` |
| `sepa-bank-transfer` | `EUR` |
| `gbp-bank-transfer` | `GBP` |
| `spei` | `MXN` |


For the full list, see [Supported Payment Methods](/products/hosted-checkout/docs/reference/supported-payment-methods).

## Next steps

- [Flutter SDK Integration Guide](/products/hosted-checkout/docs/sdk-integration/flutter-sdk-guide): end-to-end walkthrough.
- [Webhooks](/products/hosted-checkout/docs/transaction-lifecycle/webhooks): configure webhook notifications.
- [Order Statuses](/products/hosted-checkout/docs/transaction-lifecycle/order-statuses): full status reference.