@glomopay/react-native-sdk 4.1.0 → 5.0.0

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.
Files changed (124) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/MIGRATION.md +112 -0
  3. package/README.md +173 -36
  4. package/lib/bridge/file-input-handler.d.ts +25 -0
  5. package/lib/bridge/file-input-handler.d.ts.map +1 -0
  6. package/lib/bridge/file-input-handler.js +38 -0
  7. package/lib/bridge/file-save-handler.d.ts +32 -0
  8. package/lib/bridge/file-save-handler.d.ts.map +1 -0
  9. package/lib/bridge/file-save-handler.js +92 -0
  10. package/lib/bridge/message-router.d.ts +44 -0
  11. package/lib/bridge/message-router.d.ts.map +1 -0
  12. package/lib/bridge/message-router.js +48 -0
  13. package/lib/bridge/payment-payload.d.ts +18 -0
  14. package/lib/bridge/payment-payload.d.ts.map +1 -0
  15. package/lib/bridge/payment-payload.js +31 -0
  16. package/lib/capabilities/file-save.d.ts +40 -0
  17. package/lib/capabilities/file-save.d.ts.map +1 -0
  18. package/lib/capabilities/file-save.js +101 -0
  19. package/lib/capabilities/index.d.ts +22 -0
  20. package/lib/capabilities/index.d.ts.map +1 -0
  21. package/lib/capabilities/index.js +34 -0
  22. package/lib/capabilities/use-capability-reporting.d.ts +24 -0
  23. package/lib/capabilities/use-capability-reporting.d.ts.map +1 -0
  24. package/lib/capabilities/use-capability-reporting.js +44 -0
  25. package/lib/config/base.d.ts +12 -0
  26. package/lib/config/base.d.ts.map +1 -1
  27. package/lib/config/base.js +13 -1
  28. package/lib/glomo-checkout.d.ts +6 -1
  29. package/lib/glomo-checkout.d.ts.map +1 -1
  30. package/lib/glomo-checkout.js +11 -5
  31. package/lib/glomo-lrs-checkout.d.ts.map +1 -1
  32. package/lib/glomo-lrs-checkout.js +33 -10
  33. package/lib/glomo-standard-checkout.d.ts +3 -1
  34. package/lib/glomo-standard-checkout.d.ts.map +1 -1
  35. package/lib/glomo-standard-checkout.js +21 -13
  36. package/lib/glomo-subscriptions-checkout.d.ts.map +1 -1
  37. package/lib/glomo-subscriptions-checkout.js +1 -1
  38. package/lib/index.d.ts +2 -2
  39. package/lib/index.d.ts.map +1 -1
  40. package/lib/index.js +4 -3
  41. package/lib/injections/index.d.ts +24 -0
  42. package/lib/injections/index.d.ts.map +1 -1
  43. package/lib/injections/index.js +41 -0
  44. package/lib/injections/webview-flow.injection.d.ts.map +1 -1
  45. package/lib/injections/webview-flow.injection.js +76 -0
  46. package/lib/injections/webview-main.injection.d.ts.map +1 -1
  47. package/lib/injections/webview-main.injection.js +31 -0
  48. package/lib/injections/webview-standard.injection.d.ts.map +1 -1
  49. package/lib/injections/webview-standard.injection.js +31 -0
  50. package/lib/services/file-saver.d.ts +109 -0
  51. package/lib/services/file-saver.d.ts.map +1 -0
  52. package/lib/services/file-saver.js +416 -0
  53. package/lib/types/checkout.d.ts +58 -6
  54. package/lib/types/checkout.d.ts.map +1 -1
  55. package/lib/types/checkout.js +24 -2
  56. package/lib/types/standard-checkout.d.ts +6 -6
  57. package/lib/types/standard-checkout.d.ts.map +1 -1
  58. package/lib/types/subscriptions-checkout.d.ts +3 -3
  59. package/lib/types/subscriptions-checkout.d.ts.map +1 -1
  60. package/lib/use-glomo-checkout.js +2 -2
  61. package/lib/use-lrs-checkout.d.ts +9 -5
  62. package/lib/use-lrs-checkout.d.ts.map +1 -1
  63. package/lib/use-lrs-checkout.js +317 -171
  64. package/lib/use-standard-checkout.d.ts +17 -13
  65. package/lib/use-standard-checkout.d.ts.map +1 -1
  66. package/lib/use-standard-checkout.js +281 -207
  67. package/lib/utils/analytics.d.ts +177 -51
  68. package/lib/utils/analytics.d.ts.map +1 -1
  69. package/lib/utils/analytics.js +299 -81
  70. package/lib/utils/device-compliance.d.ts +57 -12
  71. package/lib/utils/device-compliance.d.ts.map +1 -1
  72. package/lib/utils/device-compliance.js +101 -37
  73. package/lib/utils/event-projections.d.ts +56 -0
  74. package/lib/utils/event-projections.d.ts.map +1 -0
  75. package/lib/utils/event-projections.js +42 -0
  76. package/lib/utils/file-metadata.d.ts +52 -0
  77. package/lib/utils/file-metadata.d.ts.map +1 -0
  78. package/lib/utils/file-metadata.js +134 -0
  79. package/lib/utils/use-sdk-session.d.ts +12 -0
  80. package/lib/utils/use-sdk-session.d.ts.map +1 -0
  81. package/lib/utils/use-sdk-session.js +37 -0
  82. package/lib/utils/validation.d.ts +37 -3
  83. package/lib/utils/validation.d.ts.map +1 -1
  84. package/lib/utils/validation.js +64 -8
  85. package/lib/webview/capability-props.d.ts +27 -0
  86. package/lib/webview/capability-props.d.ts.map +1 -0
  87. package/lib/webview/capability-props.js +61 -0
  88. package/lib/webview/use-permission-request.d.ts +33 -0
  89. package/lib/webview/use-permission-request.d.ts.map +1 -0
  90. package/lib/webview/use-permission-request.js +89 -0
  91. package/package.json +10 -2
  92. package/src/bridge/file-input-handler.ts +66 -0
  93. package/src/bridge/file-save-handler.ts +141 -0
  94. package/src/bridge/message-router.ts +92 -0
  95. package/src/bridge/payment-payload.ts +31 -0
  96. package/src/capabilities/file-save.ts +124 -0
  97. package/src/capabilities/index.ts +34 -0
  98. package/src/capabilities/use-capability-reporting.ts +55 -0
  99. package/src/config/base.ts +13 -0
  100. package/src/glomo-checkout.tsx +11 -7
  101. package/src/glomo-lrs-checkout.tsx +41 -26
  102. package/src/glomo-standard-checkout.tsx +31 -34
  103. package/src/glomo-subscriptions-checkout.tsx +1 -0
  104. package/src/index.ts +4 -2
  105. package/src/injections/index.ts +43 -0
  106. package/src/injections/webview-flow.injection.ts +76 -0
  107. package/src/injections/webview-main.injection.ts +31 -0
  108. package/src/injections/webview-standard.injection.ts +31 -0
  109. package/src/services/file-saver.ts +497 -0
  110. package/src/types/checkout.ts +62 -6
  111. package/src/types/standard-checkout.ts +6 -6
  112. package/src/types/subscriptions-checkout.ts +7 -3
  113. package/src/use-glomo-checkout.tsx +2 -2
  114. package/src/use-lrs-checkout.tsx +656 -365
  115. package/src/use-standard-checkout.tsx +728 -466
  116. package/src/utils/analytics.ts +422 -117
  117. package/src/utils/device-compliance.ts +148 -38
  118. package/src/utils/event-projections.ts +74 -0
  119. package/src/utils/file-metadata.ts +140 -0
  120. package/src/utils/use-sdk-session.ts +41 -0
  121. package/src/utils/validation.ts +70 -10
  122. package/src/webview/capability-props.ts +86 -0
  123. package/src/webview/use-permission-request.ts +114 -0
  124. package/src/jail-monkey.d.ts +0 -19
