@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
|
@@ -1,26 +1,52 @@
|
|
|
1
1
|
/** Main SDK hook for the GlomoPay Standard Checkout flow */
|
|
2
2
|
|
|
3
3
|
import React, { useState, useRef, useMemo, useCallback, useEffect } from "react";
|
|
4
|
-
import { Platform,
|
|
4
|
+
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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, toJourneyEventData } from "./utils/event-projections";
|
|
21
|
+
import { resolveDeviceCompliance, blocksCheckout, isComplianceCheckAvailable } from "./utils/device-compliance";
|
|
22
|
+
import { createMessageRouter } from "./bridge/message-router";
|
|
23
|
+
import { createFileSaveHandler } from "./bridge/file-save-handler";
|
|
24
|
+
import { createFileInputHandler } from "./bridge/file-input-handler";
|
|
25
|
+
import { resolveCapabilities, resolveFileSaveAvailability } from "./capabilities/index";
|
|
26
|
+
import { useCapabilityReporting } from "./capabilities/use-capability-reporting";
|
|
27
|
+
import { useSdkSession } from "./utils/use-sdk-session";
|
|
28
|
+
import { usePermissionRequest, type WebViewPermissionRequest } from "./webview/use-permission-request";
|
|
29
|
+
import {
|
|
30
|
+
trackIncompletePaymentPayload,
|
|
31
|
+
trackDroppedPaymentPayload,
|
|
32
|
+
trackDevicePermissionRefused,
|
|
33
|
+
trackBackNavigationViaSdkBackButton,
|
|
34
|
+
type SdkError,
|
|
35
|
+
type FlowType,
|
|
36
|
+
type CheckoutAnalyticsTrackers,
|
|
37
|
+
standardCheckoutTrackers,
|
|
38
|
+
} from "./utils/analytics";
|
|
39
|
+
|
|
40
|
+
import {
|
|
41
|
+
ASYNC_PAYMENT_EVENTS,
|
|
42
|
+
TERMINATION_SOURCES,
|
|
43
|
+
type GlomoTerminationPayload,
|
|
44
|
+
type GlomoUserJourneyCompletedPayload,
|
|
45
|
+
} from "./types/checkout";
|
|
16
46
|
import { type StandardCheckoutStatus, type GlomoStandardCheckoutPayload } from "./types/standard-checkout";
|
|
17
47
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
nativeEvent: { resources: string[] };
|
|
21
|
-
grant: (resources: string[]) => void;
|
|
22
|
-
deny: () => void;
|
|
23
|
-
}
|
|
48
|
+
/** Log prefix for everything this flow emits */
|
|
49
|
+
const LOG_PREFIX = "[Glomo-RN-SDK Standard]";
|
|
24
50
|
|
|
25
51
|
/** The options for the useStandardCheckout hook */
|
|
26
52
|
export interface UseStandardCheckoutOptions {
|
|
@@ -30,10 +56,10 @@ export interface UseStandardCheckoutOptions {
|
|
|
30
56
|
onPaymentSuccess: (payload: GlomoStandardCheckoutPayload) => void;
|
|
31
57
|
onPaymentFailure: (payload: GlomoStandardCheckoutPayload) => void;
|
|
32
58
|
onConnectionError?: (error: unknown) => void;
|
|
33
|
-
onPaymentTerminate?: () => void;
|
|
59
|
+
onPaymentTerminate?: (payload: GlomoTerminationPayload) => void;
|
|
34
60
|
onSdkError: (error: Array<SdkError>) => void;
|
|
35
61
|
onUserJourneyCompleted?: (payload: GlomoUserJourneyCompletedPayload) => void;
|
|
36
|
-
|
|
62
|
+
onUserRefusedDevicePermissions?: () => void;
|
|
37
63
|
devMode?: boolean;
|
|
38
64
|
}
|
|
39
65
|
|
|
@@ -68,7 +94,21 @@ export interface UseStandardCheckoutReturn {
|
|
|
68
94
|
*/
|
|
69
95
|
export interface UseStandardCheckoutInternalOptions {
|
|
70
96
|
analyticsTrackers?: CheckoutAnalyticsTrackers;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Which flow this hook is serving, for the telemetry that is not routed through
|
|
100
|
+
* analyticsTrackers.
|
|
101
|
+
*
|
|
102
|
+
* The tracker map cannot answer this: its whole purpose is that the caller picks a set of
|
|
103
|
+
* flow-specific functions, so a flow-indifferent event has nothing to look up there. File saves
|
|
104
|
+
* are the first of those - the capability is advertised to every WebView regardless of flow -
|
|
105
|
+
* and putting them in the map would re-couple a capability to a flow, which is the thing v5
|
|
106
|
+
* exists to undo.
|
|
107
|
+
*/
|
|
108
|
+
flowType?: FlowType;
|
|
109
|
+
|
|
71
110
|
_skipOrderIdValidation?: boolean;
|
|
111
|
+
_testInjectionScript?: string;
|
|
72
112
|
}
|
|
73
113
|
|
|
74
114
|
/** The main SDK hook for the GlomoPay Standard Checkout flow */
|
|
@@ -83,7 +123,7 @@ export function useStandardCheckout(
|
|
|
83
123
|
onPaymentTerminate,
|
|
84
124
|
onSdkError,
|
|
85
125
|
onUserJourneyCompleted,
|
|
86
|
-
|
|
126
|
+
onUserRefusedDevicePermissions,
|
|
87
127
|
devMode = false,
|
|
88
128
|
}: UseStandardCheckoutOptions,
|
|
89
129
|
mockMode: boolean,
|
|
@@ -91,6 +131,7 @@ export function useStandardCheckout(
|
|
|
91
131
|
): UseStandardCheckoutReturn {
|
|
92
132
|
// Resolving analytics trackers - defaults to standard, subscription flow overrides
|
|
93
133
|
const trackers = internalOptions?.analyticsTrackers ?? standardCheckoutTrackers;
|
|
134
|
+
const flowType = internalOptions?.flowType ?? "standard";
|
|
94
135
|
// Refs for the WebViews
|
|
95
136
|
const mainWebViewRef = useRef<WebView>(null);
|
|
96
137
|
const flowWebViewRef = useRef<WebView>(null);
|
|
@@ -112,7 +153,9 @@ export function useStandardCheckout(
|
|
|
112
153
|
const mode = mockMode ? "mock" : "live";
|
|
113
154
|
const params = baseParams.concat(
|
|
114
155
|
`mode=${encodeURIComponent(mode)}`,
|
|
115
|
-
`publicKey=${encodeURIComponent(publicKey)}
|
|
156
|
+
`publicKey=${encodeURIComponent(publicKey)}`,
|
|
157
|
+
`product=${encodeURIComponent(ANALYTICS_PRODUCT)}`,
|
|
158
|
+
`surface=${encodeURIComponent(ANALYTICS_SURFACE)}`
|
|
116
159
|
);
|
|
117
160
|
const queryString = params.join("&");
|
|
118
161
|
|
|
@@ -236,22 +279,65 @@ export function useStandardCheckout(
|
|
|
236
279
|
[devMode]
|
|
237
280
|
);
|
|
238
281
|
|
|
282
|
+
// Correlates every event from this mount, and keeps checkoutStatus current on all of them
|
|
283
|
+
useSdkSession(() => status);
|
|
284
|
+
|
|
285
|
+
// Missing optional dependencies reach the host here; start() warns on the console separately
|
|
286
|
+
useCapabilityReporting({ onSdkError, devMode });
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Handler for `file.input` - the user tapping a file input on a bank's page.
|
|
290
|
+
*
|
|
291
|
+
* Registered beside `file.save` on the same WebViews, for the same reason: uploads are not a
|
|
292
|
+
* property of one flow, and until now this message had no handler anywhere.
|
|
293
|
+
*/
|
|
294
|
+
const fileInputHandler = useMemo(
|
|
295
|
+
() =>
|
|
296
|
+
createFileInputHandler({
|
|
297
|
+
flowType: flowType,
|
|
298
|
+
orderId,
|
|
299
|
+
publicKey,
|
|
300
|
+
checkoutUrl,
|
|
301
|
+
mockMode,
|
|
302
|
+
devMode,
|
|
303
|
+
}),
|
|
304
|
+
[orderId, publicKey, checkoutUrl, mockMode, devMode, flowType]
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Handler for `file.save` - the page asking the SDK to put a document on the device.
|
|
309
|
+
*
|
|
310
|
+
* One handler, registered on the main and flow WebViews both. Not on the education carousel:
|
|
311
|
+
* that renders our own content, which has nothing to gate, and the capability is not advertised
|
|
312
|
+
* there either.
|
|
313
|
+
*/
|
|
314
|
+
const fileSaveHandler = useMemo(
|
|
315
|
+
() =>
|
|
316
|
+
createFileSaveHandler({
|
|
317
|
+
flowType,
|
|
318
|
+
orderId,
|
|
319
|
+
publicKey,
|
|
320
|
+
checkoutUrl,
|
|
321
|
+
mockMode,
|
|
322
|
+
devMode,
|
|
323
|
+
onSdkError,
|
|
324
|
+
}),
|
|
325
|
+
[flowType, orderId, publicKey, checkoutUrl, mockMode, devMode, onSdkError]
|
|
326
|
+
);
|
|
327
|
+
|
|
239
328
|
/**
|
|
240
329
|
* Handler for the main WebView messages.
|
|
241
330
|
* The main WebView hosts the checkout page. Messages arrive as JSON strings from the injected
|
|
242
331
|
* JavaScript bridge and include console logs, window.open/close interceptions, and payment
|
|
243
332
|
* lifecycle events (success, failure, terminate, bank transfer, pay via bank).
|
|
244
333
|
*/
|
|
245
|
-
const handleMainWebViewMessage =
|
|
246
|
-
(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
console.warn("[Glomo-RN-SDK Standard] Invalid message data from MainWebView");
|
|
253
|
-
}
|
|
254
|
-
// Tracking invalid message received
|
|
334
|
+
const handleMainWebViewMessage = useMemo(
|
|
335
|
+
() =>
|
|
336
|
+
createMessageRouter({
|
|
337
|
+
logPrefix: LOG_PREFIX,
|
|
338
|
+
source: "MainWebView",
|
|
339
|
+
devMode,
|
|
340
|
+
onInvalidMessage: (data) => {
|
|
255
341
|
trackers.trackInvalidMessageReceived(
|
|
256
342
|
"main",
|
|
257
343
|
data,
|
|
@@ -262,216 +348,295 @@ export function useStandardCheckout(
|
|
|
262
348
|
mockMode,
|
|
263
349
|
checkoutUrl
|
|
264
350
|
);
|
|
265
|
-
|
|
266
|
-
|
|
351
|
+
},
|
|
352
|
+
handlers: {
|
|
353
|
+
"file.save": fileSaveHandler,
|
|
354
|
+
"file.input": fileInputHandler,
|
|
267
355
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
356
|
+
// Forwarding console messages from the injected script to the React Native console
|
|
357
|
+
console: (message) => {
|
|
358
|
+
if (devMode) {
|
|
359
|
+
const level = (typeof message.level === "string" && message.level) || "log";
|
|
360
|
+
console.log(`${LOG_PREFIX} [WebView ${level.toUpperCase()}]`, message.message);
|
|
361
|
+
}
|
|
362
|
+
},
|
|
272
363
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
364
|
+
// Intercepting window.open calls - opening the target URL in the flow WebView overlay
|
|
365
|
+
"window.open": (message) => {
|
|
366
|
+
const url = message.url;
|
|
367
|
+
if (url && typeof url === "string" && isValidUrl(url)) {
|
|
368
|
+
if (devMode) {
|
|
369
|
+
console.log(`${LOG_PREFIX} window.open called from MainWebView: ${url}`);
|
|
370
|
+
}
|
|
371
|
+
setFlowWebViewUrl(url);
|
|
372
|
+
setShowFlowWebView(true);
|
|
373
|
+
// Tracking window.open interception
|
|
374
|
+
trackers.trackWindowOpen("main", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
375
|
+
} else {
|
|
376
|
+
if (devMode) {
|
|
377
|
+
console.log(
|
|
378
|
+
`${LOG_PREFIX} Invalid URL in window.open from MainWebView:`,
|
|
379
|
+
url,
|
|
380
|
+
typeof url === "undefined" ? "undefined" : ""
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
},
|
|
281
385
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const url = message.url;
|
|
285
|
-
if (url && typeof url === "string" && isValidUrl(url)) {
|
|
386
|
+
// Intercepting window.close calls - closing the flow WebView overlay
|
|
387
|
+
"window.close": () => {
|
|
286
388
|
if (devMode) {
|
|
287
|
-
console.log(
|
|
389
|
+
console.log(`${LOG_PREFIX} Child window close requested from MainWebView`);
|
|
288
390
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
// Tracking window.
|
|
292
|
-
trackers.
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
391
|
+
setShowFlowWebView(false);
|
|
392
|
+
setFlowWebViewUrl("");
|
|
393
|
+
// Tracking window.close interception
|
|
394
|
+
trackers.trackWindowClose("main", orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
// Handling merchant JS callbacks on flow completion (payment events, lifecycle events)
|
|
398
|
+
message: (message) => {
|
|
399
|
+
const messageData = message.message as
|
|
400
|
+
| { type?: string; payload?: Record<string, string | undefined> }
|
|
401
|
+
| undefined;
|
|
402
|
+
if (!messageData) return;
|
|
403
|
+
const messageType = messageData.type;
|
|
404
|
+
|
|
405
|
+
// Handling payment success - closing checkout and notifying the merchant
|
|
406
|
+
if (messageType === "payment.success" || messageType === "payment.complete") {
|
|
407
|
+
const paymentMessage = messageData;
|
|
408
|
+
const payload = paymentMessage?.payload;
|
|
409
|
+
|
|
410
|
+
if (isValidPaymentPayload(payload)) {
|
|
411
|
+
if (devMode) {
|
|
412
|
+
console.log("[Glomo-RN-SDK Standard] Payment successful via merchant callback!");
|
|
413
|
+
}
|
|
414
|
+
setShowCheckout(false);
|
|
415
|
+
setShowFlowWebView(false);
|
|
416
|
+
setFlowWebViewUrl("");
|
|
417
|
+
setStatus("payment_successful");
|
|
418
|
+
safeCallback(onPaymentSuccess, [payload], "onPaymentSuccess", devMode);
|
|
419
|
+
// Tracking payment success callback
|
|
420
|
+
trackers.trackPaymentSuccess(
|
|
421
|
+
toPaymentEventData(payload),
|
|
422
|
+
publicKey,
|
|
423
|
+
devMode,
|
|
424
|
+
mockMode,
|
|
425
|
+
checkoutUrl
|
|
426
|
+
);
|
|
427
|
+
// Fires alongside the callback, not instead of it - the outcome is real, the payload is thin
|
|
428
|
+
const missingFields = missingPaymentFields(payload);
|
|
429
|
+
if (missingFields.length > 0) {
|
|
430
|
+
trackIncompletePaymentPayload(
|
|
431
|
+
"payment.success",
|
|
432
|
+
missingFields,
|
|
433
|
+
"main",
|
|
434
|
+
payload.orderId,
|
|
435
|
+
publicKey,
|
|
436
|
+
devMode,
|
|
437
|
+
mockMode,
|
|
438
|
+
checkoutUrl,
|
|
439
|
+
flowType
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
} else {
|
|
443
|
+
/**
|
|
444
|
+
* Reported, not dropped. Before v5 this branch was a devMode-only console.error, so in a
|
|
445
|
+
* release build a terminal payment outcome the SDK could not route left no trace
|
|
446
|
+
* anywhere - no callback, no telemetry, nothing for support to find.
|
|
447
|
+
*/
|
|
448
|
+
if (devMode) {
|
|
449
|
+
console.error(
|
|
450
|
+
`${LOG_PREFIX} Unroutable payment.success payload from main:`,
|
|
451
|
+
payload
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
trackDroppedPaymentPayload(
|
|
455
|
+
"payment.success",
|
|
456
|
+
payloadKeysOf(payload),
|
|
457
|
+
"main",
|
|
458
|
+
orderId,
|
|
459
|
+
publicKey,
|
|
460
|
+
devMode,
|
|
461
|
+
mockMode,
|
|
462
|
+
checkoutUrl,
|
|
463
|
+
flowType
|
|
464
|
+
);
|
|
465
|
+
safeCallback(
|
|
466
|
+
onSdkError,
|
|
467
|
+
[[droppedPayloadSdkError("payment.success")]],
|
|
468
|
+
"onSdkError",
|
|
469
|
+
devMode
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
return;
|
|
300
473
|
}
|
|
301
|
-
}
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Intercepting window.close calls - closing the flow WebView overlay
|
|
306
|
-
if (message.type === "window.close") {
|
|
307
|
-
if (devMode) {
|
|
308
|
-
console.log("[Glomo-RN-SDK Standard] Child window close requested from MainWebView");
|
|
309
|
-
}
|
|
310
|
-
setShowFlowWebView(false);
|
|
311
|
-
setFlowWebViewUrl("");
|
|
312
|
-
// Tracking window.close interception
|
|
313
|
-
trackers.trackWindowClose("main", orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
474
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
475
|
+
// Handling payment failure - closing checkout and notifying the merchant
|
|
476
|
+
if (messageType === "payment.failure" || messageType === "payment.error") {
|
|
477
|
+
const paymentMessage = messageData;
|
|
478
|
+
const payload = paymentMessage?.payload;
|
|
479
|
+
|
|
480
|
+
if (isValidPaymentPayload(payload)) {
|
|
481
|
+
if (devMode) {
|
|
482
|
+
console.error("[Glomo-RN-SDK Standard] Payment failed via merchant callback");
|
|
483
|
+
}
|
|
484
|
+
setShowCheckout(false);
|
|
485
|
+
setShowFlowWebView(false);
|
|
486
|
+
setFlowWebViewUrl("");
|
|
487
|
+
setStatus("payment_failed");
|
|
488
|
+
safeCallback(onPaymentFailure, [payload], "onPaymentFailure", devMode);
|
|
489
|
+
// Tracking payment failure callback
|
|
490
|
+
trackers.trackPaymentFailure(
|
|
491
|
+
toPaymentEventData(payload),
|
|
492
|
+
publicKey,
|
|
493
|
+
devMode,
|
|
494
|
+
mockMode,
|
|
495
|
+
checkoutUrl
|
|
496
|
+
);
|
|
497
|
+
// Fires alongside the callback, not instead of it - the outcome is real, the payload is thin
|
|
498
|
+
const missingFields = missingPaymentFields(payload);
|
|
499
|
+
if (missingFields.length > 0) {
|
|
500
|
+
trackIncompletePaymentPayload(
|
|
501
|
+
"payment.failure",
|
|
502
|
+
missingFields,
|
|
503
|
+
"main",
|
|
504
|
+
payload.orderId,
|
|
505
|
+
publicKey,
|
|
506
|
+
devMode,
|
|
507
|
+
mockMode,
|
|
508
|
+
checkoutUrl,
|
|
509
|
+
flowType
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
} else {
|
|
513
|
+
/**
|
|
514
|
+
* Reported, not dropped. Before v5 this branch was a devMode-only console.error, so in a
|
|
515
|
+
* release build a terminal payment outcome the SDK could not route left no trace
|
|
516
|
+
* anywhere - no callback, no telemetry, nothing for support to find.
|
|
517
|
+
*/
|
|
518
|
+
if (devMode) {
|
|
519
|
+
console.error(
|
|
520
|
+
`${LOG_PREFIX} Unroutable payment.failure payload from main:`,
|
|
521
|
+
payload
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
trackDroppedPaymentPayload(
|
|
525
|
+
"payment.failure",
|
|
526
|
+
payloadKeysOf(payload),
|
|
527
|
+
"main",
|
|
528
|
+
orderId,
|
|
529
|
+
publicKey,
|
|
530
|
+
devMode,
|
|
531
|
+
mockMode,
|
|
532
|
+
checkoutUrl,
|
|
533
|
+
flowType
|
|
534
|
+
);
|
|
535
|
+
safeCallback(
|
|
536
|
+
onSdkError,
|
|
537
|
+
[[droppedPayloadSdkError("payment.failure")]],
|
|
538
|
+
"onSdkError",
|
|
539
|
+
devMode
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
326
544
|
|
|
327
|
-
|
|
545
|
+
// Handling bank transfer submission - user has submitted transfer details
|
|
546
|
+
if (messageType === "payment.bank_transfer_submitted") {
|
|
547
|
+
const payload = messageData?.payload;
|
|
328
548
|
if (devMode) {
|
|
329
|
-
console.log("[Glomo-RN-SDK Standard]
|
|
549
|
+
console.log("[Glomo-RN-SDK Standard] Bank transfer submitted:", payload);
|
|
330
550
|
}
|
|
331
551
|
setShowCheckout(false);
|
|
332
552
|
setShowFlowWebView(false);
|
|
333
553
|
setFlowWebViewUrl("");
|
|
334
|
-
setStatus("
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
payload
|
|
339
|
-
payload
|
|
554
|
+
setStatus("bank_transfer_submitted");
|
|
555
|
+
const journeyPayload: GlomoUserJourneyCompletedPayload = {
|
|
556
|
+
journeyType: ASYNC_PAYMENT_EVENTS.BANK_TRANSFER_SUBMITTED,
|
|
557
|
+
orderId: payload?.orderId,
|
|
558
|
+
senderAccountNumber: payload?.senderAccountNumber,
|
|
559
|
+
transactionReference: payload?.transactionReference,
|
|
560
|
+
};
|
|
561
|
+
safeCallback(onUserJourneyCompleted, [journeyPayload], "onUserJourneyCompleted", devMode);
|
|
562
|
+
trackers.trackBankTransferSubmitted(
|
|
563
|
+
orderId,
|
|
564
|
+
toJourneyEventData(journeyPayload),
|
|
340
565
|
publicKey,
|
|
341
566
|
devMode,
|
|
342
567
|
mockMode,
|
|
343
568
|
checkoutUrl
|
|
344
569
|
);
|
|
345
|
-
|
|
346
|
-
if (devMode) {
|
|
347
|
-
console.error("[Glomo-RN-SDK Standard] Invalid payment success payload:", payload);
|
|
348
|
-
}
|
|
570
|
+
return;
|
|
349
571
|
}
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
572
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
573
|
+
/**
|
|
574
|
+
* Handling the generic glomoCheckoutJourneyTerminate event.
|
|
575
|
+
* This event is fired by the checkout page when the pay via bank (lean/open finance)
|
|
576
|
+
* flow completes. The deduplication ref prevents firing the callback twice when both
|
|
577
|
+
* main and flow WebViews emit this event.
|
|
578
|
+
*/
|
|
579
|
+
if (messageType === "glomoCheckoutJourneyTerminate") {
|
|
580
|
+
// Deduplication - both main and flow WebViews can emit this event
|
|
581
|
+
if (payViaBankCompletedHandledRef.current) {
|
|
582
|
+
if (devMode) {
|
|
583
|
+
console.log(
|
|
584
|
+
"[Glomo-RN-SDK Standard] Pay via bank completed already handled, ignoring duplicate"
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
payViaBankCompletedHandledRef.current = true;
|
|
357
590
|
|
|
358
|
-
|
|
591
|
+
const payload = messageData?.payload;
|
|
359
592
|
if (devMode) {
|
|
360
|
-
console.
|
|
593
|
+
console.log("[Glomo-RN-SDK Standard] Pay via bank completed via MainWebView:", payload);
|
|
361
594
|
}
|
|
362
|
-
setShowCheckout(false);
|
|
363
595
|
setShowFlowWebView(false);
|
|
596
|
+
setShowCheckout(false);
|
|
364
597
|
setFlowWebViewUrl("");
|
|
365
|
-
setStatus("
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
payload
|
|
370
|
-
|
|
598
|
+
setStatus("pay_via_bank_completed");
|
|
599
|
+
const journeyPayload: GlomoUserJourneyCompletedPayload = {
|
|
600
|
+
journeyType: ASYNC_PAYMENT_EVENTS.PAY_VIA_BANK_COMPLETED,
|
|
601
|
+
orderId: payload?.orderId,
|
|
602
|
+
status: payload?.status ?? "unknown",
|
|
603
|
+
};
|
|
604
|
+
safeCallback(onUserJourneyCompleted, [journeyPayload], "onUserJourneyCompleted", devMode);
|
|
605
|
+
trackers.trackPayViaBankCompleted(
|
|
606
|
+
orderId,
|
|
607
|
+
toJourneyEventData(journeyPayload),
|
|
371
608
|
publicKey,
|
|
372
609
|
devMode,
|
|
373
610
|
mockMode,
|
|
374
611
|
checkoutUrl
|
|
375
612
|
);
|
|
376
|
-
|
|
377
|
-
if (devMode) {
|
|
378
|
-
console.error("[Glomo-RN-SDK Standard] Invalid payment failure payload:", payload);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// Handling bank transfer submission - user has submitted transfer details
|
|
385
|
-
if (messageType === "payment.bank_transfer_submitted") {
|
|
386
|
-
const payload = messageData?.payload;
|
|
387
|
-
if (devMode) {
|
|
388
|
-
console.log("[Glomo-RN-SDK Standard] Bank transfer submitted:", payload);
|
|
613
|
+
return;
|
|
389
614
|
}
|
|
390
|
-
setShowCheckout(false);
|
|
391
|
-
setShowFlowWebView(false);
|
|
392
|
-
setFlowWebViewUrl("");
|
|
393
|
-
setStatus("bank_transfer_submitted");
|
|
394
|
-
const journeyPayload: GlomoUserJourneyCompletedPayload = {
|
|
395
|
-
journeyType: ASYNC_PAYMENT_EVENTS.BANK_TRANSFER_SUBMITTED,
|
|
396
|
-
orderId: payload?.orderId,
|
|
397
|
-
senderAccountNumber: payload?.senderAccountNumber,
|
|
398
|
-
transactionReference: payload?.transactionReference,
|
|
399
|
-
};
|
|
400
|
-
safeCallback(onUserJourneyCompleted, [journeyPayload], "onUserJourneyCompleted", devMode);
|
|
401
|
-
trackers.trackBankTransferSubmitted(orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
615
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
* This event is fired by the checkout page when the pay via bank (lean/open finance)
|
|
408
|
-
* flow completes. The deduplication ref prevents firing the callback twice when both
|
|
409
|
-
* main and flow WebViews emit this event.
|
|
410
|
-
*/
|
|
411
|
-
if (messageType === "glomoCheckoutJourneyTerminate") {
|
|
412
|
-
// Deduplication - both main and flow WebViews can emit this event
|
|
413
|
-
if (payViaBankCompletedHandledRef.current) {
|
|
616
|
+
// Handling checkout.closed events
|
|
617
|
+
if (messageType === "checkout.closed") {
|
|
414
618
|
if (devMode) {
|
|
415
|
-
console.log(
|
|
416
|
-
"[Glomo-RN-SDK Standard] Pay via bank completed already handled, ignoring duplicate"
|
|
417
|
-
);
|
|
619
|
+
console.log("[Glomo-RN-SDK Standard] Checkout closed event received");
|
|
418
620
|
}
|
|
621
|
+
setShowCheckout(false);
|
|
622
|
+
setShowFlowWebView(false);
|
|
623
|
+
setFlowWebViewUrl("");
|
|
624
|
+
const termination = { source: TERMINATION_SOURCES.CHECKOUT_CLOSED };
|
|
625
|
+
safeCallback(onPaymentTerminate, [termination], "onPaymentTerminate", devMode);
|
|
626
|
+
// One object to the host and to telemetry, so the two cannot drift
|
|
627
|
+
trackers.trackPaymentTerminate(
|
|
628
|
+
orderId,
|
|
629
|
+
toTerminationEventData(termination),
|
|
630
|
+
publicKey,
|
|
631
|
+
devMode,
|
|
632
|
+
mockMode,
|
|
633
|
+
checkoutUrl
|
|
634
|
+
);
|
|
419
635
|
return;
|
|
420
636
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
if (devMode) {
|
|
425
|
-
console.log("[Glomo-RN-SDK Standard] Pay via bank completed via MainWebView:", payload);
|
|
426
|
-
}
|
|
427
|
-
setShowFlowWebView(false);
|
|
428
|
-
setShowCheckout(false);
|
|
429
|
-
setFlowWebViewUrl("");
|
|
430
|
-
setStatus("pay_via_bank_completed");
|
|
431
|
-
const journeyPayload: GlomoUserJourneyCompletedPayload = {
|
|
432
|
-
journeyType: ASYNC_PAYMENT_EVENTS.PAY_VIA_BANK_COMPLETED,
|
|
433
|
-
orderId: payload?.orderId,
|
|
434
|
-
status: payload?.status ?? "unknown",
|
|
435
|
-
};
|
|
436
|
-
safeCallback(onUserJourneyCompleted, [journeyPayload], "onUserJourneyCompleted", devMode);
|
|
437
|
-
trackers.trackPayViaBankCompleted(
|
|
438
|
-
orderId,
|
|
439
|
-
publicKey,
|
|
440
|
-
payload?.status ?? "unknown",
|
|
441
|
-
devMode,
|
|
442
|
-
mockMode,
|
|
443
|
-
checkoutUrl
|
|
444
|
-
);
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// Handling checkout.closed events
|
|
449
|
-
if (messageType === "checkout.closed") {
|
|
450
|
-
if (devMode) {
|
|
451
|
-
console.log("[Glomo-RN-SDK Standard] Checkout closed event received");
|
|
452
|
-
}
|
|
453
|
-
setShowCheckout(false);
|
|
454
|
-
setShowFlowWebView(false);
|
|
455
|
-
setFlowWebViewUrl("");
|
|
456
|
-
safeCallback(onPaymentTerminate, [], "onPaymentTerminate", devMode);
|
|
457
|
-
// Tracking payment terminate callback
|
|
458
|
-
trackers.trackPaymentTerminate(
|
|
459
|
-
orderId,
|
|
460
|
-
"checkout_closed",
|
|
461
|
-
publicKey,
|
|
462
|
-
devMode,
|
|
463
|
-
mockMode,
|
|
464
|
-
checkoutUrl
|
|
465
|
-
);
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
} catch (error) {
|
|
470
|
-
if (devMode) {
|
|
471
|
-
console.error("[Glomo-RN-SDK Standard] Error parsing MainWebView message:", error);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
},
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
}),
|
|
475
640
|
[
|
|
476
641
|
onPaymentSuccess,
|
|
477
642
|
onPaymentFailure,
|
|
@@ -483,6 +648,10 @@ export function useStandardCheckout(
|
|
|
483
648
|
publicKey,
|
|
484
649
|
checkoutUrl,
|
|
485
650
|
trackers,
|
|
651
|
+
fileSaveHandler,
|
|
652
|
+
flowType,
|
|
653
|
+
onSdkError,
|
|
654
|
+
fileInputHandler,
|
|
486
655
|
]
|
|
487
656
|
);
|
|
488
657
|
|
|
@@ -491,16 +660,13 @@ export function useStandardCheckout(
|
|
|
491
660
|
* The flow WebView hosts child pages opened via window.open (e.g. 3DS, bank redirects).
|
|
492
661
|
* Message structure mirrors the main WebView handler but operates on the overlay WebView.
|
|
493
662
|
*/
|
|
494
|
-
const handleFlowWebViewMessage =
|
|
495
|
-
(
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
console.warn("[Glomo-RN-SDK Standard] Invalid message data from FlowWebView");
|
|
502
|
-
}
|
|
503
|
-
// Tracking invalid message received
|
|
663
|
+
const handleFlowWebViewMessage = useMemo(
|
|
664
|
+
() =>
|
|
665
|
+
createMessageRouter({
|
|
666
|
+
logPrefix: LOG_PREFIX,
|
|
667
|
+
source: "FlowWebView",
|
|
668
|
+
devMode,
|
|
669
|
+
onInvalidMessage: (data) => {
|
|
504
670
|
trackers.trackInvalidMessageReceived(
|
|
505
671
|
"flow",
|
|
506
672
|
data,
|
|
@@ -511,173 +677,257 @@ export function useStandardCheckout(
|
|
|
511
677
|
mockMode,
|
|
512
678
|
checkoutUrl
|
|
513
679
|
);
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
680
|
+
},
|
|
681
|
+
handlers: {
|
|
682
|
+
/**
|
|
683
|
+
* Telemetry only, and deliberately not wired to onUserRefusedDevicePermissions.
|
|
684
|
+
* The flow injection sees this by watching getUserMedia, so coverage depends on
|
|
685
|
+
* whether a given bank uses it - partial coverage on a merchant callback is
|
|
686
|
+
* worse than none, because silence would stop being unambiguous.
|
|
687
|
+
*/
|
|
688
|
+
"permission.denied": (message) => {
|
|
689
|
+
trackDevicePermissionRefused(
|
|
690
|
+
typeof message.resource === "string" ? message.resource : "camera",
|
|
691
|
+
"get_user_media",
|
|
692
|
+
orderId,
|
|
693
|
+
publicKey,
|
|
694
|
+
devMode,
|
|
695
|
+
mockMode,
|
|
696
|
+
checkoutUrl,
|
|
697
|
+
flowType
|
|
698
|
+
);
|
|
699
|
+
},
|
|
521
700
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
if (devMode) {
|
|
525
|
-
console.log(`[Glomo-RN-SDK Standard] [Flow] ${message.message}`);
|
|
526
|
-
}
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
701
|
+
"file.save": fileSaveHandler,
|
|
702
|
+
"file.input": fileInputHandler,
|
|
529
703
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
const url = message.url;
|
|
533
|
-
if (url && typeof url === "string" && isValidUrl(url)) {
|
|
534
|
-
if (devMode) {
|
|
535
|
-
console.log(`[Glomo-RN-SDK Standard] window.open called from FlowWebView: ${url}`);
|
|
536
|
-
}
|
|
537
|
-
setFlowWebViewUrl(url);
|
|
538
|
-
// Tracking window.open interception
|
|
539
|
-
trackers.trackWindowOpen("flow", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
540
|
-
} else {
|
|
704
|
+
// Forwarding console messages from the flow WebView's injected script
|
|
705
|
+
console: (message) => {
|
|
541
706
|
if (devMode) {
|
|
542
|
-
console.log(
|
|
543
|
-
"[Glomo-RN-SDK Standard] Invalid URL in window.open from FlowWebView:",
|
|
544
|
-
url,
|
|
545
|
-
typeof url === "undefined" ? "undefined" : ""
|
|
546
|
-
);
|
|
707
|
+
console.log(`${LOG_PREFIX} [Flow] ${message.message}`);
|
|
547
708
|
}
|
|
548
|
-
}
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// Intercepting window.close calls from the flow WebView - closing the overlay
|
|
553
|
-
if (message.type === "window.close") {
|
|
554
|
-
if (devMode) {
|
|
555
|
-
console.log("[Glomo-RN-SDK Standard] FlowWebView close requested");
|
|
556
|
-
}
|
|
557
|
-
setShowFlowWebView(false);
|
|
558
|
-
setFlowWebViewUrl("");
|
|
559
|
-
// Tracking window.close interception
|
|
560
|
-
trackers.trackWindowClose("flow", orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// Handling merchant JS callbacks from the flow WebView (payment events, lifecycle events)
|
|
565
|
-
if (message.type === "message" && message.message) {
|
|
566
|
-
const messageData = message.message;
|
|
567
|
-
const messageType = messageData?.type;
|
|
568
|
-
|
|
569
|
-
// Handling payment success from the flow WebView
|
|
570
|
-
if (messageType === "payment.success" || messageType === "payment.complete") {
|
|
571
|
-
const paymentMessage = messageData;
|
|
572
|
-
const payload = paymentMessage?.payload;
|
|
709
|
+
},
|
|
573
710
|
|
|
574
|
-
|
|
711
|
+
// Intercepting window.open calls from the flow WebView - navigating within the same overlay
|
|
712
|
+
"window.open": (message) => {
|
|
713
|
+
const url = message.url;
|
|
714
|
+
if (url && typeof url === "string" && isValidUrl(url)) {
|
|
575
715
|
if (devMode) {
|
|
576
|
-
console.log(
|
|
716
|
+
console.log(`${LOG_PREFIX} window.open called from FlowWebView: ${url}`);
|
|
577
717
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
setStatus("payment_successful");
|
|
582
|
-
safeCallback(onPaymentSuccess, [payload], "onPaymentSuccess", devMode);
|
|
583
|
-
// Tracking payment success callback
|
|
584
|
-
trackers.trackPaymentSuccess(
|
|
585
|
-
payload.orderId,
|
|
586
|
-
payload.paymentId,
|
|
587
|
-
publicKey,
|
|
588
|
-
devMode,
|
|
589
|
-
mockMode,
|
|
590
|
-
checkoutUrl
|
|
591
|
-
);
|
|
718
|
+
setFlowWebViewUrl(url);
|
|
719
|
+
// Tracking window.open interception
|
|
720
|
+
trackers.trackWindowOpen("flow", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
592
721
|
} else {
|
|
593
722
|
if (devMode) {
|
|
594
|
-
console.
|
|
595
|
-
|
|
596
|
-
|
|
723
|
+
console.log(
|
|
724
|
+
`${LOG_PREFIX} Invalid URL in window.open from FlowWebView:`,
|
|
725
|
+
url,
|
|
726
|
+
typeof url === "undefined" ? "undefined" : ""
|
|
597
727
|
);
|
|
598
728
|
}
|
|
599
729
|
}
|
|
600
|
-
|
|
601
|
-
|
|
730
|
+
},
|
|
731
|
+
|
|
732
|
+
// Intercepting window.close calls from the flow WebView - closing the overlay
|
|
733
|
+
"window.close": () => {
|
|
734
|
+
if (devMode) {
|
|
735
|
+
console.log(`${LOG_PREFIX} FlowWebView close requested`);
|
|
736
|
+
}
|
|
737
|
+
setShowFlowWebView(false);
|
|
738
|
+
setFlowWebViewUrl("");
|
|
739
|
+
// Tracking window.close interception
|
|
740
|
+
trackers.trackWindowClose("flow", orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
741
|
+
},
|
|
742
|
+
|
|
743
|
+
// Handling merchant JS callbacks from the flow WebView (payment events, lifecycle events)
|
|
744
|
+
message: (message) => {
|
|
745
|
+
const messageData = message.message as
|
|
746
|
+
| { type?: string; payload?: Record<string, string | undefined> }
|
|
747
|
+
| undefined;
|
|
748
|
+
if (!messageData) return;
|
|
749
|
+
const messageType = messageData.type;
|
|
750
|
+
|
|
751
|
+
// Handling payment success from the flow WebView
|
|
752
|
+
if (messageType === "payment.success" || messageType === "payment.complete") {
|
|
753
|
+
const paymentMessage = messageData;
|
|
754
|
+
const payload = paymentMessage?.payload;
|
|
755
|
+
|
|
756
|
+
if (isValidPaymentPayload(payload)) {
|
|
757
|
+
if (devMode) {
|
|
758
|
+
console.log("[Glomo-RN-SDK Standard] Payment successful via FlowWebView callback!");
|
|
759
|
+
}
|
|
760
|
+
setShowFlowWebView(false);
|
|
761
|
+
setShowCheckout(false);
|
|
762
|
+
setFlowWebViewUrl("");
|
|
763
|
+
setStatus("payment_successful");
|
|
764
|
+
safeCallback(onPaymentSuccess, [payload], "onPaymentSuccess", devMode);
|
|
765
|
+
// Tracking payment success callback
|
|
766
|
+
trackers.trackPaymentSuccess(
|
|
767
|
+
toPaymentEventData(payload),
|
|
768
|
+
publicKey,
|
|
769
|
+
devMode,
|
|
770
|
+
mockMode,
|
|
771
|
+
checkoutUrl
|
|
772
|
+
);
|
|
773
|
+
// Fires alongside the callback, not instead of it - the outcome is real, the payload is thin
|
|
774
|
+
const missingFields = missingPaymentFields(payload);
|
|
775
|
+
if (missingFields.length > 0) {
|
|
776
|
+
trackIncompletePaymentPayload(
|
|
777
|
+
"payment.success",
|
|
778
|
+
missingFields,
|
|
779
|
+
"flow",
|
|
780
|
+
payload.orderId,
|
|
781
|
+
publicKey,
|
|
782
|
+
devMode,
|
|
783
|
+
mockMode,
|
|
784
|
+
checkoutUrl,
|
|
785
|
+
flowType
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
} else {
|
|
789
|
+
/**
|
|
790
|
+
* Reported, not dropped. Before v5 this branch was a devMode-only console.error, so in a
|
|
791
|
+
* release build a terminal payment outcome the SDK could not route left no trace
|
|
792
|
+
* anywhere - no callback, no telemetry, nothing for support to find.
|
|
793
|
+
*/
|
|
794
|
+
if (devMode) {
|
|
795
|
+
console.error(
|
|
796
|
+
`${LOG_PREFIX} Unroutable payment.success payload from flow:`,
|
|
797
|
+
payload
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
trackDroppedPaymentPayload(
|
|
801
|
+
"payment.success",
|
|
802
|
+
payloadKeysOf(payload),
|
|
803
|
+
"flow",
|
|
804
|
+
orderId,
|
|
805
|
+
publicKey,
|
|
806
|
+
devMode,
|
|
807
|
+
mockMode,
|
|
808
|
+
checkoutUrl,
|
|
809
|
+
flowType
|
|
810
|
+
);
|
|
811
|
+
safeCallback(
|
|
812
|
+
onSdkError,
|
|
813
|
+
[[droppedPayloadSdkError("payment.success")]],
|
|
814
|
+
"onSdkError",
|
|
815
|
+
devMode
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// Handling payment failure from the flow WebView
|
|
822
|
+
if (messageType === "payment.failure" || messageType === "payment.error") {
|
|
823
|
+
const paymentMessage = messageData;
|
|
824
|
+
const payload = paymentMessage?.payload;
|
|
825
|
+
|
|
826
|
+
if (isValidPaymentPayload(payload)) {
|
|
827
|
+
if (devMode) {
|
|
828
|
+
console.error("[Glomo-RN-SDK Standard] Payment failed via FlowWebView callback");
|
|
829
|
+
}
|
|
830
|
+
setShowFlowWebView(false);
|
|
831
|
+
setShowCheckout(false);
|
|
832
|
+
setFlowWebViewUrl("");
|
|
833
|
+
setStatus("payment_failed");
|
|
834
|
+
safeCallback(onPaymentFailure, [payload], "onPaymentFailure", devMode);
|
|
835
|
+
// Tracking payment failure callback
|
|
836
|
+
trackers.trackPaymentFailure(
|
|
837
|
+
toPaymentEventData(payload),
|
|
838
|
+
publicKey,
|
|
839
|
+
devMode,
|
|
840
|
+
mockMode,
|
|
841
|
+
checkoutUrl
|
|
842
|
+
);
|
|
843
|
+
// Fires alongside the callback, not instead of it - the outcome is real, the payload is thin
|
|
844
|
+
const missingFields = missingPaymentFields(payload);
|
|
845
|
+
if (missingFields.length > 0) {
|
|
846
|
+
trackIncompletePaymentPayload(
|
|
847
|
+
"payment.failure",
|
|
848
|
+
missingFields,
|
|
849
|
+
"flow",
|
|
850
|
+
payload.orderId,
|
|
851
|
+
publicKey,
|
|
852
|
+
devMode,
|
|
853
|
+
mockMode,
|
|
854
|
+
checkoutUrl,
|
|
855
|
+
flowType
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
} else {
|
|
859
|
+
/**
|
|
860
|
+
* Reported, not dropped. Before v5 this branch was a devMode-only console.error, so in a
|
|
861
|
+
* release build a terminal payment outcome the SDK could not route left no trace
|
|
862
|
+
* anywhere - no callback, no telemetry, nothing for support to find.
|
|
863
|
+
*/
|
|
864
|
+
if (devMode) {
|
|
865
|
+
console.error(
|
|
866
|
+
`${LOG_PREFIX} Unroutable payment.failure payload from flow:`,
|
|
867
|
+
payload
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
trackDroppedPaymentPayload(
|
|
871
|
+
"payment.failure",
|
|
872
|
+
payloadKeysOf(payload),
|
|
873
|
+
"flow",
|
|
874
|
+
orderId,
|
|
875
|
+
publicKey,
|
|
876
|
+
devMode,
|
|
877
|
+
mockMode,
|
|
878
|
+
checkoutUrl,
|
|
879
|
+
flowType
|
|
880
|
+
);
|
|
881
|
+
safeCallback(
|
|
882
|
+
onSdkError,
|
|
883
|
+
[[droppedPayloadSdkError("payment.failure")]],
|
|
884
|
+
"onSdkError",
|
|
885
|
+
devMode
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
602
890
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
891
|
+
// Handling glomoCheckoutJourneyTerminate from the flow WebView (pay via bank completion)
|
|
892
|
+
if (messageType === "glomoCheckoutJourneyTerminate") {
|
|
893
|
+
// Deduplication - both main and flow WebViews can emit this event
|
|
894
|
+
if (payViaBankCompletedHandledRef.current) {
|
|
895
|
+
if (devMode) {
|
|
896
|
+
console.log(
|
|
897
|
+
"[Glomo-RN-SDK Standard] Pay via bank completed already handled, ignoring duplicate"
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
payViaBankCompletedHandledRef.current = true;
|
|
607
903
|
|
|
608
|
-
|
|
904
|
+
const payload = messageData?.payload;
|
|
609
905
|
if (devMode) {
|
|
610
|
-
console.
|
|
906
|
+
console.log("[Glomo-RN-SDK Standard] Pay via bank completed via FlowWebView:", payload);
|
|
611
907
|
}
|
|
612
908
|
setShowFlowWebView(false);
|
|
613
909
|
setShowCheckout(false);
|
|
614
910
|
setFlowWebViewUrl("");
|
|
615
|
-
setStatus("
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
payload
|
|
620
|
-
|
|
911
|
+
setStatus("pay_via_bank_completed");
|
|
912
|
+
const journeyPayload: GlomoUserJourneyCompletedPayload = {
|
|
913
|
+
journeyType: ASYNC_PAYMENT_EVENTS.PAY_VIA_BANK_COMPLETED,
|
|
914
|
+
orderId: payload?.orderId,
|
|
915
|
+
status: payload?.status ?? "unknown",
|
|
916
|
+
};
|
|
917
|
+
safeCallback(onUserJourneyCompleted, [journeyPayload], "onUserJourneyCompleted", devMode);
|
|
918
|
+
trackers.trackPayViaBankCompleted(
|
|
919
|
+
orderId,
|
|
920
|
+
toJourneyEventData(journeyPayload),
|
|
621
921
|
publicKey,
|
|
622
922
|
devMode,
|
|
623
923
|
mockMode,
|
|
624
924
|
checkoutUrl
|
|
625
925
|
);
|
|
626
|
-
} else {
|
|
627
|
-
if (devMode) {
|
|
628
|
-
console.error(
|
|
629
|
-
"[Glomo-RN-SDK Standard] Invalid payment failure payload from FlowWebView:",
|
|
630
|
-
payload
|
|
631
|
-
);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
// Handling glomoCheckoutJourneyTerminate from the flow WebView (pay via bank completion)
|
|
638
|
-
if (messageType === "glomoCheckoutJourneyTerminate") {
|
|
639
|
-
// Deduplication - both main and flow WebViews can emit this event
|
|
640
|
-
if (payViaBankCompletedHandledRef.current) {
|
|
641
|
-
if (devMode) {
|
|
642
|
-
console.log(
|
|
643
|
-
"[Glomo-RN-SDK Standard] Pay via bank completed already handled, ignoring duplicate"
|
|
644
|
-
);
|
|
645
|
-
}
|
|
646
926
|
return;
|
|
647
927
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
if (devMode) {
|
|
652
|
-
console.log("[Glomo-RN-SDK Standard] Pay via bank completed via FlowWebView:", payload);
|
|
653
|
-
}
|
|
654
|
-
setShowFlowWebView(false);
|
|
655
|
-
setShowCheckout(false);
|
|
656
|
-
setFlowWebViewUrl("");
|
|
657
|
-
setStatus("pay_via_bank_completed");
|
|
658
|
-
const journeyPayload: GlomoUserJourneyCompletedPayload = {
|
|
659
|
-
journeyType: ASYNC_PAYMENT_EVENTS.PAY_VIA_BANK_COMPLETED,
|
|
660
|
-
orderId: payload?.orderId,
|
|
661
|
-
status: payload?.status ?? "unknown",
|
|
662
|
-
};
|
|
663
|
-
safeCallback(onUserJourneyCompleted, [journeyPayload], "onUserJourneyCompleted", devMode);
|
|
664
|
-
trackers.trackPayViaBankCompleted(
|
|
665
|
-
orderId,
|
|
666
|
-
publicKey,
|
|
667
|
-
payload?.status ?? "unknown",
|
|
668
|
-
devMode,
|
|
669
|
-
mockMode,
|
|
670
|
-
checkoutUrl
|
|
671
|
-
);
|
|
672
|
-
return;
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
} catch (error) {
|
|
676
|
-
if (devMode) {
|
|
677
|
-
console.error("[Glomo-RN-SDK Standard] Error parsing FlowWebView message:", error);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
},
|
|
928
|
+
},
|
|
929
|
+
},
|
|
930
|
+
}),
|
|
681
931
|
[
|
|
682
932
|
onPaymentSuccess,
|
|
683
933
|
onPaymentFailure,
|
|
@@ -689,6 +939,10 @@ export function useStandardCheckout(
|
|
|
689
939
|
flowWebViewUrl,
|
|
690
940
|
checkoutUrl,
|
|
691
941
|
trackers,
|
|
942
|
+
fileSaveHandler,
|
|
943
|
+
flowType,
|
|
944
|
+
onSdkError,
|
|
945
|
+
fileInputHandler,
|
|
692
946
|
]
|
|
693
947
|
);
|
|
694
948
|
|
|
@@ -723,10 +977,26 @@ export function useStandardCheckout(
|
|
|
723
977
|
console.log("[Glomo-RN-SDK Standard] Ignoring checkout status update for: " + status);
|
|
724
978
|
}
|
|
725
979
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
980
|
+
/**
|
|
981
|
+
* Inferred from the platform rather than read off an event, because `onRequestClose` is
|
|
982
|
+
* the only wiring here and the two platforms raise it for different gestures: Android
|
|
983
|
+
* for hardware back and the back gesture, iOS for swipe-down on the pageSheet. Nothing
|
|
984
|
+
* else routes through it, so the inference is sound - but it is an inference.
|
|
985
|
+
*/
|
|
986
|
+
const termination = {
|
|
987
|
+
source:
|
|
988
|
+
Platform.OS === "ios" ? TERMINATION_SOURCES.MODAL_DISMISS : TERMINATION_SOURCES.DEVICE_BACK_BUTTON,
|
|
989
|
+
};
|
|
990
|
+
safeCallback(onPaymentTerminate, [termination], "onPaymentTerminate", devMode);
|
|
991
|
+
// One object to the host and to telemetry, so the two cannot drift
|
|
992
|
+
trackers.trackPaymentTerminate(
|
|
993
|
+
orderId,
|
|
994
|
+
toTerminationEventData(termination),
|
|
995
|
+
publicKey,
|
|
996
|
+
devMode,
|
|
997
|
+
mockMode,
|
|
998
|
+
checkoutUrl
|
|
999
|
+
);
|
|
730
1000
|
} catch (error) {
|
|
731
1001
|
if (devMode) {
|
|
732
1002
|
console.error("[Glomo-RN-SDK Standard] Error in handleModalBackButton:", error);
|
|
@@ -741,93 +1011,46 @@ export function useStandardCheckout(
|
|
|
741
1011
|
}
|
|
742
1012
|
setShowFlowWebView(false);
|
|
743
1013
|
setFlowWebViewUrl("");
|
|
744
|
-
|
|
745
|
-
}, [devMode, orderId, publicKey, mockMode, checkoutUrl,
|
|
1014
|
+
trackBackNavigationViaSdkBackButton(orderId, publicKey, devMode, mockMode, checkoutUrl, flowType);
|
|
1015
|
+
}, [devMode, orderId, publicKey, mockMode, checkoutUrl, flowType]);
|
|
746
1016
|
|
|
747
1017
|
/**
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
1018
|
+
* Closing the checkout when the user refuses camera access, since the bank page cannot
|
|
1019
|
+
* proceed without it. Kept here rather than inside usePermissionRequest because moving the
|
|
1020
|
+
* status and notifying the merchant are flow concerns.
|
|
1021
|
+
*
|
|
1022
|
+
* Resets the flow overlay as well as the modal, matching the LRS handler. The prompt is raised
|
|
1023
|
+
* from a bank page inside that overlay, so this is the one close path where the flow state is
|
|
1024
|
+
* guaranteed non-default. Nothing visible depends on it today - the overlay renders inside the
|
|
1025
|
+
* Modal and unmounts with it - but "every close path resets derived state" should not hold in
|
|
1026
|
+
* one flow and not the other.
|
|
752
1027
|
*/
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
1028
|
+
const handleCameraPermissionDenied = useCallback(() => {
|
|
1029
|
+
setShowCheckout(false);
|
|
1030
|
+
setShowFlowWebView(false);
|
|
1031
|
+
setFlowWebViewUrl("");
|
|
1032
|
+
if (status === "payment_in_progress") {
|
|
1033
|
+
setStatus("payment_cancelled");
|
|
1034
|
+
}
|
|
1035
|
+
safeCallback(onUserRefusedDevicePermissions, [], "onUserRefusedDevicePermissions", devMode);
|
|
1036
|
+
// The permission hook only reaches here for camera; anything else is denied without notifying
|
|
1037
|
+
trackDevicePermissionRefused(
|
|
1038
|
+
"camera",
|
|
1039
|
+
"permission_request",
|
|
1040
|
+
orderId,
|
|
1041
|
+
publicKey,
|
|
1042
|
+
devMode,
|
|
1043
|
+
mockMode,
|
|
1044
|
+
checkoutUrl,
|
|
1045
|
+
flowType
|
|
1046
|
+
);
|
|
1047
|
+
}, [status, onUserRefusedDevicePermissions, devMode, orderId, publicKey, mockMode, checkoutUrl, flowType]);
|
|
773
1048
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
buttonNeutral: "Ask Me Later",
|
|
780
|
-
buttonNegative: "Cancel",
|
|
781
|
-
buttonPositive: "OK",
|
|
782
|
-
});
|
|
783
|
-
|
|
784
|
-
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
|
|
785
|
-
if (devMode) {
|
|
786
|
-
console.log("[Glomo-RN-SDK Standard] Camera permission granted");
|
|
787
|
-
}
|
|
788
|
-
request.grant(resources);
|
|
789
|
-
} else {
|
|
790
|
-
if (devMode) {
|
|
791
|
-
console.log("[Glomo-RN-SDK Standard] Camera permission denied by user");
|
|
792
|
-
}
|
|
793
|
-
setShowCheckout(false);
|
|
794
|
-
if (status === "payment_in_progress") {
|
|
795
|
-
setStatus("payment_cancelled");
|
|
796
|
-
}
|
|
797
|
-
safeCallback(onUserRefusedCameraPermissions, [], "onUserRefusedCameraPermissions", devMode);
|
|
798
|
-
request.deny();
|
|
799
|
-
}
|
|
800
|
-
} catch (err) {
|
|
801
|
-
if (devMode) {
|
|
802
|
-
console.error("[Glomo-RN-SDK Standard] Error requesting camera permission:", err);
|
|
803
|
-
}
|
|
804
|
-
setShowCheckout(false);
|
|
805
|
-
if (status === "payment_in_progress") {
|
|
806
|
-
setStatus("payment_cancelled");
|
|
807
|
-
}
|
|
808
|
-
safeCallback(onUserRefusedCameraPermissions, [], "onUserRefusedCameraPermissions", devMode);
|
|
809
|
-
request.deny();
|
|
810
|
-
}
|
|
811
|
-
} else if (Platform.OS === "ios") {
|
|
812
|
-
if (devMode) {
|
|
813
|
-
console.log("[Glomo-RN-SDK Standard] Granting camera permission request on iOS");
|
|
814
|
-
}
|
|
815
|
-
request.grant(resources);
|
|
816
|
-
} else {
|
|
817
|
-
if (devMode) {
|
|
818
|
-
console.warn("[Glomo-RN-SDK Standard] Unsupported platform for camera permissions");
|
|
819
|
-
}
|
|
820
|
-
request.deny();
|
|
821
|
-
}
|
|
822
|
-
} catch (error) {
|
|
823
|
-
if (devMode) {
|
|
824
|
-
console.error("[Glomo-RN-SDK Standard] Error in handlePermissionRequest:", error);
|
|
825
|
-
}
|
|
826
|
-
request.deny();
|
|
827
|
-
}
|
|
828
|
-
},
|
|
829
|
-
[devMode, status, onUserRefusedCameraPermissions]
|
|
830
|
-
);
|
|
1049
|
+
const handlePermissionRequest = usePermissionRequest({
|
|
1050
|
+
logPrefix: "[Glomo-RN-SDK Standard]",
|
|
1051
|
+
onDenied: handleCameraPermissionDenied,
|
|
1052
|
+
devMode,
|
|
1053
|
+
});
|
|
831
1054
|
|
|
832
1055
|
/**
|
|
833
1056
|
* Initiator method for the Standard Checkout flow.
|
|
@@ -846,11 +1069,16 @@ export function useStandardCheckout(
|
|
|
846
1069
|
* Checking device security compliance (if peerDependencies are available)
|
|
847
1070
|
* A null value indicates that recommended security dependencies were not installed and compliance checks will be skipped in this case
|
|
848
1071
|
*/
|
|
849
|
-
const
|
|
850
|
-
const
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
|
|
1072
|
+
const compliance = resolveDeviceCompliance(devMode);
|
|
1073
|
+
const complianceLibraryInstalled = isComplianceCheckAvailable();
|
|
1074
|
+
const fileSave = resolveFileSaveAvailability();
|
|
1075
|
+
const dependencyInfo = {
|
|
1076
|
+
jailbreakDetectionLibraryInstalled: complianceLibraryInstalled,
|
|
1077
|
+
jailbreakDetected: compliance.compromised,
|
|
1078
|
+
complianceSource: compliance.source,
|
|
1079
|
+
complianceSignals: compliance.trippedSignals,
|
|
1080
|
+
fileSaveAvailable: fileSave.available,
|
|
1081
|
+
missingFileSavePackages: fileSave.missing,
|
|
854
1082
|
};
|
|
855
1083
|
|
|
856
1084
|
if (devMode) {
|
|
@@ -860,37 +1088,64 @@ export function useStandardCheckout(
|
|
|
860
1088
|
}
|
|
861
1089
|
|
|
862
1090
|
// Tracking start attempt with current status
|
|
863
|
-
trackers.trackStartAttempt(status, orderId, publicKey, devMode, mockMode, checkoutUrl,
|
|
1091
|
+
trackers.trackStartAttempt(status, orderId, publicKey, devMode, mockMode, checkoutUrl, dependencyInfo);
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* Ungated on purpose, both of them. An absent optional dependency degrades silently by
|
|
1095
|
+
* design, so a console warning is the only thing that reaches a merchant who has not
|
|
1096
|
+
* noticed - and the compliance one now precedes a hard block on live keys.
|
|
1097
|
+
*/
|
|
1098
|
+
if (!complianceLibraryInstalled) {
|
|
1099
|
+
console.warn(
|
|
1100
|
+
"[Glomo-RN-SDK Standard] jail-monkey is not installed or its native module is not linked, so " +
|
|
1101
|
+
"rooted and jailbroken devices cannot be detected. As of v5 this BLOCKS live checkouts. " +
|
|
1102
|
+
"Install jail-monkey and rebuild the app - see the README."
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
if (!fileSave.available) {
|
|
1106
|
+
console.warn(
|
|
1107
|
+
`[Glomo-RN-SDK Standard] Document downloads are disabled: ${fileSave.missing.join(" and ")} ` +
|
|
1108
|
+
"could not be loaded. Install the package(s) and rebuild the app, or ignore this if your " +
|
|
1109
|
+
"checkout does not offer downloads - the page falls back to its own behaviour."
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
864
1112
|
|
|
865
|
-
if (
|
|
866
|
-
|
|
1113
|
+
if (blocksCheckout(compliance, publicKey)) {
|
|
1114
|
+
/**
|
|
1115
|
+
* Two conditions, one refusal. `compromised === true` is a rooted or jailbroken device;
|
|
1116
|
+
* `null` is a device we could not check, which on RN overwhelmingly means jail-monkey is
|
|
1117
|
+
* absent rather than that a platform probe failed. Both fail closed on a live key, and
|
|
1118
|
+
* neither fires on a test or mock key.
|
|
1119
|
+
*/
|
|
1120
|
+
const unknown = compliance.compromised === null;
|
|
867
1121
|
if (devMode) {
|
|
868
|
-
console.error(
|
|
1122
|
+
console.error(
|
|
1123
|
+
`[Glomo-RN-SDK Standard] Cannot start checkout. ${
|
|
1124
|
+
unknown ? "Device security could not be verified." : "Device is rooted or jailbroken."
|
|
1125
|
+
}`
|
|
1126
|
+
);
|
|
869
1127
|
}
|
|
870
1128
|
const errors: SdkError[] = [
|
|
871
1129
|
{
|
|
872
1130
|
type: "device_forbidden",
|
|
873
|
-
message:
|
|
1131
|
+
message: unknown
|
|
1132
|
+
? "Standard checkout could not verify this device's security. Install jail-monkey and rebuild the app."
|
|
1133
|
+
: "Standard checkout is not allowed on rooted or jailbroken devices.",
|
|
874
1134
|
},
|
|
875
1135
|
];
|
|
876
1136
|
safeCallback(onSdkError, [errors], "onSdkError", devMode);
|
|
877
1137
|
// Tracking start() failure
|
|
878
1138
|
trackers.trackStartFailure(
|
|
879
|
-
"device_forbidden",
|
|
1139
|
+
unknown ? "device_compliance_unknown" : "device_forbidden",
|
|
880
1140
|
orderId,
|
|
881
1141
|
publicKey,
|
|
882
1142
|
devMode,
|
|
883
1143
|
mockMode,
|
|
884
1144
|
checkoutUrl,
|
|
885
|
-
|
|
1145
|
+
dependencyInfo
|
|
886
1146
|
);
|
|
887
1147
|
setShowCheckout(false);
|
|
888
1148
|
return false;
|
|
889
|
-
} else if (deviceComplianceCheck === null) {
|
|
890
|
-
// 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.
|
|
891
|
-
console.warn(
|
|
892
|
-
"[Glomo-RN-SDK Standard] 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 standard checkouts from compromised devices."
|
|
893
|
-
);
|
|
894
1149
|
}
|
|
895
1150
|
|
|
896
1151
|
// Enforcing devMode requirement for development servers
|
|
@@ -911,7 +1166,7 @@ export function useStandardCheckout(
|
|
|
911
1166
|
devMode,
|
|
912
1167
|
mockMode,
|
|
913
1168
|
checkoutUrl,
|
|
914
|
-
|
|
1169
|
+
dependencyInfo
|
|
915
1170
|
);
|
|
916
1171
|
setShowCheckout(false);
|
|
917
1172
|
return false;
|
|
@@ -967,7 +1222,7 @@ export function useStandardCheckout(
|
|
|
967
1222
|
devMode,
|
|
968
1223
|
mockMode,
|
|
969
1224
|
checkoutUrl,
|
|
970
|
-
|
|
1225
|
+
dependencyInfo
|
|
971
1226
|
);
|
|
972
1227
|
setShowCheckout(false);
|
|
973
1228
|
return false;
|
|
@@ -987,7 +1242,7 @@ export function useStandardCheckout(
|
|
|
987
1242
|
devMode,
|
|
988
1243
|
mockMode,
|
|
989
1244
|
checkoutUrl,
|
|
990
|
-
|
|
1245
|
+
dependencyInfo
|
|
991
1246
|
);
|
|
992
1247
|
setShowCheckout(false);
|
|
993
1248
|
return false;
|
|
@@ -1012,7 +1267,7 @@ export function useStandardCheckout(
|
|
|
1012
1267
|
devMode,
|
|
1013
1268
|
mockMode,
|
|
1014
1269
|
checkoutUrl,
|
|
1015
|
-
|
|
1270
|
+
dependencyInfo
|
|
1016
1271
|
);
|
|
1017
1272
|
setShowCheckout(false);
|
|
1018
1273
|
return false;
|
|
@@ -1030,7 +1285,7 @@ export function useStandardCheckout(
|
|
|
1030
1285
|
devMode,
|
|
1031
1286
|
mockMode,
|
|
1032
1287
|
checkoutUrl,
|
|
1033
|
-
|
|
1288
|
+
dependencyInfo
|
|
1034
1289
|
);
|
|
1035
1290
|
setShowCheckout(false);
|
|
1036
1291
|
return false;
|
|
@@ -1047,7 +1302,7 @@ export function useStandardCheckout(
|
|
|
1047
1302
|
setStatus("payment_in_progress");
|
|
1048
1303
|
|
|
1049
1304
|
// Tracking successful start
|
|
1050
|
-
trackers.trackStartSuccess(orderId, publicKey, devMode, mockMode, checkoutUrl,
|
|
1305
|
+
trackers.trackStartSuccess(orderId, publicKey, devMode, mockMode, checkoutUrl, dependencyInfo);
|
|
1051
1306
|
|
|
1052
1307
|
// Opening the checkout modal
|
|
1053
1308
|
setShowCheckout(true);
|
|
@@ -1103,6 +1358,14 @@ export function useStandardCheckout(
|
|
|
1103
1358
|
};
|
|
1104
1359
|
}, []);
|
|
1105
1360
|
|
|
1361
|
+
/**
|
|
1362
|
+
* Scripts injected into each WebView, carrying what this build can advertise.
|
|
1363
|
+
*
|
|
1364
|
+
* Resolved once per mount rather than at module load, so a merchant who never opens a
|
|
1365
|
+
* checkout never pays for the dependency probe.
|
|
1366
|
+
*/
|
|
1367
|
+
const injectionScripts = useMemo(() => buildInjectionScripts(resolveCapabilities()), []);
|
|
1368
|
+
|
|
1106
1369
|
return {
|
|
1107
1370
|
start,
|
|
1108
1371
|
status,
|
|
@@ -1120,7 +1383,10 @@ export function useStandardCheckout(
|
|
|
1120
1383
|
handleNavigationStateChange,
|
|
1121
1384
|
handleModalBackButton,
|
|
1122
1385
|
handleFlowBack,
|
|
1123
|
-
injectedMain:
|
|
1124
|
-
|
|
1386
|
+
injectedMain:
|
|
1387
|
+
__DEV__ && internalOptions?._testInjectionScript
|
|
1388
|
+
? injectionScripts.standard + internalOptions._testInjectionScript
|
|
1389
|
+
: injectionScripts.standard,
|
|
1390
|
+
injectedFlow: injectionScripts.flow,
|
|
1125
1391
|
};
|
|
1126
1392
|
}
|