@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
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Fetches a document and offers it to the user through the platform save dialog
3
+ *
4
+ * This is the whole of the SDK's answer to a `file.save` bridge message. Nothing here is reachable
5
+ * from the merchant API - the bridge handler is the only caller.
6
+ *
7
+ * Exists because there is no web-only way to put a file on the device from inside a WebView:
8
+ * Android drops `anchor[download]` on a `blob:` URL unless the host registers a DownloadListener,
9
+ * and the Web Share API is absent from Android WebView entirely. The page sends the message only
10
+ * after finding `file.save` in the capability list the injection scripts publish.
11
+ */
12
+ import { type SdkError } from "../utils/analytics";
13
+ /**
14
+ * How a save attempt ended.
15
+ *
16
+ * Named for the condition rather than carrying a message, for the same reason FileSaveResult
17
+ * carries no filename: every value here reaches telemetry.
18
+ *
19
+ * Values are camelCase where this repo's other string enums are snake_case, and the deviation is
20
+ * deliberate. ASYNC_PAYMENT_EVENTS is merchant-facing API surface, so it follows the SDK's own
21
+ * convention; this one is telemetry that has to join against the Flutter SDK's already-shipped
22
+ * `file.save_completed`, where the binding constraint is that `outcome` reads the same on both.
23
+ */
24
+ export declare enum FILE_SAVE_OUTCOMES {
25
+ /** The user chose a destination and the bytes were written to it. */
26
+ SAVED = "saved",
27
+ /** The save dialog was dismissed without choosing one. A normal user action, not a failure. */
28
+ DISMISSED = "dismissed",
29
+ /** The URL failed the scheme check, so no request was made. */
30
+ REJECTED = "rejected",
31
+ /** The document could not be fetched: stall, transport, or non-200. */
32
+ FETCH_FAILED = "fetchFailed",
33
+ /** Larger than FILE_SAVE_MAX_BYTES. Refused mid-stream, so the excess never reached disk. */
34
+ TOO_LARGE = "tooLarge",
35
+ /** The platform refused to present the save dialog, or failed writing to the chosen target. */
36
+ SAVE_FAILED = "saveFailed",
37
+ /**
38
+ * The optional save packages are not usable in this build, so the message should not have been
39
+ * sent at all.
40
+ *
41
+ * No Flutter counterpart - there the two packages are hard dependencies and this state cannot
42
+ * exist. It is reachable here whenever a page sends `file.save` without gating on the capability
43
+ * list, which is the failure this outcome exists to make visible rather than silent.
44
+ */
45
+ UNAVAILABLE = "unavailable"
46
+ }
47
+ /**
48
+ * Outcome of one save, shaped for telemetry.
49
+ *
50
+ * Carries no filename and no URL. The agreement URLs are signed links - a signed link is a bearer
51
+ * credential, and the filename is the same class of data file-metadata exists to keep out of events.
52
+ * Extension and byte count are what a field investigation actually needs.
53
+ */
54
+ export interface FileSaveResult {
55
+ outcome: FILE_SAVE_OUTCOMES;
56
+ /** From extensionOf, so it is a known extension or a sentinel. */
57
+ fileExtension: string;
58
+ /** SIZE_UNAVAILABLE when the document was never fetched. */
59
+ fileSize: number;
60
+ /** Error name or code, never a message - RN network errors interpolate the full URL. */
61
+ failureType?: string;
62
+ }
63
+ /** The options for saveDocumentToDevice */
64
+ export interface SaveDocumentOptions {
65
+ url: string;
66
+ filename?: string;
67
+ devMode?: boolean;
68
+ }
69
+ /**
70
+ * The largest document this will put on the device.
71
+ *
72
+ * Held at the Flutter SDK's figure so a merchant does not see one platform refuse what the other
73
+ * accepts, but the reason differs and that matters if either is ever revisited. Flutter is bounded
74
+ * by memory: `FilePicker.saveFile` takes bytes, so the whole document is materialised before the
75
+ * dialog. Here nothing is ever held in memory - blob-util streams to disk and the picker takes a
76
+ * URI - so this bounds device storage and the user's patience instead.
77
+ *
78
+ * Deliberately not justified by agreement PDFs being small. `file.save` is generic device-save
79
+ * capability, reached by any bank or segment, so a response far past a typical agreement is an
80
+ * ordinary case rather than evidence of a misrouted URL.
81
+ */
82
+ export declare const FILE_SAVE_MAX_BYTES: number;
83
+ /**
84
+ * True for outcomes that mean the user did not get the document.
85
+ *
86
+ * Dismissal is excluded: the user was offered the file and declined it.
87
+ */
88
+ export declare function isFileSaveFailure(result: FileSaveResult): boolean;
89
+ /** The telemetry projection of a result. Everything here is safe to send; the inputs are not. */
90
+ export declare function toFileSaveEventData(result: FileSaveResult): Record<string, unknown>;
91
+ /**
92
+ * The host-facing form of a failure.
93
+ *
94
+ * `validation_error` rather than anything terminal because onSdkError is also the bridge's generic
95
+ * catch - without a type these arrive mixed with parse failures and a merchant cannot filter them
96
+ * apart. `field` names the message that produced it for the same reason.
97
+ *
98
+ * Must never be routed anywhere that sets checkout status: a document failing to save must not end
99
+ * the customer's payment session.
100
+ */
101
+ export declare function toFileSaveSdkError(result: FileSaveResult): SdkError;
102
+ /**
103
+ * Fetches the document at `url` and offers it to the user.
104
+ *
105
+ * Never throws: every failure is an outcome, because the caller is a bridge message handler whose
106
+ * only other option is to drop the user's tap silently.
107
+ */
108
+ export declare function saveDocumentToDevice({ url, filename, devMode, }: SaveDocumentOptions): Promise<FileSaveResult>;
109
+ //# sourceMappingURL=file-saver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-saver.d.ts","sourceRoot":"","sources":["../../src/services/file-saver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAKnD;;;;;;;;;;GAUG;AACH,oBAAY,kBAAkB;IAC1B,qEAAqE;IACrE,KAAK,UAAU;IAEf,+FAA+F;IAC/F,SAAS,cAAc;IAEvB,+DAA+D;IAC/D,QAAQ,aAAa;IAErB,uEAAuE;IACvE,YAAY,gBAAgB;IAE5B,6FAA6F;IAC7F,SAAS,aAAa;IAEtB,+FAA+F;IAC/F,WAAW,eAAe;IAE1B;;;;;;;OAOG;IACH,WAAW,gBAAgB;CAC9B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,kBAAkB,CAAC;IAE5B,kEAAkE;IAClE,aAAa,EAAE,MAAM,CAAC;IAEtB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IAEjB,wFAAwF;IACxF,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,2CAA2C;AAC3C,MAAM,WAAW,mBAAmB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mBAAmB,QAAoB,CAAC;AAuCrD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAEjE;AAED,iGAAiG;AACjG,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOnF;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG,QAAQ,CASnE;AAuED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,EACvC,GAAG,EACH,QAAQ,EACR,OAAe,GAClB,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC,CA+M/C"}
@@ -0,0 +1,416 @@
1
+ "use strict";
2
+ /**
3
+ * Fetches a document and offers it to the user through the platform save dialog
4
+ *
5
+ * This is the whole of the SDK's answer to a `file.save` bridge message. Nothing here is reachable
6
+ * from the merchant API - the bridge handler is the only caller.
7
+ *
8
+ * Exists because there is no web-only way to put a file on the device from inside a WebView:
9
+ * Android drops `anchor[download]` on a `blob:` URL unless the host registers a DownloadListener,
10
+ * and the Web Share API is absent from Android WebView entirely. The page sends the message only
11
+ * after finding `file.save` in the capability list the injection scripts publish.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.FILE_SAVE_MAX_BYTES = exports.FILE_SAVE_OUTCOMES = void 0;
15
+ exports.isFileSaveFailure = isFileSaveFailure;
16
+ exports.toFileSaveEventData = toFileSaveEventData;
17
+ exports.toFileSaveSdkError = toFileSaveSdkError;
18
+ exports.saveDocumentToDevice = saveDocumentToDevice;
19
+ const file_save_1 = require("../capabilities/file-save");
20
+ const file_metadata_1 = require("../utils/file-metadata");
21
+ /** Log prefix. Deliberately unqualified by flow - a save is not owned by one. */
22
+ const LOG_PREFIX = "[Glomo-RN-SDK]";
23
+ /**
24
+ * How a save attempt ended.
25
+ *
26
+ * Named for the condition rather than carrying a message, for the same reason FileSaveResult
27
+ * carries no filename: every value here reaches telemetry.
28
+ *
29
+ * Values are camelCase where this repo's other string enums are snake_case, and the deviation is
30
+ * deliberate. ASYNC_PAYMENT_EVENTS is merchant-facing API surface, so it follows the SDK's own
31
+ * convention; this one is telemetry that has to join against the Flutter SDK's already-shipped
32
+ * `file.save_completed`, where the binding constraint is that `outcome` reads the same on both.
33
+ */
34
+ var FILE_SAVE_OUTCOMES;
35
+ (function (FILE_SAVE_OUTCOMES) {
36
+ /** The user chose a destination and the bytes were written to it. */
37
+ FILE_SAVE_OUTCOMES["SAVED"] = "saved";
38
+ /** The save dialog was dismissed without choosing one. A normal user action, not a failure. */
39
+ FILE_SAVE_OUTCOMES["DISMISSED"] = "dismissed";
40
+ /** The URL failed the scheme check, so no request was made. */
41
+ FILE_SAVE_OUTCOMES["REJECTED"] = "rejected";
42
+ /** The document could not be fetched: stall, transport, or non-200. */
43
+ FILE_SAVE_OUTCOMES["FETCH_FAILED"] = "fetchFailed";
44
+ /** Larger than FILE_SAVE_MAX_BYTES. Refused mid-stream, so the excess never reached disk. */
45
+ FILE_SAVE_OUTCOMES["TOO_LARGE"] = "tooLarge";
46
+ /** The platform refused to present the save dialog, or failed writing to the chosen target. */
47
+ FILE_SAVE_OUTCOMES["SAVE_FAILED"] = "saveFailed";
48
+ /**
49
+ * The optional save packages are not usable in this build, so the message should not have been
50
+ * sent at all.
51
+ *
52
+ * No Flutter counterpart - there the two packages are hard dependencies and this state cannot
53
+ * exist. It is reachable here whenever a page sends `file.save` without gating on the capability
54
+ * list, which is the failure this outcome exists to make visible rather than silent.
55
+ */
56
+ FILE_SAVE_OUTCOMES["UNAVAILABLE"] = "unavailable";
57
+ })(FILE_SAVE_OUTCOMES || (exports.FILE_SAVE_OUTCOMES = FILE_SAVE_OUTCOMES = {}));
58
+ /**
59
+ * The largest document this will put on the device.
60
+ *
61
+ * Held at the Flutter SDK's figure so a merchant does not see one platform refuse what the other
62
+ * accepts, but the reason differs and that matters if either is ever revisited. Flutter is bounded
63
+ * by memory: `FilePicker.saveFile` takes bytes, so the whole document is materialised before the
64
+ * dialog. Here nothing is ever held in memory - blob-util streams to disk and the picker takes a
65
+ * URI - so this bounds device storage and the user's patience instead.
66
+ *
67
+ * Deliberately not justified by agreement PDFs being small. `file.save` is generic device-save
68
+ * capability, reached by any bank or segment, so a response far past a typical agreement is an
69
+ * ordinary case rather than evidence of a misrouted URL.
70
+ */
71
+ exports.FILE_SAVE_MAX_BYTES = 100 * 1024 * 1024;
72
+ /** Matches the Flutter SDK's fetch timeout, and blob-util applies it to connect only (see below). */
73
+ const CONNECT_TIMEOUT_MS = 30000;
74
+ /**
75
+ * How long a started download may go without delivering bytes before it is abandoned.
76
+ *
77
+ * Needed because blob-util sets `readTimeout(0)` for file-to-disk responses on Android - deliberate
78
+ * on their side, so a slow but healthy transfer of a large file is not killed by OkHttp's 60s
79
+ * default. The consequence is that a genuinely wedged socket never resolves, and the user is left
80
+ * on a button that already reported success.
81
+ *
82
+ * A stall bound rather than a total bound: a total timeout large enough for FILE_SAVE_MAX_BYTES on
83
+ * a slow connection is too large to catch anything, and one small enough to catch a stall would
84
+ * fail legitimate large downloads. The Flutter SDK applies a 30s total, which is the tradeoff worth
85
+ * revisiting there now that its own cap is 100 MB.
86
+ */
87
+ const STALL_TIMEOUT_MS = 30000;
88
+ /** Only the basename survives, and only from an allowlist */
89
+ const UNSAFE_CHARS = /[^A-Za-z0-9._-]/g;
90
+ /** Both sides of this protocol independently default to this, which is why collisions are designed out */
91
+ const DEFAULT_FILENAME = "document.pdf";
92
+ /** Reported when a thrown value carries neither a code nor a name */
93
+ const UNKNOWN_ERROR_TYPE = "UNKNOWN_ERROR_TYPE";
94
+ /** Distinguishes one save's staging directory from another's. Saves are not serialised. */
95
+ let stagingCounter = 0;
96
+ /**
97
+ * True for outcomes that mean the user did not get the document.
98
+ *
99
+ * Dismissal is excluded: the user was offered the file and declined it.
100
+ */
101
+ function isFileSaveFailure(result) {
102
+ return result.outcome !== FILE_SAVE_OUTCOMES.SAVED && result.outcome !== FILE_SAVE_OUTCOMES.DISMISSED;
103
+ }
104
+ /** The telemetry projection of a result. Everything here is safe to send; the inputs are not. */
105
+ function toFileSaveEventData(result) {
106
+ return {
107
+ outcome: result.outcome,
108
+ fileExtension: result.fileExtension,
109
+ fileSize: result.fileSize,
110
+ ...(result.failureType ? { failureType: result.failureType } : {}),
111
+ };
112
+ }
113
+ /**
114
+ * The host-facing form of a failure.
115
+ *
116
+ * `validation_error` rather than anything terminal because onSdkError is also the bridge's generic
117
+ * catch - without a type these arrive mixed with parse failures and a merchant cannot filter them
118
+ * apart. `field` names the message that produced it for the same reason.
119
+ *
120
+ * Must never be routed anywhere that sets checkout status: a document failing to save must not end
121
+ * the customer's payment session.
122
+ */
123
+ function toFileSaveSdkError(result) {
124
+ return {
125
+ type: "validation_error",
126
+ message: result.outcome === FILE_SAVE_OUTCOMES.TOO_LARGE
127
+ ? `Downloads above ${Math.floor(exports.FILE_SAVE_MAX_BYTES / (1024 * 1024))} MB are not supported.`
128
+ : "Could not save the document to your device.",
129
+ field: "file.save",
130
+ };
131
+ }
132
+ /**
133
+ * https only, except on loopback.
134
+ *
135
+ * isValidUrl admits any http URL, which is right for bank redirects and wrong here: this fetch
136
+ * carries a signed link and its bytes become a document the user is told is theirs. The loopback
137
+ * exception is what lets the checkout page's dev mock server serve agreements over http.
138
+ *
139
+ * Gated on `devMode`, so a release build is https-only with no exception at all. Ungated, a page
140
+ * could point a production build at a service listening on the customer's own device and have the
141
+ * response written to their files - a dev convenience is not worth a hole in the one rule this
142
+ * function exists to enforce.
143
+ *
144
+ * Loopback only, and deliberately not the Android emulator's `10.0.2.2` alias for the host:
145
+ * reaching a dev server from an emulator is what `adb reverse` is for, which keeps the host
146
+ * `localhost` on both platforms. No other private address belongs in shipped code.
147
+ */
148
+ function isFetchableUrl(url, devMode) {
149
+ var _a, _b, _c;
150
+ const match = /^([a-zA-Z][a-zA-Z0-9+.-]*):\/\/([^/?#]+)/.exec(url);
151
+ if (!match) {
152
+ return false;
153
+ }
154
+ const scheme = match[1].toLowerCase();
155
+ // Host only - a userinfo or port section must not be mistaken for one
156
+ const host = (_c = (_b = (_a = match[2].split("@").pop()) === null || _a === void 0 ? void 0 : _a.split(":")[0]) === null || _b === void 0 ? void 0 : _b.toLowerCase()) !== null && _c !== void 0 ? _c : "";
157
+ if (scheme === "https") {
158
+ return true;
159
+ }
160
+ return devMode && scheme === "http" && (host === "localhost" || host === "127.0.0.1");
161
+ }
162
+ /**
163
+ * Basename, allowlisted, with a `.pdf` default.
164
+ *
165
+ * The page supplies this and it becomes a path segment under the staging directory, so it is
166
+ * untrusted input: `../../databases/x` would otherwise escape. Falls back to the URL's last
167
+ * segment, then to a constant.
168
+ */
169
+ function sanitize(name) {
170
+ var _a, _b, _c;
171
+ if (!name) {
172
+ return null;
173
+ }
174
+ const base = (_c = (_b = (_a = name.split("/").pop()) === null || _a === void 0 ? void 0 : _a.split("\\").pop()) === null || _b === void 0 ? void 0 : _b.trim()) !== null && _c !== void 0 ? _c : "";
175
+ const cleaned = base.replace(UNSAFE_CHARS, "_");
176
+ // A name that is only dots and separators sanitises to something like `..`, still a traversal
177
+ if (cleaned.length === 0 || cleaned.replace(/\./g, "").length === 0) {
178
+ return null;
179
+ }
180
+ return cleaned.length > 100 ? cleaned.slice(cleaned.length - 100) : cleaned;
181
+ }
182
+ /** Prefers what the page asked for, then what the URL implies, then a constant. */
183
+ function resolveFilename(supplied, url) {
184
+ const path = url.split("?")[0].split("#")[0];
185
+ const fromUrl = path.split("/").pop();
186
+ for (const candidate of [supplied, fromUrl]) {
187
+ const cleaned = sanitize(candidate);
188
+ if (cleaned) {
189
+ return cleaned;
190
+ }
191
+ }
192
+ return DEFAULT_FILENAME;
193
+ }
194
+ /**
195
+ * Fetches the document at `url` and offers it to the user.
196
+ *
197
+ * Never throws: every failure is an outcome, because the caller is a bridge message handler whose
198
+ * only other option is to drop the user's tap silently.
199
+ */
200
+ async function saveDocumentToDevice({ url, filename, devMode = false, }) {
201
+ var _a, _b;
202
+ /**
203
+ * Resolved before every other check so a rejection still reports the real extension rather than
204
+ * the EXTRACTION_FAILED sentinel, which means something else entirely. Neither call touches the
205
+ * network or the filesystem.
206
+ */
207
+ const safeName = resolveFilename(filename, url);
208
+ const fileExtension = (0, file_metadata_1.extensionOf)(safeName);
209
+ const failed = (outcome, failureType, fileSize = file_metadata_1.SIZE_UNAVAILABLE) => ({
210
+ outcome,
211
+ fileExtension,
212
+ fileSize,
213
+ ...(failureType ? { failureType } : {}),
214
+ });
215
+ if (!isFetchableUrl(url, devMode)) {
216
+ if (devMode) {
217
+ console.log(`${LOG_PREFIX} file.save rejected: unsupported URL scheme`);
218
+ }
219
+ return failed(FILE_SAVE_OUTCOMES.REJECTED);
220
+ }
221
+ const blobUtil = (0, file_save_1.loadBlobUtil)();
222
+ const picker = (0, file_save_1.loadDocumentPicker)();
223
+ if (!blobUtil || !picker) {
224
+ // Reachable only from a page that sent the message without gating on the capability list
225
+ if (devMode) {
226
+ console.warn(`${LOG_PREFIX} file.save received but the save packages are not usable in this build`);
227
+ }
228
+ return failed(FILE_SAVE_OUTCOMES.UNAVAILABLE);
229
+ }
230
+ /**
231
+ * A directory this SDK owns, unique per save.
232
+ *
233
+ * The Flutter SDK has to hunt for a free name because `file_picker` stages into the app
234
+ * documents directory under the page-chosen name and deletes whatever is already there. Nothing
235
+ * equivalent applies here: the destination path is ours to choose, so a page-supplied name can
236
+ * only ever land inside this directory, and two concurrent saves of the same name cannot meet.
237
+ * The name still has to survive `sanitize` because it is a path segment either way - and on iOS
238
+ * it is also the name the dialog pre-fills, which is taken from the source URI.
239
+ */
240
+ const stagingDirectory = `${blobUtil.fs.dirs.CacheDir}/glomopay-file-save/${Date.now()}-${++stagingCounter}`;
241
+ const stagingPath = `${stagingDirectory}/${safeName}`;
242
+ let cancelReason = null;
243
+ let stallTimer;
244
+ try {
245
+ await blobUtil.fs.mkdir(stagingDirectory);
246
+ }
247
+ catch (error) {
248
+ return failed(FILE_SAVE_OUTCOMES.SAVE_FAILED, errorTypeOf(error));
249
+ }
250
+ try {
251
+ const task = blobUtil
252
+ .config({ path: stagingPath, timeout: CONNECT_TIMEOUT_MS, overwrite: true })
253
+ .fetch("GET", url);
254
+ const cancelWith = (reason) => {
255
+ if (cancelReason) {
256
+ return;
257
+ }
258
+ cancelReason = reason;
259
+ task.cancel();
260
+ };
261
+ const armStallTimer = () => {
262
+ if (stallTimer) {
263
+ clearTimeout(stallTimer);
264
+ }
265
+ stallTimer = setTimeout(() => cancelWith({ outcome: FILE_SAVE_OUTCOMES.FETCH_FAILED, failureType: "stalled" }), STALL_TIMEOUT_MS);
266
+ };
267
+ armStallTimer();
268
+ task.progress({ count: -1, interval: 250 }, (received, total) => {
269
+ armStallTimer();
270
+ /**
271
+ * Content-Length first when the server sends one, so an oversized response is refused
272
+ * before any of it reaches disk; the running total is the backstop for chunked
273
+ * responses that send none.
274
+ */
275
+ const advertised = total > 0 ? total : file_metadata_1.SIZE_UNAVAILABLE;
276
+ if (advertised > exports.FILE_SAVE_MAX_BYTES || received > exports.FILE_SAVE_MAX_BYTES) {
277
+ cancelWith({
278
+ outcome: FILE_SAVE_OUTCOMES.TOO_LARGE,
279
+ size: advertised > 0 ? advertised : received,
280
+ });
281
+ }
282
+ });
283
+ const response = await task;
284
+ /**
285
+ * Disarmed here rather than only in `finally`, because `finally` does not run until the
286
+ * picker has resolved and the user may sit in it for minutes choosing a folder. A timer
287
+ * left armed across that would fire on a settled request - harmless, since blob-util
288
+ * replaces `cancel` with a no-op once the promise settles, but it is a live timer holding
289
+ * this closure for no reason.
290
+ */
291
+ if (stallTimer) {
292
+ clearTimeout(stallTimer);
293
+ stallTimer = undefined;
294
+ }
295
+ /**
296
+ * A cancel we asked for does not always reject. blob-util makes `cancel` a no-op once the
297
+ * promise has settled, so a response that crosses the cap and finishes inside the 250ms
298
+ * before the next progress tick resolves normally with the oversized body already on disk.
299
+ * Checked here as well as in the catch, or the cap silently does not apply in the one case
300
+ * it exists for - a chunked response whose Content-Length could not refuse it up front.
301
+ */
302
+ if (cancelReason) {
303
+ const reason = cancelReason;
304
+ return failed(reason.outcome, reason.failureType, (_a = reason.size) !== null && _a !== void 0 ? _a : file_metadata_1.SIZE_UNAVAILABLE);
305
+ }
306
+ const info = response.info();
307
+ /**
308
+ * blob-util resolves on any status and writes the body regardless, so an error page is on
309
+ * disk at this point rather than the document
310
+ */
311
+ if (info.status !== 200) {
312
+ return failed(FILE_SAVE_OUTCOMES.FETCH_FAILED, `http_${info.status}`);
313
+ }
314
+ /**
315
+ * The scheme allowlist only ever saw the URL the page sent, and redirects are followed -
316
+ * OkHttp and NSURLSession both allow an https to http downgrade by default, so a signed
317
+ * link can land the document on a plaintext hop without isFetchableUrl being consulted
318
+ * again. Checking the chain after the fact cannot un-fetch those bytes, but it can decline
319
+ * to hand the user a document that travelled in the clear.
320
+ *
321
+ * The chain entries are full URLs, so they are signed links themselves: read here, never
322
+ * reported. The Flutter SDK has the same gap and no equivalent check.
323
+ */
324
+ const redirects = info.redirects;
325
+ if (Array.isArray(redirects) &&
326
+ !redirects.every((hop) => typeof hop === "string" && isFetchableUrl(hop, devMode))) {
327
+ if (devMode) {
328
+ console.log(`${LOG_PREFIX} file.save rejected: redirected through an unsupported scheme`);
329
+ }
330
+ return failed(FILE_SAVE_OUTCOMES.REJECTED, "insecure_redirect");
331
+ }
332
+ const fileSize = await sizeOf(blobUtil, stagingPath);
333
+ try {
334
+ /**
335
+ * `copy: false` is iOS-only and is the default - the staged file is moved to where the
336
+ * user chose, which is what keeps a copy from being left behind in the app container.
337
+ * Android always copies and the cleanup below is what handles it there.
338
+ *
339
+ * `mimeType` is derived from the extension rather than forwarded from the page. Android
340
+ * puts it straight onto an `ACTION_CREATE_DOCUMENT` intent, and the page's field is
341
+ * untrusted; the Flutter SDK declines to forward the same field for the same reason.
342
+ */
343
+ await picker.saveDocuments({
344
+ sourceUris: [encodeURI(`file://${stagingPath}`)],
345
+ fileName: safeName,
346
+ mimeType: (0, file_metadata_1.mimeTypeFor)(fileExtension),
347
+ copy: false,
348
+ });
349
+ return { outcome: FILE_SAVE_OUTCOMES.SAVED, fileExtension, fileSize };
350
+ }
351
+ catch (error) {
352
+ if (picker.isErrorWithCode(error) && error.code === picker.errorCodes.OPERATION_CANCELED) {
353
+ return { outcome: FILE_SAVE_OUTCOMES.DISMISSED, fileExtension, fileSize };
354
+ }
355
+ if (devMode) {
356
+ console.error(`${LOG_PREFIX} file.save dialog failed:`, errorTypeOf(error));
357
+ }
358
+ return failed(FILE_SAVE_OUTCOMES.SAVE_FAILED, errorTypeOf(error), fileSize);
359
+ }
360
+ }
361
+ catch (error) {
362
+ // A cancel this code asked for rejects the fetch exactly as a transport failure does
363
+ if (cancelReason) {
364
+ const reason = cancelReason;
365
+ return failed(reason.outcome, reason.failureType, (_b = reason.size) !== null && _b !== void 0 ? _b : file_metadata_1.SIZE_UNAVAILABLE);
366
+ }
367
+ if (devMode) {
368
+ console.error(`${LOG_PREFIX} file.save fetch failed:`, errorTypeOf(error));
369
+ }
370
+ return failed(FILE_SAVE_OUTCOMES.FETCH_FAILED, errorTypeOf(error));
371
+ }
372
+ finally {
373
+ if (stallTimer) {
374
+ clearTimeout(stallTimer);
375
+ }
376
+ // Recursive on both platforms, and a no-op once iOS has moved the file out
377
+ try {
378
+ await blobUtil.fs.unlink(stagingDirectory);
379
+ }
380
+ catch (error) {
381
+ if (devMode) {
382
+ console.error(`${LOG_PREFIX} file.save could not clear the staging directory:`, errorTypeOf(error));
383
+ }
384
+ }
385
+ }
386
+ }
387
+ /** Size of what was staged, or the sentinel. Never fatal - the document is already on disk. */
388
+ async function sizeOf(blobUtil, path) {
389
+ try {
390
+ const stat = await blobUtil.fs.stat(path);
391
+ const size = Number(stat.size);
392
+ return Number.isFinite(size) ? size : file_metadata_1.SIZE_UNAVAILABLE;
393
+ }
394
+ catch (_a) {
395
+ return file_metadata_1.SIZE_UNAVAILABLE;
396
+ }
397
+ }
398
+ /**
399
+ * The name or code of a thrown value, never its message.
400
+ *
401
+ * RN's network errors and blob-util's own both interpolate the request URL into `message`, which is
402
+ * the signed link. A sentinel rather than an omission, so the field is always present and "could
403
+ * not tell" is something to filter on.
404
+ */
405
+ function errorTypeOf(error) {
406
+ if (error && typeof error === "object") {
407
+ const candidate = error;
408
+ if (typeof candidate.code === "string" && candidate.code.length > 0) {
409
+ return candidate.code;
410
+ }
411
+ if (typeof candidate.name === "string" && candidate.name.length > 0) {
412
+ return candidate.name;
413
+ }
414
+ }
415
+ return UNKNOWN_ERROR_TYPE;
416
+ }
@@ -1,11 +1,20 @@
1
- /** Unified v4 type definitions for GlomoCheckout */
1
+ /** Unified type definitions for GlomoCheckout */
2
2
  import { type GlomoServer } from "../config/base";
3
3
  import { type SdkError } from "../utils/analytics";
4
- /** The payload for a successful or failed payment (unified across LRS and standard) */
4
+ /**
5
+ * The payload for a successful or failed payment (unified across LRS and standard)
6
+ *
7
+ * `paymentId` and `signature` are optional as of v5. The checkout page can report a terminal
8
+ * outcome before the backend has resolved them - open banking completes on the bank's side first -
9
+ * and the SDK used to drop those outcomes entirely rather than report an incomplete one.
10
+ *
11
+ * With `strict` on, this lands as a compile error anywhere the value is forwarded to something
12
+ * expecting a `string`, which is the point: the narrowing check is the guardrail, not a nuisance.
13
+ */
5
14
  export interface GlomoCheckoutPayload {
6
15
  orderId: string;
7
- paymentId: string;
8
- signature: string;
16
+ paymentId?: string;
17
+ signature?: string;
9
18
  }
10
19
  /**
11
20
  * Enum of asynchronous payment events that trigger the
@@ -24,6 +33,40 @@ export interface GlomoUserJourneyCompletedPayload {
24
33
  senderAccountNumber?: string;
25
34
  transactionReference?: string;
26
35
  }
36
+ /**
37
+ * How a checkout ended, when it ended without a payment outcome.
38
+ *
39
+ * **Must stay exhaustive by construction.** Any future call site that terminates a checkout adds a
40
+ * member here rather than passing `undefined` or a catch-all - the whole value of "the payload
41
+ * always carries its source" is in the "always", and one lazy call site turns `source` into a field
42
+ * every merchant has to null-check.
43
+ *
44
+ * Deliberately does not include the back chevron over the bank flow. That returns the user to the
45
+ * main checkout with the session intact and the ability to pay, so reporting it as a termination
46
+ * would be false - and for a merchant who cancels the order on terminate it would cancel live
47
+ * orders every time a user backed out of one bank to try another.
48
+ */
49
+ export declare enum TERMINATION_SOURCES {
50
+ /** The checkout page's own close control, over the bridge as `checkout.closed` */
51
+ CHECKOUT_CLOSED = "checkout_closed",
52
+ /** Android hardware back or the back gesture */
53
+ DEVICE_BACK_BUTTON = "device_back_button",
54
+ /** iOS swipe-down on the pageSheet */
55
+ MODAL_DISMISS = "modal_dismiss"
56
+ }
57
+ /**
58
+ * The payload for the onPaymentTerminate callback.
59
+ *
60
+ * An object rather than a bare enum because every other callback in this SDK takes one, and because
61
+ * the source alone is not extensible: a later confirmation step would otherwise have to arrive as a
62
+ * second positional parameter, or silently change what an existing value means.
63
+ *
64
+ * The source describes **how** the checkout ended. The **why**, where there is one, already reached
65
+ * the host through the callback it got first.
66
+ */
67
+ export interface GlomoTerminationPayload {
68
+ source: TERMINATION_SOURCES;
69
+ }
27
70
  /** Unified checkout statuses - superset of LRS + standard statuses */
28
71
  export type CheckoutStatus = "ready" | "detecting_order_type" | "payment_in_progress" | "payment_successful" | "payment_failed" | "payment_cancelled" | "bank_transfer_submitted" | "pay_via_bank_completed";
29
72
  /**
@@ -51,10 +94,19 @@ interface GlomoCheckoutBaseProps {
51
94
  onPaymentSuccess: (payload: GlomoCheckoutPayload) => void;
52
95
  onPaymentFailure: (payload: GlomoCheckoutPayload) => void;
53
96
  onConnectionError?: (error: unknown) => void;
54
- onPaymentTerminate?: () => void;
97
+ onPaymentTerminate?: (payload: GlomoTerminationPayload) => void;
55
98
  onSdkError: (error: Array<SdkError>) => void;
99
+ /**
100
+ * Optional callback for asynchronous payment journeys - bank transfers and pay via bank.
101
+ * Carries the journey type and the transfer details the page reported, not a confirmed payment.
102
+ *
103
+ * Accepted here because this component is a union over both flows, but **silently ignored on
104
+ * LRS orders**: LRS never produces an asynchronous journey, so nothing forwards it. If your
105
+ * orders are LRS-only you can leave it out entirely - supplying it is not an error, it simply
106
+ * never fires. Terminal LRS outcomes arrive through onPaymentSuccess and onPaymentFailure.
107
+ */
56
108
  onUserJourneyCompleted?: (payload: GlomoUserJourneyCompletedPayload) => void;
57
- onUserRefusedCameraPermissions?: () => void;
109
+ onUserRefusedDevicePermissions?: () => void;
58
110
  devMode?: boolean;
59
111
  }
60
112
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"checkout.d.ts","sourceRoot":"","sources":["../../src/types/checkout.ts"],"names":[],"mappings":"AAAA,oDAAoD;AAEpD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD,uFAAuF;AACvF,MAAM,WAAW,oBAAoB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,oBAAY,oBAAoB;IAC5B,uBAAuB,4BAA4B;IACnD,sBAAsB,2BAA2B;CACpD;AAED,0DAA0D;AAC1D,MAAM,WAAW,gCAAgC;IAC7C,WAAW,EAAE,oBAAoB,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,sEAAsE;AACtE,MAAM,MAAM,cAAc,GACpB,OAAO,GACP,sBAAsB,GACtB,qBAAqB,GACrB,oBAAoB,GACpB,gBAAgB,GAChB,mBAAmB,GACnB,yBAAyB,GACzB,wBAAwB,CAAC;AAE/B;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC7B,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B,SAAS,EAAE,MAAM,cAAc,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACvC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,4BAA4B,CAAC,EAAE,MAAM,CAAC;IACtC,eAAe,CAAC,EAAE,UAAU,GAAG,KAAK,CAAC;IACrC,+BAA+B,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7D;AAED,iFAAiF;AACjF,UAAU,sBAAsB;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC1D,gBAAgB,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC1D,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,UAAU,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;IAC7C,sBAAsB,CAAC,EAAE,CAAC,OAAO,EAAE,gCAAgC,KAAK,IAAI,CAAC;IAC7E,8BAA8B,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5C,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,sBAAsB,GACnD,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC,CAAC"}
1
+ {"version":3,"file":"checkout.d.ts","sourceRoot":"","sources":["../../src/types/checkout.ts"],"names":[],"mappings":"AAAA,iDAAiD;AAEjD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAoB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,oBAAY,oBAAoB;IAC5B,uBAAuB,4BAA4B;IACnD,sBAAsB,2BAA2B;CACpD;AAED,0DAA0D;AAC1D,MAAM,WAAW,gCAAgC;IAC7C,WAAW,EAAE,oBAAoB,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED;;;;;;;;;;;;GAYG;AACH,oBAAY,mBAAmB;IAC3B,kFAAkF;IAClF,eAAe,oBAAoB;IAEnC,gDAAgD;IAChD,kBAAkB,uBAAuB;IAEzC,sCAAsC;IACtC,aAAa,kBAAkB;CAClC;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,uBAAuB;IACpC,MAAM,EAAE,mBAAmB,CAAC;CAC/B;AAED,sEAAsE;AACtE,MAAM,MAAM,cAAc,GACpB,OAAO,GACP,sBAAsB,GACtB,qBAAqB,GACrB,oBAAoB,GACpB,gBAAgB,GAChB,mBAAmB,GACnB,yBAAyB,GACzB,wBAAwB,CAAC;AAE/B;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC7B,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B,SAAS,EAAE,MAAM,cAAc,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACvC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,4BAA4B,CAAC,EAAE,MAAM,CAAC;IACtC,eAAe,CAAC,EAAE,UAAU,GAAG,KAAK,CAAC;IACrC,+BAA+B,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7D;AAED,iFAAiF;AACjF,UAAU,sBAAsB;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC1D,gBAAgB,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC1D,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAC;IAChE,UAAU,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;IAC7C;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,CAAC,OAAO,EAAE,gCAAgC,KAAK,IAAI,CAAC;IAC7E,8BAA8B,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5C,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,sBAAsB,GACnD,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC,CAAC"}
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
- /** Unified v4 type definitions for GlomoCheckout */
2
+ /** Unified type definitions for GlomoCheckout */
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.ASYNC_PAYMENT_EVENTS = void 0;
4
+ exports.TERMINATION_SOURCES = exports.ASYNC_PAYMENT_EVENTS = void 0;
5
5
  /**
6
6
  * Enum of asynchronous payment events that trigger the
7
7
  * onUserJourneyCompleted callback. New events will be added
@@ -12,3 +12,25 @@ var ASYNC_PAYMENT_EVENTS;
12
12
  ASYNC_PAYMENT_EVENTS["BANK_TRANSFER_SUBMITTED"] = "bank_transfer_submitted";
13
13
  ASYNC_PAYMENT_EVENTS["PAY_VIA_BANK_COMPLETED"] = "pay_via_bank_completed";
14
14
  })(ASYNC_PAYMENT_EVENTS || (exports.ASYNC_PAYMENT_EVENTS = ASYNC_PAYMENT_EVENTS = {}));
15
+ /**
16
+ * How a checkout ended, when it ended without a payment outcome.
17
+ *
18
+ * **Must stay exhaustive by construction.** Any future call site that terminates a checkout adds a
19
+ * member here rather than passing `undefined` or a catch-all - the whole value of "the payload
20
+ * always carries its source" is in the "always", and one lazy call site turns `source` into a field
21
+ * every merchant has to null-check.
22
+ *
23
+ * Deliberately does not include the back chevron over the bank flow. That returns the user to the
24
+ * main checkout with the session intact and the ability to pay, so reporting it as a termination
25
+ * would be false - and for a merchant who cancels the order on terminate it would cancel live
26
+ * orders every time a user backed out of one bank to try another.
27
+ */
28
+ var TERMINATION_SOURCES;
29
+ (function (TERMINATION_SOURCES) {
30
+ /** The checkout page's own close control, over the bridge as `checkout.closed` */
31
+ TERMINATION_SOURCES["CHECKOUT_CLOSED"] = "checkout_closed";
32
+ /** Android hardware back or the back gesture */
33
+ TERMINATION_SOURCES["DEVICE_BACK_BUTTON"] = "device_back_button";
34
+ /** iOS swipe-down on the pageSheet */
35
+ TERMINATION_SOURCES["MODAL_DISMISS"] = "modal_dismiss";
36
+ })(TERMINATION_SOURCES || (exports.TERMINATION_SOURCES = TERMINATION_SOURCES = {}));