halo-sdk-react-native 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to `halo-sdk-react-native` will be documented here.
4
+
5
+ ---
6
+
7
+ ## [1.0.2] - 2026-03-03
8
+
9
+ ### Fixed
10
+ - Changed Halo SDK dependency declaration from `compileOnly` to `api` in `build.gradle` so it is exposed transitively to host apps. This prevents a `NoClassDefFoundError: HaloSDK` crash on launch, host apps no longer need to add the Halo SDK as a direct dependency.
11
+
12
+ ### Documentation
13
+ - Updated README to remove the now-unnecessary manual Halo SDK runtime dependency step
14
+ - Fixed incorrect `resultType` check in code examples (`'success'`-> `'Initialized'`)
15
+ - Added Result Types reference table for `HaloInitializationResult` and `HaloTransactionResult`
16
+ - Added FAQ entries for common setup issues (Metro bundler `SyntaxError`, `JWTExpired` on init)
17
+ - Corrected the JWT section to reference official Halo documentation
18
+ - Fixed Full Example to use `requestHaloPermissions()` from `src/permissions.ts`
19
+
20
+ ---
21
+
22
+ ## [1.0.1] - Initial release
23
+
24
+ - Initial React Native plugin wrapping the Halo Dot Android SDK
25
+ - Supports `initialize`, `startTransaction`, `cardRefundTransaction`, and `cancelTransaction`
26
+ - Event-driven callbacks via `NativeEventEmitter`: initialization, transaction result, UI messages, JWT requests, attestation errors, security errors, and camera events
27
+ - Scheme animations (Visa / Mastercard / Amex) on approved transactions via `AnimationActivity`
package/README.md CHANGED
@@ -19,8 +19,6 @@ The diagram below shows the SDK boundary and how it interacts with your app, the
19
19
  - [Native Module Setup](#native-module-setup)
20
20
  - [AndroidManifest Permissions](#androidmanifest-permissions)
21
21
  - [JWT — What It Is and How to Set It Up](#jwt--what-it-is-and-how-to-set-it-up)
22
- - [JWT Lifetime](#jwt-lifetime)
23
- - [JWT Claims](#jwt-claims)
24
22
  - [Usage](#usage)
25
23
  - [Step 1 — Request Permissions](#step-1--request-permissions)
26
24
  - [Step 2 — Set Up Callbacks](#step-2--set-up-callbacks)
@@ -28,6 +26,7 @@ The diagram below shows the SDK boundary and how it interacts with your app, the
28
26
  - [Step 4 — Run a Transaction](#step-4--run-a-transaction)
29
27
  - [Full Example](#full-example)
30
28
  - [API Reference](#api-reference)
29
+ - [Result Types](#result-types)
31
30
  - [Documentation](#documentation)
32
31
  - [Testing](#testing)
33
32
  - [FAQ](#faq)
@@ -50,10 +49,7 @@ You will also need:
50
49
  - A signed Non-Disclosure Agreement (NDA), available on the portal
51
50
  - A JWT — generated via the developer portal or your own backend (see [JWT section](#jwt--what-it-is-and-how-to-set-it-up))
52
51
 
53
- Recommended libraries:
54
- - [`react-native-permissions`](https://www.npmjs.com/package/react-native-permissions) `^4.0.0` — runtime permission handling
55
-
56
- > **Note:** Android is the only supported platform.
52
+ > **Note:** Android is the only supported platform for now.
57
53
 
58
54
  ---
59
55
 
@@ -80,7 +76,7 @@ You must register on the QA (UAT) environment before going to production.
80
76
  If you don't already have a React Native project, create one:
81
77
 
82
78
  ```bash
83
- npx react-native init MyHaloApp --version 0.73.0
79
+ npx react-native init MyHaloApp
84
80
  cd MyHaloApp
85
81
  ```
86
82
 
@@ -114,13 +110,7 @@ npm install halo-sdk-react-native
114
110
  yarn add halo-sdk-react-native
115
111
  ```
116
112
 
117
- **2.** Install `react-native-permissions` (recommended for runtime permission handling):
118
-
119
- ```bash
120
- npm install react-native-permissions
121
- ```
122
-
123
- **3.** The plugin downloads the Halo SDK binaries from an S3-backed Maven repo. Add your credentials (from the [developer portal](#developer-portal-registration)) to `android/local.properties` (create the file if it doesn't exist):
113
+ **2.** The plugin downloads the Halo SDK binaries from an S3-backed Maven repo. Add your credentials (from the [developer portal](#developer-portal-registration)) to `android/local.properties` (create the file if it doesn't exist):
124
114
 
125
115
  ```properties
126
116
  aws.accesskey=YOUR_ACCESS_KEY
@@ -129,7 +119,7 @@ aws.secretkey=YOUR_SECRET_KEY
129
119
 
130
120
  > **Important:** Never commit `local.properties` to source control. Add it to your `.gitignore`
131
121
 
132
- **4.** Add the following snippet to `android/app/build.gradle` so Gradle can read `local.properties` (it may already be there in newer RN templates):
122
+ **3.** Add the following snippet to `android/app/build.gradle` so Gradle can read `local.properties` (it may already be there in newer RN templates):
133
123
 
134
124
  ```gradle
135
125
  def localProperties = new Properties()
@@ -143,7 +133,7 @@ if (localPropertiesFile.exists()) {
143
133
 
144
134
  ### Native Module Setup
145
135
 
146
- **5.** Open `android/app/src/main/kotlin/.../MainActivity.kt` and extend `HaloReactActivity` instead of `ReactActivity`:
136
+ **4.** Open `android/app/src/main/kotlin/.../MainActivity.kt` and extend `HaloReactActivity` instead of `ReactActivity`:
147
137
 
148
138
  ```kotlin
149
139
  import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
@@ -161,7 +151,7 @@ class MainActivity : HaloReactActivity() {
161
151
 
162
152
  This replaces `ReactActivity` so that NFC foreground dispatch and the Halo SDK lifecycle are managed automatically.
163
153
 
164
- **6.** Open `android/app/src/main/kotlin/.../MainApplication.kt` and register `HaloSdkPackage`:
154
+ **5.** Open `android/app/src/main/kotlin/.../MainApplication.kt` and register `HaloSdkPackage`:
165
155
 
166
156
  ```kotlin
167
157
  import android.app.Application
@@ -197,7 +187,7 @@ class MainApplication : Application(), ReactApplication {
197
187
 
198
188
  ### AndroidManifest Permissions
199
189
 
200
- **7.** Add the required permissions to `android/app/src/main/AndroidManifest.xml`:
190
+ **6.** Add the required permissions to `android/app/src/main/AndroidManifest.xml`:
201
191
 
202
192
  ```xml
203
193
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
@@ -251,25 +241,21 @@ class MainApplication : Application(), ReactApplication {
251
241
 
252
242
  ## JWT — What It Is and How to Set It Up
253
243
 
254
- Every call to the Halo SDK must include a valid JWT. Think of it as a short-lived, signed pass that proves your app is authorised to accept payments for a given merchant.
255
-
256
- The JWT is issued either by the [developer portal](https://go.developerportal.qa.haloplus.io/) (for testing) or by your own backend server (for production). The portal verifies it using the RSA **public key** you submitted during registration.
244
+ Every call to the Halo SDK must include a valid JWT. The SDK requests one via the `onRequestJWT` callback whenever it needs to authenticate.
257
245
 
258
- > **For testing:** use the portal's **Generate Token** tool to get a pre-generated JWT. You do not need to implement signing yourself to get started.
246
+ A JWT must be obtained from your backend or generated using your RSA private key and the credentials from the [developer portal](https://go.developerportal.qa.haloplus.io/). See the [Halo developer documentation](https://halo-dot-developer-docs.gitbook.io/halo-dot/sdk) for the required claims. For testing, paste a valid token directly into `Config.tempJwt` as shown below.
259
247
 
260
- **Recommended approach:** split credentials and JWT retrieval into two files.
248
+ Split your config and JWT supply into two files:
261
249
 
262
- **`src/config.ts`** — stores your app's settings (never commit real values to source control):
250
+ **`src/config.ts`** — stores your app settings (never commit real values to source control):
263
251
 
264
252
  ```typescript
265
- // Copy this file to config.ts and fill in your values.
266
- // config.ts should be added to .gitignore.
267
253
  export const Config = {
268
- // Paste a JWT generated from the developer portal here for testing.
269
- // In production, fetch this from your backend instead.
254
+ // Paste a valid JWT here for testing.
255
+ // Regenerate it if the SDK reports JWTExpired on startup.
270
256
  tempJwt: 'eyJ...',
271
257
 
272
- // SDK initialisation options (all obtained from the developer portal)
258
+ // Obtained from the developer portal
273
259
  applicationPackageName: 'com.yourcompany.myapp',
274
260
  applicationVersion: '1.0.0',
275
261
  onStartTransactionTimeOut: 300000, // ms before "tap card" times out
@@ -277,46 +263,20 @@ export const Config = {
277
263
  } as const;
278
264
  ```
279
265
 
280
- **`src/jwt/JwtToken.ts`** supplies the JWT when the SDK asks for one:
266
+ **`src/jwt/JwtToken.ts`**: supplies the JWT when the SDK asks for one:
281
267
 
282
268
  ```typescript
283
269
  import { Config } from '../config';
284
270
 
285
- /**
286
- * Returns the JWT for the Halo SDK's onRequestJWT callback.
287
- *
288
- * For testing: set Config.tempJwt to a token from the developer portal.
289
- * For production: replace this with a fetch from your backend server.
290
- */
291
271
  export function getJwt(): string {
292
272
  if (Config.tempJwt) {
293
273
  return Config.tempJwt;
294
274
  }
295
- throw new Error(
296
- 'No JWT configured. Set Config.tempJwt in src/config.ts, ' +
297
- 'or implement a backend fetch here.',
298
- );
275
+ throw new Error('No JWT configured. Set Config.tempJwt in src/config.ts.');
299
276
  }
300
277
  ```
301
278
 
302
- > **Note:** `getJwt` is synchronous here for simplicity. If you fetch the JWT from a backend, make it `async` and update the `onRequestJWT` callback accordingly.
303
-
304
- ### JWT Lifetime
305
-
306
- Keep the lifetime short — 15 minutes is the recommended maximum. A stolen JWT can only be abused for as long as it remains valid.
307
-
308
- ### JWT Claims
309
-
310
- | Field | Type | Description |
311
- |---|---|---|
312
- | `alg` | String | `RS512` — RSA + SHA-512. Asymmetric signing is required so the server can verify without being able to forge tokens. |
313
- | `iss` | String | Your issuer name, registered on the developer portal (e.g. `authserver.haloplus.io`). |
314
- | `sub` | String | Your merchant or application ID. |
315
- | `aud` | String | The Halo kernel server URL (e.g. `kernelserver.qa.haloplus.io`). |
316
- | `usr` | String | The signed-in operator/user identifier. |
317
- | `iat` | NumericDate | UTC timestamp of when the token was created. |
318
- | `exp` | NumericDate | UTC expiry timestamp. |
319
- | `aud_fingerprints` | String | CSV of SHA-256 TLS fingerprints for the kernel server. Supports multiple values for certificate rotation. |
279
+ > **Important:** Add `config.ts` to `.gitignore` so you never accidentally commit a real token to source control.
320
280
 
321
281
  ---
322
282
 
@@ -376,12 +336,13 @@ export function buildCallbacks(options: {
376
336
  onTransactionResult: (result: HaloTransactionResult) => void;
377
337
  }): IHaloCallbacks {
378
338
  return {
379
- // Called when the SDK finishes starting up
339
+ // Called when the SDK finishes starting up.
340
+ // Check resultType === 'Initialized' for successful init.
380
341
  onInitializationResult(result: HaloInitializationResult) {
381
- if (result.resultType === 'success') {
342
+ if (result.resultType === 'Initialized') {
382
343
  options.onStatusChange('SDK ready — present a card to pay');
383
344
  } else {
384
- options.onError(`Initialisation failed: ${result.errorCode}`);
345
+ options.onError(`Initialisation failed: ${result.resultType} (${result.errorCode})`);
385
346
  }
386
347
  },
387
348
 
@@ -471,7 +432,7 @@ const refund = await HaloSdk.cardRefundTransaction(10.50, 'ORDER-001', 'ZAR');
471
432
  await HaloSdk.cancelTransaction();
472
433
  ```
473
434
 
474
- `startTransaction` and `cardRefundTransaction` resolve immediately once the tap is registered the final payment outcome arrives through `onHaloTransactionResult`.
435
+ `startTransaction` and `cardRefundTransaction` resolve immediately once the tap is registered, the final payment outcome arrives through `onHaloTransactionResult`.
475
436
 
476
437
  ### Full Example
477
438
 
@@ -482,8 +443,6 @@ Below is a minimal but complete payment screen taken directly from the example a
482
443
  import React, { useEffect, useState } from 'react';
483
444
  import {
484
445
  ActivityIndicator,
485
- PermissionsAndroid,
486
- Platform,
487
446
  Text,
488
447
  TextInput,
489
448
  TouchableOpacity,
@@ -496,6 +455,7 @@ import {
496
455
  } from 'halo-sdk-react-native';
497
456
  import { getJwt } from './src/jwt/JwtToken';
498
457
  import { Config } from './src/config';
458
+ import { requestHaloPermissions } from './src/permissions';
499
459
 
500
460
  export default function App() {
501
461
  const [amount, setAmount] = useState('');
@@ -508,33 +468,15 @@ export default function App() {
508
468
  }, []);
509
469
 
510
470
  async function initSdk() {
511
- // Request permissions
512
- if (Platform.OS === 'android') {
513
- const sdkVersion =
514
- typeof Platform.Version === 'number'
515
- ? Platform.Version
516
- : parseInt(Platform.Version, 10);
517
-
518
- const perms: string[] = [
519
- PermissionsAndroid.PERMISSIONS.CAMERA,
520
- PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
521
- ];
522
- if (sdkVersion >= 31) {
523
- perms.push(
524
- PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
525
- PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
526
- );
527
- }
528
- await PermissionsAndroid.requestMultiple(perms);
529
- }
471
+ await requestHaloPermissions();
530
472
 
531
473
  const callbacks: IHaloCallbacks = {
532
474
  onInitializationResult(result) {
533
- setIsReady(result.resultType === 'success');
475
+ setIsReady(result.resultType === 'Initialized');
534
476
  setStatus(
535
- result.resultType === 'success'
477
+ result.resultType === 'Initialized'
536
478
  ? 'Ready — enter amount and tap Charge'
537
- : `Init failed: ${result.errorCode}`,
479
+ : `Init failed: ${result.resultType} (${result.errorCode})`,
538
480
  );
539
481
  },
540
482
  onHaloTransactionResult(result: HaloTransactionResult) {
@@ -656,13 +598,42 @@ Cancels the current in-progress transaction (e.g. if the user presses Cancel whi
656
598
  | Callback | When it fires |
657
599
  |---|---|
658
600
  | `onInitializationResult(result)` | SDK startup complete (success or failure) |
659
- | `onHaloTransactionResult(result)` | Final payment outcome approved, declined, or error |
601
+ | `onHaloTransactionResult(result)` | Final payment outcome; approved, declined, or error |
660
602
  | `onHaloUIMessage(message)` | Prompt to show the user during a tap: `PRESENT_CARD`, `PROCESSING`, `APPROVED`, etc. |
661
- | `onRequestJWT(jwtCallback)` | SDK needs a fresh JWT call `jwtCallback(yourJwtString)` |
603
+ | `onRequestJWT(jwtCallback)` | SDK needs a fresh JWT; call `jwtCallback(yourJwtString)` |
662
604
  | `onAttestationError(details)` | Device integrity check failed (rooted device, emulator, etc.) |
663
605
  | `onSecurityError(errorCode)` | JWT invalid, merchant revoked, or other security failure |
664
606
  | `onCameraControlLost()` | SDK has finished using the camera |
665
607
 
608
+ ### Result Types
609
+
610
+ `resultType` and `errorCode` are plain strings serialised from native Android enums. Use these known values in your conditional logic.
611
+
612
+ **`HaloInitializationResult.resultType`**
613
+
614
+ | Value | Meaning |
615
+ |---|---|
616
+ | `'Initialized'` | SDK ready; safe to call `startTransaction` |
617
+ | `'RemoteAttestationFailure'` | Cloud attestation failed; inspect `errorCode` for the reason |
618
+ | `'LocalAttestationFailure'` | Device integrity check failed (rooted device, emulator, etc.) |
619
+
620
+ **`HaloInitializationResult.errorCode`** (when `resultType !== 'Initialized'`)
621
+
622
+ | Value | Meaning |
623
+ |---|---|
624
+ | `'OK'` | No error — accompanies `resultType: 'Initialized'` |
625
+ | `'JWTExpired'` | The JWT has expired — generate a fresh one |
626
+
627
+
628
+ **`HaloTransactionResult.resultType`**
629
+
630
+ | Value | Meaning |
631
+ |---|---|
632
+ | `'Approved'` | Transaction approved by the payment network |
633
+ | `'Declined'` | Card declined |
634
+ | `'Cancelled'` | Transaction cancelled (e.g. via `cancelTransaction()`) |
635
+ | `'Error'` | An error occurred — inspect `errorCode` and `errorDetails` |
636
+
666
637
  ---
667
638
 
668
639
  ## Documentation
@@ -686,7 +657,7 @@ You can simulate card taps using a virtual NFC card app such as [Visa Mobile CDE
686
657
  You can define them in `android/local.properties` and read them in Gradle:
687
658
 
688
659
  ```properties
689
- sdk.dir=/Users/yourname/Library/Android/sdk
660
+ sdk.dir=C\:\\Users\\yourname\\Library\\Android/Sdk
690
661
  aws.accesskey=YOUR_ACCESS_KEY
691
662
  aws.secretkey=YOUR_SECRET_KEY
692
663
  compileSdkVersion=34
@@ -726,6 +697,16 @@ compileSdkVersion localProperties.getProperty('compileSdkVersion').toInteger()
726
697
 
727
698
  ---
728
699
 
729
- **Q: How do I get a test JWT quickly without implementing RS512 signing?**
700
+ **Q: How do I get a JWT for testing?**
701
+
702
+ A JWT must be generated using your RSA private key and the credentials from the [developer portal](https://go.developerportal.qa.haloplus.io/). See the [Halo developer documentation](https://halo-dot-developer-docs.gitbook.io/halo-dot/sdk) for the required claims and signing algorithm. Once you have a valid token, paste it into `Config.tempJwt` in your `config.ts`.
703
+
704
+ ---
705
+
706
+ **Q: `onInitializationResult` fires with `resultType: 'RemoteAttestationFailure'` and `errorCode: 'JWTExpired'`.**
707
+
708
+ Your temp JWT has expired. Developer-portal tokens are short-lived (typically 15 minutes). This is expected behaviour — the SDK fires a failure callback with the cached/expired token, then automatically requests a new JWT via `onRequestJWT` and retries. If `onInitializationResult` fires a **second time** shortly after with `resultType: 'Initialized'`, everything is working correctly.
730
709
 
731
- Log into the [developer portal](https://go.developerportal.qa.haloplus.io/), navigate to your app, and use the portal's **Generate Token** tool. Paste the result into `Config.tempJwt` in your `config.ts`. Remember to implement proper signing before going to production.
710
+ If the second successful callback never arrives:
711
+ 1. Generate a fresh token and update `Config.tempJwt`
712
+ 2. Make sure your `onRequestJWT` callback calls `jwtCallback(yourJwt)` synchronously — async calls are not supported without extra handling
@@ -81,8 +81,8 @@ dependencies {
81
81
  implementation 'androidx.appcompat:appcompat:1.6.1'
82
82
 
83
83
  // Halo SDK
84
- releaseImplementation group: "za.co.synthesis.halo", name: "sdk", version: "$sdkVersion", changing: true
85
- debugImplementation group: "za.co.synthesis.halo", name: "sdk", version: "$sdkVersion-debug", changing: true
84
+ releaseApi group: "za.co.synthesis.halo", name: "sdk", version: "$sdkVersion", changing: true
85
+ debugApi group: "za.co.synthesis.halo", name: "sdk", version: "$sdkVersion-debug", changing: true
86
86
 
87
87
  implementation(name: 'VisaSensoryBranding', ext: 'aar')
88
88
  implementation(name: 'sonic-sdk-release-1.5.0', ext: 'aar')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "halo-sdk-react-native",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "React Native plugin for Halo SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -10,7 +10,8 @@
10
10
  "android/libs",
11
11
  "android/build.gradle",
12
12
  "android/settings.gradle",
13
- "README.md"
13
+ "README.md",
14
+ "CHANGELOG.md"
14
15
  ],
15
16
  "scripts": {
16
17
  "build": "tsc"