Skip to main content

halo_sdk_ui 0.1.3 copy "halo_sdk_ui: ^0.1.3" to clipboard
halo_sdk_ui: ^0.1.3 copied to clipboard

Halo Dot tap-on-phone payments for Flutter. Wraps the native sdk_ui library behind one Dart class, so an app can take a card payment on the phone itself without writing native code. Android only.

halo_sdk_ui #

Platform Min SDK sdk_ui

A Flutter plugin for Halo Dot tap-on-phone payments. It wraps the native sdk_ui library behind one Dart class, HaloSdkUi, so a Flutter app can take a card payment on the phone itself without writing native code or holding a MethodChannel.

The native SDK owns the whole payment experience — amount entry, the card-reading screens, the PIN pad, the result screen and the receipt. This plugin is the bridge: it hands your calls to the SDK and hands the SDK's answers back to Dart.

Android only. There is no sdk_ui for iOS yet, so the pubspec declares Android alone. On any other platform every call throws a MissingPluginException, which a cross-platform host should guard for.

Contents #

What you get · Requirements · Installation · Quick start · The API · The token provider · Branding · Taking a payment · Testing

What you get #

  • The whole payment UI, from Dart. One call opens the SDK's native tap flow and returns the result.
  • Two lines of native code, both in your MainActivity — a base class and one call. Nothing else.
  • The surrounding surfaces too — NFC state and settings, scanned payment links, camera coordination, the Push to Terminal device id, language, and the browser hand-off for a link on a domain the SDK claims.

Requirements #

Requirement Detail
Platform Android only
Android Gradle Plugin 9.0 or later, which is what compileSdk = 37 needs. Kotlin comes from AGP itself; a host that sets android.builtInKotlin=false has the plugin apply org.jetbrains.kotlin.android for its own module instead
minSdkVersion 29 or higher (the SDK's floor)
compileSdk 37 or higher (the SDK's AAR metadata requires it)
Host Activity Must be a FlutterFragmentActivity — see below
Halo artifact credentials An AWS access key and secret, to resolve sdk_ui from the Halo S3 Maven repository
A Halo SDK token (JWT) Issued by Halo; supplied to the plugin via setTokenProvider
A brand file android/app/src/main/assets/halo/brand.json — see Branding

Installation #

1. Make MainActivity a FlutterFragmentActivity #

The SDK's UI is Jetpack Compose and needs an androidx.activity.ComponentActivity host. The default FlutterActivity is not one; FlutterFragmentActivity is. Without this the plugin never holds an Activity, and every Activity-dependent call — nfc, prepare, init, transact — answers null, false or no_activity instead of doing anything.

2. Attach the SDK in onCreate #

HaloSdkUi.attach is the one call the plugin cannot make for you, and it goes on the first line after super.onCreate:

// android/app/src/main/kotlin/.../MainActivity.kt
import android.os.Bundle
import io.flutter.embedding.android.FlutterFragmentActivity
import za.co.synthesis.halo.sdk_ui.HaloSdkUi

class MainActivity : FlutterFragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        HaloSdkUi.attach(this, savedInstanceState)
    }
}

Two things make this yours rather than the plugin's. It loads the payment kernel, which is the longest single step of bringing the SDK up, and a plugin is handed its Activity well after onCreate — so attaching there would move that cost onto whoever is waiting next. And savedInstanceState exists only here: it is what lets the SDK restore its own state across a process death, and what tells a genuine launch from a recreated Activity, which is the difference between opening a pushed payment and charging it twice.

Skipping it is legal — prepare and init attach again — and costs the speed, the restore and the notification-tap path.

You need no sdk_ui dependency of your own: the plugin exposes it.

3. Add the Halo repository #

sdk_ui is published to Halo's private S3 Maven repository, which authenticates with AWS credentials rather than a username and password. Registering on the developer portal gets you a key and a secret; put them in your app's android/local.properties, which git ignores:

aws.accesskey=<your access key>
aws.secretkey=<your secret>

The repository itself goes in your build rather than the plugin's. Gradle resolves each configuration against the repositories of the project doing the resolving, and sdk_ui is fetched twice — once for the plugin library and once as a transitive dependency of :app — so a declaration inside the plugin would answer for only half of it. allprojects answers for both:

// android/build.gradle.kts
import java.util.Properties

val localProperties = Properties().apply {
    val file = File(rootDir, "local.properties")
    if (file.isFile) file.inputStream().use { load(it) }
}

