Skip to content
Last updated

Flutter SDK Reference

For the complete documentation index, see llms.txt. Append .md to any page URL for its markdown version.

The Banxa Flutter SDK (banxa_payments_flutter) is the payment execution layer of Banxa Native for Flutter apps. It is the Dart counterpart to the React Native SDK: used alongside the Native API to present the native payment sheet for cards, Apple Pay, and Google Pay, the payment methods that cannot be handled through the API alone due to PCI requirements.

Bank transfers are handled directly by the Native API and do not involve the SDK. For the end-to-end integration walkthrough, see the Integration Guide.


Which parts are relevant for Banxa Native

Your backend still owns orchestration

In a Banxa Native integration your backend handles pricing, identity, and eligibility through the Native API. The SDK's role is limited to payment execution: BanxaPayments.startPayment and the BanxaPayments.checkoutEvents stream.

The SDK also ships catalog, quote, and eligibility helpers that call partner-api v2 under x-api-key. Prefer the equivalent Native API endpoints for those, so that configuration, pricing, and eligibility stay on one HMAC-authenticated surface. The helpers are documented below for completeness.


Requirements

RequirementVersion
Dart>=3.12.2 <4.0.0
Flutter>=3.44.9
iOS15.0 and above
AndroidminSdk 24 and above

The plugin ships iOS and Android implementations only.

You also need a Banxa partner account, which supplies the apiKey and partnerId used to configure the SDK.


Installation

Add the dependency to pubspec.yaml:

dependencies:
  banxa_payments_flutter: 0.1.0

Or from the command line:

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. After flutter pub get, build once with Flutter before opening the iOS project in Xcode:

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. Hosted checkout and Primer may open the camera or photo library for KYC document capture, and those permissions must be declared in the host app.

PlatformDeclare
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.

The SDK is TLS only (https) and does not certificate-pin.


Configuration

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

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

ParameterTypeRequiredDescription
apiKeyStringYesYour Banxa API key from the Partner Dashboard.
partnerIdStringYesYour partner identifier.
environmentBanxaEnvironmentNoDefaults to BanxaEnvironment.sandbox.
applePayMerchantIdentifierString?NoApple Pay merchant identifier, for example merchant.com.example. Primer refuses Apple Pay without it.
applePayMerchantNameString?NoFallback 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:

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.

Environments

BanxaEnvironmentHost
sandboxhttps://api.banxa-sandbox.com
preprodhttps://api.banxa-preprod.com
productionhttps://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.


Starting a payment

startPayment creates the order and launches checkout in one call. It returns a sealed BanxaCheckoutLaunch telling you which route was taken.

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

switch (launch) {
  case BanxaPrimerCheckoutLaunched():
    // Primer 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),
        ),
      ),
    );
}

Launch results

TypeMeaning
BanxaPrimerCheckoutLaunchedThe order returned a nativeToken, the payment method can run on this device, and Primer has been presented. Outcomes arrive on checkoutEvents.
BanxaHostedCheckoutRequiredNo usable native route. Render BanxaHostedCheckoutView with this value. Carries checkoutUrl and the request's redirectUrl.

Both types expose the created order as a CreateOrderResponse.

If the order has neither a usable native route nor a valid Banxa https checkout URL, startPayment throws NativeCheckoutNotEligibleException.

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.

Gate on eligibility before you call startPayment

startPayment does not call any eligibility endpoint. Call POST /eapi/v0/eligibility from your backend and confirm paymentReady: true before invoking the SDK, so you can drive the KYC remediation loop from the requirements[] array.

See Interpreting Eligibility.

The SDK does not accept a quoteId. Use indicative pricing from GET /eapi/v0/price and re-price close to the point of payment to reduce rate drift.

Creating an order without presenting checkout

final order = await BanxaPayments.createOrder(request);

createOrder posts to /buy or /sell and returns the CreateOrderResponse without presenting any checkout UI.

Do not create orders in advance

Banxa checkout URLs must be loaded within about one minute of order creation. Prefer startPayment, which creates and presents in a single call. If you use createOrder, present checkout immediately.


Checkout events

Subscribe to BanxaPayments.checkoutEvents before calling startPayment. The stream carries outcomes from both native Primer checkout and BanxaHostedCheckoutView.

