halo_sdk_flutter_plugin 0.0.2 halo_sdk_flutter_plugin: ^0.0.2 copied to clipboard
A flutter plugin of the Halo Dot SDK that allows integrators to embed contactless payments into their own Flutter mobile apps. The SDK provides a fully isolated SDK that meets requirements of PCI MPoC.
Halo SDK Flutter Plugin #
A flutter implementation of the Halo Dot SDK.
The Halo Dot SDK is an Isolating MPoC SDK payment processing MPOC Software with Attestation & Monitoring Capabilities.
The Architecture of this isolating MPoC Payment Software is described in the diagram below.
The below diagram also showcases the SDK boundary and the interaction between the SDK its integrating channels, and the 3rd party payment gateway. It also describes the boundary of the SDK and how it interacts with integrators and the third party payments. It also includes details of how the data is communicated sent in-between the boundary.
Table of Content #
Requirements #
The following is a list of requirements needed to implement the Halo Dot SDK
- A developers account (Register at the developer portal)
- Sign a Non Disclosure Agreement (NDA)
- A public key and private key to generate a JWT
- Kotlin 1.3.72 (@note: a higher version is in work)
- Flutter 2.10.5 (@note: a higher version is in work)
If you have a different version we can recommend using fvm - Dart 2.9.2 (@note: a higher version is in work)
- Java 11
- IDE (Android Studio Preferably)
- Recommended Libraries
- permission_handler : ^11.0.0
- dart_jsonwebtoken : ^2.4.2
- FAQ
Developer portal registration #
You are required to register on our QA (test environment) before testing in production.
The developer portal enables you to
- Accept the Non Disclosure Agreement
- Access the SDK
- Submit your public key
- Give you details for your JWT
Registration #
- Access the developer portal and register
- Verify your account through OTP
- Click on access to the SDK
- Download and Accept the Non Disclosure Agreement
- Submit your public key and create an Issuer name. This will be used to verify the JWT you will create.
- Access key and Secret key will be created. This will be used in your IDE to access the Halo SDK see where it is used
Getting started #
Make sure you have your environment set up to build Flutter apps. You can follow the instructions here.
Flutter App #
Create a Flutter app or integrate it into your current application.
Ensure that Android and IOs platforms are added, they are currently the only supported platforms.
#using flutter
flutter create . --project-name my_sdk_flutter_plugin --org za.co.synthesis.halo.test.plugin
#using fvm
fvm spawn 2.10.5 create . --project-name my_sdk_flutter_plugin --org za.co.synthesis.halo.test.plugin
Environment #
The Android SDK that is implemented was built with Kotlin 1.3.72
, your project should be on the same version (>= 1.4.x
has breaking changes).
Check this in your android/build.gradle
file. You should have something like this:
See FAQ if your file looks different
ext {
kotlin_version = '1.3.72' <-- version defined here
buildscript {
dependencies {
// ... classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // <-- version used here
}
}
-
The SDK was tested using Java 11. We cannot confirm yet if anything before or later will work.
-
The SDK was tested using Flutter
2.10.5
and Dart2.16.2
(DevTools2.9.2
). -
The
minSdkVersion
for the Android project should be29
or higher. Check this in yourandroid/app/build.gradle
file: -
See the FAQ if you have issue with setting the minSdkVersion
defaultConfig {
applicationId "za.co.synthesis.halo.sdkflutterplugin_example"
minSdkVersion 29 // <-- this should be 29 or higher
// ...
}
Plugin Installation #
- Run
flutter pub add halo_sdk_flutter_plugin
to add the flutter plugin to your flutter project - We recommend also installing
flutter pub add permission_handler
- The plugin will need to download the SDK binaries from the Halo S3 bucket.
To do this, you will need credentials to access the SDK. Find youraccesskey
andsecretkey
Developer portal .
Add these to yourlocal.properties
file in your android root folder (create one if it doesn't exist):
aws.accesskey=<accesskey>
aws.secretkey=<secretkey>
NB: mind the casing of the values
- Then add this to your
android/app/build.gradle
file (this might already exist):
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) }}
- Finally make sure you have Maven Central for Kotlin
1.3.72
and some more config in theandroid/build.gradle
file:
allprojects {
repositories {
google()
mavenCentral() // <-- add this for kotlin 1.3.72
maven { url 'https://jitpack.io' }
// add this block
configurations.all {
resolutionStrategy.cacheChangingModulesFor 1, 'days'
resolutionStrategy.dependencySubstitution {
substitute(module("androidx.core:core-ktx")).with(module("androidx.core:core-ktx:(*, 1.3.2]"))
substitute(module("org.jetbrains.kotlin:kotlin-stdlib-jdk7")).with(module("org.jetbrains.kotlin:kotlin-stdlib-jdk7:(*, 1.3.72]"))
substitute(module("org.jetbrains.kotlin:kotlin-stdlib-jdk8")).with(module("org.jetbrains.kotlin:kotlin-stdlib-jdk7:(*, 1.3.72]"))
substitute(module("androidx.window:window-java")).with(module("androidx.core:core-ktx:(*, 1.3.2]"))
substitute(module("com.google.firebase:firebase-analytics-ktx")).with(module("com.google.firebase:firebase-analytics-ktx:19.0.0"))
}
}
}
// ...
}
Requirements on the Mobile Back-End #
JWT
All calls made to the Halo.SDK requires a valid JWT.
The details to create the JWT can be found on the Developer portal.
See the section registration
We recommend installing flutter pub add dart_jsonwebtoken
to generate the JWT.
Our recommendation is to the creation of the JWT.
Create two files config.dart
where you will keep the credentials and jwt_token.dart
which will be used to create the JWT.
config.dart
class Config {
static const String privateKeyPem = String.fromEnvironment('PRIVATE_KEY', defaultValue: '');
static const String issuer = "{get from the [Developer portal]}";
static const String username = "{get from the [Developer portal]";
static const String merchantId = "{get from the [Developer portal]";
static const String host = "{get from the [Developer portal]";
static const String aud = "{get from the [Developer portal]";
static const String ksk = "{get from the [Developer portal]";
}
jwt_token.dart
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
import './config.dart'; // include the above config file
class JwtToken {
static String getJwt() {
final jwt = JWT(
{'aud_fingerprints': Config.aud, 'ksk_pin': Config.ksk, 'usr': Config.username},
audience: Audience([Config.host]),
issuer: Config.issuer,
subject: Config.merchantId,
);
final key = RSAPrivateKey(Config.privateKeyPem);
final token = jwt.sign(key, algorithm: JWTAlgorithm.RS512);
return token;
}
}
The private key should not be stored in the repo.
You should provide this JWT to the callback function when onRequestJWT
is invoked by the Halo Dot SDK.
JWT LifeTime
Since the JWT essentially authorizes payment acceptance for a given merchant user, it is essential that the JWT lifetime be kept as short as possible, in order to limit the amount of time an attacker has to crack the key itself and then to limit the scope of damage in the event of a key compromise.
A lifetime of 15 minutes is recommended.
JWT Signing Public Key Format
The JWT public key should be published as a certificate, in a text-friendly format, e.g. B64 encoded PEM (.crt, .pem).
JWT Claims
The JWT must make a number of claims - all of them standard except for aud_fingerprints
(Audience Fingerprints):
field | type | Note |
---|---|---|
alg | String | The signing algorithm is RSA signed SHA-256 hash, aliased as RS256. An asymmetric encryption(signing) scheme is required to allow the Kernel Server to be able to validate the token without being able to generate it. If symmetric key encryption was used to sign the auth token (e.g., using the HMAC algorithm), then non-repudiation would be lost. |
sub | String | The Payment Processor Merchant-User ID, or Application ID |
iss | String | This is a unique (from the perspective of Halo server) identifier for the JWT issuer, agreed upon by the JWT issuer and Synthesis, and configured in advance by Synthesis in the Halo server, e.g. authserver.haloplus.io. This can be retrieved at the Developer portal |
aud | String | URL of Halo server TLS endpoint, e.g. 'kernelserver.qa.haloplus.io'. This value should be obtained from Synthesis (different per environment) e.g. for QA it would be 'kernelserver.qa.haloplus.io' and for DEV 'kernelserver.za.dev.haloplus.io' |
usr | String | The details of the user performing the transaction, typically the username used to sign into the Integrators application. |
iat | NumericDate | The UTC timestamp of when the JWT was generated. |
exp | NumericDate | The UTC time of expiration of the JWT. |
aud_fingerprints | String | a CSV list of expected SHA-256 fingerprints for the Kernel Server TLS endpoint. This list may contain multiple values to support certificate rotation. In the QA environment, the expected value as of writing this would be: "sha256/zc6c97JhKPZUa+rIrVqjknDE1lDcDK77G41sDo+1ay0=" |
All these values can be validated by making a request to https://kernelserver.qa.haloplus.io/<sdk-version>/tokens/checkjwt
.
Method: POST
Header: Bearer Auth
Usage #
- First you need to request the permissions needed by the SDK. Add the following permissions to your
AndroidManifest.xml
file:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="za.co.synthesis.halo.sdkflutterplugin_example">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.NFC"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<!-- .... -->
</manifest xmlns:android="http://schemas.android.com/apk/res/android">
- Add the plugin to help you request permissions in your Flutter application. Add the following to your
pubspec.yaml
file:
dependencies:
permission_handler: ^11.0.0 # ...
Notice the version, later versions expect a higher version of Flutter and Dart.
Also if you use this plugin. Be sure in your android/app/build.gradle
your compileSdkVersion
and targetSdkVersion
are at 34
or higher.
Remember to run flutter pub get
after making changes to your pubspec file
- Request the permissions in your Flutter application before initializing the SDK. Here is an example of how to do this (please handle cases where the user denies the permissions):
We recommend you create a file calledpermission.dart
that will request all needed permissions, see below.
Future<void> checkPermissions() async {
var permissions = [
Permission.camera,
Permission.phone,
Permission.storage,
Permission.notification,
Permission.location,
];
for (var permission in permissions) {
await requestPermission(permission);
}
}
Future<void> requestPermission(Permission permission) async {
var permissionStatus = await permission.status;
if (permissionStatus.isGranted) {
debugPrint("${permission} permission is granted, not requesting");
} else if (permissionStatus.isPermanentlyDenied) {
debugPrint("${permission} permission is permanently denied, enable in settings");
} else {
var requestResult = await permission.request();
if (requestResult.isGranted) {
debugPrint("${permission} permission is granted");
} else if (requestResult.isPermanentlyDenied) {
debugPrint("${permission} permission is permanently denied, enable in settings");
}
}
}
- Your Android
MainActivity
(usually located @ app/src/main/AndroidManifest.xml) class should extendHaloActivity
which hooks into the SDK lifecycle methods for you. (FYI:HaloActivity
extendsFlutterFragmentActivity
).
e.g
import za.co.synthesis.halo.sdkflutterplugin.HaloActivity
class MainActivity: HaloActivity() {
}
- In your Flutter project, you can now use the plugin to interact with the SDK. Here is an example of how to use the plugin:
- First you have to implement the
IHaloCallbacks
interface in your Flutter project. This will allow you to receive callbacks from the SDK. Here is an example of how to do this:
// call this file halo_sdk.dart
import './jwt_token';
class HaloCallbacks implements IHaloCallbacks {
@override
void onAttestationError(HaloAttestationHealthResult details) {
debugPrint("example app: attestation error: $details");
}
@override
void onHaloTransactionResult(HaloTransactionResult result) {
debugPrint("example app: transaction result: $result");
}
@override
void onHaloUIMessage(HaloUIMessage message) {
debugPrint("example app: UI message: $message");
}
@override
void onInitializationResult(HaloInitializationResult result) {
debugPrint("example app: initialization message: $result");
}
@override
void onRequestJWT(void Function(String jwt) callback) {
debugPrint("example app: onRequestJWT");
// JwtToken is imported from jwt_token.dart
var jwt = JwtToken.getJwt(); // you need to pass the created JWT.
callback(jwt);
}
@override
void onSecurityError(errorCode) {
debugPrint("example app: security error: $errorCode");
}
}
You can decide, based on what the SDK sends via the callbacks, how you would like to affect the UI
- Next you want to initialize the SDK, ideally when the Widget that will handle the transaction is opened. Here is an example of how to do this:
void onInitializeSdk(BuildContext context) {
var haloCallbacks = HaloCallbacks(); // Class is define in previous code above
String package = "za.co.synthesis.halo.sdkflutterplugin_example";
String appVersion = "0.0.2";
int onStartTransactionTimeOut = 300000;
try {
Sdkflutterplugin.initializeHaloSDK(haloCallbacks, package, appVersion, onStartTransactionTimeOut);
} on PlatformException catch (e) {
String message = "SDK initialisation error: ${e.code} ${e.message}";
setUiMessage(UiMessage(message, Colors.red));
}
}
- Next you can start a transaction. Here is an example of how to do this:
Sdkflutterplugin.startTransaction(1.00, 'Some merchant reference', 'ZAR');
From this point, a number of UI messages will be pushed to the registered callbacks.
You will use this to show the user the appropriate UI/text.
FAQ #
My android/build.gradle
looks different
: New version of android studio have the version of Kotlin set as ext.kotlin_version = '1.3.72'
How do I set my compileSdkVersion
if it is currently set as flutter.compileSdkVersion
: You can set the compileSdkVersion
in the local.properties
file
sdk.dir=/home/{me}/android-sdk/
flutter.sdk=/home/{me}/fvm/versions/2.10.5
flutter.buildMode=debug
flutter.versionName=1.0.0
flutter.versionCode=1
flutter.compileSdkVersion=34
flutter.minSdkVersion=29
You can then reference this in android/app/build.gradle
file
compileSdkVersion localProperties.getProperty('flutter.compileSdkVersion').toInteger()
How do I set my minSdkVersion
if it is currently set as flutter.minSdkVersion
: See answer above
I am not able to import the Halo SDK.
: * Try opening the android folder from Android studio and running gradle sync
: * Ensure you have the plugin install flutter pub add halo_sdk_flutter_plugin
: * Ensure you have the correct Java 11, Kotlin ^1.3.72, Flutter ^2.10.5
: * Ensure that the minSdkVersion
= 29 or above
: * Ensure that the compileSdkVersion
and targetSdkVersion
are at 34
or higher
: * You have the correct accesskey
and secretkey
setup in local.properties