@glomopay/react-native-sdk 4.0.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.
- package/CHANGELOG.md +29 -0
- package/MIGRATION.md +112 -0
- package/README.md +173 -36
- package/lib/bridge/file-input-handler.d.ts +25 -0
- package/lib/bridge/file-input-handler.d.ts.map +1 -0
- package/lib/bridge/file-input-handler.js +38 -0
- package/lib/bridge/file-save-handler.d.ts +32 -0
- package/lib/bridge/file-save-handler.d.ts.map +1 -0
- package/lib/bridge/file-save-handler.js +92 -0
- package/lib/bridge/message-router.d.ts +44 -0
- package/lib/bridge/message-router.d.ts.map +1 -0
- package/lib/bridge/message-router.js +48 -0
- package/lib/bridge/payment-payload.d.ts +18 -0
- package/lib/bridge/payment-payload.d.ts.map +1 -0
- package/lib/bridge/payment-payload.js +31 -0
- package/lib/capabilities/file-save.d.ts +40 -0
- package/lib/capabilities/file-save.d.ts.map +1 -0
- package/lib/capabilities/file-save.js +101 -0
- package/lib/capabilities/index.d.ts +22 -0
- package/lib/capabilities/index.d.ts.map +1 -0
- package/lib/capabilities/index.js +34 -0
- package/lib/capabilities/use-capability-reporting.d.ts +24 -0
- package/lib/capabilities/use-capability-reporting.d.ts.map +1 -0
- package/lib/capabilities/use-capability-reporting.js +44 -0
- package/lib/config/base.d.ts +12 -0
- package/lib/config/base.d.ts.map +1 -1
- package/lib/config/base.js +13 -1
- package/lib/glomo-checkout.d.ts +9 -1
- package/lib/glomo-checkout.d.ts.map +1 -1
- package/lib/glomo-checkout.js +14 -5
- package/lib/glomo-lrs-checkout.d.ts +3 -0
- package/lib/glomo-lrs-checkout.d.ts.map +1 -1
- package/lib/glomo-lrs-checkout.js +34 -11
- package/lib/glomo-standard-checkout.d.ts +4 -1
- package/lib/glomo-standard-checkout.d.ts.map +1 -1
- package/lib/glomo-standard-checkout.js +21 -13
- package/lib/glomo-subscriptions-checkout.d.ts.map +1 -1
- package/lib/glomo-subscriptions-checkout.js +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -3
- package/lib/injections/index.d.ts +24 -0
- package/lib/injections/index.d.ts.map +1 -1
- package/lib/injections/index.js +41 -0
- package/lib/injections/webview-flow.injection.d.ts.map +1 -1
- package/lib/injections/webview-flow.injection.js +76 -0
- package/lib/injections/webview-main.injection.d.ts.map +1 -1
- package/lib/injections/webview-main.injection.js +31 -0
- package/lib/injections/webview-standard.injection.d.ts.map +1 -1
- package/lib/injections/webview-standard.injection.js +31 -0
- package/lib/services/file-saver.d.ts +109 -0
- package/lib/services/file-saver.d.ts.map +1 -0
- package/lib/services/file-saver.js +416 -0
- package/lib/types/checkout.d.ts +67 -6
- package/lib/types/checkout.d.ts.map +1 -1
- package/lib/types/checkout.js +24 -2
- package/lib/types/standard-checkout.d.ts +6 -6
- package/lib/types/standard-checkout.d.ts.map +1 -1
- package/lib/types/subscriptions-checkout.d.ts +5 -3
- package/lib/types/subscriptions-checkout.d.ts.map +1 -1
- package/lib/use-glomo-checkout.d.ts +2 -2
- package/lib/use-glomo-checkout.d.ts.map +1 -1
- package/lib/use-glomo-checkout.js +25 -4
- package/lib/use-lrs-checkout.d.ts +18 -6
- package/lib/use-lrs-checkout.d.ts.map +1 -1
- package/lib/use-lrs-checkout.js +330 -171
- package/lib/use-standard-checkout.d.ts +18 -13
- package/lib/use-standard-checkout.d.ts.map +1 -1
- package/lib/use-standard-checkout.js +282 -206
- package/lib/utils/analytics.d.ts +177 -51
- package/lib/utils/analytics.d.ts.map +1 -1
- package/lib/utils/analytics.js +299 -81
- package/lib/utils/device-compliance.d.ts +57 -12
- package/lib/utils/device-compliance.d.ts.map +1 -1
- package/lib/utils/device-compliance.js +101 -37
- package/lib/utils/event-projections.d.ts +56 -0
- package/lib/utils/event-projections.d.ts.map +1 -0
- package/lib/utils/event-projections.js +42 -0
- package/lib/utils/file-metadata.d.ts +52 -0
- package/lib/utils/file-metadata.d.ts.map +1 -0
- package/lib/utils/file-metadata.js +134 -0
- package/lib/utils/use-sdk-session.d.ts +12 -0
- package/lib/utils/use-sdk-session.d.ts.map +1 -0
- package/lib/utils/use-sdk-session.js +37 -0
- package/lib/utils/validation.d.ts +37 -3
- package/lib/utils/validation.d.ts.map +1 -1
- package/lib/utils/validation.js +64 -8
- package/lib/webview/capability-props.d.ts +27 -0
- package/lib/webview/capability-props.d.ts.map +1 -0
- package/lib/webview/capability-props.js +61 -0
- package/lib/webview/use-permission-request.d.ts +33 -0
- package/lib/webview/use-permission-request.d.ts.map +1 -0
- package/lib/webview/use-permission-request.js +89 -0
- package/package.json +13 -3
- package/src/bridge/file-input-handler.ts +66 -0
- package/src/bridge/file-save-handler.ts +141 -0
- package/src/bridge/message-router.ts +92 -0
- package/src/bridge/payment-payload.ts +31 -0
- package/src/capabilities/file-save.ts +124 -0
- package/src/capabilities/index.ts +34 -0
- package/src/capabilities/use-capability-reporting.ts +55 -0
- package/src/config/base.ts +13 -0
- package/src/glomo-checkout.tsx +28 -11
- package/src/glomo-lrs-checkout.tsx +49 -27
- package/src/glomo-standard-checkout.tsx +33 -34
- package/src/glomo-subscriptions-checkout.tsx +1 -0
- package/src/index.ts +4 -2
- package/src/injections/index.ts +43 -0
- package/src/injections/webview-flow.injection.ts +76 -0
- package/src/injections/webview-main.injection.ts +31 -0
- package/src/injections/webview-standard.injection.ts +31 -0
- package/src/services/file-saver.ts +497 -0
- package/src/types/checkout.ts +72 -6
- package/src/types/standard-checkout.ts +6 -6
- package/src/types/subscriptions-checkout.ts +9 -3
- package/src/use-glomo-checkout.tsx +32 -7
- package/src/use-lrs-checkout.tsx +684 -366
- package/src/use-standard-checkout.tsx +731 -465
- package/src/utils/analytics.ts +422 -117
- package/src/utils/device-compliance.ts +148 -38
- package/src/utils/event-projections.ts +74 -0
- package/src/utils/file-metadata.ts +140 -0
- package/src/utils/use-sdk-session.ts +41 -0
- package/src/utils/validation.ts +70 -10
- package/src/webview/capability-props.ts +86 -0
- package/src/webview/use-permission-request.ts +114 -0
- package/src/jail-monkey.d.ts +0 -19
package/src/use-lrs-checkout.tsx
CHANGED
|
@@ -5,11 +5,27 @@ import { Platform, type NativeSyntheticEvent } from "react-native";
|
|
|
5
5
|
|
|
6
6
|
import { type WebViewNavigation, type WebViewMessageEvent, type WebView } from "react-native-webview";
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { buildInjectionScripts } from "./injections/index";
|
|
9
9
|
import { initializeSegment } from "./config/segment";
|
|
10
|
-
import { resolveServerConfig, type GlomoServer } from "./config/base";
|
|
11
|
-
import {
|
|
12
|
-
|
|
10
|
+
import { resolveServerConfig, ANALYTICS_PRODUCT, ANALYTICS_SURFACE, type GlomoServer } from "./config/base";
|
|
11
|
+
import {
|
|
12
|
+
isValidPublicKey,
|
|
13
|
+
isValidOrderId,
|
|
14
|
+
isValidUrl,
|
|
15
|
+
isValidPaymentPayload,
|
|
16
|
+
missingPaymentFields,
|
|
17
|
+
safeCallback,
|
|
18
|
+
} from "./utils/validation";
|
|
19
|
+
import { droppedPayloadSdkError, payloadKeysOf } from "./bridge/payment-payload";
|
|
20
|
+
import { toPaymentEventData, toTerminationEventData } from "./utils/event-projections";
|
|
21
|
+
import { resolveDeviceCompliance, blocksCheckout, isComplianceCheckAvailable } from "./utils/device-compliance";
|
|
22
|
+
import { usePermissionRequest, type WebViewPermissionRequest } from "./webview/use-permission-request";
|
|
23
|
+
import { createMessageRouter } from "./bridge/message-router";
|
|
24
|
+
import { createFileSaveHandler } from "./bridge/file-save-handler";
|
|
25
|
+
import { createFileInputHandler } from "./bridge/file-input-handler";
|
|
26
|
+
import { resolveCapabilities, resolveFileSaveAvailability } from "./capabilities/index";
|
|
27
|
+
import { useCapabilityReporting } from "./capabilities/use-capability-reporting";
|
|
28
|
+
import { useSdkSession } from "./utils/use-sdk-session";
|
|
13
29
|
import {
|
|
14
30
|
trackStartAttempt,
|
|
15
31
|
trackStartSuccess,
|
|
@@ -24,8 +40,16 @@ import {
|
|
|
24
40
|
trackEducationStepsFailedToShow,
|
|
25
41
|
trackSdkError,
|
|
26
42
|
trackInvalidMessageReceived,
|
|
43
|
+
trackIncompletePaymentPayload,
|
|
44
|
+
trackDroppedPaymentPayload,
|
|
45
|
+
trackDevicePermissionRefused,
|
|
46
|
+
trackBackNavigationViaSdkBackButton,
|
|
27
47
|
type SdkError,
|
|
28
48
|
} from "./utils/analytics";
|
|
49
|
+
import { TERMINATION_SOURCES, type GlomoTerminationPayload } from "./types/checkout";
|
|
50
|
+
|
|
51
|
+
/** Log prefix for everything this flow emits */
|
|
52
|
+
const LOG_PREFIX = "[Glomo-RN-SDK LRS]";
|
|
29
53
|
|
|
30
54
|
/** LRS checkout statuses */
|
|
31
55
|
export type LrsCheckoutStatus =
|
|
@@ -58,11 +82,11 @@ export type LrsCheckoutStatus =
|
|
|
58
82
|
*/
|
|
59
83
|
export type EducationCarouselState = "pending" | "hasContent";
|
|
60
84
|
|
|
61
|
-
/** The payload for a successful or failed payment */
|
|
85
|
+
/** The payload for a successful or failed payment. See GlomoCheckoutPayload on the optional fields. */
|
|
62
86
|
export interface GlomoLrsCheckoutPayload {
|
|
63
87
|
orderId: string;
|
|
64
|
-
paymentId
|
|
65
|
-
signature
|
|
88
|
+
paymentId?: string;
|
|
89
|
+
signature?: string;
|
|
66
90
|
}
|
|
67
91
|
|
|
68
92
|
/** The options for the useLrsCheckout hook */
|
|
@@ -73,8 +97,9 @@ export interface UseLrsCheckoutOptions {
|
|
|
73
97
|
onPaymentSuccess: (payload: GlomoLrsCheckoutPayload) => void;
|
|
74
98
|
onPaymentFailure: (payload: GlomoLrsCheckoutPayload) => void;
|
|
75
99
|
onConnectionError?: (error: unknown) => void;
|
|
76
|
-
onPaymentTerminate?: () => void;
|
|
100
|
+
onPaymentTerminate?: (payload: GlomoTerminationPayload) => void;
|
|
77
101
|
onSdkError: (error: Array<SdkError>) => void;
|
|
102
|
+
onUserRefusedDevicePermissions?: () => void;
|
|
78
103
|
devMode?: boolean;
|
|
79
104
|
}
|
|
80
105
|
|
|
@@ -101,12 +126,22 @@ export interface UseLrsCheckoutReturn {
|
|
|
101
126
|
handleNavigationStateChange: (navState: WebViewNavigation) => void;
|
|
102
127
|
handleModalBackButton: () => void;
|
|
103
128
|
handleFlowBack: () => void;
|
|
129
|
+
handlePermissionRequest: (request: WebViewPermissionRequest) => void;
|
|
104
130
|
injectedMain?: string;
|
|
105
131
|
injectedFlow?: string;
|
|
106
132
|
injectedEducationCarousel?: string;
|
|
107
133
|
}
|
|
108
134
|
|
|
109
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* The main SDK hook for the GlomoPay LRS Checkout flow
|
|
137
|
+
* Internal options meant to simulate an LRS checkout are for automated tests only.
|
|
138
|
+
*/
|
|
139
|
+
export interface UseLrsCheckoutInternalOptions {
|
|
140
|
+
_testInjectionScript?: string;
|
|
141
|
+
_testCarouselInjectionScript?: string;
|
|
142
|
+
_onEducationCarouselStateChange?: (state: string) => void;
|
|
143
|
+
}
|
|
144
|
+
|
|
110
145
|
export function useLrsCheckout(
|
|
111
146
|
{
|
|
112
147
|
server,
|
|
@@ -117,9 +152,11 @@ export function useLrsCheckout(
|
|
|
117
152
|
onConnectionError,
|
|
118
153
|
onPaymentTerminate,
|
|
119
154
|
onSdkError,
|
|
155
|
+
onUserRefusedDevicePermissions,
|
|
120
156
|
devMode = false,
|
|
121
157
|
}: UseLrsCheckoutOptions,
|
|
122
|
-
mockMode: boolean
|
|
158
|
+
mockMode: boolean,
|
|
159
|
+
internalOptions?: UseLrsCheckoutInternalOptions
|
|
123
160
|
): UseLrsCheckoutReturn {
|
|
124
161
|
// Refs for the WebViews
|
|
125
162
|
const mainWebViewRef = useRef<WebView>(null);
|
|
@@ -141,7 +178,9 @@ export function useLrsCheckout(
|
|
|
141
178
|
const mode = mockMode ? "mock" : "live";
|
|
142
179
|
const params = baseParams.concat(
|
|
143
180
|
`mode=${encodeURIComponent(mode)}`,
|
|
144
|
-
`publicKey=${encodeURIComponent(publicKey)}
|
|
181
|
+
`publicKey=${encodeURIComponent(publicKey)}`,
|
|
182
|
+
`product=${encodeURIComponent(ANALYTICS_PRODUCT)}`,
|
|
183
|
+
`surface=${encodeURIComponent(ANALYTICS_SURFACE)}`
|
|
145
184
|
);
|
|
146
185
|
const queryString = params.join("&");
|
|
147
186
|
|
|
@@ -179,7 +218,9 @@ export function useLrsCheckout(
|
|
|
179
218
|
const separator = baseEducationCarouselUrl.includes("?") ? "&" : "?";
|
|
180
219
|
const finalUrl =
|
|
181
220
|
`${baseEducationCarouselUrl}${separator}orderId=${encodeURIComponent(orderId)}` +
|
|
182
|
-
`&publicKey=${encodeURIComponent(publicKey)}
|
|
221
|
+
`&publicKey=${encodeURIComponent(publicKey)}` +
|
|
222
|
+
`&product=${encodeURIComponent(ANALYTICS_PRODUCT)}` +
|
|
223
|
+
`&surface=${encodeURIComponent(ANALYTICS_SURFACE)}`;
|
|
183
224
|
|
|
184
225
|
if (!isValidUrl(finalUrl)) {
|
|
185
226
|
if (devMode) {
|
|
@@ -208,7 +249,18 @@ export function useLrsCheckout(
|
|
|
208
249
|
|
|
209
250
|
const [showFlowWebView, setShowFlowWebView] = useState(false);
|
|
210
251
|
const [flowWebViewUrl, setFlowWebViewUrl] = useState<string>("");
|
|
211
|
-
const [educationCarouselState,
|
|
252
|
+
const [educationCarouselState, setEducationCarouselStateRaw] = useState<EducationCarouselState>("pending");
|
|
253
|
+
|
|
254
|
+
/** Wrapping the raw state setter to also fire the internal test callback */
|
|
255
|
+
const testCarouselCallbackRef = useRef(internalOptions?._onEducationCarouselStateChange);
|
|
256
|
+
testCarouselCallbackRef.current = internalOptions?._onEducationCarouselStateChange;
|
|
257
|
+
|
|
258
|
+
const setEducationCarouselState = useCallback((nextState: EducationCarouselState) => {
|
|
259
|
+
setEducationCarouselStateRaw(nextState);
|
|
260
|
+
if (__DEV__) {
|
|
261
|
+
testCarouselCallbackRef.current?.(nextState);
|
|
262
|
+
}
|
|
263
|
+
}, []);
|
|
212
264
|
|
|
213
265
|
useEffect(() => {
|
|
214
266
|
if (devMode) {
|
|
@@ -298,16 +350,60 @@ export function useLrsCheckout(
|
|
|
298
350
|
[devMode]
|
|
299
351
|
);
|
|
300
352
|
|
|
353
|
+
// Correlates every event from this mount, and keeps checkoutStatus current on all of them
|
|
354
|
+
useSdkSession(() => status);
|
|
355
|
+
|
|
356
|
+
// Missing optional dependencies reach the host here; start() warns on the console separately
|
|
357
|
+
useCapabilityReporting({ onSdkError, devMode });
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Handler for `file.input` - the user tapping a file input on a bank's page.
|
|
361
|
+
*
|
|
362
|
+
* Registered beside `file.save` on the same WebViews, for the same reason: uploads are not a
|
|
363
|
+
* property of one flow, and until now this message had no handler anywhere.
|
|
364
|
+
*/
|
|
365
|
+
const fileInputHandler = useMemo(
|
|
366
|
+
() =>
|
|
367
|
+
createFileInputHandler({
|
|
368
|
+
flowType: "lrs",
|
|
369
|
+
orderId,
|
|
370
|
+
publicKey,
|
|
371
|
+
checkoutUrl,
|
|
372
|
+
mockMode,
|
|
373
|
+
devMode,
|
|
374
|
+
}),
|
|
375
|
+
[orderId, publicKey, checkoutUrl, mockMode, devMode]
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Handler for `file.save` - the page asking the SDK to put a document on the device.
|
|
380
|
+
*
|
|
381
|
+
* One handler, registered on the main and flow WebViews both. Not on the education carousel:
|
|
382
|
+
* that renders our own content, which has nothing to gate, and the capability is not advertised
|
|
383
|
+
* there either.
|
|
384
|
+
*/
|
|
385
|
+
const fileSaveHandler = useMemo(
|
|
386
|
+
() =>
|
|
387
|
+
createFileSaveHandler({
|
|
388
|
+
flowType: "lrs",
|
|
389
|
+
orderId,
|
|
390
|
+
publicKey,
|
|
391
|
+
checkoutUrl,
|
|
392
|
+
mockMode,
|
|
393
|
+
devMode,
|
|
394
|
+
onSdkError,
|
|
395
|
+
}),
|
|
396
|
+
[orderId, publicKey, checkoutUrl, mockMode, devMode, onSdkError]
|
|
397
|
+
);
|
|
398
|
+
|
|
301
399
|
// Handler for the main WebView messages
|
|
302
|
-
const handleMainWebViewMessage =
|
|
303
|
-
(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
// Tracking invalid message received
|
|
400
|
+
const handleMainWebViewMessage = useMemo(
|
|
401
|
+
() =>
|
|
402
|
+
createMessageRouter({
|
|
403
|
+
logPrefix: LOG_PREFIX,
|
|
404
|
+
source: "MainWebView",
|
|
405
|
+
devMode,
|
|
406
|
+
onInvalidMessage: (data) => {
|
|
311
407
|
trackInvalidMessageReceived(
|
|
312
408
|
"main",
|
|
313
409
|
data,
|
|
@@ -318,158 +414,242 @@ export function useLrsCheckout(
|
|
|
318
414
|
mockMode,
|
|
319
415
|
checkoutUrl
|
|
320
416
|
);
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
if (devMode) {
|
|
326
|
-
console.log("[Glomo-RN-SDK LRS] MainWebView Message received:", message.type, message);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (message.type === "console") {
|
|
330
|
-
if (devMode) {
|
|
331
|
-
const level = message.level || "log";
|
|
332
|
-
console.log(`[Glomo-RN-SDK LRS] [WebView ${level.toUpperCase()}]`, message.message);
|
|
333
|
-
}
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
417
|
+
},
|
|
418
|
+
handlers: {
|
|
419
|
+
"file.save": fileSaveHandler,
|
|
420
|
+
"file.input": fileInputHandler,
|
|
336
421
|
|
|
337
|
-
|
|
338
|
-
const url = message.url;
|
|
339
|
-
if (url && typeof url === "string" && isValidUrl(url)) {
|
|
340
|
-
if (devMode) {
|
|
341
|
-
console.log(`[Glomo-RN-SDK LRS] window.open called from MainWebView: ${url}`);
|
|
342
|
-
}
|
|
343
|
-
setFlowWebViewUrl(url);
|
|
344
|
-
setShowFlowWebView(true);
|
|
345
|
-
setEducationCarouselState("pending");
|
|
346
|
-
// Tracking window.open interception
|
|
347
|
-
trackWindowOpen("main", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
348
|
-
} else {
|
|
422
|
+
console: (message) => {
|
|
349
423
|
if (devMode) {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
url,
|
|
353
|
-
typeof url === "undefined" ? "undefined" : ""
|
|
354
|
-
);
|
|
424
|
+
const level = (typeof message.level === "string" && message.level) || "log";
|
|
425
|
+
console.log(`${LOG_PREFIX} [WebView ${level.toUpperCase()}]`, message.message);
|
|
355
426
|
}
|
|
356
|
-
}
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
if (message.type === "window.close") {
|
|
361
|
-
if (devMode) {
|
|
362
|
-
console.log("[Glomo-RN-SDK LRS] Child window close requested from MainWebView");
|
|
363
|
-
}
|
|
364
|
-
setShowFlowWebView(false);
|
|
365
|
-
setFlowWebViewUrl("");
|
|
366
|
-
setEducationCarouselState("pending");
|
|
367
|
-
// Tracking window.close interception
|
|
368
|
-
trackWindowClose("main", orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
427
|
+
},
|
|
371
428
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const messageType = messageData?.type;
|
|
376
|
-
|
|
377
|
-
if (messageType === "payment.success" || messageType === "payment.complete") {
|
|
378
|
-
const paymentMessage = messageData;
|
|
379
|
-
const payload = paymentMessage?.payload;
|
|
380
|
-
|
|
381
|
-
if (isValidPaymentPayload(payload)) {
|
|
429
|
+
"window.open": (message) => {
|
|
430
|
+
const url = message.url;
|
|
431
|
+
if (url && typeof url === "string" && isValidUrl(url)) {
|
|
382
432
|
if (devMode) {
|
|
383
|
-
console.log(
|
|
433
|
+
console.log(`${LOG_PREFIX} window.open called from MainWebView: ${url}`);
|
|
384
434
|
}
|
|
385
|
-
|
|
386
|
-
setShowFlowWebView(
|
|
387
|
-
setFlowWebViewUrl("");
|
|
435
|
+
setFlowWebViewUrl(url);
|
|
436
|
+
setShowFlowWebView(true);
|
|
388
437
|
setEducationCarouselState("pending");
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
// Tracking payment success callback
|
|
392
|
-
trackPaymentSuccess(
|
|
393
|
-
payload.orderId,
|
|
394
|
-
payload.paymentId,
|
|
395
|
-
publicKey,
|
|
396
|
-
devMode,
|
|
397
|
-
mockMode,
|
|
398
|
-
checkoutUrl
|
|
399
|
-
);
|
|
438
|
+
// Tracking window.open interception
|
|
439
|
+
trackWindowOpen("main", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
400
440
|
} else {
|
|
401
441
|
if (devMode) {
|
|
402
|
-
console.
|
|
442
|
+
console.log(
|
|
443
|
+
`${LOG_PREFIX} Invalid URL in window.open from MainWebView:`,
|
|
444
|
+
url,
|
|
445
|
+
typeof url === "undefined" ? "undefined" : ""
|
|
446
|
+
);
|
|
403
447
|
}
|
|
404
448
|
}
|
|
405
|
-
|
|
406
|
-
}
|
|
449
|
+
},
|
|
407
450
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
451
|
+
"window.close": () => {
|
|
452
|
+
if (devMode) {
|
|
453
|
+
console.log(`${LOG_PREFIX} Child window close requested from MainWebView`);
|
|
454
|
+
}
|
|
455
|
+
setShowFlowWebView(false);
|
|
456
|
+
setFlowWebViewUrl("");
|
|
457
|
+
setEducationCarouselState("pending");
|
|
458
|
+
// Tracking window.close interception
|
|
459
|
+
trackWindowClose("main", orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
460
|
+
},
|
|
461
|
+
|
|
462
|
+
// Handling merchant JS callbacks on flow completion
|
|
463
|
+
message: (message) => {
|
|
464
|
+
const messageData = message.message as { type?: string; payload?: unknown } | undefined;
|
|
465
|
+
if (!messageData) return;
|
|
466
|
+
const messageType = messageData.type;
|
|
467
|
+
|
|
468
|
+
if (messageType === "payment.success" || messageType === "payment.complete") {
|
|
469
|
+
const payload = messageData.payload;
|
|
470
|
+
|
|
471
|
+
if (isValidPaymentPayload(payload)) {
|
|
472
|
+
if (devMode) {
|
|
473
|
+
console.log(`${LOG_PREFIX} Payment successful via merchant callback!`);
|
|
474
|
+
}
|
|
475
|
+
setShowCheckout(false);
|
|
476
|
+
setShowFlowWebView(false);
|
|
477
|
+
setFlowWebViewUrl("");
|
|
478
|
+
setEducationCarouselState("pending");
|
|
479
|
+
setStatus("payment_successful");
|
|
480
|
+
safeCallback(onPaymentSuccess, [payload], "onPaymentSuccess", devMode);
|
|
481
|
+
// Tracking payment success callback
|
|
482
|
+
trackPaymentSuccess(
|
|
483
|
+
toPaymentEventData(payload),
|
|
484
|
+
publicKey,
|
|
485
|
+
devMode,
|
|
486
|
+
mockMode,
|
|
487
|
+
checkoutUrl
|
|
488
|
+
);
|
|
489
|
+
// Fires alongside the callback, not instead of it - the outcome is real, the payload is thin
|
|
490
|
+
const missingFields = missingPaymentFields(payload);
|
|
491
|
+
if (missingFields.length > 0) {
|
|
492
|
+
trackIncompletePaymentPayload(
|
|
493
|
+
"payment.success",
|
|
494
|
+
missingFields,
|
|
495
|
+
"main",
|
|
496
|
+
payload.orderId,
|
|
497
|
+
publicKey,
|
|
498
|
+
devMode,
|
|
499
|
+
mockMode,
|
|
500
|
+
checkoutUrl,
|
|
501
|
+
"lrs"
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
} else {
|
|
505
|
+
/**
|
|
506
|
+
* Reported, not dropped. Before v5 this branch was a devMode-only console.error, so in a
|
|
507
|
+
* release build a terminal payment outcome the SDK could not route left no trace
|
|
508
|
+
* anywhere - no callback, no telemetry, nothing for support to find.
|
|
509
|
+
*/
|
|
510
|
+
if (devMode) {
|
|
511
|
+
console.error(
|
|
512
|
+
`${LOG_PREFIX} Unroutable payment.success payload from main:`,
|
|
513
|
+
payload
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
trackDroppedPaymentPayload(
|
|
517
|
+
"payment.success",
|
|
518
|
+
payloadKeysOf(payload),
|
|
519
|
+
"main",
|
|
520
|
+
orderId,
|
|
521
|
+
publicKey,
|
|
522
|
+
devMode,
|
|
523
|
+
mockMode,
|
|
524
|
+
checkoutUrl,
|
|
525
|
+
"lrs"
|
|
526
|
+
);
|
|
527
|
+
safeCallback(
|
|
528
|
+
onSdkError,
|
|
529
|
+
[[droppedPayloadSdkError("payment.success")]],
|
|
530
|
+
"onSdkError",
|
|
531
|
+
devMode
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (messageType === "payment.failure" || messageType === "payment.error") {
|
|
538
|
+
const payload = messageData.payload;
|
|
539
|
+
|
|
540
|
+
if (isValidPaymentPayload(payload)) {
|
|
541
|
+
if (devMode) {
|
|
542
|
+
console.error(`${LOG_PREFIX} Payment failed via merchant callback`);
|
|
543
|
+
}
|
|
544
|
+
setShowCheckout(false);
|
|
545
|
+
setShowFlowWebView(false);
|
|
546
|
+
setFlowWebViewUrl("");
|
|
547
|
+
setEducationCarouselState("pending");
|
|
548
|
+
setStatus("payment_failed");
|
|
549
|
+
safeCallback(onPaymentFailure, [payload], "onPaymentFailure", devMode);
|
|
550
|
+
// Tracking payment failure callback
|
|
551
|
+
trackPaymentFailure(
|
|
552
|
+
toPaymentEventData(payload),
|
|
553
|
+
publicKey,
|
|
554
|
+
devMode,
|
|
555
|
+
mockMode,
|
|
556
|
+
checkoutUrl
|
|
557
|
+
);
|
|
558
|
+
// Fires alongside the callback, not instead of it - the outcome is real, the payload is thin
|
|
559
|
+
const missingFields = missingPaymentFields(payload);
|
|
560
|
+
if (missingFields.length > 0) {
|
|
561
|
+
trackIncompletePaymentPayload(
|
|
562
|
+
"payment.failure",
|
|
563
|
+
missingFields,
|
|
564
|
+
"main",
|
|
565
|
+
payload.orderId,
|
|
566
|
+
publicKey,
|
|
567
|
+
devMode,
|
|
568
|
+
mockMode,
|
|
569
|
+
checkoutUrl,
|
|
570
|
+
"lrs"
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
} else {
|
|
574
|
+
/**
|
|
575
|
+
* Reported, not dropped. Before v5 this branch was a devMode-only console.error, so in a
|
|
576
|
+
* release build a terminal payment outcome the SDK could not route left no trace
|
|
577
|
+
* anywhere - no callback, no telemetry, nothing for support to find.
|
|
578
|
+
*/
|
|
579
|
+
if (devMode) {
|
|
580
|
+
console.error(
|
|
581
|
+
`${LOG_PREFIX} Unroutable payment.failure payload from main:`,
|
|
582
|
+
payload
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
trackDroppedPaymentPayload(
|
|
586
|
+
"payment.failure",
|
|
587
|
+
payloadKeysOf(payload),
|
|
588
|
+
"main",
|
|
589
|
+
orderId,
|
|
590
|
+
publicKey,
|
|
591
|
+
devMode,
|
|
592
|
+
mockMode,
|
|
593
|
+
checkoutUrl,
|
|
594
|
+
"lrs"
|
|
595
|
+
);
|
|
596
|
+
safeCallback(
|
|
597
|
+
onSdkError,
|
|
598
|
+
[[droppedPayloadSdkError("payment.failure")]],
|
|
599
|
+
"onSdkError",
|
|
600
|
+
devMode
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
411
605
|
|
|
412
|
-
|
|
606
|
+
// Handling checkout.closed events
|
|
607
|
+
if (messageType === "checkout.closed") {
|
|
413
608
|
if (devMode) {
|
|
414
|
-
console.
|
|
609
|
+
console.log(`${LOG_PREFIX} Checkout closed event received`);
|
|
415
610
|
}
|
|
416
611
|
setShowCheckout(false);
|
|
417
612
|
setShowFlowWebView(false);
|
|
418
613
|
setFlowWebViewUrl("");
|
|
419
614
|
setEducationCarouselState("pending");
|
|
420
|
-
|
|
421
|
-
safeCallback(
|
|
422
|
-
//
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
615
|
+
const termination = { source: TERMINATION_SOURCES.CHECKOUT_CLOSED };
|
|
616
|
+
safeCallback(onPaymentTerminate, [termination], "onPaymentTerminate", devMode);
|
|
617
|
+
// One object to the host and to telemetry, so the two cannot drift
|
|
618
|
+
trackPaymentTerminate(
|
|
619
|
+
orderId,
|
|
620
|
+
toTerminationEventData(termination),
|
|
426
621
|
publicKey,
|
|
427
622
|
devMode,
|
|
428
623
|
mockMode,
|
|
429
624
|
checkoutUrl
|
|
430
625
|
);
|
|
431
|
-
} else {
|
|
432
|
-
if (devMode) {
|
|
433
|
-
console.error("[Glomo-RN-SDK LRS] Invalid payment failure payload:", payload);
|
|
434
|
-
}
|
|
435
626
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
} catch (error) {
|
|
455
|
-
if (devMode) {
|
|
456
|
-
console.error("[Glomo-RN-SDK LRS] Error parsing MainWebView message:", error);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
},
|
|
460
|
-
[onPaymentSuccess, onPaymentFailure, onPaymentTerminate, devMode, mockMode, orderId, publicKey, checkoutUrl]
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
}),
|
|
630
|
+
[
|
|
631
|
+
onPaymentSuccess,
|
|
632
|
+
onPaymentFailure,
|
|
633
|
+
onPaymentTerminate,
|
|
634
|
+
devMode,
|
|
635
|
+
mockMode,
|
|
636
|
+
orderId,
|
|
637
|
+
publicKey,
|
|
638
|
+
checkoutUrl,
|
|
639
|
+
fileSaveHandler,
|
|
640
|
+
onSdkError,
|
|
641
|
+
fileInputHandler,
|
|
642
|
+
]
|
|
461
643
|
);
|
|
462
644
|
|
|
463
645
|
// Handler for the flow WebView messages
|
|
464
|
-
const handleFlowWebViewMessage =
|
|
465
|
-
(
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
}
|
|
472
|
-
// Tracking invalid message received
|
|
646
|
+
const handleFlowWebViewMessage = useMemo(
|
|
647
|
+
() =>
|
|
648
|
+
createMessageRouter({
|
|
649
|
+
logPrefix: LOG_PREFIX,
|
|
650
|
+
source: "FlowWebView",
|
|
651
|
+
devMode,
|
|
652
|
+
onInvalidMessage: (data) => {
|
|
473
653
|
trackInvalidMessageReceived(
|
|
474
654
|
"flow",
|
|
475
655
|
data,
|
|
@@ -480,179 +660,261 @@ export function useLrsCheckout(
|
|
|
480
660
|
mockMode,
|
|
481
661
|
checkoutUrl
|
|
482
662
|
);
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
663
|
+
},
|
|
664
|
+
handlers: {
|
|
665
|
+
/**
|
|
666
|
+
* Telemetry only, and deliberately not wired to onUserRefusedDevicePermissions.
|
|
667
|
+
* The flow injection sees this by watching getUserMedia, so coverage depends on
|
|
668
|
+
* whether a given bank uses it - partial coverage on a merchant callback is
|
|
669
|
+
* worse than none, because silence would stop being unambiguous.
|
|
670
|
+
*/
|
|
671
|
+
"permission.denied": (message) => {
|
|
672
|
+
trackDevicePermissionRefused(
|
|
673
|
+
typeof message.resource === "string" ? message.resource : "camera",
|
|
674
|
+
"get_user_media",
|
|
675
|
+
orderId,
|
|
676
|
+
publicKey,
|
|
677
|
+
devMode,
|
|
678
|
+
mockMode,
|
|
679
|
+
checkoutUrl,
|
|
680
|
+
"lrs"
|
|
681
|
+
);
|
|
682
|
+
},
|
|
490
683
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
console.log(`[Glomo-RN-SDK LRS] [Flow] ${message.message}`);
|
|
494
|
-
}
|
|
495
|
-
return;
|
|
496
|
-
}
|
|
684
|
+
"file.save": fileSaveHandler,
|
|
685
|
+
"file.input": fileInputHandler,
|
|
497
686
|
|
|
498
|
-
|
|
499
|
-
const url = message.url;
|
|
500
|
-
if (url && typeof url === "string" && isValidUrl(url)) {
|
|
687
|
+
console: (message) => {
|
|
501
688
|
if (devMode) {
|
|
502
|
-
console.log(
|
|
689
|
+
console.log(`${LOG_PREFIX} [Flow] ${message.message}`);
|
|
503
690
|
}
|
|
504
|
-
|
|
505
|
-
setEducationCarouselState("pending");
|
|
506
|
-
// Tracking window.open interception
|
|
507
|
-
trackWindowOpen("flow", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
508
|
-
} else {
|
|
509
|
-
if (devMode) {
|
|
510
|
-
console.log(
|
|
511
|
-
"[Glomo-RN-SDK LRS] Invalid URL in window.open from FlowWebView:",
|
|
512
|
-
url,
|
|
513
|
-
typeof url === "undefined" ? "undefined" : ""
|
|
514
|
-
);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
return;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
if (message.type === "window.close") {
|
|
521
|
-
if (devMode) {
|
|
522
|
-
console.log("[Glomo-RN-SDK LRS] FlowWebView close requested");
|
|
523
|
-
}
|
|
524
|
-
setShowFlowWebView(false);
|
|
525
|
-
setFlowWebViewUrl("");
|
|
526
|
-
setEducationCarouselState("pending");
|
|
527
|
-
// Tracking window.close interception
|
|
528
|
-
trackWindowClose("flow", orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
529
|
-
return;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
if (message.type === "message" && message.message) {
|
|
533
|
-
const messageData = message.message;
|
|
534
|
-
const messageType = messageData?.type;
|
|
535
|
-
|
|
536
|
-
if (messageType === "payment.success" || messageType === "payment.complete") {
|
|
537
|
-
const paymentMessage = messageData;
|
|
538
|
-
const payload = paymentMessage?.payload;
|
|
691
|
+
},
|
|
539
692
|
|
|
540
|
-
|
|
693
|
+
"window.open": (message) => {
|
|
694
|
+
const url = message.url;
|
|
695
|
+
if (url && typeof url === "string" && isValidUrl(url)) {
|
|
541
696
|
if (devMode) {
|
|
542
|
-
console.log(
|
|
697
|
+
console.log(`${LOG_PREFIX} window.open called from FlowWebView: ${url}`);
|
|
543
698
|
}
|
|
544
|
-
|
|
545
|
-
setShowCheckout(false);
|
|
546
|
-
setFlowWebViewUrl("");
|
|
699
|
+
setFlowWebViewUrl(url);
|
|
547
700
|
setEducationCarouselState("pending");
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
// Tracking payment success callback
|
|
551
|
-
trackPaymentSuccess(
|
|
552
|
-
payload.orderId,
|
|
553
|
-
payload.paymentId,
|
|
554
|
-
publicKey,
|
|
555
|
-
devMode,
|
|
556
|
-
mockMode,
|
|
557
|
-
checkoutUrl
|
|
558
|
-
);
|
|
701
|
+
// Tracking window.open interception
|
|
702
|
+
trackWindowOpen("flow", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
559
703
|
} else {
|
|
560
704
|
if (devMode) {
|
|
561
|
-
console.
|
|
562
|
-
|
|
563
|
-
|
|
705
|
+
console.log(
|
|
706
|
+
`${LOG_PREFIX} Invalid URL in window.open from FlowWebView:`,
|
|
707
|
+
url,
|
|
708
|
+
typeof url === "undefined" ? "undefined" : ""
|
|
564
709
|
);
|
|
565
710
|
}
|
|
566
711
|
}
|
|
567
|
-
|
|
568
|
-
}
|
|
712
|
+
},
|
|
569
713
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
714
|
+
"window.close": () => {
|
|
715
|
+
if (devMode) {
|
|
716
|
+
console.log(`${LOG_PREFIX} FlowWebView close requested`);
|
|
717
|
+
}
|
|
718
|
+
setShowFlowWebView(false);
|
|
719
|
+
setFlowWebViewUrl("");
|
|
720
|
+
setEducationCarouselState("pending");
|
|
721
|
+
// Tracking window.close interception
|
|
722
|
+
trackWindowClose("flow", orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
723
|
+
},
|
|
724
|
+
|
|
725
|
+
// Handling merchant JS callbacks on flow completion
|
|
726
|
+
message: (message) => {
|
|
727
|
+
const messageData = message.message as { type?: string; payload?: unknown } | undefined;
|
|
728
|
+
if (!messageData) return;
|
|
729
|
+
const messageType = messageData.type;
|
|
730
|
+
|
|
731
|
+
if (messageType === "payment.success" || messageType === "payment.complete") {
|
|
732
|
+
const payload = messageData.payload;
|
|
733
|
+
|
|
734
|
+
if (isValidPaymentPayload(payload)) {
|
|
735
|
+
if (devMode) {
|
|
736
|
+
console.log(`${LOG_PREFIX} Payment successful via FlowWebView callback!`);
|
|
737
|
+
}
|
|
738
|
+
setShowFlowWebView(false);
|
|
739
|
+
setShowCheckout(false);
|
|
740
|
+
setFlowWebViewUrl("");
|
|
741
|
+
setEducationCarouselState("pending");
|
|
742
|
+
setStatus("payment_successful");
|
|
743
|
+
safeCallback(onPaymentSuccess, [payload], "onPaymentSuccess", devMode);
|
|
744
|
+
// Tracking payment success callback
|
|
745
|
+
trackPaymentSuccess(
|
|
746
|
+
toPaymentEventData(payload),
|
|
747
|
+
publicKey,
|
|
748
|
+
devMode,
|
|
749
|
+
mockMode,
|
|
750
|
+
checkoutUrl
|
|
751
|
+
);
|
|
752
|
+
// Fires alongside the callback, not instead of it - the outcome is real, the payload is thin
|
|
753
|
+
const missingFields = missingPaymentFields(payload);
|
|
754
|
+
if (missingFields.length > 0) {
|
|
755
|
+
trackIncompletePaymentPayload(
|
|
756
|
+
"payment.success",
|
|
757
|
+
missingFields,
|
|
758
|
+
"flow",
|
|
759
|
+
payload.orderId,
|
|
760
|
+
publicKey,
|
|
761
|
+
devMode,
|
|
762
|
+
mockMode,
|
|
763
|
+
checkoutUrl,
|
|
764
|
+
"lrs"
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
} else {
|
|
768
|
+
/**
|
|
769
|
+
* Reported, not dropped. Before v5 this branch was a devMode-only console.error, so in a
|
|
770
|
+
* release build a terminal payment outcome the SDK could not route left no trace
|
|
771
|
+
* anywhere - no callback, no telemetry, nothing for support to find.
|
|
772
|
+
*/
|
|
773
|
+
if (devMode) {
|
|
774
|
+
console.error(
|
|
775
|
+
`${LOG_PREFIX} Unroutable payment.success payload from flow:`,
|
|
776
|
+
payload
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
trackDroppedPaymentPayload(
|
|
780
|
+
"payment.success",
|
|
781
|
+
payloadKeysOf(payload),
|
|
782
|
+
"flow",
|
|
783
|
+
orderId,
|
|
784
|
+
publicKey,
|
|
785
|
+
devMode,
|
|
786
|
+
mockMode,
|
|
787
|
+
checkoutUrl,
|
|
788
|
+
"lrs"
|
|
789
|
+
);
|
|
790
|
+
safeCallback(
|
|
791
|
+
onSdkError,
|
|
792
|
+
[[droppedPayloadSdkError("payment.success")]],
|
|
793
|
+
"onSdkError",
|
|
794
|
+
devMode
|
|
795
|
+
);
|
|
577
796
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (messageType === "payment.failure" || messageType === "payment.error") {
|
|
801
|
+
const payload = messageData.payload;
|
|
802
|
+
|
|
803
|
+
if (isValidPaymentPayload(payload)) {
|
|
804
|
+
if (devMode) {
|
|
805
|
+
console.error(`${LOG_PREFIX} Payment failed via FlowWebView callback`);
|
|
806
|
+
}
|
|
807
|
+
setShowFlowWebView(false);
|
|
808
|
+
setShowCheckout(false);
|
|
809
|
+
setFlowWebViewUrl("");
|
|
810
|
+
setEducationCarouselState("pending");
|
|
811
|
+
setStatus("payment_failed");
|
|
812
|
+
safeCallback(onPaymentFailure, [payload], "onPaymentFailure", devMode);
|
|
813
|
+
// Tracking payment failure callback
|
|
814
|
+
trackPaymentFailure(
|
|
815
|
+
toPaymentEventData(payload),
|
|
816
|
+
publicKey,
|
|
817
|
+
devMode,
|
|
818
|
+
mockMode,
|
|
819
|
+
checkoutUrl
|
|
820
|
+
);
|
|
821
|
+
// Fires alongside the callback, not instead of it - the outcome is real, the payload is thin
|
|
822
|
+
const missingFields = missingPaymentFields(payload);
|
|
823
|
+
if (missingFields.length > 0) {
|
|
824
|
+
trackIncompletePaymentPayload(
|
|
825
|
+
"payment.failure",
|
|
826
|
+
missingFields,
|
|
827
|
+
"flow",
|
|
828
|
+
payload.orderId,
|
|
829
|
+
publicKey,
|
|
830
|
+
devMode,
|
|
831
|
+
mockMode,
|
|
832
|
+
checkoutUrl,
|
|
833
|
+
"lrs"
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
} else {
|
|
837
|
+
/**
|
|
838
|
+
* Reported, not dropped. Before v5 this branch was a devMode-only console.error, so in a
|
|
839
|
+
* release build a terminal payment outcome the SDK could not route left no trace
|
|
840
|
+
* anywhere - no callback, no telemetry, nothing for support to find.
|
|
841
|
+
*/
|
|
842
|
+
if (devMode) {
|
|
843
|
+
console.error(
|
|
844
|
+
`${LOG_PREFIX} Unroutable payment.failure payload from flow:`,
|
|
845
|
+
payload
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
trackDroppedPaymentPayload(
|
|
849
|
+
"payment.failure",
|
|
850
|
+
payloadKeysOf(payload),
|
|
851
|
+
"flow",
|
|
852
|
+
orderId,
|
|
853
|
+
publicKey,
|
|
854
|
+
devMode,
|
|
855
|
+
mockMode,
|
|
856
|
+
checkoutUrl,
|
|
857
|
+
"lrs"
|
|
858
|
+
);
|
|
859
|
+
safeCallback(
|
|
860
|
+
onSdkError,
|
|
861
|
+
[[droppedPayloadSdkError("payment.failure")]],
|
|
862
|
+
"onSdkError",
|
|
863
|
+
devMode
|
|
598
864
|
);
|
|
599
865
|
}
|
|
600
866
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
867
|
+
},
|
|
868
|
+
},
|
|
869
|
+
}),
|
|
870
|
+
[
|
|
871
|
+
onPaymentSuccess,
|
|
872
|
+
onPaymentFailure,
|
|
873
|
+
devMode,
|
|
874
|
+
mockMode,
|
|
875
|
+
orderId,
|
|
876
|
+
publicKey,
|
|
877
|
+
flowWebViewUrl,
|
|
878
|
+
checkoutUrl,
|
|
879
|
+
fileSaveHandler,
|
|
880
|
+
onSdkError,
|
|
881
|
+
fileInputHandler,
|
|
882
|
+
]
|
|
611
883
|
);
|
|
612
884
|
|
|
613
885
|
// Handler for the education carousel WebView messages
|
|
614
|
-
const handleEducationCarouselWebViewMessage =
|
|
615
|
-
(
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
if (devMode) {
|
|
627
|
-
console.log("[Glomo-RN-SDK LRS] EducationCarouselWebView Message received:", message.type, message);
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
if (message.type === "console") {
|
|
631
|
-
if (devMode) {
|
|
632
|
-
console.log(`[Glomo-RN-SDK LRS] [EducationCarousel] ${message.message}`);
|
|
633
|
-
}
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
886
|
+
const handleEducationCarouselWebViewMessage = useMemo(
|
|
887
|
+
() =>
|
|
888
|
+
createMessageRouter({
|
|
889
|
+
logPrefix: LOG_PREFIX,
|
|
890
|
+
source: "EducationCarouselWebView",
|
|
891
|
+
devMode,
|
|
892
|
+
handlers: {
|
|
893
|
+
console: (message) => {
|
|
894
|
+
if (devMode) {
|
|
895
|
+
console.log(`${LOG_PREFIX} [EducationCarousel] ${message.message}`);
|
|
896
|
+
}
|
|
897
|
+
},
|
|
636
898
|
|
|
637
|
-
|
|
638
|
-
|
|
899
|
+
"lrs.has_education_steps": (message) => {
|
|
900
|
+
if (devMode) {
|
|
901
|
+
console.log(
|
|
902
|
+
`${LOG_PREFIX} Education carousel signal received: value=${String(message.value)} source=${message.source || "unknown"} payload=${JSON.stringify(message.payload || {})}`
|
|
903
|
+
);
|
|
904
|
+
}
|
|
639
905
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
906
|
+
trackEducationStepsShown(
|
|
907
|
+
orderId,
|
|
908
|
+
publicKey,
|
|
909
|
+
devMode,
|
|
910
|
+
mockMode,
|
|
911
|
+
checkoutUrl,
|
|
912
|
+
typeof message.source === "string" ? message.source : undefined
|
|
643
913
|
);
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
} catch (error) {
|
|
651
|
-
if (devMode) {
|
|
652
|
-
console.error("[Glomo-RN-SDK LRS] Error parsing EducationCarouselWebView message:", error);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
},
|
|
914
|
+
setEducationCarouselState("hasContent");
|
|
915
|
+
},
|
|
916
|
+
},
|
|
917
|
+
}),
|
|
656
918
|
[checkoutUrl, devMode, mockMode, orderId, publicKey]
|
|
657
919
|
);
|
|
658
920
|
|
|
@@ -689,10 +951,26 @@ export function useLrsCheckout(
|
|
|
689
951
|
console.log("[Glomo-RN-SDK LRS] Ignoring checkout status update for: " + status);
|
|
690
952
|
}
|
|
691
953
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
954
|
+
/**
|
|
955
|
+
* Inferred from the platform rather than read off an event, because `onRequestClose` is
|
|
956
|
+
* the only wiring here and the two platforms raise it for different gestures: Android
|
|
957
|
+
* for hardware back and the back gesture, iOS for swipe-down on the pageSheet. Nothing
|
|
958
|
+
* else routes through it, so the inference is sound - but it is an inference.
|
|
959
|
+
*/
|
|
960
|
+
const termination = {
|
|
961
|
+
source:
|
|
962
|
+
Platform.OS === "ios" ? TERMINATION_SOURCES.MODAL_DISMISS : TERMINATION_SOURCES.DEVICE_BACK_BUTTON,
|
|
963
|
+
};
|
|
964
|
+
safeCallback(onPaymentTerminate, [termination], "onPaymentTerminate", devMode);
|
|
965
|
+
// One object to the host and to telemetry, so the two cannot drift
|
|
966
|
+
trackPaymentTerminate(
|
|
967
|
+
orderId,
|
|
968
|
+
toTerminationEventData(termination),
|
|
969
|
+
publicKey,
|
|
970
|
+
devMode,
|
|
971
|
+
mockMode,
|
|
972
|
+
checkoutUrl
|
|
973
|
+
);
|
|
696
974
|
} catch (error) {
|
|
697
975
|
if (devMode) {
|
|
698
976
|
console.error("[Glomo-RN-SDK LRS] Error in handleModalBackButton:", error);
|
|
@@ -700,6 +978,39 @@ export function useLrsCheckout(
|
|
|
700
978
|
}
|
|
701
979
|
}, [showFlowWebView, devMode, onPaymentTerminate, status, orderId, publicKey, mockMode, checkoutUrl]);
|
|
702
980
|
|
|
981
|
+
/**
|
|
982
|
+
* Closing the checkout when the user refuses camera access, since the bank page cannot
|
|
983
|
+
* proceed without it. Kept here rather than inside usePermissionRequest because moving the
|
|
984
|
+
* status and notifying the merchant are flow concerns.
|
|
985
|
+
*/
|
|
986
|
+
const handleDevicePermissionDenied = useCallback(() => {
|
|
987
|
+
setShowCheckout(false);
|
|
988
|
+
setShowFlowWebView(false);
|
|
989
|
+
setFlowWebViewUrl("");
|
|
990
|
+
setEducationCarouselState("pending");
|
|
991
|
+
if (status === "payment_in_progress") {
|
|
992
|
+
setStatus("payment_cancelled");
|
|
993
|
+
}
|
|
994
|
+
safeCallback(onUserRefusedDevicePermissions, [], "onUserRefusedDevicePermissions", devMode);
|
|
995
|
+
// The permission hook only reaches here for camera; anything else is denied without notifying
|
|
996
|
+
trackDevicePermissionRefused(
|
|
997
|
+
"camera",
|
|
998
|
+
"permission_request",
|
|
999
|
+
orderId,
|
|
1000
|
+
publicKey,
|
|
1001
|
+
devMode,
|
|
1002
|
+
mockMode,
|
|
1003
|
+
checkoutUrl,
|
|
1004
|
+
"lrs"
|
|
1005
|
+
);
|
|
1006
|
+
}, [status, onUserRefusedDevicePermissions, devMode, orderId, publicKey, mockMode, checkoutUrl]);
|
|
1007
|
+
|
|
1008
|
+
const handlePermissionRequest = usePermissionRequest({
|
|
1009
|
+
logPrefix: "[Glomo-RN-SDK LRS]",
|
|
1010
|
+
onDenied: handleDevicePermissionDenied,
|
|
1011
|
+
devMode,
|
|
1012
|
+
});
|
|
1013
|
+
|
|
703
1014
|
/** Closes the flow WebView and returns to the main checkout */
|
|
704
1015
|
const handleFlowBack = useCallback(() => {
|
|
705
1016
|
if (devMode) {
|
|
@@ -708,7 +1019,7 @@ export function useLrsCheckout(
|
|
|
708
1019
|
setShowFlowWebView(false);
|
|
709
1020
|
setFlowWebViewUrl("");
|
|
710
1021
|
setEducationCarouselState("pending");
|
|
711
|
-
|
|
1022
|
+
trackBackNavigationViaSdkBackButton(orderId, publicKey, devMode, mockMode, checkoutUrl, "lrs");
|
|
712
1023
|
}, [devMode, orderId, publicKey, mockMode, checkoutUrl]);
|
|
713
1024
|
|
|
714
1025
|
/**
|
|
@@ -728,11 +1039,16 @@ export function useLrsCheckout(
|
|
|
728
1039
|
* Checking device security compliance (if peerDependencies are available)
|
|
729
1040
|
* A null value indicates that recommended security dependencies were not installed and compliance checks will be skipped in this case
|
|
730
1041
|
*/
|
|
731
|
-
const
|
|
732
|
-
const
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
|
|
1042
|
+
const compliance = resolveDeviceCompliance(devMode);
|
|
1043
|
+
const complianceLibraryInstalled = isComplianceCheckAvailable();
|
|
1044
|
+
const fileSave = resolveFileSaveAvailability();
|
|
1045
|
+
const dependencyInfo = {
|
|
1046
|
+
jailbreakDetectionLibraryInstalled: complianceLibraryInstalled,
|
|
1047
|
+
jailbreakDetected: compliance.compromised,
|
|
1048
|
+
complianceSource: compliance.source,
|
|
1049
|
+
complianceSignals: compliance.trippedSignals,
|
|
1050
|
+
fileSaveAvailable: fileSave.available,
|
|
1051
|
+
missingFileSavePackages: fileSave.missing,
|
|
736
1052
|
};
|
|
737
1053
|
|
|
738
1054
|
if (devMode) {
|
|
@@ -740,37 +1056,64 @@ export function useLrsCheckout(
|
|
|
740
1056
|
}
|
|
741
1057
|
|
|
742
1058
|
// Tracking start attempt with current status
|
|
743
|
-
trackStartAttempt(status, orderId, publicKey, devMode, mockMode, checkoutUrl,
|
|
1059
|
+
trackStartAttempt(status, orderId, publicKey, devMode, mockMode, checkoutUrl, dependencyInfo);
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Ungated on purpose, both of them. An absent optional dependency degrades silently by
|
|
1063
|
+
* design, so a console warning is the only thing that reaches a merchant who has not
|
|
1064
|
+
* noticed - and the compliance one now precedes a hard block on live keys.
|
|
1065
|
+
*/
|
|
1066
|
+
if (!complianceLibraryInstalled) {
|
|
1067
|
+
console.warn(
|
|
1068
|
+
"[Glomo-RN-SDK LRS] jail-monkey is not installed or its native module is not linked, so " +
|
|
1069
|
+
"rooted and jailbroken devices cannot be detected. As of v5 this BLOCKS live checkouts. " +
|
|
1070
|
+
"Install jail-monkey and rebuild the app - see the README."
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
if (!fileSave.available) {
|
|
1074
|
+
console.warn(
|
|
1075
|
+
`[Glomo-RN-SDK LRS] Document downloads are disabled: ${fileSave.missing.join(" and ")} ` +
|
|
1076
|
+
"could not be loaded. Install the package(s) and rebuild the app, or ignore this if your " +
|
|
1077
|
+
"checkout does not offer downloads - the page falls back to its own behaviour."
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
744
1080
|
|
|
745
|
-
if (
|
|
746
|
-
|
|
1081
|
+
if (blocksCheckout(compliance, publicKey)) {
|
|
1082
|
+
/**
|
|
1083
|
+
* Two conditions, one refusal. `compromised === true` is a rooted or jailbroken device;
|
|
1084
|
+
* `null` is a device we could not check, which on RN overwhelmingly means jail-monkey is
|
|
1085
|
+
* absent rather than that a platform probe failed. Both fail closed on a live key, and
|
|
1086
|
+
* neither fires on a test or mock key.
|
|
1087
|
+
*/
|
|
1088
|
+
const unknown = compliance.compromised === null;
|
|
747
1089
|
if (devMode) {
|
|
748
|
-
console.error(
|
|
1090
|
+
console.error(
|
|
1091
|
+
`[Glomo-RN-SDK LRS] Cannot start checkout. ${
|
|
1092
|
+
unknown ? "Device security could not be verified." : "Device is rooted or jailbroken."
|
|
1093
|
+
}`
|
|
1094
|
+
);
|
|
749
1095
|
}
|
|
750
1096
|
const errors: SdkError[] = [
|
|
751
1097
|
{
|
|
752
1098
|
type: "device_forbidden",
|
|
753
|
-
message:
|
|
1099
|
+
message: unknown
|
|
1100
|
+
? "LRS checkout could not verify this device's security. Install jail-monkey and rebuild the app."
|
|
1101
|
+
: "LRS checkout is not allowed on rooted or jailbroken devices.",
|
|
754
1102
|
},
|
|
755
1103
|
];
|
|
756
1104
|
safeCallback(onSdkError, [errors], "onSdkError", devMode);
|
|
757
1105
|
// Tracking start() failure
|
|
758
1106
|
trackStartFailure(
|
|
759
|
-
"device_forbidden",
|
|
1107
|
+
unknown ? "device_compliance_unknown" : "device_forbidden",
|
|
760
1108
|
orderId,
|
|
761
1109
|
publicKey,
|
|
762
1110
|
devMode,
|
|
763
1111
|
mockMode,
|
|
764
1112
|
checkoutUrl,
|
|
765
|
-
|
|
1113
|
+
dependencyInfo
|
|
766
1114
|
);
|
|
767
1115
|
setShowCheckout(false);
|
|
768
1116
|
return false;
|
|
769
|
-
} else if (deviceComplianceCheck === null) {
|
|
770
|
-
// This warning was intentionally not gated behind a devMode check to ensure merchants are aware of the strong recommendation to install jail-monkey for enhanced security compliance.
|
|
771
|
-
console.warn(
|
|
772
|
-
"[Glomo-RN-SDK LRS] Device security library not installed - the SDK won't be able to automatically detect rooted/jailbroken devices. It is HIGHLY recommended to install jail-monkey as per the README, and prevent LRS checkouts from compromised devices."
|
|
773
|
-
);
|
|
774
1117
|
}
|
|
775
1118
|
|
|
776
1119
|
// Enforcing devMode requirement for development servers
|
|
@@ -784,15 +1127,7 @@ export function useLrsCheckout(
|
|
|
784
1127
|
];
|
|
785
1128
|
safeCallback(onSdkError, [errors], "onSdkError", devMode);
|
|
786
1129
|
trackSdkError(errors, orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
787
|
-
trackStartFailure(
|
|
788
|
-
"validation_error",
|
|
789
|
-
orderId,
|
|
790
|
-
publicKey,
|
|
791
|
-
devMode,
|
|
792
|
-
mockMode,
|
|
793
|
-
checkoutUrl,
|
|
794
|
-
jailbreakDetectionInfo
|
|
795
|
-
);
|
|
1130
|
+
trackStartFailure("validation_error", orderId, publicKey, devMode, mockMode, checkoutUrl, dependencyInfo);
|
|
796
1131
|
setShowCheckout(false);
|
|
797
1132
|
return false;
|
|
798
1133
|
}
|
|
@@ -840,15 +1175,7 @@ export function useLrsCheckout(
|
|
|
840
1175
|
safeCallback(onSdkError, [errors], "onSdkError", devMode);
|
|
841
1176
|
// Tracking start() failure
|
|
842
1177
|
trackSdkError(errors, orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
843
|
-
trackStartFailure(
|
|
844
|
-
"validation_error",
|
|
845
|
-
orderId,
|
|
846
|
-
publicKey,
|
|
847
|
-
devMode,
|
|
848
|
-
mockMode,
|
|
849
|
-
checkoutUrl,
|
|
850
|
-
jailbreakDetectionInfo
|
|
851
|
-
);
|
|
1178
|
+
trackStartFailure("validation_error", orderId, publicKey, devMode, mockMode, checkoutUrl, dependencyInfo);
|
|
852
1179
|
setShowCheckout(false);
|
|
853
1180
|
return false;
|
|
854
1181
|
}
|
|
@@ -860,15 +1187,7 @@ export function useLrsCheckout(
|
|
|
860
1187
|
);
|
|
861
1188
|
}
|
|
862
1189
|
// Tracking start() failure
|
|
863
|
-
trackStartFailure(
|
|
864
|
-
"payment_successful",
|
|
865
|
-
orderId,
|
|
866
|
-
publicKey,
|
|
867
|
-
devMode,
|
|
868
|
-
mockMode,
|
|
869
|
-
checkoutUrl,
|
|
870
|
-
jailbreakDetectionInfo
|
|
871
|
-
);
|
|
1190
|
+
trackStartFailure("payment_successful", orderId, publicKey, devMode, mockMode, checkoutUrl, dependencyInfo);
|
|
872
1191
|
setShowCheckout(false);
|
|
873
1192
|
return false;
|
|
874
1193
|
}
|
|
@@ -885,15 +1204,7 @@ export function useLrsCheckout(
|
|
|
885
1204
|
console.log(`[Glomo-RN-SDK LRS] Cannot start checkout. Unknown status: ${status}.`);
|
|
886
1205
|
}
|
|
887
1206
|
// Tracking start() failure
|
|
888
|
-
trackStartFailure(
|
|
889
|
-
"invalid_status",
|
|
890
|
-
orderId,
|
|
891
|
-
publicKey,
|
|
892
|
-
devMode,
|
|
893
|
-
mockMode,
|
|
894
|
-
checkoutUrl,
|
|
895
|
-
jailbreakDetectionInfo
|
|
896
|
-
);
|
|
1207
|
+
trackStartFailure("invalid_status", orderId, publicKey, devMode, mockMode, checkoutUrl, dependencyInfo);
|
|
897
1208
|
setShowCheckout(false);
|
|
898
1209
|
return false;
|
|
899
1210
|
}
|
|
@@ -903,15 +1214,7 @@ export function useLrsCheckout(
|
|
|
903
1214
|
console.error("[Glomo-RN-SDK LRS] Cannot start checkout. Invalid checkout URL.");
|
|
904
1215
|
}
|
|
905
1216
|
// Tracking start() failure
|
|
906
|
-
trackStartFailure(
|
|
907
|
-
"invalid_url",
|
|
908
|
-
orderId,
|
|
909
|
-
publicKey,
|
|
910
|
-
devMode,
|
|
911
|
-
mockMode,
|
|
912
|
-
checkoutUrl,
|
|
913
|
-
jailbreakDetectionInfo
|
|
914
|
-
);
|
|
1217
|
+
trackStartFailure("invalid_url", orderId, publicKey, devMode, mockMode, checkoutUrl, dependencyInfo);
|
|
915
1218
|
setShowCheckout(false);
|
|
916
1219
|
return false;
|
|
917
1220
|
}
|
|
@@ -926,7 +1229,7 @@ export function useLrsCheckout(
|
|
|
926
1229
|
setStatus("payment_in_progress");
|
|
927
1230
|
|
|
928
1231
|
// Tracking successful start
|
|
929
|
-
trackStartSuccess(orderId, publicKey, devMode, mockMode, checkoutUrl,
|
|
1232
|
+
trackStartSuccess(orderId, publicKey, devMode, mockMode, checkoutUrl, dependencyInfo);
|
|
930
1233
|
|
|
931
1234
|
setShowCheckout(true);
|
|
932
1235
|
return true;
|
|
@@ -970,6 +1273,14 @@ export function useLrsCheckout(
|
|
|
970
1273
|
};
|
|
971
1274
|
}, []);
|
|
972
1275
|
|
|
1276
|
+
/**
|
|
1277
|
+
* Scripts injected into each WebView, carrying what this build can advertise.
|
|
1278
|
+
*
|
|
1279
|
+
* Resolved once per mount rather than at module load, so a merchant who never opens a
|
|
1280
|
+
* checkout never pays for the dependency probe.
|
|
1281
|
+
*/
|
|
1282
|
+
const injectionScripts = useMemo(() => buildInjectionScripts(resolveCapabilities()), []);
|
|
1283
|
+
|
|
973
1284
|
return {
|
|
974
1285
|
start,
|
|
975
1286
|
status,
|
|
@@ -990,8 +1301,15 @@ export function useLrsCheckout(
|
|
|
990
1301
|
handleNavigationStateChange,
|
|
991
1302
|
handleModalBackButton,
|
|
992
1303
|
handleFlowBack,
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1304
|
+
handlePermissionRequest,
|
|
1305
|
+
injectedMain:
|
|
1306
|
+
__DEV__ && internalOptions?._testInjectionScript
|
|
1307
|
+
? injectionScripts.main + internalOptions._testInjectionScript
|
|
1308
|
+
: injectionScripts.main,
|
|
1309
|
+
injectedFlow: injectionScripts.flow,
|
|
1310
|
+
injectedEducationCarousel:
|
|
1311
|
+
__DEV__ && internalOptions?._testCarouselInjectionScript
|
|
1312
|
+
? injectionScripts.educationCarousel + internalOptions._testCarouselInjectionScript
|
|
1313
|
+
: injectionScripts.educationCarousel,
|
|
996
1314
|
};
|
|
997
1315
|
}
|