BanxaPayments.checkoutEvents.listen((event) {
  switch (event) {
    case BanxaCheckoutCompleted(:final paymentId, :final orderId, :final status):
      // Success.
    case BanxaCheckoutFailed(:final message):
      // Error.
    case BanxaCheckoutDismissed():
      // Sheet closed.
  }
});
EventFires whenFields
BanxaCheckoutCompletedThe customer completed payment.paymentId, orderId, status, all String?
BanxaCheckoutFailedAny checkout failure.message, a short reason, never a URL
BanxaCheckoutDismissedThe 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 before you credit a customer.


Hosted checkout fallback

When startPayment returns BanxaHostedCheckoutRequired, render BanxaHostedCheckoutView:

BanxaHostedCheckoutView(checkout: launch)
ParameterTypeRequiredDescription
checkoutBanxaHostedCheckoutRequiredYesThe launch result from startPayment.

Behaviour to design around:

  • The view reports on checkoutEvents and never pops itself. Dismiss it 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. Later navigations may leave Banxa for bank or wallet pages during 3DS, but must stay https.
  • JavaScript is enabled for the hosted UI. There is no JavaScript bridge into your app.

Apple Pay and Google Pay

Apple Pay requires a merchant identifier and the matching com.apple.developer.in-app-payments entitlement, added in Xcode under Signing & Capabilities, Apple Pay.

await BanxaPayments.configure(
  const BanxaConfig(
    apiKey: 'YOUR_API_KEY',
    partnerId: 'your-partner-id',
    applePayMerchantIdentifier: 'merchant.com.yourcompany.yourapp',
    applePayMerchantName: 'Your Store',
  ),
);

startPayment returns BanxaHostedCheckoutRequired instead of presenting Primer when:

  • No applePayMerchantIdentifier is configured.
  • The app is running on the iOS simulator. Apple Pay does not run on the simulator.
  • The device has no usable card in Wallet.
  • The request is for Apple Pay on Android.
  • Google Play services are missing, for Google Pay.

See Apple Pay and Google Pay for platform setup.


Models

CreateOrderRequest

FieldTypeRequiredDescription
orderTypeOrderTypeYesOrderType.buy or OrderType.sell. Selects POST /buy or POST /sell.
cryptoStringYesCrypto asset symbol, for example ETH.
fiatStringYesFiat currency code, for example EUR.
fiatAmountStringYesFiat amount as a string.
walletAddressStringYesDestination wallet address.
emailStringYesCustomer email address.
redirectUrlStringYesURL the customer returns to after hosted checkout.
idString?NoYour order id.
paymentMethodIdString?NoBanxa payment method slug, for example apple-pay. Required in practice: native checkout is skipped without it.
blockchainString?NoExplicit blockchain network.
cryptoAmountString?NoCrypto amount when ordering by crypto value.
walletAddressTagString?NoTag or memo for chains that require it.
subPartnerIdString?NoSub-partner identifier.
metadataString?NoOpaque metadata string.
externalCustomerIdString?NoYour customer identifier. Pass the same value you use as identityReference.
externalOrderIdString?NoYour order reference.
discountCodeString?NoPromotion 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

FieldTypeNotes
idStringBanxa order id.
checkoutUrlString?Hosted checkout URL. Drives the BanxaHostedCheckoutRequired route.
nativeTokenString?Primer client token. Present when native checkout is available.
statusString?Order status at creation.
paymentMethodIdString?Catalog slug. Numeric wire values are stringified.
paymentMethodTypeString?Payment method type.
orderTypeString?buy or sell.
fiat, fiatAmount, crypto, cryptoAmount, blockchainString?Echoed order amounts and assets.
walletAddress, walletAddressTagString?Echoed destination.
externalCustomerId, externalOrderIdString?Echoed partner references.
createdAt, updatedAtString?Timestamps.

Catalog and quote helpers

These call partner-api v2 under x-api-key and require a configured SDK. In a Banxa Native integration, prefer the equivalent Native API endpoints so that configuration and pricing stay on the HMAC-authenticated surface.

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',
);
MethodEndpointReturns
fetchCountries()GET /countriesList<Country>
fetchFiats({required orderType})GET /fiats/{buy|sell}List<Fiat>
fetchCrypto({required orderType})GET /crypto/{buy|sell}List<Crypto>
fetchPaymentMethods({required orderType, fiat})GET /payment-methods/{buy|sell}List<PaymentMethod>
fetchQuotes({required orderType, required request})GET /quotes/{buy|sell}List<Quote>

Catalog types

