@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
|
@@ -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,6 +94,19 @@ 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;
|
|
72
111
|
_testInjectionScript?: string;
|
|
73
112
|
}
|
|
@@ -84,7 +123,7 @@ export function useStandardCheckout(
|
|
|
84
123
|
onPaymentTerminate,
|
|
85
124
|
onSdkError,
|
|
86
125
|
onUserJourneyCompleted,
|
|
87
|
-
|
|
126
|
+
onUserRefusedDevicePermissions,
|
|
88
127
|
devMode = false,
|
|
89
128
|
}: UseStandardCheckoutOptions,
|
|
90
129
|
mockMode: boolean,
|
|
@@ -92,6 +131,7 @@ export function useStandardCheckout(
|
|
|
92
131
|
): UseStandardCheckoutReturn {
|
|
93
132
|
// Resolving analytics trackers - defaults to standard, subscription flow overrides
|
|
94
133
|
const trackers = internalOptions?.analyticsTrackers ?? standardCheckoutTrackers;
|
|
134
|
+
const flowType = internalOptions?.flowType ?? "standard";
|
|
95
135
|
// Refs for the WebViews
|
|
96
136
|
const mainWebViewRef = useRef<WebView>(null);
|
|
97
137
|
const flowWebViewRef = useRef<WebView>(null);
|
|
@@ -113,7 +153,9 @@ export function useStandardCheckout(
|
|
|
113
153
|
const mode = mockMode ? "mock" : "live";
|
|
114
154
|
const params = baseParams.concat(
|
|
115
155
|
`mode=${encodeURIComponent(mode)}`,
|
|
116
|
-
`publicKey=${encodeURIComponent(publicKey)}
|
|
156
|
+
`publicKey=${encodeURIComponent(publicKey)}`,
|
|
157
|
+
`product=${encodeURIComponent(ANALYTICS_PRODUCT)}`,
|
|
158
|
+
`surface=${encodeURIComponent(ANALYTICS_SURFACE)}`
|
|
117
159
|
);
|
|
118
160
|
const queryString = params.join("&");
|
|
119
161
|
|
|
@@ -237,22 +279,65 @@ export function useStandardCheckout(
|
|
|
237
279
|
[devMode]
|
|
238
280
|
);
|
|
239
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
|
+
|
|
240
328
|
/**
|
|
241
329
|
* Handler for the main WebView messages.
|
|
242
330
|
* The main WebView hosts the checkout page. Messages arrive as JSON strings from the injected
|
|
243
331
|
* JavaScript bridge and include console logs, window.open/close interceptions, and payment
|
|
244
332
|
* lifecycle events (success, failure, terminate, bank transfer, pay via bank).
|
|
245
333
|
*/
|
|
246
|
-
const handleMainWebViewMessage =
|
|
247
|
-
(
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
console.warn("[Glomo-RN-SDK Standard] Invalid message data from MainWebView");
|
|
254
|
-
}
|
|
255
|
-
// Tracking invalid message received
|
|
334
|
+
const handleMainWebViewMessage = useMemo(
|
|
335
|
+
() =>
|
|
336
|
+
createMessageRouter({
|
|
337
|
+
logPrefix: LOG_PREFIX,
|
|
338
|
+
source: "MainWebView",
|
|
339
|
+
devMode,
|
|
340
|
+
onInvalidMessage: (data) => {
|
|
256
341
|
trackers.trackInvalidMessageReceived(
|
|
257
342
|
"main",
|
|
258
343
|
data,
|
|
@@ -263,216 +348,295 @@ export function useStandardCheckout(
|
|
|
263
348
|
mockMode,
|
|
264
349
|
checkoutUrl
|
|
265
350
|
);
|
|
266
|
-
|
|
267
|
-
|
|
351
|
+
},
|
|
352
|
+
handlers: {
|
|
353
|
+
"file.save": fileSaveHandler,
|
|
354
|
+
"file.input": fileInputHandler,
|
|
268
355
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
+
},
|
|
273
363
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
+
},
|
|
282
385
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const url = message.url;
|
|
286
|
-
if (url && typeof url === "string" && isValidUrl(url)) {
|
|
386
|
+
// Intercepting window.close calls - closing the flow WebView overlay
|
|
387
|
+
"window.close": () => {
|
|
287
388
|
if (devMode) {
|
|
288
|
-
console.log(
|
|
389
|
+
console.log(`${LOG_PREFIX} Child window close requested from MainWebView`);
|
|
289
390
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
// Tracking window.
|
|
293
|
-
trackers.
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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;
|
|
301
473
|
}
|
|
302
|
-
}
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Intercepting window.close calls - closing the flow WebView overlay
|
|
307
|
-
if (message.type === "window.close") {
|
|
308
|
-
if (devMode) {
|
|
309
|
-
console.log("[Glomo-RN-SDK Standard] Child window close requested from MainWebView");
|
|
310
|
-
}
|
|
311
|
-
setShowFlowWebView(false);
|
|
312
|
-
setFlowWebViewUrl("");
|
|
313
|
-
// Tracking window.close interception
|
|
314
|
-
trackers.trackWindowClose("main", orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
474
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
+
}
|
|
327
544
|
|
|
328
|
-
|
|
545
|
+
// Handling bank transfer submission - user has submitted transfer details
|
|
546
|
+
if (messageType === "payment.bank_transfer_submitted") {
|
|
547
|
+
const payload = messageData?.payload;
|
|
329
548
|
if (devMode) {
|
|
330
|
-
console.log("[Glomo-RN-SDK Standard]
|
|
549
|
+
console.log("[Glomo-RN-SDK Standard] Bank transfer submitted:", payload);
|
|
331
550
|
}
|
|
332
551
|
setShowCheckout(false);
|
|
333
552
|
setShowFlowWebView(false);
|
|
334
553
|
setFlowWebViewUrl("");
|
|
335
|
-
setStatus("
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
payload
|
|
340
|
-
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),
|
|
341
565
|
publicKey,
|
|
342
566
|
devMode,
|
|
343
567
|
mockMode,
|
|
344
568
|
checkoutUrl
|
|
345
569
|
);
|
|
346
|
-
|
|
347
|
-
if (devMode) {
|
|
348
|
-
console.error("[Glomo-RN-SDK Standard] Invalid payment success payload:", payload);
|
|
349
|
-
}
|
|
570
|
+
return;
|
|
350
571
|
}
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
572
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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;
|
|
358
590
|
|
|
359
|
-
|
|
591
|
+
const payload = messageData?.payload;
|
|
360
592
|
if (devMode) {
|
|
361
|
-
console.
|
|
593
|
+
console.log("[Glomo-RN-SDK Standard] Pay via bank completed via MainWebView:", payload);
|
|
362
594
|
}
|
|
363
|
-
setShowCheckout(false);
|
|
364
595
|
setShowFlowWebView(false);
|
|
596
|
+
setShowCheckout(false);
|
|
365
597
|
setFlowWebViewUrl("");
|
|
366
|
-
setStatus("
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
payload
|
|
371
|
-
|
|
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),
|
|
372
608
|
publicKey,
|
|
373
609
|
devMode,
|
|
374
610
|
mockMode,
|
|
375
611
|
checkoutUrl
|
|
376
612
|
);
|
|
377
|
-
|
|
378
|
-
if (devMode) {
|
|
379
|
-
console.error("[Glomo-RN-SDK Standard] Invalid payment failure payload:", payload);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Handling bank transfer submission - user has submitted transfer details
|
|
386
|
-
if (messageType === "payment.bank_transfer_submitted") {
|
|
387
|
-
const payload = messageData?.payload;
|
|
388
|
-
if (devMode) {
|
|
389
|
-
console.log("[Glomo-RN-SDK Standard] Bank transfer submitted:", payload);
|
|
613
|
+
return;
|
|
390
614
|
}
|
|
391
|
-
setShowCheckout(false);
|
|
392
|
-
setShowFlowWebView(false);
|
|
393
|
-
setFlowWebViewUrl("");
|
|
394
|
-
setStatus("bank_transfer_submitted");
|
|
395
|
-
const journeyPayload: GlomoUserJourneyCompletedPayload = {
|
|
396
|
-
journeyType: ASYNC_PAYMENT_EVENTS.BANK_TRANSFER_SUBMITTED,
|
|
397
|
-
orderId: payload?.orderId,
|
|
398
|
-
senderAccountNumber: payload?.senderAccountNumber,
|
|
399
|
-
transactionReference: payload?.transactionReference,
|
|
400
|
-
};
|
|
401
|
-
safeCallback(onUserJourneyCompleted, [journeyPayload], "onUserJourneyCompleted", devMode);
|
|
402
|
-
trackers.trackBankTransferSubmitted(orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
615
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
* This event is fired by the checkout page when the pay via bank (lean/open finance)
|
|
409
|
-
* flow completes. The deduplication ref prevents firing the callback twice when both
|
|
410
|
-
* main and flow WebViews emit this event.
|
|
411
|
-
*/
|
|
412
|
-
if (messageType === "glomoCheckoutJourneyTerminate") {
|
|
413
|
-
// Deduplication - both main and flow WebViews can emit this event
|
|
414
|
-
if (payViaBankCompletedHandledRef.current) {
|
|
616
|
+
// Handling checkout.closed events
|
|
617
|
+
if (messageType === "checkout.closed") {
|
|
415
618
|
if (devMode) {
|
|
416
|
-
console.log(
|
|
417
|
-
"[Glomo-RN-SDK Standard] Pay via bank completed already handled, ignoring duplicate"
|
|
418
|
-
);
|
|
619
|
+
console.log("[Glomo-RN-SDK Standard] Checkout closed event received");
|
|
419
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
|
+
);
|
|
420
635
|
return;
|
|
421
636
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if (devMode) {
|
|
426
|
-
console.log("[Glomo-RN-SDK Standard] Pay via bank completed via MainWebView:", payload);
|
|
427
|
-
}
|
|
428
|
-
setShowFlowWebView(false);
|
|
429
|
-
setShowCheckout(false);
|
|
430
|
-
setFlowWebViewUrl("");
|
|
431
|
-
setStatus("pay_via_bank_completed");
|
|
432
|
-
const journeyPayload: GlomoUserJourneyCompletedPayload = {
|
|
433
|
-
journeyType: ASYNC_PAYMENT_EVENTS.PAY_VIA_BANK_COMPLETED,
|
|
434
|
-
orderId: payload?.orderId,
|
|
435
|
-
status: payload?.status ?? "unknown",
|
|
436
|
-
};
|
|
437
|
-
safeCallback(onUserJourneyCompleted, [journeyPayload], "onUserJourneyCompleted", devMode);
|
|
438
|
-
trackers.trackPayViaBankCompleted(
|
|
439
|
-
orderId,
|
|
440
|
-
publicKey,
|
|
441
|
-
payload?.status ?? "unknown",
|
|
442
|
-
devMode,
|
|
443
|
-
mockMode,
|
|
444
|
-
checkoutUrl
|
|
445
|
-
);
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Handling checkout.closed events
|
|
450
|
-
if (messageType === "checkout.closed") {
|
|
451
|
-
if (devMode) {
|
|
452
|
-
console.log("[Glomo-RN-SDK Standard] Checkout closed event received");
|
|
453
|
-
}
|
|
454
|
-
setShowCheckout(false);
|
|
455
|
-
setShowFlowWebView(false);
|
|
456
|
-
setFlowWebViewUrl("");
|
|
457
|
-
safeCallback(onPaymentTerminate, [], "onPaymentTerminate", devMode);
|
|
458
|
-
// Tracking payment terminate callback
|
|
459
|
-
trackers.trackPaymentTerminate(
|
|
460
|
-
orderId,
|
|
461
|
-
"checkout_closed",
|
|
462
|
-
publicKey,
|
|
463
|
-
devMode,
|
|
464
|
-
mockMode,
|
|
465
|
-
checkoutUrl
|
|
466
|
-
);
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
} catch (error) {
|
|
471
|
-
if (devMode) {
|
|
472
|
-
console.error("[Glomo-RN-SDK Standard] Error parsing MainWebView message:", error);
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
},
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
}),
|
|
476
640
|
[
|
|
477
641
|
onPaymentSuccess,
|
|
478
642
|
onPaymentFailure,
|
|
@@ -484,6 +648,10 @@ export function useStandardCheckout(
|
|
|
484
648
|
publicKey,
|
|
485
649
|
checkoutUrl,
|
|
486
650
|
trackers,
|
|
651
|
+
fileSaveHandler,
|
|
652
|
+
flowType,
|
|
653
|
+
onSdkError,
|
|
654
|
+
fileInputHandler,
|
|
487
655
|
]
|
|
488
656
|
);
|
|
489
657
|
|
|
@@ -492,16 +660,13 @@ export function useStandardCheckout(
|
|
|
492
660
|
* The flow WebView hosts child pages opened via window.open (e.g. 3DS, bank redirects).
|
|
493
661
|
* Message structure mirrors the main WebView handler but operates on the overlay WebView.
|
|
494
662
|
*/
|
|
495
|
-
const handleFlowWebViewMessage =
|
|
496
|
-
(
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
console.warn("[Glomo-RN-SDK Standard] Invalid message data from FlowWebView");
|
|
503
|
-
}
|
|
504
|
-
// Tracking invalid message received
|
|
663
|
+
const handleFlowWebViewMessage = useMemo(
|
|
664
|
+
() =>
|
|
665
|
+
createMessageRouter({
|
|
666
|
+
logPrefix: LOG_PREFIX,
|
|
667
|
+
source: "FlowWebView",
|
|
668
|
+
devMode,
|
|
669
|
+
onInvalidMessage: (data) => {
|
|
505
670
|
trackers.trackInvalidMessageReceived(
|
|
506
671
|
"flow",
|
|
507
672
|
data,
|
|
@@ -512,173 +677,257 @@ export function useStandardCheckout(
|
|
|
512
677
|
mockMode,
|
|
513
678
|
checkoutUrl
|
|
514
679
|
);
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
+
},
|
|
522
700
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
if (devMode) {
|
|
526
|
-
console.log(`[Glomo-RN-SDK Standard] [Flow] ${message.message}`);
|
|
527
|
-
}
|
|
528
|
-
return;
|
|
529
|
-
}
|
|
701
|
+
"file.save": fileSaveHandler,
|
|
702
|
+
"file.input": fileInputHandler,
|
|
530
703
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
const url = message.url;
|
|
534
|
-
if (url && typeof url === "string" && isValidUrl(url)) {
|
|
535
|
-
if (devMode) {
|
|
536
|
-
console.log(`[Glomo-RN-SDK Standard] window.open called from FlowWebView: ${url}`);
|
|
537
|
-
}
|
|
538
|
-
setFlowWebViewUrl(url);
|
|
539
|
-
// Tracking window.open interception
|
|
540
|
-
trackers.trackWindowOpen("flow", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
541
|
-
} else {
|
|
704
|
+
// Forwarding console messages from the flow WebView's injected script
|
|
705
|
+
console: (message) => {
|
|
542
706
|
if (devMode) {
|
|
543
|
-
console.log(
|
|
544
|
-
"[Glomo-RN-SDK Standard] Invalid URL in window.open from FlowWebView:",
|
|
545
|
-
url,
|
|
546
|
-
typeof url === "undefined" ? "undefined" : ""
|
|
547
|
-
);
|
|
707
|
+
console.log(`${LOG_PREFIX} [Flow] ${message.message}`);
|
|
548
708
|
}
|
|
549
|
-
}
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// Intercepting window.close calls from the flow WebView - closing the overlay
|
|
554
|
-
if (message.type === "window.close") {
|
|
555
|
-
if (devMode) {
|
|
556
|
-
console.log("[Glomo-RN-SDK Standard] FlowWebView close requested");
|
|
557
|
-
}
|
|
558
|
-
setShowFlowWebView(false);
|
|
559
|
-
setFlowWebViewUrl("");
|
|
560
|
-
// Tracking window.close interception
|
|
561
|
-
trackers.trackWindowClose("flow", orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// Handling merchant JS callbacks from the flow WebView (payment events, lifecycle events)
|
|
566
|
-
if (message.type === "message" && message.message) {
|
|
567
|
-
const messageData = message.message;
|
|
568
|
-
const messageType = messageData?.type;
|
|
569
|
-
|
|
570
|
-
// Handling payment success from the flow WebView
|
|
571
|
-
if (messageType === "payment.success" || messageType === "payment.complete") {
|
|
572
|
-
const paymentMessage = messageData;
|
|
573
|
-
const payload = paymentMessage?.payload;
|
|
709
|
+
},
|
|
574
710
|
|
|
575
|
-
|
|
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)) {
|
|
576
715
|
if (devMode) {
|
|
577
|
-
console.log(
|
|
716
|
+
console.log(`${LOG_PREFIX} window.open called from FlowWebView: ${url}`);
|
|
578
717
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
setStatus("payment_successful");
|
|
583
|
-
safeCallback(onPaymentSuccess, [payload], "onPaymentSuccess", devMode);
|
|
584
|
-
// Tracking payment success callback
|
|
585
|
-
trackers.trackPaymentSuccess(
|
|
586
|
-
payload.orderId,
|
|
587
|
-
payload.paymentId,
|
|
588
|
-
publicKey,
|
|
589
|
-
devMode,
|
|
590
|
-
mockMode,
|
|
591
|
-
checkoutUrl
|
|
592
|
-
);
|
|
718
|
+
setFlowWebViewUrl(url);
|
|
719
|
+
// Tracking window.open interception
|
|
720
|
+
trackers.trackWindowOpen("flow", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
|
|
593
721
|
} else {
|
|
594
722
|
if (devMode) {
|
|
595
|
-
console.
|
|
596
|
-
|
|
597
|
-
|
|
723
|
+
console.log(
|
|
724
|
+
`${LOG_PREFIX} Invalid URL in window.open from FlowWebView:`,
|
|
725
|
+
url,
|
|
726
|
+
typeof url === "undefined" ? "undefined" : ""
|
|
598
727
|
);
|
|
599
728
|
}
|
|
600
729
|
}
|
|
601
|
-
|
|
602
|
-
|
|
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
|
+
}
|
|
603
890
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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;
|
|
608
903
|
|
|
609
|
-
|
|
904
|
+
const payload = messageData?.payload;
|
|
610
905
|
if (devMode) {
|
|
611
|
-
console.
|
|
906
|
+
console.log("[Glomo-RN-SDK Standard] Pay via bank completed via FlowWebView:", payload);
|
|
612
907
|
}
|
|
613
908
|
setShowFlowWebView(false);
|
|
614
909
|
setShowCheckout(false);
|
|
615
910
|
setFlowWebViewUrl("");
|
|
616
|
-
setStatus("
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
payload
|
|
621
|
-
|
|
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),
|
|
622
921
|
publicKey,
|
|
623
922
|
devMode,
|
|
624
923
|
mockMode,
|
|
625
924
|
checkoutUrl
|
|
626
925
|
);
|
|
627
|
-
} else {
|
|
628
|
-
if (devMode) {
|
|
629
|
-
console.error(
|
|
630
|
-
"[Glomo-RN-SDK Standard] Invalid payment failure payload from FlowWebView:",
|
|
631
|
-
payload
|
|
632
|
-
);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// Handling glomoCheckoutJourneyTerminate from the flow WebView (pay via bank completion)
|
|
639
|
-
if (messageType === "glomoCheckoutJourneyTerminate") {
|
|
640
|
-
// Deduplication - both main and flow WebViews can emit this event
|
|
641
|
-
if (payViaBankCompletedHandledRef.current) {
|
|
642
|
-
if (devMode) {
|
|
643
|
-
console.log(
|
|
644
|
-
"[Glomo-RN-SDK Standard] Pay via bank completed already handled, ignoring duplicate"
|
|
645
|
-
);
|
|
646
|
-
}
|
|
647
926
|
return;
|
|
648
927
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
if (devMode) {
|
|
653
|
-
console.log("[Glomo-RN-SDK Standard] Pay via bank completed via FlowWebView:", payload);
|
|
654
|
-
}
|
|
655
|
-
setShowFlowWebView(false);
|
|
656
|
-
setShowCheckout(false);
|
|
657
|
-
setFlowWebViewUrl("");
|
|
658
|
-
setStatus("pay_via_bank_completed");
|
|
659
|
-
const journeyPayload: GlomoUserJourneyCompletedPayload = {
|
|
660
|
-
journeyType: ASYNC_PAYMENT_EVENTS.PAY_VIA_BANK_COMPLETED,
|
|
661
|
-
orderId: payload?.orderId,
|
|
662
|
-
status: payload?.status ?? "unknown",
|
|
663
|
-
};
|
|
664
|
-
safeCallback(onUserJourneyCompleted, [journeyPayload], "onUserJourneyCompleted", devMode);
|
|
665
|
-
trackers.trackPayViaBankCompleted(
|
|
666
|
-
orderId,
|
|
667
|
-
publicKey,
|
|
668
|
-
payload?.status ?? "unknown",
|
|
669
|
-
devMode,
|
|
670
|
-
mockMode,
|
|
671
|
-
checkoutUrl
|
|
672
|
-
);
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
} catch (error) {
|
|
677
|
-
if (devMode) {
|
|
678
|
-
console.error("[Glomo-RN-SDK Standard] Error parsing FlowWebView message:", error);
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
},
|
|
928
|
+
},
|
|
929
|
+
},
|
|
930
|
+
}),
|
|
682
931
|
[
|
|
683
932
|
onPaymentSuccess,
|
|
684
933
|
onPaymentFailure,
|
|
@@ -690,6 +939,10 @@ export function useStandardCheckout(
|
|
|
690
939
|
flowWebViewUrl,
|
|
691
940
|
checkoutUrl,
|
|
692
941
|
trackers,
|
|
942
|
+
fileSaveHandler,
|
|
943
|
+
flowType,
|
|
944
|
+
onSdkError,
|
|
945
|
+
fileInputHandler,
|
|
693
946
|
]
|
|
694
947
|
);
|
|
695
948
|
|
|
@@ -724,10 +977,26 @@ export function useStandardCheckout(
|
|
|
724
977
|
console.log("[Glomo-RN-SDK Standard] Ignoring checkout status update for: " + status);
|
|
725
978
|
}
|
|
726
979
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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
|
+
);
|
|
731
1000
|
} catch (error) {
|
|
732
1001
|
if (devMode) {
|
|
733
1002
|
console.error("[Glomo-RN-SDK Standard] Error in handleModalBackButton:", error);
|
|
@@ -742,93 +1011,46 @@ export function useStandardCheckout(
|
|
|
742
1011
|
}
|
|
743
1012
|
setShowFlowWebView(false);
|
|
744
1013
|
setFlowWebViewUrl("");
|
|
745
|
-
|
|
746
|
-
}, [devMode, orderId, publicKey, mockMode, checkoutUrl,
|
|
1014
|
+
trackBackNavigationViaSdkBackButton(orderId, publicKey, devMode, mockMode, checkoutUrl, flowType);
|
|
1015
|
+
}, [devMode, orderId, publicKey, mockMode, checkoutUrl, flowType]);
|
|
747
1016
|
|
|
748
1017
|
/**
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
*
|
|
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.
|
|
753
1027
|
*/
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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]);
|
|
774
1048
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
buttonNeutral: "Ask Me Later",
|
|
781
|
-
buttonNegative: "Cancel",
|
|
782
|
-
buttonPositive: "OK",
|
|
783
|
-
});
|
|
784
|
-
|
|
785
|
-
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
|
|
786
|
-
if (devMode) {
|
|
787
|
-
console.log("[Glomo-RN-SDK Standard] Camera permission granted");
|
|
788
|
-
}
|
|
789
|
-
request.grant(resources);
|
|
790
|
-
} else {
|
|
791
|
-
if (devMode) {
|
|
792
|
-
console.log("[Glomo-RN-SDK Standard] Camera permission denied by user");
|
|
793
|
-
}
|
|
794
|
-
setShowCheckout(false);
|
|
795
|
-
if (status === "payment_in_progress") {
|
|
796
|
-
setStatus("payment_cancelled");
|
|
797
|
-
}
|
|
798
|
-
safeCallback(onUserRefusedCameraPermissions, [], "onUserRefusedCameraPermissions", devMode);
|
|
799
|
-
request.deny();
|
|
800
|
-
}
|
|
801
|
-
} catch (err) {
|
|
802
|
-
if (devMode) {
|
|
803
|
-
console.error("[Glomo-RN-SDK Standard] Error requesting camera permission:", err);
|
|
804
|
-
}
|
|
805
|
-
setShowCheckout(false);
|
|
806
|
-
if (status === "payment_in_progress") {
|
|
807
|
-
setStatus("payment_cancelled");
|
|
808
|
-
}
|
|
809
|
-
safeCallback(onUserRefusedCameraPermissions, [], "onUserRefusedCameraPermissions", devMode);
|
|
810
|
-
request.deny();
|
|
811
|
-
}
|
|
812
|
-
} else if (Platform.OS === "ios") {
|
|
813
|
-
if (devMode) {
|
|
814
|
-
console.log("[Glomo-RN-SDK Standard] Granting camera permission request on iOS");
|
|
815
|
-
}
|
|
816
|
-
request.grant(resources);
|
|
817
|
-
} else {
|
|
818
|
-
if (devMode) {
|
|
819
|
-
console.warn("[Glomo-RN-SDK Standard] Unsupported platform for camera permissions");
|
|
820
|
-
}
|
|
821
|
-
request.deny();
|
|
822
|
-
}
|
|
823
|
-
} catch (error) {
|
|
824
|
-
if (devMode) {
|
|
825
|
-
console.error("[Glomo-RN-SDK Standard] Error in handlePermissionRequest:", error);
|
|
826
|
-
}
|
|
827
|
-
request.deny();
|
|
828
|
-
}
|
|
829
|
-
},
|
|
830
|
-
[devMode, status, onUserRefusedCameraPermissions]
|
|
831
|
-
);
|
|
1049
|
+
const handlePermissionRequest = usePermissionRequest({
|
|
1050
|
+
logPrefix: "[Glomo-RN-SDK Standard]",
|
|
1051
|
+
onDenied: handleCameraPermissionDenied,
|
|
1052
|
+
devMode,
|
|
1053
|
+
});
|
|
832
1054
|
|
|
833
1055
|
/**
|
|
834
1056
|
* Initiator method for the Standard Checkout flow.
|
|
@@ -847,11 +1069,16 @@ export function useStandardCheckout(
|
|
|
847
1069
|
* Checking device security compliance (if peerDependencies are available)
|
|
848
1070
|
* A null value indicates that recommended security dependencies were not installed and compliance checks will be skipped in this case
|
|
849
1071
|
*/
|
|
850
|
-
const
|
|
851
|
-
const
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
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,
|
|
855
1082
|
};
|
|
856
1083
|
|
|
857
1084
|
if (devMode) {
|
|
@@ -861,37 +1088,64 @@ export function useStandardCheckout(
|
|
|
861
1088
|
}
|
|
862
1089
|
|
|
863
1090
|
// Tracking start attempt with current status
|
|
864
|
-
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
|
+
}
|
|
865
1112
|
|
|
866
|
-
if (
|
|
867
|
-
|
|
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;
|
|
868
1121
|
if (devMode) {
|
|
869
|
-
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
|
+
);
|
|
870
1127
|
}
|
|
871
1128
|
const errors: SdkError[] = [
|
|
872
1129
|
{
|
|
873
1130
|
type: "device_forbidden",
|
|
874
|
-
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.",
|
|
875
1134
|
},
|
|
876
1135
|
];
|
|
877
1136
|
safeCallback(onSdkError, [errors], "onSdkError", devMode);
|
|
878
1137
|
// Tracking start() failure
|
|
879
1138
|
trackers.trackStartFailure(
|
|
880
|
-
"device_forbidden",
|
|
1139
|
+
unknown ? "device_compliance_unknown" : "device_forbidden",
|
|
881
1140
|
orderId,
|
|
882
1141
|
publicKey,
|
|
883
1142
|
devMode,
|
|
884
1143
|
mockMode,
|
|
885
1144
|
checkoutUrl,
|
|
886
|
-
|
|
1145
|
+
dependencyInfo
|
|
887
1146
|
);
|
|
888
1147
|
setShowCheckout(false);
|
|
889
1148
|
return false;
|
|
890
|
-
} else if (deviceComplianceCheck === null) {
|
|
891
|
-
// 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.
|
|
892
|
-
console.warn(
|
|
893
|
-
"[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."
|
|
894
|
-
);
|
|
895
1149
|
}
|
|
896
1150
|
|
|
897
1151
|
// Enforcing devMode requirement for development servers
|
|
@@ -912,7 +1166,7 @@ export function useStandardCheckout(
|
|
|
912
1166
|
devMode,
|
|
913
1167
|
mockMode,
|
|
914
1168
|
checkoutUrl,
|
|
915
|
-
|
|
1169
|
+
dependencyInfo
|
|
916
1170
|
);
|
|
917
1171
|
setShowCheckout(false);
|
|
918
1172
|
return false;
|
|
@@ -968,7 +1222,7 @@ export function useStandardCheckout(
|
|
|
968
1222
|
devMode,
|
|
969
1223
|
mockMode,
|
|
970
1224
|
checkoutUrl,
|
|
971
|
-
|
|
1225
|
+
dependencyInfo
|
|
972
1226
|
);
|
|
973
1227
|
setShowCheckout(false);
|
|
974
1228
|
return false;
|
|
@@ -988,7 +1242,7 @@ export function useStandardCheckout(
|
|
|
988
1242
|
devMode,
|
|
989
1243
|
mockMode,
|
|
990
1244
|
checkoutUrl,
|
|
991
|
-
|
|
1245
|
+
dependencyInfo
|
|
992
1246
|
);
|
|
993
1247
|
setShowCheckout(false);
|
|
994
1248
|
return false;
|
|
@@ -1013,7 +1267,7 @@ export function useStandardCheckout(
|
|
|
1013
1267
|
devMode,
|
|
1014
1268
|
mockMode,
|
|
1015
1269
|
checkoutUrl,
|
|
1016
|
-
|
|
1270
|
+
dependencyInfo
|
|
1017
1271
|
);
|
|
1018
1272
|
setShowCheckout(false);
|
|
1019
1273
|
return false;
|
|
@@ -1031,7 +1285,7 @@ export function useStandardCheckout(
|
|
|
1031
1285
|
devMode,
|
|
1032
1286
|
mockMode,
|
|
1033
1287
|
checkoutUrl,
|
|
1034
|
-
|
|
1288
|
+
dependencyInfo
|
|
1035
1289
|
);
|
|
1036
1290
|
setShowCheckout(false);
|
|
1037
1291
|
return false;
|
|
@@ -1048,7 +1302,7 @@ export function useStandardCheckout(
|
|
|
1048
1302
|
setStatus("payment_in_progress");
|
|
1049
1303
|
|
|
1050
1304
|
// Tracking successful start
|
|
1051
|
-
trackers.trackStartSuccess(orderId, publicKey, devMode, mockMode, checkoutUrl,
|
|
1305
|
+
trackers.trackStartSuccess(orderId, publicKey, devMode, mockMode, checkoutUrl, dependencyInfo);
|
|
1052
1306
|
|
|
1053
1307
|
// Opening the checkout modal
|
|
1054
1308
|
setShowCheckout(true);
|
|
@@ -1104,6 +1358,14 @@ export function useStandardCheckout(
|
|
|
1104
1358
|
};
|
|
1105
1359
|
}, []);
|
|
1106
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
|
+
|
|
1107
1369
|
return {
|
|
1108
1370
|
start,
|
|
1109
1371
|
status,
|
|
@@ -1123,8 +1385,8 @@ export function useStandardCheckout(
|
|
|
1123
1385
|
handleFlowBack,
|
|
1124
1386
|
injectedMain:
|
|
1125
1387
|
__DEV__ && internalOptions?._testInjectionScript
|
|
1126
|
-
?
|
|
1127
|
-
:
|
|
1128
|
-
injectedFlow:
|
|
1388
|
+
? injectionScripts.standard + internalOptions._testInjectionScript
|
|
1389
|
+
: injectionScripts.standard,
|
|
1390
|
+
injectedFlow: injectionScripts.flow,
|
|
1129
1391
|
};
|
|
1130
1392
|
}
|