{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-products/hosted-checkout/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["admonition"]},"type":"markdown"},"seo":{"title":"Flutter SDK Integration Guide","description":"Official Banxa API documentation – on-ramp and off-ramp transfers with identity verification and compliance.","llmstxt":{"title":"Banxa Developer Documentation","description":"Integrate crypto-fiat exchange with Banxa's licensed infrastructure: payments, KYC, compliance, and settlement handled. 150+ countries, 45 global licences.","details":{"content":"Two integration products, chosen by how much of the payment experience the partner owns: **Banxa Native API** for payments that happen inside the partner's product (headless bank transfers, HMAC server-to-server; card, Apple Pay and Google Pay on a native sheet via SDK, no Banxa-hosted screens); **Banxa Hosted Checkout** for a Banxa-run payment flow with the broadest payment method coverage (three paths: Referral URL, API, or SDK). Either product can run KYC on either side. Both use the same sandbox and production environments at `https://api.banxa-sandbox.com` and `https://api.banxa.com`.\n\n## When to use Banxa\n\nReach for Banxa when a product needs to let people **buy crypto with fiat or sell crypto for fiat** without the partner holding money-transmitter or VASP licences. Banxa is the licensed counterparty: it takes the payment, runs compliance, and delivers the asset. Typical fits are wallets, exchanges, dApps, gaming platforms and fintech apps adding an on-ramp or off-ramp.\n\nPick the product by how much of the payment experience the partner wants to own. KYC is configurable on both and does not decide it.\n\n- **Banxa Native API** - the payment experience lives inside the partner's product. Bank transfers run completely headless through the API; card, Apple Pay and Google Pay run on a native payment sheet through a Banxa SDK, with no redirect or webview. Needs a backend. Days to weeks to integrate.\n- **Banxa Hosted Checkout** - Banxa runs the payment flow, reached by redirect, embedded iFrame, or SDK. Broadest payment method coverage. No backend required for the referral path. Hours to days to integrate.\n- **Alternative payment methods** (PayPal, iDEAL, Klarna, PIX, VietQR and local methods) are not supported headless. Native integrations offer them by creating those orders through Hosted Checkout, so most Native integrations use both products.\n- **Legacy API** - existing integrations only. Do not start here.\n\nBanxa is not the right tool for crypto-to-crypto swaps, custody or wallet infrastructure, or for any flow where the partner wants to settle funds itself.\n\n## Constraints\n\n- **Authentication**: HMAC credentials must be stored server-side only, never expose in frontend, mobile, or client-side code. HMAC is required for all Native API calls and for KYC sharing in Hosted Checkout; `x-api-key` is used for all other Hosted Checkout endpoints.\n- **`externalCustomerId`**: Required on every buy and sell order. Use a stable opaque identifier, never PII.\n- **`identityReference`**: Must remain constant for the same user across all requests. Must not contain PII. If you attempt to create an identity for an email that already exists, you will receive a 422 / code 81, so retrieve the existing record rather than retrying creation. Use `GET /eapi/v0/identities/{identityReference}?email=user@example.com` to look up the real `identityReference` linked to an email.\n- **`quoteId`**: Only supported by `POST /eapi/v0/ramps` (bank transfer). The React Native SDK and Embedded Payment Button do not accept a `quoteId`, so use indicative pricing (`GET /eapi/v0/price`) for SDK and Embedded Payment Button flows.\n- **Quotes**: Indicative prices are not rate-locked, so refresh close to order creation to minimise rate drift. Locked quotes (`GET /eapi/v0/quote`) expire after approximately 3 minutes and are only valid for bank transfer ramp creation.\n- **Eligibility gate**: Never create a ramp or invoke the SDK when `paymentReady` is `false`. Always check eligibility and satisfy all requirements before payment execution.\n- **Webhooks**: Verify all inbound webhook signatures with HMAC-SHA256 before processing. Return HTTP 200 immediately and process asynchronously.\n- **Product selection**: Choose by payment experience, not by KYC — both products support partner-run or Banxa-run KYC. Banxa Native is for payments rendered inside the partner's product; Banxa Hosted Checkout is for a Banxa-run payment flow. They are separate surfaces with separate credentials and base paths, so never substitute one product's endpoint for the other's. Combining them is expected in one case only: alternative payment methods are not supported headless, so a Native integration creates those specific orders through Hosted Checkout.\n- **SDK scope**: The React Native SDK has no `banxa.customerIdentity` module. Identity and KYC are handled through the Native API only. `primerCallbacks` and `primerSettings` are Native context only and must not be referenced in Hosted Checkout integrations.\n- **Payment method naming**: Never use \"eAPI\" or \"EAPI\" as a product name. The correct name is \"Banxa Native API\". The URL path prefix `/eapi/` is correct and should not be changed.\n"}}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"flutter-sdk-integration-guide","__idx":0},"children":["Flutter SDK Integration Guide"]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For the complete documentation index, see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://docs.banxa.com/llms.txt"},"children":["llms.txt"]},". Append ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".md"]}," to any page URL for its markdown version."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This guide walks through a complete Banxa Hosted Checkout integration in a Flutter app using the Banxa Flutter SDK (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["banxa_payments_flutter"]},"). By the end you will have a working buy flow: configure the SDK once, start a payment with a single call, and confirm the final order status from your backend."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The Flutter SDK is a hybrid of the other mobile SDKs. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["startPayment"]}," presents the native payment sheet when the order supports it, and hands you a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BanxaHostedCheckoutView"]}," widget to render when it does not. You control where that widget sits in your navigation stack."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For the full API surface, see the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/hosted-checkout/docs/sdk-integration/flutter-sdk-reference"},"children":["Flutter SDK Reference"]},"."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"before-you-start","__idx":1},"children":["Before you start"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"prerequisites","__idx":2},"children":["Prerequisites"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A Flutter app on Flutter ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[">=3.44.9"]}," and Dart ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[">=3.12.2 <4.0.0"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["iOS 15.0 and above, Android minSdk 24 and above. The plugin ships iOS and Android implementations only."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Your Banxa partner reference and API key. Use sandbox for development, production after approval."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A configured webhook endpoint. Optional but recommended for order status tracking."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"install-the-sdk","__idx":3},"children":["Install the SDK"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"yaml","header":{"controls":{"copy":{}}},"source":"dependencies:\n  banxa_payments_flutter: 0.1.0\n","lang":"yaml"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"sh","header":{"controls":{"copy":{}}},"source":"flutter pub add banxa_payments_flutter:0.1.0\n","lang":"sh"},"children":[]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"warning","name":"Pin the exact version"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Pin ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["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 (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["3.0.0-beta.2"]},"; iOS is on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["2.49.0"]},")."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Do not depend on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["banxa_payments_flutter_ios"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["banxa_payments_flutter_android"]},", or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["banxa_payments_flutter_platform_interface"]}," directly."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"platform-setup","__idx":4},"children":["Platform setup"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Set the iOS deployment target to 15.0 in Xcode, then build once with Flutter before opening the iOS project:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"sh","header":{"controls":{"copy":{}}},"source":"flutter build ios --config-only\n","lang":"sh"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Without this, Xcode can still show a 13.0 minimum and fail to resolve the plugin."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Declare camera and photo library permissions in the host app, because KYC document capture runs inside checkout:"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Platform"},"children":["Platform"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Declare"},"children":["Declare"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["iOS (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Info.plist"]},")"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["NSCameraUsageDescription"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["NSPhotoLibraryUsageDescription"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["NSMicrophoneUsageDescription"]}," if your flow includes liveness capture"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Android (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["AndroidManifest.xml"]},")"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["android.permission.CAMERA"]}]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Missing iOS usage strings crash the app when capture starts."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-1-configure-the-sdk","__idx":5},"children":["Step 1: Configure the SDK"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Configure once at app launch. Any SDK method called before ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["configure"]}," throws ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SdkNotConfiguredException"]},"."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"dart","header":{"controls":{"copy":{}}},"source":"import 'package:banxa_payments_flutter/banxa_payments_flutter.dart';\n\nawait BanxaPayments.configure(\n  const BanxaConfig(\n    apiKey: 'YOUR_API_KEY',\n    partnerId: 'your-partner-id',\n    environment: BanxaEnvironment.sandbox,\n  ),\n);\n","lang":"dart"},"children":[]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Field"},"children":["Field"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["apiKey"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Your v2 API key from the Partner Dashboard."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["partnerId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Your partner identifier."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["environment"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BanxaEnvironment.sandbox"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".preprod"]},", or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".production"]},". Credentials are not interchangeable."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Requests go to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{host}/{partnerId}/v2"]}," with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["x-api-key"]},". All partner-api traffic is issued from Dart, so it shows up in the DevTools network view. Pass your own ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["http.Client"]}," as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["httpClient"]}," if you need to intercept or log it."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-2-subscribe-to-checkout-events","__idx":6},"children":["Step 2: Subscribe to checkout events"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Subscribe before you start a payment. This one stream carries outcomes from both the native payment sheet and hosted checkout, so you only handle results in one place."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"dart","header":{"controls":{"copy":{}}},"source":"late final StreamSubscription<BanxaCheckoutEvent> _sub;\n\n@override\nvoid initState() {\n  super.initState();\n  _sub = BanxaPayments.checkoutEvents.listen((event) {\n    switch (event) {\n      case BanxaCheckoutCompleted(:final paymentId, :final orderId):\n        showProcessingState();\n      case BanxaCheckoutFailed(:final message):\n        showRetry(message);\n      case BanxaCheckoutDismissed():\n        returnToAmountEntry();\n    }\n  });\n}\n\n@override\nvoid dispose() {\n  _sub.cancel();\n  super.dispose();\n}\n","lang":"dart"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BanxaCheckoutCompleted"]}," is a UI signal, not the authoritative order state. Do not credit the customer on it."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Primer may emit ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dismissed"]}," after ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["completed"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["failed"]},", so treat the events as distinct rather than mutually exclusive, and guard against handling a terminal outcome twice."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-3-get-a-quote","__idx":7},"children":["Step 3: Get a quote"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Fetch live pricing before you show an amount to the customer. Call this close to when the price is displayed, because crypto rates move quickly and quotes are indicative."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"dart","header":{"controls":{"copy":{}}},"source":"final quotes = await BanxaPayments.fetchQuotes(\n  orderType: OrderType.buy,\n  request: const QuoteRequest(\n    paymentMethodId: 'debit-credit-card',\n    crypto: 'ETH',\n    blockchain: 'ETH',\n    fiat: 'USD',\n    fiatAmount: '200',\n  ),\n);\n\ndebugPrint('Receive: ${quotes.first.cryptoAmount ?? \"-\"}');\ndebugPrint('Processing fee: ${quotes.first.processingFee ?? \"-\"}');\ndebugPrint('Network fee: ${quotes.first.networkFee ?? \"-\"}');\n","lang":"dart"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Provide either ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fiatAmount"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["cryptoAmount"]},". Quotes carry no quote id, so there is nothing to pass into the payment call."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetchQuotes"]}," always returns a list, because Banxa returns multiple entries when discount codes apply."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To populate your own currency and payment method pickers, use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetchCountries"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetchFiats"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetchCrypto"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetchPaymentMethods"]},". See the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/hosted-checkout/docs/sdk-integration/flutter-sdk-reference#catalog-and-quote-helpers"},"children":["Flutter SDK Reference"]},"."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-4-start-the-payment","__idx":8},"children":["Step 4: Start the payment"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Build a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CreateOrderRequest"]}," and call ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["startPayment"]}," when the customer confirms. It creates the order and launches checkout in one call, returning which route was taken."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"dart","header":{"controls":{"copy":{}}},"source":"final launch = await BanxaPayments.startPayment(\n  const CreateOrderRequest(\n    orderType: OrderType.buy,\n    crypto: 'ETH',\n    fiat: 'EUR',\n    fiatAmount: '40',\n    walletAddress: '0x0000000000000000000000000000000000000000',\n    email: 'user@example.com',\n    redirectUrl: 'https://example.com/redirect',\n    paymentMethodId: 'debit-credit-card',\n    blockchain: 'ETH',\n  ),\n);\n","lang":"dart"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"required-fields","__idx":9},"children":["Required fields"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Field"},"children":["Field"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Notes"},"children":["Notes"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["orderType"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OrderType.buy"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OrderType.sell"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["crypto"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fiat"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fiatAmount"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The order amounts and assets."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["walletAddress"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The customer's receiving wallet address."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["email"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The customer's email address."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redirectUrl"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Where the customer returns after hosted checkout."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["paymentMethodId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Optional in the Dart signature, but required in practice: native checkout is skipped entirely without it."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Pass ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["externalCustomerId"]}," as well. It is your stable per-customer identifier, and Banxa uses it to recognise returning customers so they do not repeat KYC."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"warning","name":"Do not create orders in advance"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["startPayment"]}," creates the order and presents checkout in a single call, which keeps the order inside the one-minute window in which Banxa checkout must be loaded. Call it at the moment the customer confirms. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["createOrder"]}," exists for creating an order without presenting checkout, but there is no supported pattern for creating an order early and presenting it later."]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"warning","name":"A missing memo can permanently lose funds"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["XRP, XLM, EOS, and ATOM require a memo or tag. Pass it as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["walletAddressTag"]}," on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CreateOrderRequest"]},"."]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-5-handle-the-launch-result","__idx":10},"children":["Step 5: Handle the launch result"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["startPayment"]}," returns a sealed ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BanxaCheckoutLaunch"]},". Switch on it exhaustively."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"dart","header":{"controls":{"copy":{}}},"source":"switch (launch) {\n  case BanxaPrimerCheckoutLaunched():\n    // The native payment sheet is already on screen.\n    // Nothing to render. Wait for checkoutEvents.\n  case BanxaHostedCheckoutRequired():\n    await Navigator.of(context).push(\n      MaterialPageRoute<void>(\n        builder: (_) => Scaffold(\n          appBar: AppBar(title: const Text('Checkout')),\n          body: BanxaHostedCheckoutView(checkout: launch),\n        ),\n      ),\n    );\n}\n","lang":"dart"},"children":[]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Result"},"children":["Result"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"What it means"},"children":["What it means"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"What you do"},"children":["What you do"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BanxaPrimerCheckoutLaunched"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The native payment sheet has been presented."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Nothing. Wait for ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["checkoutEvents"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BanxaHostedCheckoutRequired"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["No usable native route for this order on this device."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Render ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BanxaHostedCheckoutView"]}," with the result."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You get ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BanxaHostedCheckoutRequired"]}," when the order has no ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["nativeToken"]},", or when the requested method cannot run on the device: Apple Pay with no merchant identifier configured, on the simulator, or on a device with no card in Wallet; Apple Pay requested on Android; Google Pay without Google Play services."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If there is neither a usable native route nor a valid Banxa ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https"]}," checkout URL, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["startPayment"]}," throws ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["NativeCheckoutNotEligibleException"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"working-with-banxahostedcheckoutview","__idx":11},"children":["Working with BanxaHostedCheckoutView"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The widget never pops itself. It reports on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["checkoutEvents"]},", and you decide when to dismiss the route."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Disposing it before a terminal URL emits ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BanxaCheckoutDismissed"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A first page that has not loaded within 30 seconds emits ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BanxaCheckoutFailed"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The first URL must be ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https"]}," on a Banxa-owned host. Later navigations may leave Banxa for bank or wallet pages during 3DS, but stay ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["There is no JavaScript bridge from the hosted UI into your app."]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-6-confirm-order-status","__idx":12},"children":["Step 6: Confirm order status"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK does not expose order lookup. Confirm the final state from your backend using the Banxa API."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Only terminal statuses are final. Do not credit the customer until the order reaches ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["complete"]},". For the full list, see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/hosted-checkout/docs/transaction-lifecycle/order-statuses"},"children":["Order Statuses"]},", and for lookup see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/hosted-checkout/docs/transaction-lifecycle/order-lookup"},"children":["Order Lookup"]},"."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-7-handle-webhooks","__idx":13},"children":["Step 7: Handle webhooks"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Webhooks fire on every order status change and are the reliable mechanism for order tracking. Configure your webhook URL in the Partner Dashboard."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The typical pattern:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BanxaCheckoutCompleted"]},": optimistic UI update, \"your order is processing\"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Webhook to your backend: authoritative order state."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Backend pushes the update to the app, or the app re-fetches on resume."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/hosted-checkout/docs/transaction-lifecycle/webhooks"},"children":["Webhooks"]}," for payload structure and signature verification. Webhook signatures are verified with your HMAC secret, not the v2 ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["x-api-key"]},"."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"apple-pay-and-google-pay","__idx":14},"children":["Apple Pay and Google Pay"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Apple Pay needs a merchant identifier and the matching ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["com.apple.developer.in-app-payments"]}," entitlement, added in Xcode under ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Signing & Capabilities, Apple Pay"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"dart","header":{"controls":{"copy":{}}},"source":"await BanxaPayments.configure(\n  const BanxaConfig(\n    apiKey: 'YOUR_API_KEY',\n    partnerId: 'your-partner-id',\n    applePayMerchantIdentifier: 'merchant.com.yourcompany.yourapp',\n    applePayMerchantName: 'Your Store',\n  ),\n);\n","lang":"dart"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Without the merchant identifier, Apple Pay is disabled and orders fall back to hosted checkout."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For Google Pay on Android, Google requires app-level approval before you can process live payments. Banxa's own approval does not cover payments presented inside your app, so start that process early."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"error-handling","__idx":15},"children":["Error handling"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Every SDK failure is a subclass of the sealed ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BanxaPaymentsException"]},". The ones you will meet most often during integration:"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Exception"},"children":["Exception"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Cause"},"children":["Cause"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SdkNotConfiguredException"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["A method was called before ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BanxaPayments.configure"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MissingCredentialsException"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["apiKey"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["partnerId"]}," was blank."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["UnauthorizedException"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["401"]}," from Banxa. Check the key matches the environment."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ValidationException"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["400"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["422"]},", or a blank required order field. Exposes ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["errors: List<FieldError>"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["NativeCheckoutNotEligibleException"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["No usable native route and no valid hosted ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["checkoutUrl"]},"."]}]}]}]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"dart","header":{"controls":{"copy":{}}},"source":"try {\n  final launch = await BanxaPayments.startPayment(request);\n  // ...\n} on ValidationException catch (e) {\n  for (final error in e.errors) {\n    debugPrint('${error.field}: ${error.messages.join(', ')}');\n  }\n} on BanxaPaymentsException catch (e) {\n  showRetry(e.message);\n}\n","lang":"dart"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Show user-facing messages only from validated error fields. Do not expose raw error strings that may include internal detail. See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/hosted-checkout/docs/reference/error-codes"},"children":["Error Codes"]},"."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"testing","__idx":16},"children":["Testing"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use sandbox for all development:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"dart","header":{"controls":{"copy":{}}},"source":"await BanxaPayments.configure(\n  const BanxaConfig(\n    apiKey: 'YOUR_SANDBOX_API_KEY',\n    partnerId: 'your-partner-id',\n    environment: BanxaEnvironment.sandbox,\n  ),\n);\n","lang":"dart"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Apple Pay cannot be tested on the iOS simulator. On the simulator, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["startPayment"]}," returns ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BanxaHostedCheckoutRequired"]}," rather than presenting the native sheet, so test the native path on a real device with at least one card added to Wallet."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For test credentials, see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/hosted-checkout/docs/testing/sandbox-test-data"},"children":["Sandbox Test Data"]},"."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"native-payment-sheet","__idx":17},"children":["Native payment sheet"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK presents a native payment sheet for card, Apple Pay, and Google Pay when the customer is cleared for it, and falls back to Banxa hosted checkout when they are not. Driving that behaviour explicitly, including reading the eligibility result and acting on outstanding requirements, is a Banxa Native capability for partners who verify their own users and run their own KYC. See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://banxa-enterprise.redocly.app/enterprise-api/v0-beta"},"children":["Banxa Native"]},", or talk to Banxa about whether it is relevant to your integration."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"next-steps","__idx":18},"children":["Next steps"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/hosted-checkout/docs/sdk-integration/flutter-sdk-reference"},"children":["Flutter SDK Reference"]},": full method, model, and error reference."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/hosted-checkout/docs/transaction-lifecycle/webhooks"},"children":["Webhooks"]},": configure webhook notifications."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/hosted-checkout/docs/transaction-lifecycle/order-statuses"},"children":["Order Statuses"]},": full status reference."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/hosted-checkout/docs/testing/sandbox-test-data"},"children":["Sandbox Test Data"]},": credentials and test values."]}]}]},"headings":[{"value":"Flutter SDK Integration Guide","id":"flutter-sdk-integration-guide","depth":1},{"value":"Before you start","id":"before-you-start","depth":2},{"value":"Prerequisites","id":"prerequisites","depth":3},{"value":"Install the SDK","id":"install-the-sdk","depth":3},{"value":"Platform setup","id":"platform-setup","depth":3},{"value":"Step 1: Configure the SDK","id":"step-1-configure-the-sdk","depth":2},{"value":"Step 2: Subscribe to checkout events","id":"step-2-subscribe-to-checkout-events","depth":2},{"value":"Step 3: Get a quote","id":"step-3-get-a-quote","depth":2},{"value":"Step 4: Start the payment","id":"step-4-start-the-payment","depth":2},{"value":"Required fields","id":"required-fields","depth":3},{"value":"Step 5: Handle the launch result","id":"step-5-handle-the-launch-result","depth":2},{"value":"Working with BanxaHostedCheckoutView","id":"working-with-banxahostedcheckoutview","depth":3},{"value":"Step 6: Confirm order status","id":"step-6-confirm-order-status","depth":2},{"value":"Step 7: Handle webhooks","id":"step-7-handle-webhooks","depth":2},{"value":"Apple Pay and Google Pay","id":"apple-pay-and-google-pay","depth":2},{"value":"Error handling","id":"error-handling","depth":2},{"value":"Testing","id":"testing","depth":2},{"value":"Native payment sheet","id":"native-payment-sheet","depth":2},{"value":"Next steps","id":"next-steps","depth":2}],"frontmatter":{"title":"Flutter SDK Integration Guide | Banxa Docs","description":"End-to-end Flutter integration for Banxa Hosted Checkout with the Banxa Flutter SDK: install, configure, quote, start a payment, handle checkout events, and confirm order status.","seo":{"title":"Flutter SDK Integration Guide"}},"lastModified":"2026-09-04T06:00:19.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/products/hosted-checkout/docs/sdk-integration/flutter-sdk-guide","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}