fun haloCredential(property: String, environmentVariable: String): String? =
    localProperties.getProperty(property)?.takeIf { it.isNotBlank() }
        ?: System.getenv(environmentVariable)?.takeIf { it.isNotBlank() }

allprojects {
    repositories {
        google()
        mavenCentral()
        listOf("releases", "snapshots").forEach { repository ->
            maven {
                name = repository
                url = uri("s3://synthesis-halo-artifacts/$repository")
                credentials(AwsCredentials::class) {
                    accessKey = haloCredential("aws.accesskey", "AWS_ACCESS_KEY_ID")
                    secretKey = haloCredential("aws.secretkey", "AWS_SECRET_ACCESS_KEY")
                    sessionToken = haloCredential("aws.token", "AWS_SESSION_TOKEN")
                }
            }
        }
    }
}

The environment-variable fallbacks are for CI, which carries no local.properties and exports temporary credentials under those names. A build that centralises repositories in dependencyResolutionManagement declares the same repository there instead; the plugin declares none of its own either way, so nothing here conflicts with RepositoriesMode.FAIL_ON_PROJECT_REPOS.

example/android/build.gradle.kts is this file, working.

4. Add the plugin #

# pubspec.yaml
dependencies:
  halo_sdk_ui: ^0.1.3

5. Set minSdk and compileSdk #

// android/app/build.gradle.kts
android {
    compileSdk = 37
    defaultConfig { minSdk = 29 }
}

Quick start #

import 'package:halo_sdk_ui/halo_sdk_ui.dart';

final halo = HaloSdkUi();

// 1. Tell the SDK where to get a token. Set before init.
halo.setTokenProvider(() async => myBackend.freshJwt());

// 2. Warm the SDK at your splash. Optional, and faster.
await halo.prepare();

// 3. Bring the SDK up once you have a session.
final outcome = await halo.init();
if (outcome['resultType'] != 'Initialized') {
  // outcome['errorCode'] says why.
}

// 4. Take a payment. Pass an amount, or null to use the SDK's own keypad.
final result = await halo.transact('10.00', 'ZAR', reference: 'order-1234');
print(result['resultType']); // Approved / Declined / Cancelled / …

The order is the design. Each call needs strictly more than the one before it, so each can run the moment that thing exists: prepare needs only your brand file, init needs a token, transact needs a live SDK.

The API #

Everything is a method on HaloSdkUi.

Bring-up #

Method Returns What it does
setTokenProvider(provider) — Registers the callback the SDK calls for a fresh JWT. Set before init.
prepare() Future<void> Warms branding and artwork. Call at your splash. Returns at once; cannot fail.
init() Future<Map<String, String?>> Brings the SDK up. Suspends until the real outcome. Returns {resultType, errorCode}.

Payments #

Method Returns What it does
transact(amount, currency, {reference, presentation}) Future<Map<String, String?>> Opens the native payment UI for one charge. amount is a decimal string ("10.00") — or null to let the SDK show its own keypad. currency is ZAR/GBP/EUR/USD. reference is optional; presentation is "FULL_SCREEN" (default) or "SHEET". Returns the tender map (below).
payment(url) Future<bool> Hands a scanned payment link to the SDK's own activity. Returns whether it launched.

transact returns the outcome plus the tender a till can reconcile against:

Key Meaning
resultType The SDK's own outcome name: Approved, Declined, Cancelled, CardTapTimeOutExpired, NetworkError… or null if no card was read
reference The acquirer's reconciliation reference
maskedPan The card, already masked by the SDK
authCode The acquirer's authorisation code
date / time The acquirer's own timestamp (the receipt's is the fallback)

NFC #

Method Returns What it does
nfc() Future<bool?> Tri-state: null = no NFC hardware (or no Activity), true = on, false = present but off.
openNfcSettings() Future<bool> Opens the system NFC settings page (falls back to wireless settings). Returns whether a page opened.

Camera (scan-to-pay) #

Method Returns What it does
camera(inUse) Future<void> Coordinates the camera with the payment kernel. Call camera(true) before opening your scanner and camera(false) after, so the kernel's monitor doesn't cancel a transaction.

Push to Terminal #

Method Returns What it does
deviceInstallationId() Future<String?> This device's Halo installation id — which row it is in the merchant's estate. Null until init has registered it at least once.
forgetRegistration() Future<void> Tells the SDK to forget its terminal registration, so it re-registers on the next token. Call after deleting this device's estate row.