package/CHANGELOG.md CHANGED
@@ -5,6 +5,35 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [5.0.0] - 2026-09-22
9
+
10
+ ### Breaking Changes
11
+
12
+ - **`jail-monkey` is now a required peer dependency, and device compliance fails closed.** It was optional in v4 and the SDK warned and proceeded when it was missing. A device whose security cannot be verified is now refused on a `live_` key rather than assumed clean: `start()` returns `false` and `onSdkError` fires with `type: "device_forbidden"`. Mock and test keys are never blocked, so this does not show up in a `test_` key QA pass. **See [MIGRATION.md](MIGRATION.md) first** - this can take a working live integration off the air.
13
+ - **`onUserRefusedCameraPermissions` renamed to `onUserRefusedDevicePermissions`.** The signature is unchanged, so the compiler points at every call site. The new name reflects what the callback covers - a device permission the bank's page asked for - rather than the camera specifically.
14
+ - **`paymentId` and `signature` are optional on `GlomoCheckoutPayload`.** The checkout page can report a terminal outcome before the backend has resolved those ids; v4 dropped such outcomes entirely rather than report an incomplete one. With `strict` enabled this surfaces as a compile error wherever either value is forwarded to something expecting a `string`, which is the intended guardrail.
15
+ - **`onPaymentTerminate` receives a `GlomoTerminationPayload`.** Carries `source`, from the new `TERMINATION_SOURCES` enum. An existing `() => void` handler stays assignable and compiles unchanged.
16
+
17
+ ### Added
18
+
19
+ - **Document downloads.** A checkout page can now ask the SDK to save a document to the device, which a page cannot do for itself inside a WebView. Available on every flow. Requires two optional peer dependencies - `react-native-blob-util` and `@react-native-documents/picker` - and a rebuild of the app after installing them; without both the SDK does not advertise the capability and the page keeps its existing behaviour, so nothing breaks by leaving them out. A save that fails reports through `onSdkError` with `field: "file.save"` and never affects the payment session.
20
+ - **`TERMINATION_SOURCES` enum and `GlomoTerminationPayload` type**, both exported.
21
+ - Emulators and simulators now pass device compliance on live keys. The SDK computes its own root verdict from `jail-monkey`'s per-check breakdown instead of its aggregate, which reports every stock Android emulator image as rooted. Every strong root indicator is still enforced.
22
+ - `product` and `surface` query parameters on the checkout and education carousel URLs, so an SDK-originated order is distinguishable from a dashboard one.
23
+
24
+ ### Fixed
25
+
26
+ - **A `mock_` public key ran in live mode.** It passed validation and then had `mode=live` built into its checkout URL.
27
+ - **Camera now works on LRS checkouts.** The LRS flow had no permission handling wired on either platform, so a bank page requesting camera access got no prompt on Android and no grant on iOS. It now behaves identically to the standard checkout.
28
+ - **`onUserRefusedDevicePermissions` is now forwarded to LRS.** The unified `GlomoCheckout` accepted the callback and advertised it in its types, but never passed it to the LRS flow - so on an LRS order it could never fire. Note it remains Android-only: on iOS the WebView raises its own prompt and never reports the answer back.
29
+ - **Terminal payment outcomes are no longer dropped silently.** A payload the SDK could not route left no trace in a release build - no callback, no telemetry, nothing for support to find. It now reports through `onSdkError` and telemetry.
30
+ - The SDK's back chevron has a touch target that meets the 44dp / 44pt minimum, an accessibility label and a button role.
31
+
32
+ ### Internal
33
+
34
+ - WebView configuration, permission handling and bridge message routing moved into a shared layer used by every checkout flow, so capabilities land on all flows at once rather than being wired per flow.
35
+ - Improved SDK diagnostics: events now correlate per checkout attempt, carry current checkout status, and record dependency availability, upload attempts, document save outcomes and refused device permissions.
36
+
8
37
  ## [4.0.0] - 2026-06-11
