@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.
- 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 +6 -1
- package/lib/glomo-checkout.d.ts.map +1 -1
- package/lib/glomo-checkout.js +11 -5
- package/lib/glomo-lrs-checkout.d.ts.map +1 -1
- package/lib/glomo-lrs-checkout.js +33 -10
- package/lib/glomo-standard-checkout.d.ts +3 -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 +58 -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 +3 -3
- package/lib/types/subscriptions-checkout.d.ts.map +1 -1
- package/lib/use-glomo-checkout.js +2 -2
- package/lib/use-lrs-checkout.d.ts +9 -5
- package/lib/use-lrs-checkout.d.ts.map +1 -1
- package/lib/use-lrs-checkout.js +317 -171
- package/lib/use-standard-checkout.d.ts +17 -13
- package/lib/use-standard-checkout.d.ts.map +1 -1
- package/lib/use-standard-checkout.js +281 -207
- 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 +10 -2
- 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 +11 -7
- package/src/glomo-lrs-checkout.tsx +41 -26
- package/src/glomo-standard-checkout.tsx +31 -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 +62 -6
- package/src/types/standard-checkout.ts +6 -6
- package/src/types/subscriptions-checkout.ts +7 -3
- package/src/use-glomo-checkout.tsx +2 -2
- package/src/use-lrs-checkout.tsx +656 -365
- package/src/use-standard-checkout.tsx +728 -466
- 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,6 +126,7 @@ 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;
|
|
@@ -126,6 +152,7 @@ export function useLrsCheckout(
|
|
|
126
152
|
onConnectionError,
|
|
127
153
|
onPaymentTerminate,
|
|
128
154
|
onSdkError,
|
|
155
|
+
onUserRefusedDevicePermissions,
|
|
129
156
|
devMode = false,
|
|
130
157
|
}: UseLrsCheckoutOptions,
|
|
131
158
|
mockMode: boolean,
|
|
@@ -151,7 +178,9 @@ export function useLrsCheckout(
|
|
|
151
178
|
const mode = mockMode ? "mock" : "live";
|
|
152
179
|
const params = baseParams.concat(
|
|
153
180
|
`mode=${encodeURIComponent(mode)}`,
|
|
154
|
-
`publicKey=${encodeURIComponent(publicKey)}
|
|
181
|
+
`publicKey=${encodeURIComponent(publicKey)}`,
|
|
182
|
+
`product=${encodeURIComponent(ANALYTICS_PRODUCT)}`,
|
|
183
|
+
`surface=${encodeURIComponent(ANALYTICS_SURFACE)}`
|
|
155
184
|
);
|
|
156
185
|
const queryString = params.join("&");
|
|
157
186
|
|
|
@@ -189,7 +218,9 @@ export function useLrsCheckout(
|
|
|
189
218
|
const separator = baseEducationCarouselUrl.includes("?") ? "&" : "?";
|
|
190
219
|
const finalUrl =
|
|
191
220
|
`${baseEducationCarouselUrl}${separator}orderId=${encodeURIComponent(orderId)}` +
|
|
192
|
-
`&publicKey=${encodeURIComponent(publicKey)}
|
|
221
|
+
`&publicKey=${encodeURIComponent(publicKey)}` +
|
|
222
|
+
`&product=${encodeURIComponent(ANALYTICS_PRODUCT)}` +
|
|
223
|
+
`&surface=${encodeURIComponent(ANALYTICS_SURFACE)}`;
|
|
193
224
|
|
|
194
225
|
if (!isValidUrl(finalUrl)) {
|
|
195
226
|
if (devMode) {
|
|
@@ -319,16 +350,60 @@ export function useLrsCheckout(
|
|
|
319
350
|
[devMode]
|
|
320
351
|
);
|
|
321
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
|
+
|
|
322
399
|
// Handler for the main WebView messages
|
|
323
|
-
const handleMainWebViewMessage =
|
|
324
|
-
(
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
// Tracking invalid message received
|
|
400
|
+
const handleMainWebViewMessage = useMemo(
|
|
401
|
+
() =>
|
|
402
|
+
createMessageRouter({
|
|
403
|
+
logPrefix: LOG_PREFIX,
|
|
404
|
+
source: "MainWebView",
|
|
405
|
+
devMode,
|
|
406
|
+
onInvalidMessage: (data) => {
|
|
332
407
|
trackInvalidMessageReceived(
|
|
333
408
|
"main",
|
|
334
409
|
data,
|
|
@@ -339,158 +414,242 @@ export function useLrsCheckout(
|
|
|
339
414
|
mockMode,
|
|
340
415
|
checkoutUrl
|
|
341
416
|
);
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (devMode) {
|
|
347
|
-
console.log("[Glomo-RN-SDK LRS] MainWebView Message received:", message.type, message);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if (message.type === "console") {
|
|
351
|
-
if (devMode) {
|
|
352
|
-
const level = message.level || "log";
|
|
353
|
-
console.log(`[Glomo-RN-SDK LRS] [WebView ${level.toUpperCase()}]`, message.message);
|
|
354
|
-
}
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
417
|
+
},
|
|
418
|
+
handlers: {
|
|
419
|
+
"file.save": fileSaveHandler,
|
|
420
|
+
"file.input": fileInputHandler,
|
|
357
421
|
|
|
358
|
-
|
|
359
|
-
const url = message.url;
|
|
360
|
-
if (url && typeof url === "string" && isValidUrl(url)) {
|
|
422
|
+
console: (message) => {
|
|
361
423
|
if (devMode) {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
setFlowWebViewUrl(url);
|
|
365
|
-
setShowFlowWebView(true);
|
|
366
|
-
setEducationCarouselState("pending");
|
|
367
|
-
// Tracking window.open interception
|
|
368
|
-
trackWindowOpen("main", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
369
|
-
} else {
|
|
370
|
-
if (devMode) {
|
|
371
|
-
console.log(
|
|
372
|
-
"[Glomo-RN-SDK LRS] Invalid URL in window.open from MainWebView:",
|
|
373
|
-
url,
|
|
374
|
-
typeof url === "undefined" ? "undefined" : ""
|
|
375
|
-
);
|
|
424
|
+
const level = (typeof message.level === "string" && message.level) || "log";
|
|
425
|
+
console.log(`${LOG_PREFIX} [WebView ${level.toUpperCase()}]`, message.message);
|
|
376
426
|
}
|
|
377
|
-
}
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
if (message.type === "window.close") {
|
|
382
|
-
if (devMode) {
|
|
383
|
-
console.log("[Glomo-RN-SDK LRS] Child window close requested from MainWebView");
|
|
384
|
-
}
|
|
385
|
-
setShowFlowWebView(false);
|
|
386
|
-
setFlowWebViewUrl("");
|
|
387
|
-
setEducationCarouselState("pending");
|
|
388
|
-
// Tracking window.close interception
|
|
389
|
-
trackWindowClose("main", orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// Handling merchant JS callbacks on flow completion
|
|
394
|
-
if (message.type === "message" && message.message) {
|
|
395
|
-
const messageData = message.message;
|
|
396
|
-
const messageType = messageData?.type;
|
|
397
|
-
|
|
398
|
-
if (messageType === "payment.success" || messageType === "payment.complete") {
|
|
399
|
-
const paymentMessage = messageData;
|
|
400
|
-
const payload = paymentMessage?.payload;
|
|
427
|
+
},
|
|
401
428
|
|
|
402
|
-
|
|
429
|
+
"window.open": (message) => {
|
|
430
|
+
const url = message.url;
|
|
431
|
+
if (url && typeof url === "string" && isValidUrl(url)) {
|
|
403
432
|
if (devMode) {
|
|
404
|
-
console.log(
|
|
433
|
+
console.log(`${LOG_PREFIX} window.open called from MainWebView: ${url}`);
|
|
405
434
|
}
|
|
406
|
-
|
|
407
|
-
setShowFlowWebView(
|
|
408
|
-
setFlowWebViewUrl("");
|
|
435
|
+
setFlowWebViewUrl(url);
|
|
436
|
+
setShowFlowWebView(true);
|
|
409
437
|
setEducationCarouselState("pending");
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
// Tracking payment success callback
|
|
413
|
-
trackPaymentSuccess(
|
|
414
|
-
payload.orderId,
|
|
415
|
-
payload.paymentId,
|
|
416
|
-
publicKey,
|
|
417
|
-
devMode,
|
|
418
|
-
mockMode,
|
|
419
|
-
checkoutUrl
|
|
420
|
-
);
|
|
438
|
+
// Tracking window.open interception
|
|
439
|
+
trackWindowOpen("main", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
421
440
|
} else {
|
|
422
441
|
if (devMode) {
|
|
423
|
-
console.
|
|
442
|
+
console.log(
|
|
443
|
+
`${LOG_PREFIX} Invalid URL in window.open from MainWebView:`,
|
|
444
|
+
url,
|
|
445
|
+
typeof url === "undefined" ? "undefined" : ""
|
|
446
|
+
);
|
|
424
447
|
}
|
|
425
448
|
}
|
|
426
|
-
|
|
427
|
-
|
|
449
|
+
},
|
|
450
|
+
|
|
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
|
+
}
|
|
428
536
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
+
}
|
|
432
605
|
|
|
433
|
-
|
|
606
|
+
// Handling checkout.closed events
|
|
607
|
+
if (messageType === "checkout.closed") {
|
|
434
608
|
if (devMode) {
|
|
435
|
-
console.
|
|
609
|
+
console.log(`${LOG_PREFIX} Checkout closed event received`);
|
|
436
610
|
}
|
|
437
611
|
setShowCheckout(false);
|
|
438
612
|
setShowFlowWebView(false);
|
|
439
613
|
setFlowWebViewUrl("");
|
|
440
614
|
setEducationCarouselState("pending");
|
|
441
|
-
|
|
442
|
-
safeCallback(
|
|
443
|
-
//
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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),
|
|
447
621
|
publicKey,
|
|
448
622
|
devMode,
|
|
449
623
|
mockMode,
|
|
450
624
|
checkoutUrl
|
|
451
625
|
);
|
|
452
|
-
} else {
|
|
453
|
-
if (devMode) {
|
|
454
|
-
console.error("[Glomo-RN-SDK LRS] Invalid payment failure payload:", payload);
|
|
455
|
-
}
|
|
456
626
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
} catch (error) {
|
|
476
|
-
if (devMode) {
|
|
477
|
-
console.error("[Glomo-RN-SDK LRS] Error parsing MainWebView message:", error);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
},
|
|
481
|
-
[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
|
+
]
|
|
482
643
|
);
|
|
483
644
|
|
|
484
645
|
// Handler for the flow WebView messages
|
|
485
|
-
const handleFlowWebViewMessage =
|
|
486
|
-
(
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}
|
|
493
|
-
// Tracking invalid message received
|
|
646
|
+
const handleFlowWebViewMessage = useMemo(
|
|
647
|
+
() =>
|
|
648
|
+
createMessageRouter({
|
|
649
|
+
logPrefix: LOG_PREFIX,
|
|
650
|
+
source: "FlowWebView",
|
|
651
|
+
devMode,
|
|
652
|
+
onInvalidMessage: (data) => {
|
|
494
653
|
trackInvalidMessageReceived(
|
|
495
654
|
"flow",
|
|
496
655
|
data,
|
|
@@ -501,179 +660,261 @@ export function useLrsCheckout(
|
|
|
501
660
|
mockMode,
|
|
502
661
|
checkoutUrl
|
|
503
662
|
);
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
+
},
|
|
511
683
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
console.log(`[Glomo-RN-SDK LRS] [Flow] ${message.message}`);
|
|
515
|
-
}
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
684
|
+
"file.save": fileSaveHandler,
|
|
685
|
+
"file.input": fileInputHandler,
|
|
518
686
|
|
|
519
|
-
|
|
520
|
-
const url = message.url;
|
|
521
|
-
if (url && typeof url === "string" && isValidUrl(url)) {
|
|
687
|
+
console: (message) => {
|
|
522
688
|
if (devMode) {
|
|
523
|
-
console.log(
|
|
524
|
-
}
|
|
525
|
-
setFlowWebViewUrl(url);
|
|
526
|
-
setEducationCarouselState("pending");
|
|
527
|
-
// Tracking window.open interception
|
|
528
|
-
trackWindowOpen("flow", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
529
|
-
} else {
|
|
530
|
-
if (devMode) {
|
|
531
|
-
console.log(
|
|
532
|
-
"[Glomo-RN-SDK LRS] Invalid URL in window.open from FlowWebView:",
|
|
533
|
-
url,
|
|
534
|
-
typeof url === "undefined" ? "undefined" : ""
|
|
535
|
-
);
|
|
689
|
+
console.log(`${LOG_PREFIX} [Flow] ${message.message}`);
|
|
536
690
|
}
|
|
537
|
-
}
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
if (message.type === "window.close") {
|
|
542
|
-
if (devMode) {
|
|
543
|
-
console.log("[Glomo-RN-SDK LRS] FlowWebView close requested");
|
|
544
|
-
}
|
|
545
|
-
setShowFlowWebView(false);
|
|
546
|
-
setFlowWebViewUrl("");
|
|
547
|
-
setEducationCarouselState("pending");
|
|
548
|
-
// Tracking window.close interception
|
|
549
|
-
trackWindowClose("flow", orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
if (message.type === "message" && message.message) {
|
|
554
|
-
const messageData = message.message;
|
|
555
|
-
const messageType = messageData?.type;
|
|
556
|
-
|
|
557
|
-
if (messageType === "payment.success" || messageType === "payment.complete") {
|
|
558
|
-
const paymentMessage = messageData;
|
|
559
|
-
const payload = paymentMessage?.payload;
|
|
691
|
+
},
|
|
560
692
|
|
|
561
|
-
|
|
693
|
+
"window.open": (message) => {
|
|
694
|
+
const url = message.url;
|
|
695
|
+
if (url && typeof url === "string" && isValidUrl(url)) {
|
|
562
696
|
if (devMode) {
|
|
563
|
-
console.log(
|
|
697
|
+
console.log(`${LOG_PREFIX} window.open called from FlowWebView: ${url}`);
|
|
564
698
|
}
|
|
565
|
-
|
|
566
|
-
setShowCheckout(false);
|
|
567
|
-
setFlowWebViewUrl("");
|
|
699
|
+
setFlowWebViewUrl(url);
|
|
568
700
|
setEducationCarouselState("pending");
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
// Tracking payment success callback
|
|
572
|
-
trackPaymentSuccess(
|
|
573
|
-
payload.orderId,
|
|
574
|
-
payload.paymentId,
|
|
575
|
-
publicKey,
|
|
576
|
-
devMode,
|
|
577
|
-
mockMode,
|
|
578
|
-
checkoutUrl
|
|
579
|
-
);
|
|
701
|
+
// Tracking window.open interception
|
|
702
|
+
trackWindowOpen("flow", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
580
703
|
} else {
|
|
581
704
|
if (devMode) {
|
|
582
|
-
console.
|
|
583
|
-
|
|
584
|
-
|
|
705
|
+
console.log(
|
|
706
|
+
`${LOG_PREFIX} Invalid URL in window.open from FlowWebView:`,
|
|
707
|
+
url,
|
|
708
|
+
typeof url === "undefined" ? "undefined" : ""
|
|
585
709
|
);
|
|
586
710
|
}
|
|
587
711
|
}
|
|
588
|
-
|
|
589
|
-
}
|
|
712
|
+
},
|
|
590
713
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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
|
+
);
|
|
598
796
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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
|
|
619
864
|
);
|
|
620
865
|
}
|
|
621
866
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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
|
+
]
|
|
632
883
|
);
|
|
633
884
|
|
|
634
885
|
// Handler for the education carousel WebView messages
|
|
635
|
-
const handleEducationCarouselWebViewMessage =
|
|
636
|
-
(
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
if (devMode) {
|
|
648
|
-
console.log("[Glomo-RN-SDK LRS] EducationCarouselWebView Message received:", message.type, message);
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
if (message.type === "console") {
|
|
652
|
-
if (devMode) {
|
|
653
|
-
console.log(`[Glomo-RN-SDK LRS] [EducationCarousel] ${message.message}`);
|
|
654
|
-
}
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
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
|
+
},
|
|
657
898
|
|
|
658
|
-
|
|
659
|
-
|
|
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
|
+
}
|
|
660
905
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
906
|
+
trackEducationStepsShown(
|
|
907
|
+
orderId,
|
|
908
|
+
publicKey,
|
|
909
|
+
devMode,
|
|
910
|
+
mockMode,
|
|
911
|
+
checkoutUrl,
|
|
912
|
+
typeof message.source === "string" ? message.source : undefined
|
|
664
913
|
);
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
return;
|
|
670
|
-
}
|
|
671
|
-
} catch (error) {
|
|
672
|
-
if (devMode) {
|
|
673
|
-
console.error("[Glomo-RN-SDK LRS] Error parsing EducationCarouselWebView message:", error);
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
},
|
|
914
|
+
setEducationCarouselState("hasContent");
|
|
915
|
+
},
|
|
916
|
+
},
|
|
917
|
+
}),
|
|
677
918
|
[checkoutUrl, devMode, mockMode, orderId, publicKey]
|
|
678
919
|
);
|
|
679
920
|
|
|
@@ -710,10 +951,26 @@ export function useLrsCheckout(
|
|
|
710
951
|
console.log("[Glomo-RN-SDK LRS] Ignoring checkout status update for: " + status);
|
|
711
952
|
}
|
|
712
953
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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
|
+
);
|
|
717
974
|
} catch (error) {
|
|
718
975
|
if (devMode) {
|
|
719
976
|
console.error("[Glomo-RN-SDK LRS] Error in handleModalBackButton:", error);
|
|
@@ -721,6 +978,39 @@ export function useLrsCheckout(
|
|
|
721
978
|
}
|
|
722
979
|
}, [showFlowWebView, devMode, onPaymentTerminate, status, orderId, publicKey, mockMode, checkoutUrl]);
|
|
723
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
|
+
|
|
724
1014
|
/** Closes the flow WebView and returns to the main checkout */
|
|
725
1015
|
const handleFlowBack = useCallback(() => {
|
|
726
1016
|
if (devMode) {
|
|
@@ -729,7 +1019,7 @@ export function useLrsCheckout(
|
|
|
729
1019
|
setShowFlowWebView(false);
|
|
730
1020
|
setFlowWebViewUrl("");
|
|
731
1021
|
setEducationCarouselState("pending");
|
|
732
|
-
|
|
1022
|
+
trackBackNavigationViaSdkBackButton(orderId, publicKey, devMode, mockMode, checkoutUrl, "lrs");
|
|
733
1023
|
}, [devMode, orderId, publicKey, mockMode, checkoutUrl]);
|
|
734
1024
|
|
|
735
1025
|
/**
|
|
@@ -749,11 +1039,16 @@ export function useLrsCheckout(
|
|
|
749
1039
|
* Checking device security compliance (if peerDependencies are available)
|
|
750
1040
|
* A null value indicates that recommended security dependencies were not installed and compliance checks will be skipped in this case
|
|
751
1041
|
*/
|
|
752
|
-
const
|
|
753
|
-
const
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
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,
|
|
757
1052
|
};
|
|
758
1053
|
|
|
759
1054
|
if (devMode) {
|
|
@@ -761,37 +1056,64 @@ export function useLrsCheckout(
|
|
|
761
1056
|
}
|
|
762
1057
|
|
|
763
1058
|
// Tracking start attempt with current status
|
|
764
|
-
trackStartAttempt(status, orderId, publicKey, devMode, mockMode, checkoutUrl,
|
|
1059
|
+
trackStartAttempt(status, orderId, publicKey, devMode, mockMode, checkoutUrl, dependencyInfo);
|
|
765
1060
|
|
|
766
|
-
|
|
767
|
-
|
|
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
|
+
}
|
|
1080
|
+
|
|
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;
|
|
768
1089
|
if (devMode) {
|
|
769
|
-
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
|
+
);
|
|
770
1095
|
}
|
|
771
1096
|
const errors: SdkError[] = [
|
|
772
1097
|
{
|
|
773
1098
|
type: "device_forbidden",
|
|
774
|
-
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.",
|
|
775
1102
|
},
|
|
776
1103
|
];
|
|
777
1104
|
safeCallback(onSdkError, [errors], "onSdkError", devMode);
|
|
778
1105
|
// Tracking start() failure
|
|
779
1106
|
trackStartFailure(
|
|
780
|
-
"device_forbidden",
|
|
1107
|
+
unknown ? "device_compliance_unknown" : "device_forbidden",
|
|
781
1108
|
orderId,
|
|
782
1109
|
publicKey,
|
|
783
1110
|
devMode,
|
|
784
1111
|
mockMode,
|
|
785
1112
|
checkoutUrl,
|
|
786
|
-
|
|
1113
|
+
dependencyInfo
|
|
787
1114
|
);
|
|
788
1115
|
setShowCheckout(false);
|
|
789
1116
|
return false;
|
|
790
|
-
} else if (deviceComplianceCheck === null) {
|
|
791
|
-
// 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.
|
|
792
|
-
console.warn(
|
|
793
|
-
"[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."
|
|
794
|
-
);
|
|
795
1117
|
}
|
|
796
1118
|
|
|
797
1119
|
// Enforcing devMode requirement for development servers
|
|
@@ -805,15 +1127,7 @@ export function useLrsCheckout(
|
|
|
805
1127
|
];
|
|
806
1128
|
safeCallback(onSdkError, [errors], "onSdkError", devMode);
|
|
807
1129
|
trackSdkError(errors, orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
808
|
-
trackStartFailure(
|
|
809
|
-
"validation_error",
|
|
810
|
-
orderId,
|
|
811
|
-
publicKey,
|
|
812
|
-
devMode,
|
|
813
|
-
mockMode,
|
|
814
|
-
checkoutUrl,
|
|
815
|
-
jailbreakDetectionInfo
|
|
816
|
-
);
|
|
1130
|
+
trackStartFailure("validation_error", orderId, publicKey, devMode, mockMode, checkoutUrl, dependencyInfo);
|
|
817
1131
|
setShowCheckout(false);
|
|
818
1132
|
return false;
|
|
819
1133
|
}
|
|
@@ -861,15 +1175,7 @@ export function useLrsCheckout(
|
|
|
861
1175
|
safeCallback(onSdkError, [errors], "onSdkError", devMode);
|
|
862
1176
|
// Tracking start() failure
|
|
863
1177
|
trackSdkError(errors, orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
864
|
-
trackStartFailure(
|
|
865
|
-
"validation_error",
|
|
866
|
-
orderId,
|
|
867
|
-
publicKey,
|
|
868
|
-
devMode,
|
|
869
|
-
mockMode,
|
|
870
|
-
checkoutUrl,
|
|
871
|
-
jailbreakDetectionInfo
|
|
872
|
-
);
|
|
1178
|
+
trackStartFailure("validation_error", orderId, publicKey, devMode, mockMode, checkoutUrl, dependencyInfo);
|
|
873
1179
|
setShowCheckout(false);
|
|
874
1180
|
return false;
|
|
875
1181
|
}
|
|
@@ -881,15 +1187,7 @@ export function useLrsCheckout(
|
|
|
881
1187
|
);
|
|
882
1188
|
}
|
|
883
1189
|
// Tracking start() failure
|
|
884
|
-
trackStartFailure(
|
|
885
|
-
"payment_successful",
|
|
886
|
-
orderId,
|
|
887
|
-
publicKey,
|
|
888
|
-
devMode,
|
|
889
|
-
mockMode,
|
|
890
|
-
checkoutUrl,
|
|
891
|
-
jailbreakDetectionInfo
|
|
892
|
-
);
|
|
1190
|
+
trackStartFailure("payment_successful", orderId, publicKey, devMode, mockMode, checkoutUrl, dependencyInfo);
|
|
893
1191
|
setShowCheckout(false);
|
|
894
1192
|
return false;
|
|
895
1193
|
}
|
|
@@ -906,15 +1204,7 @@ export function useLrsCheckout(
|
|
|
906
1204
|
console.log(`[Glomo-RN-SDK LRS] Cannot start checkout. Unknown status: ${status}.`);
|
|
907
1205
|
}
|
|
908
1206
|
// Tracking start() failure
|
|
909
|
-
trackStartFailure(
|
|
910
|
-
"invalid_status",
|
|
911
|
-
orderId,
|
|
912
|
-
publicKey,
|
|
913
|
-
devMode,
|
|
914
|
-
mockMode,
|
|
915
|
-
checkoutUrl,
|
|
916
|
-
jailbreakDetectionInfo
|
|
917
|
-
);
|
|
1207
|
+
trackStartFailure("invalid_status", orderId, publicKey, devMode, mockMode, checkoutUrl, dependencyInfo);
|
|
918
1208
|
setShowCheckout(false);
|
|
919
1209
|
return false;
|
|
920
1210
|
}
|
|
@@ -924,15 +1214,7 @@ export function useLrsCheckout(
|
|
|
924
1214
|
console.error("[Glomo-RN-SDK LRS] Cannot start checkout. Invalid checkout URL.");
|
|
925
1215
|
}
|
|
926
1216
|
// Tracking start() failure
|
|
927
|
-
trackStartFailure(
|
|
928
|
-
"invalid_url",
|
|
929
|
-
orderId,
|
|
930
|
-
publicKey,
|
|
931
|
-
devMode,
|
|
932
|
-
mockMode,
|
|
933
|
-
checkoutUrl,
|
|
934
|
-
jailbreakDetectionInfo
|
|
935
|
-
);
|
|
1217
|
+
trackStartFailure("invalid_url", orderId, publicKey, devMode, mockMode, checkoutUrl, dependencyInfo);
|
|
936
1218
|
setShowCheckout(false);
|
|
937
1219
|
return false;
|
|
938
1220
|
}
|
|
@@ -947,7 +1229,7 @@ export function useLrsCheckout(
|
|
|
947
1229
|
setStatus("payment_in_progress");
|
|
948
1230
|
|
|
949
1231
|
// Tracking successful start
|
|
950
|
-
trackStartSuccess(orderId, publicKey, devMode, mockMode, checkoutUrl,
|
|
1232
|
+
trackStartSuccess(orderId, publicKey, devMode, mockMode, checkoutUrl, dependencyInfo);
|
|
951
1233
|
|
|
952
1234
|
setShowCheckout(true);
|
|
953
1235
|
return true;
|
|
@@ -991,6 +1273,14 @@ export function useLrsCheckout(
|
|
|
991
1273
|
};
|
|
992
1274
|
}, []);
|
|
993
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
|
+
|
|
994
1284
|
return {
|
|
995
1285
|
start,
|
|
996
1286
|
status,
|
|
@@ -1011,14 +1301,15 @@ export function useLrsCheckout(
|
|
|
1011
1301
|
handleNavigationStateChange,
|
|
1012
1302
|
handleModalBackButton,
|
|
1013
1303
|
handleFlowBack,
|
|
1304
|
+
handlePermissionRequest,
|
|
1014
1305
|
injectedMain:
|
|
1015
1306
|
__DEV__ && internalOptions?._testInjectionScript
|
|
1016
|
-
?
|
|
1017
|
-
:
|
|
1018
|
-
injectedFlow:
|
|
1307
|
+
? injectionScripts.main + internalOptions._testInjectionScript
|
|
1308
|
+
: injectionScripts.main,
|
|
1309
|
+
injectedFlow: injectionScripts.flow,
|
|
1019
1310
|
injectedEducationCarousel:
|
|
1020
1311
|
__DEV__ && internalOptions?._testCarouselInjectionScript
|
|
1021
|
-
?
|
|
1022
|
-
:
|
|
1312
|
+
? injectionScripts.educationCarousel + internalOptions._testCarouselInjectionScript
|
|
1313
|
+
: injectionScripts.educationCarousel,
|
|
1023
1314
|
};
|
|
1024
1315
|
}
|