TypeFields
Countryid, description, states: List<CountryState>
CountryStateid, description
Fiatid, description?, symbol?, supportedPaymentMethods: List<FiatPaymentMethod>
FiatPaymentMethodid, name?, minimum?, maximum?
Cryptoid, description?, blockchains: List<Blockchain>
Blockchainid, description?, isDefaultBlockchain?, address?, network?, minimum?, unsupportedCountries: Map<String, List<String>>
PaymentMethodid, 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

final quotes = await BanxaPayments.fetchQuotes(
  orderType: OrderType.buy,
  request: const QuoteRequest(
    paymentMethodId: 'debit-credit-card',
    crypto: 'ETH',
    blockchain: 'ETH',
    fiat: 'AUD',
    fiatAmount: '200',
  ),
);
QuoteRequest fieldTypeRequiredNotes
paymentMethodIdStringYesPayment method slug.
cryptoStringYesCrypto asset symbol.
blockchainStringYesBlockchain network.
fiatStringYesFiat currency code.
fiatAmountString?ConditionalProvide this or cryptoAmount.
cryptoAmountString?ConditionalProvide this or fiatAmount.
externalCustomerIdString?NoYour customer identifier.
ipAddressString?NoCustomer IP for regional pricing.
discountCodeString?NoPromotion or discount code.
Quote fieldTypeNotes
paymentMethodIdString?Payment method the quote applies to.
cryptoAmountString?Crypto the customer receives.
fiatAmountString?Fiat the customer spends.
processingFeeString?Banxa processing fee in fiat.
networkFeeString?Blockchain network fee in fiat.
discountQuoteDiscount?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.


Eligibility helper

final eligibility = await BanxaPayments.checkEligibility(request);
if (eligibility.paymentReady == true) {
  await BanxaPayments.startPayment(request);
}

checkEligibility posts the same body as createOrder to partner-api POST /eligibility and returns:

EligibilityResponse fieldTypeNotes
idString?Eligibility check identifier.
paymentReadybool?Whether the customer can pay now.
kycRequirementsList<String>Outstanding KYC requirement identifiers.
messageString?Human-readable detail.

This is opt-in, and startPayment never calls it. For a Banxa Native integration, use POST /eapi/v0/eligibility from your backend instead: it returns the full requirements[] dictionary you need to drive remediation, on the HMAC-authenticated surface. See Interpreting Eligibility.


Primer session updates

await BanxaPayments.updatePrimerSession(
  primerToken: 'PRIMER_CLIENT_TOKEN',
  savedCard: false,
);

updatePrimerSession posts to /primer/session for mid-flow session patches, and accepts an optional additionalFields map of String values. Most integrations do not need it.


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.

ExceptionCause
SdkNotConfiguredExceptionA method was called before BanxaPayments.configure.
MissingCredentialsExceptionapiKey or partnerId was blank. Exposes fields.
UnauthorizedExceptionHTTP 401. Check the key matches the environment.
ValidationExceptionHTTP 400 or 422, or local field validation. Exposes errors: List<FieldError>, each with field and messages.
ServerExceptionAny other non-success HTTP status. Exposes statusCode.
NetworkExceptionTransport failure, including TLS errors and timeouts.
CheckoutFailedExceptionNative Primer presentation failed.
NativeCheckoutNotEligibleExceptionThe order has no usable native Primer path and no valid hosted checkoutUrl.
UnknownExceptionUnexpected local failure, for example a JSON decode error.
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.


Not available in the SDK

CapabilityUse instead
Identity creation, KYC sharing, document sharingNative API. See Identity and KYC
The full eligibility requirements[] dictionaryPOST /eapi/v0/eligibility from your backend
Locked pricing (quoteId)Bank transfer ramps only. See Quotes and Pricing
Order lookup and reconciliationWebhooks and the Native API

Google Pay approval

When your Android app presents Google Pay, Google requires app-level approval before you can process live payments. Banxa's own approval does not cover payments presented inside your app. Start the approval process early: it is the most common cause of a delayed Android launch. See the Google Pay guide.


Payment methods and supported fiats

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

Payment method idSupported fiats
debit-credit-cardAED, 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
google-payAUD, EUR, USD
apple-payAUD, EUR, GBP, USD
payid-bank-transferAUD
pixBRL
zar-bank-transferZAR
pseCOP
khipuCLP
interac-bank-transferCAD
klarna-paynowEUR
ideal-bank-transferAUD, EUR
sepa-bank-transferEUR
gbp-bank-transferGBP
speiMXN

Next steps