9
38
 
10
39
  ### Breaking Changes
package/MIGRATION.md CHANGED
@@ -1,5 +1,117 @@
1
1
  # Migration Guide
2
2
 
3
+ ## v4 to v5
4
+
5
+ ### Start here: `jail-monkey` is now required, and it can block live checkouts
6
+
7
+ This is the one change that can take a working integration off the air, so do it before anything else.
8
+
9
+ `jail-monkey` was an optional peer dependency in v4. The SDK warned when it was missing and carried on. In v5 it is a required peer dependency and the compliance gate **fails closed**: a device whose security the SDK cannot verify is refused on a `live_` key rather than assumed clean.
10
+
11
+ ```bash
12
+ npm install jail-monkey
13
+ cd ios && pod install
14
+ ```
15
+
16
+ Then rebuild the app. Installing without rebuilding leaves the native module unlinked, which the SDK treats exactly as if the package were absent.
17
+
18
+ If you skip this, every live checkout returns `false` from `start()` and fires `onSdkError` with `type: "device_forbidden"`. Mock and test keys are unaffected, so this will not show up in a `test_` key QA pass — check with a live key before you ship.
19
+
20
+ **Emulators are no longer a problem.** v4 relied on `jail-monkey`'s aggregate verdict, which reports stock Android emulator images as rooted because they ship AOSP test-keys and debuggable system properties. v5 computes its own verdict from the per-check breakdown and ignores those two signals, so integration-testing with a live key on an emulator now works. Every strong root indicator is still enforced.
21
+
22
+ ### Renamed Callbacks
23
+
24
+ | v4 | v5 |
25
+ |---|---|
26
+ | `onUserRefusedCameraPermissions` | `onUserRefusedDevicePermissions` |
27
+
28
+ The rename is mechanical - the signature is unchanged and the compiler points at every call site. The new name reflects what the callback has always covered: a device permission the bank's page asked for, rather than the camera specifically.
29
+
30
+ ### `onUserRefusedDevicePermissions` now works on LRS checkouts
31
+
32
+ Previously the unified `GlomoCheckout` accepted this callback but never forwarded it to the LRS flow, so it could not fire on an LRS order - and LRS had no camera support at all on either platform. Both are fixed. If your integration is LRS-only and you skipped this callback because it never fired, it is now worth supplying.
33
+
34
+ It remains Android-only: on iOS the WebView raises its own permission prompt and never reports the answer back, so the SDK cannot know what the user chose.
35
+
36
+ ### `paymentId` and `signature` are now optional on payment payloads
37
+
38
+ ```ts
39
+ interface GlomoCheckoutPayload {
40
+ orderId: string;
41
+ paymentId?: string; // was: string
42
+ signature?: string; // was: string
43
+ }
44
+ ```
45
+
46
+ This is a widening, not a new failure mode. The checkout page has always been able to report a terminal outcome before the backend resolved those ids — open banking completes on the bank's side first — and v4 responded by **dropping the outcome entirely** behind a `devMode`-only log. In a release build that meant no callback, no telemetry, and no trace of a payment that actually happened. v5 reports it with the fields it has.
47
+
48
+ With `strict` on, this surfaces at compile time anywhere you forward either value to something expecting a `string`:
49
+
50
+ ```tsx
51
+ onPaymentSuccess={(payload) => {
52
+ // v4: compiled
53
+ // v5: Type 'string | undefined' is not assignable to type 'string'
54
+ confirmOnOurBackend(payload.paymentId);
55
+
56
+ // v5: narrow first
57
+ if (payload.paymentId && payload.signature) {
58
+ confirmOnOurBackend(payload.paymentId, payload.signature);
59
+ } else {
60
+ awaitWebhookFor(payload.orderId);
61
+ }
62
+ }}
63
+ ```
64
+
65
+ That error is the migration, and it is the point: the alternative was forwarding `undefined` to your backend at runtime.
66
+
67
+ ### `onPaymentTerminate` now receives a payload
68
+
69
+ ```ts
70
+ interface GlomoTerminationPayload {
71
+ source: TERMINATION_SOURCES; // "checkout_closed" | "device_back_button" | "modal_dismiss"
72
+ }
73
+ ```
74
+
75
+ **Nothing breaks.** A handler may take fewer parameters than the signature provides, so your existing `() => void` stays assignable and compiles unchanged. Take the payload when you want to tell the page's own close control apart from a device gesture.
76
+
77
+ The back chevron over a bank flow still does **not** fire this callback, deliberately. It returns the customer to the checkout with the session intact and the ability to pay. If you cancel the order on terminate, wiring the chevron here would cancel live orders every time a customer backed out of one bank to try another.
78
+
79
+ ### A `mock_` public key now runs in mock mode
80
+
81
+ `isValidPublicKey` accepted `live_`, `mock_` and `test_`, but only `test_` selected mock mode — so a `mock_` key passed validation and then ran against live. If you have been using a `mock_` key and compensating for it, stop.
82
+
83
+ ### New: document downloads (optional)
84
+
85
+ Some checkout flows offer a document for the customer to save. Install both packages and rebuild to enable it:
86
+
87
+ ```bash
88
+ npm install react-native-blob-util @react-native-documents/picker
89
+ cd ios && pod install
90
+ ```
91
+
92
+ On React Native < 0.79, pin `@react-native-documents/picker` to `10.1.x`.
93
+
94
+ Leaving them out is supported: the SDK does not advertise the capability, the page keeps its own download behaviour, and nothing breaks. `start()` warns on the console, and `onSdkError` fires once per mount — see below, because that last part changes what `onSdkError` means.
95
+
96
+ ### `onSdkError` can now report a missing capability, not just a misconfiguration
97
+
98
+ **Check this if your `onSdkError` handler aborts the checkout or shows a blocking error.**
99
+
100
+ In v4, `onSdkError` only ever meant the integration was misconfigured — a malformed `publicKey`, a missing `orderId` — so treating any error on that callback as fatal was a reasonable reading. In v5 it also fires once per mount, with `type: "validation_error"` and `field: "file.save"`, when the two document-download packages above are not installed. Nothing is wrong in that case: the checkout works and the page keeps its own download path.
101
+
102
+ If you treat every `onSdkError` as fatal and you have not installed those packages, your checkout will now stop on a condition that is not an error. Filter on `field` rather than on `type`:
103
+
104
+ ```tsx
105
+ onSdkError={(errors) => {
106
+ const blocking = errors.filter((e) => e.field !== "file.save");
107
+ if (blocking.length > 0) {
108
+ // your existing handling
109
+ }
110
+ }}
111
+ ```
112
+
113
+ The report is deliberately not tied to whether a page actually requests a download: whether this build can write a file to the device is a property of the build, so you learn it once at integration rather than the first time a customer taps download in production.
114
+
3
115
  ## v3 to v4
