Paolo Gianfelici
All work
On pub.dev · recorded in Nexi's sandboxCase study

nexi_payment

A Flutter plugin for Nexi's payment gateways: Nexi's checkout on Android and iOS, and an outcome the app can trust.

Role
Author and maintainer: the Dart API, the Android (Java) and iOS (Swift) code, tests, CI and releases
Type
Open-source Flutter plugin, MIT licence, on pub.dev
Period
2020 – today (2.3.0, July 2026)
Platforms
Android and iOS
  • Flutter
  • Dart
  • Java
  • Swift
  • Platform channels
  • Nexi XPay SDK
  • Nexi NPG SDK
  • Patrol
  • GitHub Actions

The product

nexi_payment lets a Flutter app take payments through Nexi. The app hands over the order, Nexi's own checkout collects the card (the app never sees it), and the app gets back what happened: paid, cancelled, or failed and why.

I wrote it in June 2020, while adding Nexi to the Criluma Viaggi app, and published it on pub.dev. In July 2026 I rewrote it as 2.0: Nexi's newer NPG gateway next to the classic XPay one, one error contract on both platforms, tests that drive the real SDKs, and fixes for the moments when Nexi's native SDKs fall silent or report the wrong thing.

7
stable releases on pub.dev, from 1.0.0 in June 2020 to 2.3.0 in July 2026
53
of the 58 commits are mine; the others came as contributors' pull requests
2
Nexi gateways behind one Dart API: classic XPay and NPG
49
automated tests: 35 unit tests and 14 integration tests that run on a device

Walkthrough

The app, in motion

Pick a recording, or jump straight to a chapter.

A card payment, first frame
0:00 / 1:43

Recorded on an Android emulator from a release build of the example app, paying in Nexi's public sandbox. The checkout's fields are found through DevTools and touched like a finger would; the sandbox page needs two small fixes to work, applied the same way.

What it does

From a button in the app to an outcome it can trust

01

One call opens Nexi's checkout

The app passes the order (id, amount, currency, language) and the plugin opens Nexi's Hosted Payment Page through the native SDK: a card, or one of the other methods Nexi offers. The card is typed on Nexi's page, never in the app.

02

Summary and 3-D Secure

Nexi shows the summary with the masked card, then hands over to the cardholder's bank for 3-D Secure: in the sandbox, DemoBank, which can be told to pass or to fail.

03

An outcome the app can trust

The native SDKs call their "completed" callback for any payment that finished, declined ones included. The plugin reports success only for a final EXECUTED or AUTHORIZED operation; a failed authentication comes back as a failure with Nexi's code. Before 2.0, iOS even reported a denied payment as a plain string; both platforms now raise the same coded errors.

04

Every way out ends the call

The back button, Nexi's own cancel link, a sheet dragged away on iOS: each one resolves the payment as cancelled, and the next payment opens normally. Two of those exits used to leave the app waiting for an answer that never came.

Under the hood

How it was built

Filling the SDKs' silences

XPaySDK closes its checkout on Android's back button without calling back, and NPGSDK on iOS can let its sheet go without a word. The plugin watches the checkout's lifecycle and resolves with a cancel when it disappears having said nothing (a real outcome always wins), and a guard refuses a second payment while one is open instead of leaving two calls pending.

When the SDK cannot read its own answer

NPGSDK 1.1.0 declares an operation's additionalData as a map of strings, but the backend nests an object in it, so on Android the SDK can fail to parse the outcome of a payment that went through. The plugin then reads the order back from NPG's orders API and reports what really happened: it never invents a success, and says "outcome unknown" when it cannot tell.

Packaging, tests and releases

Nexi publishes no Maven artifacts and its iOS pod has no simulator slice: the Android SDKs ship inside the plugin, and the iOS frameworks are fetched from Nexi's release repositories at pod install and checked against pinned SHA-256 checksums. Unit tests pin the channel contract, Patrol tests drive the real SDKs against Nexi's sandbox, CI analyses, tests and builds on every push and pull request, and a version tag publishes to pub.dev.

Recording a payment plugin

A real checkout, in Nexi's sandbox.

A plugin has no screens of its own, so the recordings show its example app, a release build on an Android emulator, paying for real in Nexi's public sandbox.

  1. 1

    The published code, built for release

    The repository at the 2.3.0 tag, the example app built in release mode with the sandbox credentials Nexi publishes, installed on an emulator. No private keys: the classic XPay buttons need a merchant's own test terminal and are not shown.

  2. 2

    Into Nexi's page through DevTools

    The checkout is a web page inside the SDK's WebView. The recording script finds its fields through Chrome DevTools, which also tells where the WebView sits on the screen, and touches them on the emulator's touchscreen like a finger, typing Nexi's test card.

  3. 3

    Two sandbox quirks

    The sandbox page sometimes leaves its card button without a label, and never enables its pay button for a typed card. The script fixes both from DevTools, the second exactly as the plugin's own device-testing runbook does.

  4. 4

    Three endings

    A payment that goes through, one whose 3-D Secure fails, and two ways of walking away. Each take starts from a cleared app and a new order.

The payment pages, the sandbox merchant (WWW.CHARTA.IT) and DemoBank are Nexi's public sandbox. The card is Nexi's published test card; the cardholder is invented.

Hosted Payment Page

Nothing to try here: the plugin runs inside Android and iOS apps. It is on pub.dev and GitHub.

Every screen

The full set of screenshots

  • The example appBoth gateways, card storage and a recurring charge, one button each.
  • Hosted Payment PageNexi's page, opened by the native SDK: card or another method.
  • The cardNexi's published test card; the app never sees the number.
  • SummaryMerchant, amount, order and the masked card.
  • 3-D SecureThe sandbox's DemoBank asks the cardholder to confirm.
  • ExecutedThe plugin reports success only for an executed or authorised payment.
  • FailedTHREEDS_FAILED, not a success: the plugin checks the final operation.
  • CanceledBack button: the plugin turns the SDK's silence into a cancel.
  • Leaving the pageNexi's own cancel asks for confirmation.

Next case study

Referi →