Miscellaneous #

Method Returns What it does
sdkInfo() Future<String?> The underlying Halo SDK's version string. Available before init.
language(code) Future<void> Sets the SDK's language ("en", "af"…) to match your app's picker. Null or an unshipped code resets to the SDK's default.
browser(url) Future<bool> Opens url in the device browser rather than the SDK — for a link on a domain the SDK's activity claims (e.g. the merchant portal). Returns whether a browser opened it.

The token provider #

Every call into the Halo SDK needs a valid, short-lived JWT. The plugin does not know how to get one — your app does. So you register a callback, and the plugin invokes it whenever the SDK asks:

halo.setTokenProvider(() async {
  // Return a fresh JWT — typically from your backend, refreshed as needed.
  return await myBackend.freshJwt();
});

Set this before init; the SDK cannot come up without a token. The callback is async, so fetching the token over the network needs no extra plumbing. See the Halo developer portal for how the JWT is issued and what claims it carries.

Branding #

Everything about the SDK's appearance and addressing — colours, logo, corner radius, which card schemes to show, the language, the payment-link scheme, the kernel those links resolve against, receivePush, shareReceipt and the telemetry switches — is not passed through this plugin. The SDK reads it from a file your app ships:

android/app/src/main/assets/halo/brand.json

This is deliberate. The SDK's config (HDConfig) carries only the two things a file cannot hold — the host Activity and the token callback — and the plugin supplies both. Everything else is a file because an inbound payment (a link, a push) brings the SDK up natively, with no Flutter engine running, so it has to be able to paint and address itself with no host code involved.

A brand.json, from the example app in this repository:

{
  "light": { "primary": "#FF292CF5", "secondary": "#FF292CF5" },
  "dark":  { "primary": "#FF292CF5", "secondary": "#FF292CF5" },
  "shape": 20,
  "language": "en",
  "presentation": "SHEET",
  "analytics": true,
  "crashReports": true
}

Every key is optional; what you leave out is Halo's default. Colours are #AARRGGBB.

Two more keys, kernel and kernelPins, name the kernel your payment links resolve against and the certificate that authenticates it. They are needed only for the one link shape that carries a reference and no configJwt — every other flow reads the address off the token. Halo issues both values with your credentials, for the environment your token is issued for; they are not written down here, because a kernel address and its pin are yours and go stale. Ask your Halo contact, and see the sdk_ui documentation, which lists every key.

example/android/app/src/main/assets/halo/brand.json is a working file with every key commented.

Taking a payment #

transact opens the SDK's native payment UI and returns when the transaction is done. The merchant taps the card on the device; the SDK draws every screen. Your app supplies the amount and gets the outcome — it draws none of the payment UI itself.

final result = await halo.transact('49.99', 'ZAR', reference: 'invoice-7781');
switch (result['resultType']) {
  case 'Approved':
    // result['reference'], result['maskedPan'], result['authCode'] … for a
    // till to reconcile against. See the tender table above.
    break;
  case 'Declined':
    break;
  default: // Cancelled, timeout, or null — no card was read.
}

Pass an amount and the SDK opens straight on the tap screen. Pass null and the SDK shows its own keypad first, so an app with no amount-entry UI of its own can still take a payment:

await halo.transact(null, 'ZAR');            // the merchant enters the amount on the SDK's keypad
await halo.transact('10.00', 'ZAR', presentation: 'SHEET'); // a bottom sheet, not full-screen

Testing #

The Dart layer is unit-tested against a mock method channel — the method names, the argument shapes and the return unwrapping for every call, including the reverse getToken bridge:

flutter test

The native layer's tests cover what the plugin answers with no Activity attached, which is the ordinary state between a host's screens. They run through the example app's Gradle build, so run the example once first — that is what creates its wrapper:

cd example/android && ./gradlew :halo_sdk_ui:testDebugUnitTest

License #

Copyright © 2026 Halo Dot. All rights reserved. See LICENSE.

Support #

Halo Developer Portal

1
likes
0
points
1.15k
downloads

Documentation

Documentation

Publisher

verified publisherhalodot.io

Weekly Downloads

Halo Dot tap-on-phone payments for Flutter. Wraps the native sdk_ui library behind one Dart class, so an app can take a card payment on the phone itself without writing native code. Android only.

Repository (GitHub)
View/report issues

Topics

#payments #nfc #android #point-of-sale

License

unknown (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on halo_sdk_ui

Packages that implement halo_sdk_ui