4
116
 
5
117
  v4 consolidates the asynchronous payment flow callbacks into a single generic callback.
package/README.md CHANGED
@@ -12,39 +12,74 @@ Official React Native SDK for integrating GlomoPay payment checkout flows into y
12
12
 
13
13
  ## System Requirements
14
14
 
15
- | Requirement | Version |
16
- | ---------------------- | ------------ |
17
- | Node.js | >= 16.0.0 |
18
- | npm | >= 8.0.0 |
19
- | React | >= 17.0.0 |
20
- | React Native | >= 0.68.0 |
21
- | react-native-webview | ^13.0.0 |
22
- | jail-monkey (optional) | ^2.6.0 |
15
+ | Requirement | Version |
16
+ | ------------------------------------------ | --------- |
17
+ | Node.js | >= 16.0.0 |
18
+ | npm | >= 8.0.0 |
19
+ | React | >= 17.0.0 |
20
+ | React Native | >= 0.68.0 |
21
+ | react-native-webview | ^13.0.0 |
22
+ | jail-monkey | ^2.6.0 |
23
+ | react-native-blob-util (optional) | ^0.25.0 |
24
+ | @react-native-documents/picker (optional) | >= 10.1.3 |
23
25
 
24
26
  ## Installation
25
27
 
26
28
  ### React Native CLI
27
29
 
28
30
  ```bash
29
- npm install @glomopay/react-native-sdk react-native-webview
31
+ npm install @glomopay/react-native-sdk react-native-webview jail-monkey
30
32
  cd ios && pod install
31
33
  ```
32
34
 
33
35
  ### Expo
34
36
 
35
37
  ```bash
36
- npx expo install @glomopay/react-native-sdk react-native-webview
38
+ npx expo install @glomopay/react-native-sdk react-native-webview jail-monkey
37
39
  ```
38
40
 
39
- ### Recommended: Device Security Compliance
41
+ ### Required: Device Security Compliance
40
42
 
41
- For rooted/jailbroken device detection (strongly recommended for regulated flows):
43
+ `jail-monkey` is a **required** peer dependency as of v5. It was optional in v4.
42
44
 
43
45
  ```bash
44
46
  npm install jail-monkey
45
47
  ```
46
48
 
47
- The SDK works without `jail-monkey`, but installing it enables automatic device security checks. Without it, the SDK logs a warning on every `start()` call.
49
+ **Without it, live checkouts are blocked.** The SDK cannot tell a clean device from a compromised
50
+ one, and on a `live_` key it refuses to start rather than assume the device is safe. `start()`
51
+ returns `false` and `onSdkError` fires with `type: "device_forbidden"`.
52
+
53
+ Test and mock keys are never blocked, so QA on a rooted handset or an emulator still works.
54
+
55
+ ### Optional: Document Downloads
56
+
57
+ Some checkout flows offer a document (an agreement PDF, for example) for the customer to save. A
58
+ web page cannot write a file to the device from inside a WebView, so the SDK does it — but only if
59
+ both of these are installed:
60
+
61
+ ```bash
62
+ npm install react-native-blob-util @react-native-documents/picker
63
+ cd ios && pod install
64
+ ```
65
+
66
+ Leaving them out is a supported choice: the SDK does not advertise the capability, the checkout page
67
+ keeps its own download behaviour, and nothing breaks.
68
+
69
+ Five things worth knowing before you install them:
70
+
71
+ 1. **Installing is not enabling.** Both ship native code, so the app must be rebuilt. Until then the
72
+ SDK treats them exactly as if they were absent.
73
+ 2. **Reset the Metro cache** after installing (`npx react-native start --reset-cache`). A stale
74
+ bundle resolves the old module graph and the capability stays off.
75
+ 3. **Expo needs a development build.** Neither package works in Expo Go. `react-native-blob-util`
76
+ ships a config plugin; `@react-native-documents/picker` does not, so add it to your config
77
+ plugins list yourself if your setup needs one.
78
+ 4. **`@react-native-documents/picker` 11 and above requires React Native >= 0.79.** On older React
79
+ Native, pin `10.1.x` — the API this SDK uses is identical.
80
+ 5. **Degradation is silent by design**, which is why it is noisy in development: `start()` logs an
81
+ ungated warning naming the missing package, and `onSdkError` fires once per mount with
82
+ `field: "file.save"`.
48
83
 
