{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-platform/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"Migration Guide","llmstxt":{"title":"Glomopay developer documentation","description":"Cross-border payments APIs, organised by product. Each product section carries both the explanation of its objects and the job-shaped integration guides that set them up.","details":{"content":"How to use these docs:\n\n- Get started is the front door. It carries a routing table from\n  \"what are you building\" to the page that builds it, plus how to get\n  API keys.\n- The product sections - Payin, Payout, Multi-currency accounts,\n  Verification, Request for Information (RFI) - each hold two kinds of page.\n  Pages titled as a noun (\"Subscriptions\", \"Bank Transfer\") explain what\n  an object is. Pages titled as a job (\"Set up recurring subscriptions\",\n  \"Collect LRS remittances from India\") are complete integration flows\n  and live one level under the object they set up. Match a user's goal\n  against the job-shaped titles first.\n- Reports and Identity and Access Management are the two subjects with no\n  API counterpart. There are no endpoints for reports, users, roles or\n  invites; both pages document the merchant dashboard. A dashboard\n  procedure for something the API also does (a refund, a payout) is NOT\n  here - it stays in the product that owns it.\n- API reference is generated from OpenAPI and carries endpoint detail.\n- The OpenAPI description itself is published as a single self-contained\n  document at /openapi.yaml, and the same document as JSON at\n  /openapi.json. Every $ref is resolved into it, so one fetch is the\n  whole contract: 61 paths, 81 operations, 68 schemas - the same\n  endpoints the API reference pages render. Fetch that instead of\n  reading the 129 pages under /api-reference/.\n- Developer resources covers what every integration touches and no single\n  product owns: auth, errors, pagination, rate limits, webhooks,\n  versioning, supported countries, quotes, and the client SDKs.\n"},"sections":[{"title":"Get started","description":"The front door. The routing table from a merchant's goal to the flow that delivers it, how to obtain API keys, and the platform glossary.","includeFiles":["**/get-started/**"]},{"title":"Payin","description":"Collecting money: customers, orders, checkout, payment links, payment methods (cards, bank transfer, pay via bank), refunds, disputes, purpose codes, fee models, custom fields, subscriptions, and resident India remittance under LRS. Includes the flows that set each up - bank transfers, subscriptions, LRS remittances, and server-to-server card payments.","includeFiles":["**/payin/**"]},{"title":"Payout","description":"Sending money: the payout life cycle, queued payouts, creating and cancelling a payout, rails, beneficiaries, purpose codes, and the action required state.","includeFiles":["**/payout/**"]},{"title":"Multi-currency accounts","description":"Holding money: balances, balance conversion, adding balance, withdrawing balance, and the settlement holiday calendar - the Indian and US bank and FX holidays on which settlement to the merchant's bank account, and money movement generally, is affected.","includeFiles":["**/multi-currency-account/**"]},{"title":"Verification","description":"Proving who someone is and that an account is theirs: KYC, bank account validation, and third-party verification (TPV) with the flow that verifies a payer's bank account.","includeFiles":["**/verification/**"]},{"title":"Request for Information (RFI)","description":"Responding to a compliance request for information, per object - payment link, order, payment, payout - plus compliance reviews and RFIs raised on already-successful payments.","includeFiles":["**/request-for-information/**"]},{"title":"Reports","description":"The downloadable and scheduled reports a merchant pulls from the dashboard for reconciliation, compliance and treasury: payment, subscription, settlement breakup, balance statement and KYC reports, plus scheduling one for recurring email delivery. Dashboard only - there is no reports API.","includeFiles":["**/reports/**"]},{"title":"Identity and Access Management","description":"Administering who can use a merchant account: one set of credentials across multiple MIDs, switching between them without logging out, the Admin / Member / Sales Partner roles and what each may see and do, and inviting a new member. Dashboard only - there is no users, roles or invites API.","includeFiles":["**/access-management/**"]},{"title":"API reference","description":"Endpoint reference generated from the Glomopay OpenAPI description.","includeFiles":["**/api-reference/**"]},{"title":"Developer resources","description":"What every integration touches and no single product owns: auth, errors, pagination, rate limits, webhooks, versioning, API validations, security, supported countries, quotes, and the client SDKs - React Native, Flutter, Android and the Unified Web SDK, with per-version references and changelogs. Reports and Identity and Access Management used to be listed here and are now their own sections.","includeFiles":["**/platform/**"]}]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"migration-guide","__idx":0},"children":["Migration Guide"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#v200-to-v210"},"children":["v2.0.0 to v2.1.0"]}," - nothing to do"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#v111x-to-v200"},"children":["v1.11.x to v2.0.0"]}," - for merchants currently on v1"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#v00x-to-v1"},"children":["v0.0.x to v1"]}," - historical reference"]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"v200-to-v210","__idx":1},"children":["v2.0.0 to v2.1.0"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Bump the constraint to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["^2.1.0"]}," and rebuild. No API changed, nothing is deprecated, and no code has to move."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Two things to be aware of, neither of which needs a change:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onSdkError"]}," can now receive a non-terminal error."]}," A failed ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/sdk/flutter-sdk/v2#document-downloads"},"children":["document download"]}," arrives as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SdkErrorType.validationError"]}," with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["field"]}," set to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["'file.save'"]},", and the checkout keeps running. If your ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onSdkError"]}," closes the checkout or shows a blocking screen for every error it receives, skip the ones with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["field == 'file.save'"]}," - the SDK has already told the user, and the payment session is still alive."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["A throw from ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onEvent"]}," no longer surfaces."]}," It used to propagate into the bridge's catch and reach ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onSdkError"]},"; it is now caught and logged. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onEvent"]}," is deprecated and this only affects code that was throwing from it."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Downloads themselves need no integration - no callback, no configuration, no permission and no new dependency."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"v111x-to-v200","__idx":2},"children":["v1.11.x to v2.0.0"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["v2.0.0 renames nothing. It is a behavioural release: the same callbacks you already supply now fire at different times, and each failure reports its actual semantic instead of being funnelled into ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onPaymentFailure"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Two changes break compilation, and the compiler will point at both:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The new required ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onUserJourneyCompleted"]}," callback (",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#1-supply-the-new-onuserjourneycompleted-callback"},"children":["step 1"]},")"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The removal of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["devMode"]}," from ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GlomoPayConfig"]}," (",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#2-remove-devmode-from-glomopayconfig"},"children":["step 2"]},")"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The remaining steps apply only if they describe your code. The SDK now closes itself on every path that ends a checkout, so nothing here can leave a user stranded while you migrate."]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For the complete v2 API reference, see the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/sdk/flutter-sdk/v2"},"children":["Flutter SDK v2 documentation"]},"."," ","For the full list of changes, see the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/sdk/flutter-sdk/changelog"},"children":["Changelog"]},"."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Update the dependency first:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"yaml","header":{"controls":{"copy":{}}},"source":"dependencies:\n  glomopay_sdk: ^2.0.0\n","lang":"yaml"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"1-supply-the-new-onuserjourneycompleted-callback","__idx":3},"children":["1. Supply the new ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onUserJourneyCompleted"]}," callback"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Required for every integration."]}," Your build will not compile until you do, which is deliberate."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"dart","header":{"controls":{"copy":{}}},"source":"onUserJourneyCompleted: (GlomoPayUserJourneyPayload journey) {\n  // The user submitted their bank transfer details. A settlement may not have happened and\n  // there is no paymentId or signature, so reconcile journey.orderId on your\n  // backend rather than treating this as paid.\n},\n","lang":"dart"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Before v2.0.0, a submitted bank transfer was delivered to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onPaymentSuccess"]}," with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["paymentId"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["signature"]}," both ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["null"]}," - a payment reported as received that your backend had nothing to verify. It now reports here instead, and ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["no longer reaches ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onPaymentSuccess"]}]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["If ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onPaymentSuccess"]}," currently special-cases a null ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["paymentId"]},", move that branch into this callback."]}," It will no longer be reached where it is."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you do not take bank transfers, an empty body for ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onUserJourneyCompleted"]}," is a complete migration - the callback simply never fires. It was made required rather than optional to make the behavioural breaking change loud: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["orderType"]}," and merchant configuration decide, on the server, whether an order can accept bank transfers. An optional callback would have let an integration upgrade, keep compiling, and silently stop hearing about a journey it used to be told about."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"2-remove-devmode-from-glomopayconfig","__idx":4},"children":["2. Remove ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["devMode"]}," from ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GlomoPayConfig"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If your ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GlomoPayConfig"]}," sets ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["devMode"]},", remove the argument. It is no longer part of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GlomoPayConfig"]},", so the compiler will reject it:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"No named parameter with the name 'devMode'\n"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["There is no replacement to pass; deleting the line is the whole migration. Behaviour is unchanged for any integration that was not setting it, which is the default."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Before"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"dart","header":{"controls":{"copy":{}}},"source":"config: const GlomoPayConfig(\n  publicKey: 'live_pk_abc123',\n  orderId: 'order_xyz789',\n  devMode: true,\n),\n","lang":"dart"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["After"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"dart","header":{"controls":{"copy":{}}},"source":"config: const GlomoPayConfig(\n  publicKey: 'live_pk_abc123',\n  orderId: 'order_xyz789',\n),\n","lang":"dart"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"3-do-you-close-the-checkout-or-run-cleanup-inside-onpaymentfailure-","__idx":5},"children":["3. Do you close the checkout or run cleanup inside ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onPaymentFailure"]},"?"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onPaymentFailure"]}," is now reserved for a payment the backend confirms failed. This is the correct behaviour across all Glomo mobile SDKs, and Flutter now reflects it too. It no longer fires when the checkout could not load, or when the user exits one of the SDK's default error screens - no payment is attempted in either case, so it never belonged there."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Those paths now report:"]},{"$$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":"What happened"},"children":["What happened"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Callback"},"children":["Callback"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The SDK or the order could not proceed"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onSdkError"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Connectivity failed, or the page did not load in time"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onConnectionError"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The user then exited the error screen"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onPaymentTerminate(TerminationSource.userDismiss)"]}]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Before"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"dart","header":{"controls":{"copy":{}}},"source":"onPaymentFailure: (payload) {\n  logFailure(payload);\n  Navigator.pop(context);\n},\n","lang":"dart"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["After"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"dart","header":{"controls":{"copy":{}}},"source":"void _endCheckout() {\n  Navigator.pop(context);\n}\n\nonPaymentFailure: (payload) {\n  logFailure(payload);       // a real, backend-confirmed payment failure\n  _endCheckout();\n},\nonPaymentTerminate: (source) {\n  _endCheckout();            // user left, including from an SDK error screen\n},\n","lang":"dart"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Supplying ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onPaymentTerminate"]}," is safe to do incrementally: the SDK closes the checkout whether or not you supply it, and if you close from the callback as well the SDK will not close a second time."]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Note: confirmed payment failures were previously being checked against the rule written for a payment ",{"$$mdtype":"Tag","name":"em","attributes":{},"children":["success"]},", which requires a signature that a failure payload has never carried. Every confirmed decline was discarded before reaching your app, in every release build. If you have been treating ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onPaymentFailure"]}," as unreliable, it is now the callback for exactly this."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"4-do-you-use-onevent-","__idx":6},"children":["4. Do you use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onEvent"]},"?"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onEvent"]}," is deprecated and will be removed in a future major version. It carries information for internal debugging that is subject to change, and was never part of the SDK's API contract."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you rely on an event with no callback equivalent, contact the Glomo mobile team so it can be covered before removal."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"5-do-you-supply-your-own-glomopaycontroller-and-call-addeventlistener-","__idx":7},"children":["5. Do you supply your own ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GlomoPayController"]}," and call ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["addEventListener"]},"?"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The same deprecation applies, and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["glomo_flutter_sdk.checkout.terminal_error"]}," now fires when the error is raised rather than after its dialog is dismissed. Contact the Glomo mobile team for the full event mapping."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"6-did-your-app-inherit-the-sdks-error-handlers","__idx":8},"children":["6. Did your app inherit the SDK's error handlers?"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["v2.0.0 no longer installs ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["FlutterError.onError"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PlatformDispatcher.instance.onError"]},", or the native crash handlers inside your application. ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["If your app was relying on SDK-installed handlers, it loses them at this upgrade"]}," - install your own."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["In exchange, the SDK no longer constrains versions for any error-tracking package, so you are free to use your own."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"behaviour-changes-that-need-no-code","__idx":9},"children":["Behaviour changes that need no code"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["These change what your users see, not what you write."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["The user can always exit."]}," The back button works while a payment is in progress; the SDK's error screens can no longer be rendered invisible by your app's theme; and closing an error screen closes the checkout rather than returning to a loading screen. Depending on the flow, leaving from inside a bank page may still take two presses, so an accidental press cannot end a payment."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["One failure, one callback."]}," A single main-frame load failure delivers ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onConnectionError"]}," alone - it used to deliver ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onSdkError"]}," as well."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["One ending per checkout."]}," A backend-confirmed payment result is the ending; a user closing what remains afterwards no longer also reports ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onPaymentTerminate"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["A load timeout is advisory."]}," If the page renders after the timeout expires, the SDK withdraws its own error and the checkout continues. Prefer not to tear the checkout down on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConnectionErrorType.timeout"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["The SDK no longer draws an error over the checkout page's own."]}," When the checkout webpage reports a failure it handles itself, the SDK stays out of the way."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Error callbacks no longer wait on a dialog."]}," They fire as soon as the condition is detected, so your app is informed even if the default dialog is never dismissed."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["The checkout page's own close control works in every state."]}," It stopped working once the page had reported a payment as pending, which every bank hand-off does."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["A slow checkout that recovers still reports success."]}," A payment completed after a load-timeout error had been reported is no longer overridden by it."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["File uploads are no longer filtered by type."]}," Whatever the bank's page asks for can be selected, including formats the picker previously greyed out. The bank's page is the authority on what it accepts."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"additions-that-need-no-migration","__idx":10},"children":["Additions that need no migration"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CheckoutStatus"]}," gained ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bankTransferSubmitted"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["error"]},"."]}," Both are ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["appended"]},", so existing members keep the indices they shipped with - anything persisting a raw ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["status.index"]}," keeps reading what it wrote. Only an exhaustive ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["switch"]}," over ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CheckoutStatus"]}," needs new arms."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["path_provider"]}," is now a direct dependency (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["^2.0.0"]},")."]}," It was already resolved transitively, so no new package enters your dependency graph, but the constraint is now the SDK's own and may affect resolution in a pinned host app."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Diagnostics survive process death."]}," SDK diagnostics are spooled to the application support directory and flushed on the next initialisation. Only SDK diagnostics are written there, and no payment data."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"before--after","__idx":11},"children":["Before / After"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["v1.11.x:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"dart","header":{"controls":{"copy":{}}},"source":"GlomoPayCheckout(\n  config: const GlomoPayConfig(\n    publicKey: 'live_pk_abc123',\n    orderId: 'order_xyz789',\n    devMode: true,\n  ),\n  onPaymentSuccess: (payload) {\n    if (payload.paymentId == null) {\n      // Bank transfer submitted - nothing to verify\n      markAwaitingFunds(payload.orderId);\n    } else {\n      verifyOnBackend(payload.paymentId!, payload.signature!);\n    }\n  },\n  onPaymentFailure: (payload) {\n    logFailure(payload);\n    Navigator.pop(context);\n  },\n  onSdkError: (errors) => showError(errors.first.message),\n  onConnectionError: (error) => showError(error.message),\n)\n","lang":"dart"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["v2.0.0:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"dart","header":{"controls":{"copy":{}}},"source":"GlomoPayCheckout(\n  config: const GlomoPayConfig(\n    publicKey: 'live_pk_abc123',\n    orderId: 'order_xyz789',\n  ),\n  onPaymentSuccess: (payload) {\n    // paymentId and signature are now always present here\n    verifyOnBackend(payload.paymentId!, payload.signature!);\n  },\n  onUserJourneyCompleted: (journey) {\n    // The null-paymentId branch moved here\n    markAwaitingFunds(journey.orderId);\n  },\n  onPaymentFailure: (payload) {\n    logFailure(payload);   // backend-confirmed declines only\n    _endCheckout();\n  },\n  onPaymentTerminate: (source) {\n    _endCheckout();        // user left, including from an SDK error screen\n  },\n  onSdkError: (errors) => showDismissibleError(errors.first.message),\n  onConnectionError: (error) {\n    if (error.type == ConnectionErrorType.timeout) {\n      logTimeout(error);   // advisory - the page may still render\n      return;\n    }\n    showDismissibleError(error.message);\n  },\n)\n","lang":"dart"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"v00x-to-v1","__idx":12},"children":["v0.0.x to v1"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The v0.0.x releases predate the current callback surface and are past end of life. Versions ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["1.0.3"]}," and below are deprecated."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you are still on a v0.0.x release, upgrade straight to the latest v2 rather than stepping through v1, and treat the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/sdk/flutter-sdk/v2"},"children":["v2 documentation"]}," as the integration reference. The deprecated pages below are kept for historical context only:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/sdk/flutter-sdk/v0.0.15"},"children":["Flutter SDK v0.0.15 (Deprecated)"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/sdk/flutter-sdk/v0.0.13"},"children":["Flutter SDK v0.0.13 (Deprecated)"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/sdk/flutter-sdk/v0.0.3"},"children":["Flutter SDK v0.0.3 (Deprecated)"]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"related","__idx":13},"children":["Related"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/sdk/flutter-sdk/v2"},"children":["Flutter SDK v2 documentation"]}," - complete v2 API reference"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/sdk/flutter-sdk/v1"},"children":["Flutter SDK v1 documentation (Archived)"]}," - v1.11.2 API reference"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/sdk/flutter-sdk/changelog"},"children":["Changelog"]}," - all release notes"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/sdk/react-native-sdk/migration"},"children":["React Native SDK Migration Guide"]}," - the equivalent upgrade path for React Native"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/payin/checkout"},"children":["Checkout overview"]}," - server-side checkout integration and signature verification"]}]}]},"headings":[{"value":"Migration Guide","id":"migration-guide","depth":1},{"value":"v2.0.0 to v2.1.0","id":"v200-to-v210","depth":2},{"value":"v1.11.x to v2.0.0","id":"v111x-to-v200","depth":2},{"value":"1. Supply the new onUserJourneyCompleted callback","id":"1-supply-the-new-onuserjourneycompleted-callback","depth":3},{"value":"2. Remove devMode from GlomoPayConfig","id":"2-remove-devmode-from-glomopayconfig","depth":3},{"value":"3. Do you close the checkout or run cleanup inside onPaymentFailure ?","id":"3-do-you-close-the-checkout-or-run-cleanup-inside-onpaymentfailure-","depth":3},{"value":"4. Do you use onEvent ?","id":"4-do-you-use-onevent-","depth":3},{"value":"5. Do you supply your own GlomoPayController and call addEventListener ?","id":"5-do-you-supply-your-own-glomopaycontroller-and-call-addeventlistener-","depth":3},{"value":"6. Did your app inherit the SDK's error handlers?","id":"6-did-your-app-inherit-the-sdks-error-handlers","depth":3},{"value":"Behaviour changes that need no code","id":"behaviour-changes-that-need-no-code","depth":3},{"value":"Additions that need no migration","id":"additions-that-need-no-migration","depth":3},{"value":"Before / After","id":"before--after","depth":3},{"value":"v0.0.x to v1","id":"v00x-to-v1","depth":2},{"value":"Related","id":"related","depth":2}],"frontmatter":{"title":"Flutter SDK Migration Guide","seo":{"title":"Migration Guide"}},"lastModified":"2026-09-22T05:15:29.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/platform/sdk/flutter-sdk/migration","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}