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


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. It is TLS only (https) and does not certificate-pin.


Installation

dependencies:
  banxa_payments_flutter: 0.1.0
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:

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.

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.


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

A blank apiKey or partnerId throws MissingCredentialsException.

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.


Method summary

MethodEndpointReturns
configure(config, {httpClient})Future<void>
checkoutEventsStream<BanxaCheckoutEvent>
fetchCountries()GET /countriesFuture<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 /eligibilityFuture<EligibilityResponse>
createOrder(request)POST /buy or POST /sellFuture<CreateOrderResponse>
startPayment(request)POST /buy or POST /sell, then checkoutFuture<BanxaCheckoutLaunch>
updatePrimerSession({required primerToken, required savedCard, additionalFields})POST /primer/sessionFuture<void>

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


startPayment

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():
    // 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.

TypeMeaningExtra fields
BanxaPrimerCheckoutLaunchedThe native payment sheet has been presented.
BanxaHostedCheckoutRequiredCheckout 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.

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 or Order Lookup before you credit a customer.


BanxaHostedCheckoutView

BanxaHostedCheckoutView(checkout: launch)
ParameterTypeRequiredDescription
checkoutBanxaHostedCheckoutRequiredYesThe 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

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 stable customer identifier. Banxa uses it to recognise returning customers.
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.

EligibilityResponse

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

Catalog and quote helpers

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

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.


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
Order lookup and reconciliationOrder Lookup and Webhooks
Full order list across all customersGET /{partnerRef}/v2/orders via API integration
KYC data sharing (Sumsub token share)API integration. 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 idSupported fiats
debit-credit-cardAED, ARS, AUD, 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-payAUD, EUR, GBP, USD
google-payAUD, EUR, USD
payid-bank-transferAUD
zar-bank-transferZAR
pseCOP
khipuCLP
interac-bank-transferCAD
klarna-paynowEUR
ideal-bank-transferAUD, EUR
sepa-bank-transferEUR
gbp-bank-transferGBP
speiMXN

For the full list, see Supported Payment Methods.


Next steps