49
84
  ## Quick Start
50
85
 
@@ -99,8 +134,8 @@ export default function PaymentScreen() {
99
134
  break;
100
135
  }
101
136
  }}
102
- onUserRefusedCameraPermissions={() => {
103
- console.log("User refused camera - cannot proceed with bank authentication");
137
+ onUserRefusedDevicePermissions={() => {
138
+ console.log("User refused a device permission - cannot proceed with bank authentication");
104
139
  }}
105
140
  onSdkError={(errors: SdkError[]) => {
106
141
  errors.forEach((e) => console.error(e.type, e.message, e.field));
@@ -111,6 +146,27 @@ export default function PaymentScreen() {
111
146
  }
112
147
  ```
113
148
 
149
+ ## Breaking Changes (v5)
150
+
151
+ - **`jail-monkey` is now required, and an unverifiable device blocks a live checkout.** It was an
152
+ optional peer dependency in v4 and warned when absent; it now fails closed. Mock and test keys are
153
+ never blocked.
154
+ - **`onUserRefusedCameraPermissions` renamed to `onUserRefusedDevicePermissions`.** Signature
155
+ unchanged, so the compiler points at every call site.
156
+ - **`paymentId` and `signature` are optional on payment payloads.** The page can report a terminal
157
+ outcome before the backend has resolved them, and the SDK used to drop those outcomes entirely
158
+ rather than report an incomplete one. With `strict` on, this surfaces as a compile error wherever
159
+ you forward the value to something expecting a `string` — that narrowing check is the point.
160
+ - **`onPaymentTerminate` now receives a payload.** `GlomoTerminationPayload` carries a `source` from
161
+ the new `TERMINATION_SOURCES` enum. An existing `() => void` handler stays assignable, so this
162
+ compiles unchanged.
163
+ - **A `mock_` public key now runs in mock mode.** It passed validation in v4 and then ran against
164
+ live, which was a bug.
165
+ - New exports: `TERMINATION_SOURCES` enum and `GlomoTerminationPayload` type.
166
+ - New optional capability: document downloads, behind two optional peer dependencies.
167
+
168
+ For migration details, see MIGRATION.md included in this package.
169
+
114
170
  ## Breaking Changes (v4)
115
171
 
116
172
  - `GlomoLrsCheckout` has been replaced by `GlomoCheckout` - the new component now works for all checkout orders (including LRS)
@@ -158,8 +214,9 @@ When `subscriptionId` is provided:
158
214
  - **TypeScript** - full type definitions for all exports
159
215
  - **Unified checkout** - renders the correct checkout flow automatically based on your order
160
216
  - **Asynchronous payment flows** - single `onUserJourneyCompleted` callback for bank transfer and pay via bank events
161
- - **Camera permissions** - built-in handling for bank authentication
162
- - **Device security** - optional **(but strongly recommended)** `jail-monkey` integration for rooted/jailbroken device detection
217
+ - **Device permissions** - built-in camera handling for bank authentication, on every checkout flow
218
+ - **Document downloads** - saves a document the page offers to the customer's device, on every checkout flow, behind two optional peer dependencies
219
+ - **Device security** - required `jail-monkey` integration for rooted/jailbroken device detection, failing closed on live keys
163
220
  - **Input validation** - publicKey and orderId format enforcement before checkout starts
164
221
  - **Mock mode** - test with `test_` / `mock_` prefixed keys without hitting production
165
222
 
@@ -214,10 +271,10 @@ The unified checkout component. Place it in your render tree and control it via
214
271
  | `onPaymentSuccess` | `(payload: GlomoCheckoutPayload) => void` | Yes | Called when payment succeeds |
215
272
  | `onPaymentFailure` | `(payload: GlomoCheckoutPayload) => void` | Yes | Called when payment fails |
216
273
  | `onConnectionError` | `(error: unknown) => void` | No | Called on network/connection errors |
217
- | `onPaymentTerminate` | `() => void` | No | Called when user dismisses checkout (back button or swipe) |
274
+ | `onPaymentTerminate` | `(payload: GlomoTerminationPayload) => void` | No | Called when the checkout ends without a payment outcome. `payload.source` names how. An existing `() => void` handler stays assignable. |
218
275
  | `onSdkError` | `(errors: SdkError[]) => void` | Yes | Called on validation errors or device compliance failures |
219
- | `onUserJourneyCompleted` | `(payload: GlomoUserJourneyCompletedPayload) => void` | No | Called when an asynchronous payment flow completes (bank transfer submission, pay via bank completion). Check `payload.journeyType` to determine the flow. |
220
- | `onUserRefusedCameraPermissions` | `() => void` | No | Called when user denies camera access needed for bank authentication |
276
+ | `onUserJourneyCompleted` | `(payload: GlomoUserJourneyCompletedPayload) => void` | No | Called when an asynchronous payment flow completes (bank transfer submission, pay via bank completion) - not a confirmed payment. Check `payload.journeyType` to determine the flow. Accepted on `GlomoCheckout` but silently ignored on LRS orders - LRS never produces an asynchronous journey, so it never fires there. |
277
+ | `onUserRefusedDevicePermissions` | `() => void` | No | Called when the user denies a device permission the bank's page asked for - camera today. Android only; see [Device Permissions](#device-permissions). |
221
278
 
222
279
  *Exactly one of `orderId` or `subscriptionId` must be provided. If both or neither are set, `onSdkError` fires and `start()` returns `false`.
223
280
 
@@ -248,11 +305,16 @@ The unified checkout component. Place it in your render tree and control it via
248
305
  ```ts
249
306
  interface GlomoCheckoutPayload {
250
307
  orderId: string;
251
- paymentId: string;
252
- signature: string;
308
+ paymentId?: string;
309
+ signature?: string;
253
310
  }
254
311
  ```
255
312
 
313
+ `paymentId` and `signature` became optional in v5. The checkout page can report a terminal outcome
314
+ before the backend has resolved them - open banking completes on the bank's side first - and the SDK
315
+ previously dropped those outcomes rather than report an incomplete one. Narrow before forwarding
316
+ either value.
317
+
256
318
  #### ASYNC_PAYMENT_EVENTS
257
319
 
258
320
  ```ts
@@ -274,16 +336,52 @@ interface GlomoUserJourneyCompletedPayload {
274
336
  }
275
337
  ```
276
338
 
339
+ #### TERMINATION_SOURCES
340
+
341
+ ```ts
342
+ enum TERMINATION_SOURCES {
343
+ CHECKOUT_CLOSED = "checkout_closed", // the page's own close control
344
+ DEVICE_BACK_BUTTON = "device_back_button", // Android hardware back or back gesture
345
+ MODAL_DISMISS = "modal_dismiss", // iOS swipe-down on the modal
346
+ }
347
+ ```
348
+
349
+ The back chevron over a bank flow is deliberately **not** in this list. It returns the user to the
350
+ checkout with the session intact and the ability to pay, so reporting it as a termination would be
351
+ false - and for a merchant who cancels the order on terminate, it would cancel live orders every
352
+ time a customer backed out of one bank to try another.
353
+
354
+ #### GlomoTerminationPayload
355
+
356
+ ```ts
357
+ interface GlomoTerminationPayload {
358
+ source: TERMINATION_SOURCES;
359
+ }
360
+ ```
361
+
362
+ An object rather than a bare enum, so a later field can be added without changing the signature or
363
+ redefining what an existing value means.
364
+
277
365
  #### SdkError
278
366
 
279
367
  ```ts
280
368
  interface SdkError {
281
369
  type: "validation_error" | "device_forbidden";
282
370
  message: string;
283
- field?: "publicKey" | "orderId" | "subscriptionId" | "baseCheckoutUrl" | "generatedCheckoutUrl";
371
+ field?:
372
+ | "publicKey"
373
+ | "orderId"
374
+ | "subscriptionId"
375
+ | "baseCheckoutUrl"
376
+ | "generatedCheckoutUrl"
377
+ | "file.save";
284
378
  }
285
379
  ```
286
380
 
381
+ The first five name configuration you supplied. `file.save` is a bridge message type, and is here so
382
+ a document that failed to save can be filtered apart from a bad public key on a callback that carries
383
+ both — see [Document downloads](#document-downloads) for when it fires.
384
+
287
385
  ## Asynchronous Payment Flows
288
386
 
289
387
  The SDK fires `onUserJourneyCompleted` when an asynchronous payment flow completes. Use `payload.journeyType` to determine which flow triggered the callback:
@@ -313,9 +411,11 @@ onUserJourneyCompleted={(payload) => {
313
411
 
314
412
  The checkout status transitions to `bank_transfer_submitted` or `pay_via_bank_completed` respectively. Use `getStatus()` for granular programmatic checks.
315
413
 
316
- ## Camera Permissions
414
+ ## Device Permissions
317
415
 
318
- Some checkout flows may require camera access for bank authentication. The SDK handles permission prompts automatically, but you must declare the permissions in your native project config.
416
+ Some bank pages require camera access for authentication. The SDK handles permission prompts automatically on every checkout flow, but you must declare the permissions in your native project config.
417
+
418
+ File upload permissions are requested by `react-native-webview` itself and never reach the SDK, so they do not surface through `onUserRefusedDevicePermissions`.
319
419
 
320
420
  ### Android
321
421
 
@@ -336,8 +436,8 @@ Add to `ios/<YourApp>/Info.plist`:
336
436
 
337
437
  ### Behavior
338
438
 
339
- - **Android**: SDK prompts the user via a native permission dialog. If denied, checkout closes and `onUserRefusedCameraPermissions` fires.
340
- - **iOS**: SDK grants the WebView permission request; iOS shows its own system prompt. If the user denies at the system level, the WebView handles the denial.
439
+ - **Android**: SDK prompts the user via a native permission dialog. If denied, checkout closes and `onUserRefusedDevicePermissions` fires.
440
+ - **iOS**: SDK grants the WebView permission request; iOS shows its own system prompt. If the user denies at the system level, the WebView handles the denial and never reports it back, so `onUserRefusedDevicePermissions` does not fire on iOS.
341
441
 
342
442
  ## Platform Behavior
343
443
 
@@ -346,11 +446,29 @@ Add to `ios/<YourApp>/Info.plist`:
346
446
 
347
447
  ## Device Security Compliance
348
448
 
349
- The SDK optionally integrates with `jail-monkey` to detect rooted (Android) or jailbroken (iOS) devices.
449
+ The SDK uses `jail-monkey` to detect rooted (Android) or jailbroken (iOS) devices. As of v5 it is a
450
+ required peer dependency and the gate **fails closed**: a device the SDK cannot verify is refused on
451
+ a live key rather than assumed clean.
452
+
453
+ | Device | `test_` / `mock_` key | `live_` key |
454
+ | ------------------------------------- | --------------------- | ----------- |
455
+ | Clean | allowed | allowed |
456
+ | Emulator / simulator | allowed | allowed |
457
+ | Rooted / jailbroken | allowed | **blocked** |
458
+ | Unverifiable (`jail-monkey` missing) | allowed | **blocked** |
459
+
460
+ When blocked, `start()` returns `false` and `onSdkError` fires with `type: "device_forbidden"`. The
461
+ message distinguishes a compromised device from an unverifiable one.
462
+
463
+ **Emulators are allowed on live keys, deliberately.** Android emulators ship AOSP test-keys and
464
+ debuggable system properties, which trip two of the checks behind `isJailBroken()` — so the
465
+ aggregate verdict reports every stock emulator as rooted. The SDK computes its own verdict from
466
+ `jail-monkey`'s per-check breakdown and ignores those two, because merchants integration-test with
467
+ live keys on emulators and an emulator is not the threat this gate exists for. Every strong root
468
+ indicator (su binaries, Magisk, root-manager apps, remounted system paths) is still enforced.
350
469
 
351
- - If `jail-monkey` is installed and the device is compromised: `start()` returns `false` and `onSdkError` fires with `type: "device_forbidden"`.
352
- - If `jail-monkey` is installed and the device is clean: checkout proceeds normally.
353
- - If `jail-monkey` is not installed: SDK logs a warning and proceeds without checking. This is not recommended for production deployments handling regulated transactions.
470
+ The cost is custom-ROM detection on physical devices, the weakest signal in the set. On iOS, and on
471
+ any build that exposes no breakdown, the SDK falls back to `isJailBroken()` unchanged.
354
472
 
355
473
  ## Mock Mode
356
474
 
@@ -387,9 +505,11 @@ Validation failures fire `onSdkError` with `type: "validation_error"` and the re
387
505
 
388
506
  Providing both `orderId` and `subscriptionId` (or neither) also fires `onSdkError`.
389
507
 
390
- ## Migration from v1/v3
508
+ ## Migration
391
509
 
392
- See MIGRATION.md included in this package for migration guides with before/after code examples.
510
+ See MIGRATION.md included in this package for migration guides with before/after code examples,
511
+ covering v1 through v5. **If you are upgrading from v4, read the v5 section first** - `jail-monkey`
512
+ became a required dependency and can block live checkouts.
393
513
 
394
514
  ## Troubleshooting
395
515
 
@@ -400,17 +520,33 @@ Check `onSdkError` for details. Common causes:
400
520
  - Invalid `orderId` format (must start with `order_`)
401
521
  - Invalid `subscriptionId` format (must start with `sub_`, non-empty after trimming)
402
522
  - Both `orderId` and `subscriptionId` provided (or neither)
403
- - Device is rooted/jailbroken (when `jail-monkey` is installed)
523
+ - Device is rooted or jailbroken, on a `live_` key
524
+ - Device security could not be verified on a `live_` key, because `jail-monkey` is missing or its
525
+ native module is not linked. Install it and rebuild the app; the console warning names this case.
404
526
 
405
527
  ### Checkout opens but shows a blank screen
406
528
 
407
529
  - Verify network connectivity
408
530
  - Check that the `publicKey` and `orderId` are valid
409
531
 
532
+ ### Document downloads do nothing
533
+
534
+ The capability is only advertised when both `react-native-blob-util` and
535
+ `@react-native-documents/picker` load successfully, and the checkout page falls back to its own
536
+ behaviour when it is absent. Check, in order:
537
+
538
+ - Both packages installed, and the app rebuilt afterwards - installing alone is not enough
539
+ - Metro cache reset (`npx react-native start --reset-cache`)
540
+ - Not running in Expo Go, which cannot load either package
541
+ - On React Native < 0.79, `@react-native-documents/picker` pinned to `10.1.x`
542
+
543
+ `start()` logs an ungated warning naming the package it could not load, and `onSdkError` fires once
544
+ per mount with `field: "file.save"`.
545
+
410
546
  ### Camera permission denied
411
547
 
412
548
  - Ensure `AndroidManifest.xml` and `Info.plist` include camera permission declarations
413
- - The `onUserRefusedCameraPermissions` callback will fire when the user denies camera permissions to your app
549
+ - The `onUserRefusedDevicePermissions` callback will fire when the user denies camera permissions to your app, on Android only
414
550
 
415
551
  ## Exports
416
552
 
@@ -422,7 +558,7 @@ export { GlomoCheckout } from "@glomopay/react-native-sdk";
422
558
  export { useGlomoCheckout } from "@glomopay/react-native-sdk";
423
559
 
424
560
  // Enums
425
- export { ASYNC_PAYMENT_EVENTS } from "@glomopay/react-native-sdk";
561
+ export { ASYNC_PAYMENT_EVENTS, TERMINATION_SOURCES } from "@glomopay/react-native-sdk";
426
562
 
427
563
  // Types
428
564
  export type {
@@ -431,6 +567,7 @@ export type {
431
567
  GlomoCheckoutPayload,
432
568
  CheckoutStatus,
433
569
  GlomoUserJourneyCompletedPayload,
570
+ GlomoTerminationPayload,
434
571
  UseGlomoCheckoutReturn, // deprecated
435
572
  SdkError,
436
573
  } from "@glomopay/react-native-sdk";
@@ -0,0 +1,25 @@
1
+ /** The `file.input` bridge message - the user tapped a file input on a bank's page */
2
+ import { type WebViewMessageHandler } from "./message-router";
3
+ import { type FlowType } from "../utils/analytics";
4
+ /** The context an upload attempt needs in order to report itself */
5
+ export interface CreateFileInputHandlerOptions {
6
+ flowType: FlowType;
7
+ orderId?: string;
8
+ publicKey: string;
9
+ checkoutUrl: string;
10
+ mockMode: boolean;
11
+ devMode: boolean;
12
+ }
13
+ /**
14
+ * Builds the handler for `file.input`.
15
+ *
16
+ * Telemetry only, and there is nothing else it could be: the chooser is opened and resolved
17
+ * natively by react-native-webview, so this message is the attempt and no outcome follows it.
18
+ * Nothing merchant-facing fires here - an SDK that reported "upload started" with no way to report
19
+ * "upload finished" would be worse than silent.
20
+ *
21
+ * Reports the shape of the input, never its identity. `accept` is normalised so it joins with
22
+ * anything else reported for the same click.
23
+ */
24
+ export declare function createFileInputHandler({ flowType, orderId, publicKey, checkoutUrl, mockMode, devMode, }: CreateFileInputHandlerOptions): WebViewMessageHandler;
25
+ //# sourceMappingURL=file-input-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-input-handler.d.ts","sourceRoot":"","sources":["../../src/bridge/file-input-handler.ts"],"names":[],"mappings":"AAAA,sFAAsF;AAEtF,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAE9D,OAAO,EAAE,KAAK,QAAQ,EAA4B,MAAM,oBAAoB,CAAC;AAE7E,oEAAoE;AACpE,MAAM,WAAW,6BAA6B;IAC1C,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,EACnC,QAAQ,EACR,OAAO,EACP,SAAS,EACT,WAAW,EACX,QAAQ,EACR,OAAO,GACV,EAAE,6BAA6B,GAAG,qBAAqB,CA+BvD"}
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ /** The `file.input` bridge message - the user tapped a file input on a bank's page */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.createFileInputHandler = createFileInputHandler;
5
+ const file_metadata_1 = require("../utils/file-metadata");
6
+ const analytics_1 = require("../utils/analytics");
7
+ /**
8
+ * Builds the handler for `file.input`.
9
+ *
10
+ * Telemetry only, and there is nothing else it could be: the chooser is opened and resolved
11
+ * natively by react-native-webview, so this message is the attempt and no outcome follows it.
12
+ * Nothing merchant-facing fires here - an SDK that reported "upload started" with no way to report
13
+ * "upload finished" would be worse than silent.
14
+ *
15
+ * Reports the shape of the input, never its identity. `accept` is normalised so it joins with
16
+ * anything else reported for the same click.
17
+ */
18
+ function createFileInputHandler({ flowType, orderId, publicKey, checkoutUrl, mockMode, devMode, }) {
19
+ return (message) => {
20
+ const accept = typeof message.accept === "string" ? message.accept : undefined;
21
+ /**
22
+ * Read as a boolean, not derived from the attribute's value. `capture` is an enumerated
23
+ * attribute and its commonest spelling on a bank's KYC form is valueless - `<input
24
+ * type="file" capture>` - for which getAttribute returns "". Measuring string length there
25
+ * reported hasCapture: false on exactly the camera-backed inputs this exists to count.
26
+ */
27
+ const hasCapture = message.hasCapture === true;
28
+ /**
29
+ * Logged as well as tracked. Nothing merchant-facing fires for an upload attempt - the
30
+ * chooser is opened and resolved natively - so without this there is no observable signal
31
+ * that the bridge received it, and the path cannot be tested.
32
+ */
33
+ if (devMode) {
34
+ console.log(`[Glomo-RN-SDK] file.input requested: hasCapture=${hasCapture}`);
35
+ }
36
+ (0, analytics_1.trackFileUploadRequested)((0, file_metadata_1.normalizeAcceptTypes)(accept), hasCapture, orderId, publicKey, devMode, mockMode, checkoutUrl, flowType);
37
+ };
38
+ }
@@ -0,0 +1,32 @@
1
+ /** The `file.save` bridge message, wired once and registered on every WebView that can receive it */
2
+ import { type WebViewMessageHandler } from "./message-router";
3
+ import { type FlowType, type SdkError } from "../utils/analytics";
4
+ /** The context a save needs in order to report itself. Supplied by whichever flow registered it. */
5
+ export interface CreateFileSaveHandlerOptions {
6
+ flowType: FlowType;
7
+ orderId?: string;
8
+ publicKey: string;
9
+ checkoutUrl: string;
10
+ mockMode: boolean;
11
+ devMode: boolean;
12
+ /**
13
+ * The merchant's error callback.
14
+ *
15
+ * Reached directly, and deliberately not through whatever a flow uses to end a checkout: a
16
+ * document failing to save must not touch checkout status or end the customer's payment session.
17
+ */
18
+ onSdkError: (errors: Array<SdkError>) => void;
19
+ }
20
+ /**
21
+ * Builds the handler for `file.save`.
22
+ *
23
+ * Fire-and-forget by contract - the page gets no acknowledgement and must not wait for one. Once
24
+ * past the guard below, every outcome lands on `File Save Completed`, and a failure additionally
25
+ * reaches the host's onSdkError without ending the checkout.
26
+ *
27
+ * The handler itself is synchronous because the router's handlers are; the save runs detached and
28
+ * reports through telemetry rather than a return value, which is the same shape the contract
29
+ * describes on the page side.
30
+ */
31
+ export declare function createFileSaveHandler({ flowType, orderId, publicKey, checkoutUrl, mockMode, devMode, onSdkError, }: CreateFileSaveHandlerOptions): WebViewMessageHandler;
32
+ //# sourceMappingURL=file-save-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-save-handler.d.ts","sourceRoot":"","sources":["../../src/bridge/file-save-handler.ts"],"names":[],"mappings":"AAAA,qGAAqG;AAErG,OAAO,EAAuB,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAUnF,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAkD,MAAM,oBAAoB,CAAC;AAGlH,oGAAoG;AACpG,MAAM,WAAW,4BAA4B;IACzC,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IAEjB;;;;;OAKG;IACH,UAAU,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;CACjD;AAqBD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,EAClC,QAAQ,EACR,OAAO,EACP,SAAS,EACT,WAAW,EACX,QAAQ,EACR,OAAO,EACP,UAAU,GACb,EAAE,4BAA4B,GAAG,qBAAqB,CAqEtD"}