@glomopay/react-native-sdk 2.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/MIGRATION.md +50 -0
  3. package/README.md +348 -316
  4. package/lib/config/base.d.ts +17 -11
  5. package/lib/config/base.d.ts.map +1 -1
  6. package/lib/config/base.js +19 -7
  7. package/lib/config/segment.js +3 -3
  8. package/lib/glomo-checkout.d.ts +8 -0
  9. package/lib/glomo-checkout.d.ts.map +1 -0
  10. package/lib/glomo-checkout.js +106 -0
  11. package/lib/glomo-lrs-checkout.js +9 -9
  12. package/lib/glomo-standard-checkout.d.ts +25 -0
  13. package/lib/glomo-standard-checkout.d.ts.map +1 -0
  14. package/lib/glomo-standard-checkout.js +229 -0
  15. package/lib/glomo-subscriptions-checkout.d.ts +8 -0
  16. package/lib/glomo-subscriptions-checkout.d.ts.map +1 -0
  17. package/lib/glomo-subscriptions-checkout.js +85 -0
  18. package/lib/index.d.ts +5 -4
  19. package/lib/index.d.ts.map +1 -1
  20. package/lib/index.js +7 -5
  21. package/lib/injections/index.d.ts +1 -0
  22. package/lib/injections/index.d.ts.map +1 -1
  23. package/lib/injections/index.js +2 -0
  24. package/lib/injections/webview-flow.injection.d.ts.map +1 -1
  25. package/lib/injections/webview-flow.injection.js +106 -69
  26. package/lib/injections/webview-main.injection.d.ts.map +1 -1
  27. package/lib/injections/webview-main.injection.js +112 -77
  28. package/lib/injections/webview-standard.injection.d.ts +3 -0
  29. package/lib/injections/webview-standard.injection.d.ts.map +1 -0
  30. package/lib/injections/webview-standard.injection.js +214 -0
  31. package/lib/services/order-type-fetcher.d.ts +28 -0
  32. package/lib/services/order-type-fetcher.d.ts.map +1 -0
  33. package/lib/services/order-type-fetcher.js +99 -0
  34. package/lib/types/checkout.d.ts +65 -0
  35. package/lib/types/checkout.d.ts.map +1 -0
  36. package/lib/types/checkout.js +3 -0
  37. package/lib/types/standard-checkout.d.ts +40 -0
  38. package/lib/types/standard-checkout.d.ts.map +1 -0
  39. package/lib/types/standard-checkout.js +3 -0
  40. package/lib/types/subscriptions-checkout.d.ts +29 -0
  41. package/lib/types/subscriptions-checkout.d.ts.map +1 -0
  42. package/lib/types/subscriptions-checkout.js +3 -0
  43. package/lib/use-glomo-checkout.d.ts +54 -0
  44. package/lib/use-glomo-checkout.d.ts.map +1 -0
  45. package/lib/use-glomo-checkout.js +261 -0
  46. package/lib/use-lrs-checkout.d.ts +9 -4
  47. package/lib/use-lrs-checkout.d.ts.map +1 -1
  48. package/lib/use-lrs-checkout.js +76 -93
  49. package/lib/use-standard-checkout.d.ts +74 -0
  50. package/lib/use-standard-checkout.d.ts.map +1 -0
  51. package/lib/use-standard-checkout.js +849 -0
  52. package/lib/utils/analytics.d.ts +188 -2
  53. package/lib/utils/analytics.d.ts.map +1 -1
  54. package/lib/utils/analytics.js +636 -22
  55. package/lib/utils/device-compliance.d.ts.map +1 -1
  56. package/lib/utils/device-compliance.js +3 -4
  57. package/lib/utils/validation.d.ts.map +1 -1
  58. package/lib/utils/validation.js +7 -6
  59. package/package.json +17 -5
  60. package/src/config/base.ts +36 -17
  61. package/src/config/segment.ts +3 -3
  62. package/src/glomo-checkout.tsx +147 -0
  63. package/src/glomo-lrs-checkout.tsx +9 -9
  64. package/src/glomo-standard-checkout.tsx +353 -0
  65. package/src/glomo-subscriptions-checkout.tsx +83 -0
  66. package/src/index.ts +13 -7
  67. package/src/injections/index.ts +2 -0
  68. package/src/injections/webview-flow.injection.ts +106 -69
  69. package/src/injections/webview-main.injection.ts +112 -77
  70. package/src/injections/webview-standard.injection.ts +211 -0
  71. package/src/services/order-type-fetcher.ts +86 -0
  72. package/src/types/checkout.ts +72 -0
  73. package/src/types/standard-checkout.ts +49 -0
  74. package/src/types/subscriptions-checkout.ts +31 -0
  75. package/src/use-glomo-checkout.tsx +369 -0
  76. package/src/use-lrs-checkout.tsx +91 -111
  77. package/src/use-standard-checkout.tsx +1203 -0
  78. package/src/utils/analytics.ts +908 -34
  79. package/src/utils/device-compliance.ts +3 -4
  80. package/src/utils/validation.ts +7 -8
@@ -0,0 +1,1203 @@
1
+ /** Main SDK hook for the GlomoPay Standard Checkout flow */
2
+
3
+ import React, { useState, useRef, useMemo, useCallback, useEffect } from "react";
4
+ import { Platform, PermissionsAndroid, type NativeSyntheticEvent } from "react-native";
5
+
6
+ import { type WebViewNavigation, type WebViewMessageEvent, type WebView } from "react-native-webview";
7
+
8
+ import { InjectionScripts } from "./injections/index";
9
+ import { initializeSegment } from "./config/segment";
10
+ import { resolveServerConfig, type GlomoServer } from "./config/base";
11
+ import { isValidPublicKey, isValidOrderId, isValidUrl, isValidPaymentPayload, safeCallback } from "./utils/validation";
12
+ import { checkDeviceCompliance, isComplianceCheckAvailable } from "./utils/device-compliance";
13
+ import { type SdkError, type CheckoutAnalyticsTrackers, standardCheckoutTrackers } from "./utils/analytics";
14
+
15
+ import { type GlomoBankTransferPayload, type GlomoPayViaBankConnectionPayload } from "./types/checkout";
16
+ import { type StandardCheckoutStatus, type GlomoStandardCheckoutPayload } from "./types/standard-checkout";
17
+
18
+ /** Android-only WebView permission request, not yet in react-native-webview type definitions */
19
+ export interface WebViewPermissionRequest {
20
+ nativeEvent: { resources: string[] };
21
+ grant: (resources: string[]) => void;
22
+ deny: () => void;
23
+ }
24
+
25
+ /** The options for the useStandardCheckout hook */
26
+ export interface UseStandardCheckoutOptions {
27
+ server?: GlomoServer;
28
+ publicKey: string;
29
+ orderId: string;
30
+ onPaymentSuccess: (payload: GlomoStandardCheckoutPayload) => void;
31
+ onPaymentFailure: (payload: GlomoStandardCheckoutPayload) => void;
32
+ onConnectionError?: (error: unknown) => void;
33
+ onPaymentTerminate?: () => void;
34
+ onSdkError?: (error: Array<SdkError>) => void;
35
+ onBankTransferSubmitted?: (payload: GlomoBankTransferPayload | null | undefined) => void;
36
+ onPayViaBankCompleted?: (payload: { status: string }) => void;
37
+ onPayViaBankBankConnectionSuccessful?: (payload: GlomoPayViaBankConnectionPayload | null | undefined) => void;
38
+ onUserRefusedCameraPermissions?: () => void;
39
+ devMode?: boolean;
40
+ }
41
+
42
+ /** The return values for the useStandardCheckout hook */
43
+ export interface UseStandardCheckoutReturn {
44
+ start: () => boolean;
45
+ status: StandardCheckoutStatus;
46
+ showCheckout: boolean;
47
+ flowWebViewUrl: string;
48
+ showFlowWebView: boolean;
49
+ checkoutUrl: string;
50
+ mainWebViewRef: React.RefObject<WebView>;
51
+ flowWebViewRef: React.RefObject<WebView>;
52
+ handleMainWebViewMessage: (event: WebViewMessageEvent) => void;
53
+ handleFlowWebViewMessage: (event: WebViewMessageEvent) => void;
54
+ handleError: (
55
+ syntheticEvent: NativeSyntheticEvent<{ code?: number; domain?: string; description?: string }>
56
+ ) => void;
57
+ handleHttpError: (syntheticEvent: NativeSyntheticEvent<{ statusCode: number; description?: string }>) => void;
58
+ handleNavigationStateChange: (navState: WebViewNavigation) => void;
59
+ handlePermissionRequest: (request: WebViewPermissionRequest) => void;
60
+ handleModalBackButton: () => void;
61
+ handleFlowBack: () => void;
62
+ injectedMain?: string;
63
+ injectedFlow?: string;
64
+ }
65
+
66
+ /**
67
+ * Internal options for useStandardCheckout.
68
+ * Used by subscription checkout to inject subscription-specific analytics trackers.
69
+ * Not exported to merchants.
70
+ */
71
+ export interface UseStandardCheckoutInternalOptions {
72
+ analyticsTrackers?: CheckoutAnalyticsTrackers;
73
+ _skipOrderIdValidation?: boolean;
74
+ }
75
+
76
+ /** The main SDK hook for the GlomoPay Standard Checkout flow */
77
+ export function useStandardCheckout(
78
+ {
79
+ server,
80
+ publicKey,
81
+ orderId,
82
+ onPaymentSuccess,
83
+ onPaymentFailure,
84
+ onConnectionError,
85
+ onPaymentTerminate,
86
+ onSdkError,
87
+ onBankTransferSubmitted,
88
+ onPayViaBankCompleted,
89
+ onPayViaBankBankConnectionSuccessful,
90
+ onUserRefusedCameraPermissions,
91
+ devMode = false,
92
+ }: UseStandardCheckoutOptions,
93
+ mockMode: boolean,
94
+ internalOptions?: UseStandardCheckoutInternalOptions
95
+ ): UseStandardCheckoutReturn {
96
+ // Resolving analytics trackers - defaults to standard, subscription flow overrides
97
+ const trackers = internalOptions?.analyticsTrackers ?? standardCheckoutTrackers;
98
+ // Refs for the WebViews
99
+ const mainWebViewRef = useRef<WebView>(null);
100
+ const flowWebViewRef = useRef<WebView>(null);
101
+
102
+ // Deduplication guard - prevents firing onPayViaBankCompleted twice when both main and flow WebViews emit the event
103
+ const payViaBankCompletedHandledRef = useRef(false);
104
+
105
+ // Resolving checkout URL from the selected SDK server
106
+ const serverConfig = useMemo(() => resolveServerConfig(server), [server]);
107
+ const baseCheckoutUrl = serverConfig.baseUrl.standardCheckout;
108
+
109
+ /**
110
+ * The main checkout URL for the standard flow
111
+ */
112
+ const checkoutUrl = useMemo(() => {
113
+ try {
114
+ // Building the query string manually since URLSearchParams is not fully supported in React Native
115
+ const baseParams = [`orderId=${encodeURIComponent(orderId)}`];
116
+ const mode = mockMode ? "mock" : "live";
117
+ const params = baseParams.concat(
118
+ `mode=${encodeURIComponent(mode)}`,
119
+ `publicKey=${encodeURIComponent(publicKey)}`
120
+ );
121
+ const queryString = params.join("&");
122
+
123
+ // Handling base URL with or without existing query parameters
124
+ const separator = baseCheckoutUrl.includes("?") ? "&" : "?";
125
+ const finalUrl = `${baseCheckoutUrl}${separator}${queryString}`;
126
+
127
+ // Validating the final URL
128
+ if (!isValidUrl(finalUrl)) {
129
+ if (devMode) {
130
+ console.error("[Glomo-RN-SDK Standard] Generated invalid checkout URL:", finalUrl);
131
+ }
132
+ return "";
133
+ }
134
+
135
+ if (devMode) {
136
+ console.log("[Glomo-RN-SDK Standard] Checkout URL:", finalUrl);
137
+ }
138
+ return finalUrl;
139
+ } catch (error) {
140
+ if (devMode) {
141
+ console.error("[Glomo-RN-SDK Standard] Error building checkout URL:", error);
142
+ }
143
+ return "";
144
+ }
145
+ }, [baseCheckoutUrl, orderId, publicKey, mockMode, devMode]);
146
+
147
+ // Checkout status state
148
+ const [status, setStatus] = useState<StandardCheckoutStatus>("ready");
149
+
150
+ // Visibility state for the main checkout modal
151
+ const [showCheckout, setShowCheckout] = useState(false);
152
+
153
+ const [showFlowWebView, setShowFlowWebView] = useState(false);
154
+ const [flowWebViewUrl, setFlowWebViewUrl] = useState<string>("");
155
+
156
+ useEffect(() => {
157
+ if (devMode) {
158
+ console.log(
159
+ `[Glomo-RN-SDK Standard] State snapshot: status=${status}, showCheckout=${showCheckout}, showFlowWebView=${showFlowWebView}, flowWebViewUrl=${flowWebViewUrl || "<empty>"}`
160
+ );
161
+ }
162
+ }, [devMode, status, showCheckout, showFlowWebView, flowWebViewUrl]);
163
+
164
+ // Handler for the WebView errors
165
+ const handleError = useCallback(
166
+ (syntheticEvent: NativeSyntheticEvent<{ code?: number; domain?: string; description?: string }>) => {
167
+ try {
168
+ const { nativeEvent } = syntheticEvent;
169
+
170
+ // Now checking for connection errors
171
+
172
+ // iOS: NSURLErrorDomain errors
173
+ const isIOSConnectionError =
174
+ nativeEvent?.domain === "NSURLErrorDomain" &&
175
+ (nativeEvent?.code === -1004 || // Could not connect to the server
176
+ nativeEvent?.code === -1009 || // The Internet connection appears to be offline
177
+ nativeEvent?.code === -1001 || // The request timed out
178
+ nativeEvent?.code === -1003); // Cannot find host
179
+
180
+ // Android: Network errors
181
+ const description = nativeEvent?.description || "";
182
+ const isAndroidConnectionError =
183
+ typeof description === "string" &&
184
+ (description.includes("ERR_EMPTY_RESPONSE") || // Server unreachable
185
+ description.includes("ERR_CONNECTION_REFUSED") || // Connection refused
186
+ description.includes("ERR_NAME_NOT_RESOLVED") || // DNS resolution failed
187
+ description.includes("ERR_INTERNET_DISCONNECTED") || // No internet
188
+ description.includes("ERR_CONNECTION_TIMED_OUT") || // Connection timeout
189
+ description.includes("ERR_NETWORK_CHANGED")); // Network changed
190
+
191
+ if (isIOSConnectionError || isAndroidConnectionError) {
192
+ if (devMode) {
193
+ console.error("[Glomo-RN-SDK Standard] Connection error:", nativeEvent);
194
+ }
195
+
196
+ // Closing the checkout modal
197
+ setShowCheckout(false);
198
+ setShowFlowWebView(false);
199
+ setFlowWebViewUrl("");
200
+
201
+ // Calling onConnectionError callback if provided
202
+ safeCallback(onConnectionError, [nativeEvent], "onConnectionError", devMode);
203
+ // Tracking connection error
204
+ trackers.trackConnectionError(
205
+ orderId,
206
+ nativeEvent?.code,
207
+ publicKey,
208
+ devMode,
209
+ mockMode,
210
+ checkoutUrl
211
+ );
212
+ } else {
213
+ if (devMode) {
214
+ console.error("[Glomo-RN-SDK Standard] WebView error:", nativeEvent);
215
+ }
216
+ }
217
+ } catch (error) {
218
+ if (devMode) {
219
+ console.error("[Glomo-RN-SDK Standard] Error in handleError:", error);
220
+ }
221
+ }
222
+ },
223
+ [devMode, onConnectionError, checkoutUrl, orderId, publicKey, mockMode, trackers]
224
+ );
225
+
226
+ // Handler for the WebView HTTP errors
227
+ const handleHttpError = useCallback(
228
+ (syntheticEvent: NativeSyntheticEvent<{ statusCode: number; description?: string }>) => {
229
+ try {
230
+ const { nativeEvent } = syntheticEvent;
231
+ if (devMode) {
232
+ console.error("[Glomo-RN-SDK Standard] WebView HTTP error:", nativeEvent);
233
+ }
234
+ } catch (error) {
235
+ if (devMode) {
236
+ console.error("[Glomo-RN-SDK Standard] Error in handleHttpError:", error);
237
+ }
238
+ }
239
+ },
240
+ [devMode]
241
+ );
242
+
243
+ /**
244
+ * Handler for the main WebView messages.
245
+ * The main WebView hosts the checkout page. Messages arrive as JSON strings from the injected
246
+ * JavaScript bridge and include console logs, window.open/close interceptions, and payment
247
+ * lifecycle events (success, failure, terminate, bank transfer, pay via bank).
248
+ */
249
+ const handleMainWebViewMessage = useCallback(
250
+ (event: WebViewMessageEvent) => {
251
+ try {
252
+ // Extracting and validating the raw message data from the WebView event
253
+ const data = event.nativeEvent?.data;
254
+ if (!data || typeof data !== "string") {
255
+ if (devMode) {
256
+ console.warn("[Glomo-RN-SDK Standard] Invalid message data from MainWebView");
257
+ }
258
+ // Tracking invalid message received
259
+ trackers.trackInvalidMessageReceived(
260
+ "main",
261
+ data,
262
+ checkoutUrl,
263
+ orderId,
264
+ publicKey,
265
+ devMode,
266
+ mockMode,
267
+ checkoutUrl
268
+ );
269
+ return;
270
+ }
271
+
272
+ const message = JSON.parse(data);
273
+ if (devMode) {
274
+ console.log("[Glomo-RN-SDK Standard] MainWebView Message received:", message.type, message);
275
+ }
276
+
277
+ // Forwarding console messages from the injected script to the React Native console
278
+ if (message.type === "console") {
279
+ if (devMode) {
280
+ const level = message.level || "log";
281
+ console.log(`[Glomo-RN-SDK Standard] [WebView ${level.toUpperCase()}]`, message.message);
282
+ }
283
+ return;
284
+ }
285
+
286
+ // Intercepting window.open calls - opening the target URL in the flow WebView overlay
287
+ if (message.type === "window.open") {
288
+ const url = message.url;
289
+ if (url && typeof url === "string" && isValidUrl(url)) {
290
+ if (devMode) {
291
+ console.log(`[Glomo-RN-SDK Standard] window.open called from MainWebView: ${url}`);
292
+ }
293
+ setFlowWebViewUrl(url);
294
+ setShowFlowWebView(true);
295
+ // Tracking window.open interception
296
+ trackers.trackWindowOpen("main", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
297
+ } else {
298
+ if (devMode) {
299
+ console.log(
300
+ "[Glomo-RN-SDK Standard] Invalid URL in window.open from MainWebView:",
301
+ url,
302
+ typeof url === "undefined" ? "undefined" : ""
303
+ );
304
+ }
305
+ }
306
+ return;
307
+ }
308
+
309
+ // Intercepting window.close calls - closing the flow WebView overlay
310
+ if (message.type === "window.close") {
311
+ if (devMode) {
312
+ console.log("[Glomo-RN-SDK Standard] Child window close requested from MainWebView");
313
+ }
314
+ setShowFlowWebView(false);
315
+ setFlowWebViewUrl("");
316
+ // Tracking window.close interception
317
+ trackers.trackWindowClose("main", orderId, publicKey, devMode, mockMode, checkoutUrl);
318
+ return;
319
+ }
320
+
321
+ // Handling merchant JS callbacks on flow completion (payment events, lifecycle events)
322
+ if (message.type === "message" && message.message) {
323
+ const messageData = message.message;
324
+ const messageType = messageData?.type;
325
+
326
+ // Handling payment success - closing checkout and notifying the merchant
327
+ if (messageType === "payment.success" || messageType === "payment.complete") {
328
+ const paymentMessage = messageData;
329
+ const payload = paymentMessage?.payload;
330
+
331
+ if (isValidPaymentPayload(payload)) {
332
+ if (devMode) {
333
+ console.log("[Glomo-RN-SDK Standard] Payment successful via merchant callback!");
334
+ }
335
+ setShowCheckout(false);
336
+ setShowFlowWebView(false);
337
+ setFlowWebViewUrl("");
338
+ setStatus("payment_successful");
339
+ safeCallback(onPaymentSuccess, [payload], "onPaymentSuccess", devMode);
340
+ // Tracking payment success callback
341
+ trackers.trackPaymentSuccess(
342
+ payload.orderId,
343
+ payload.paymentId,
344
+ publicKey,
345
+ devMode,
346
+ mockMode,
347
+ checkoutUrl
348
+ );
349
+ } else {
350
+ if (devMode) {
351
+ console.error("[Glomo-RN-SDK Standard] Invalid payment success payload:", payload);
352
+ }
353
+ }
354
+ return;
355
+ }
356
+
357
+ // Handling payment failure - closing checkout and notifying the merchant
358
+ if (messageType === "payment.failure" || messageType === "payment.error") {
359
+ const paymentMessage = messageData;
360
+ const payload = paymentMessage?.payload;
361
+
362
+ if (isValidPaymentPayload(payload)) {
363
+ if (devMode) {
364
+ console.error("[Glomo-RN-SDK Standard] Payment failed via merchant callback");
365
+ }
366
+ setShowCheckout(false);
367
+ setShowFlowWebView(false);
368
+ setFlowWebViewUrl("");
369
+ setStatus("payment_failed");
370
+ safeCallback(onPaymentFailure, [payload], "onPaymentFailure", devMode);
371
+ // Tracking payment failure callback
372
+ trackers.trackPaymentFailure(
373
+ payload.orderId,
374
+ payload.paymentId,
375
+ publicKey,
376
+ devMode,
377
+ mockMode,
378
+ checkoutUrl
379
+ );
380
+ } else {
381
+ if (devMode) {
382
+ console.error("[Glomo-RN-SDK Standard] Invalid payment failure payload:", payload);
383
+ }
384
+ }
385
+ return;
386
+ }
387
+
388
+ // Handling bank transfer submission - user has submitted transfer details
389
+ if (messageType === "payment.bank_transfer_submitted") {
390
+ const payload = messageData?.payload;
391
+ if (devMode) {
392
+ console.log("[Glomo-RN-SDK Standard] Bank transfer submitted:", payload);
393
+ }
394
+ setShowCheckout(false);
395
+ setShowFlowWebView(false);
396
+ setFlowWebViewUrl("");
397
+ setStatus("bank_transfer_submitted");
398
+ safeCallback(
399
+ onBankTransferSubmitted,
400
+ [
401
+ {
402
+ orderId: payload?.orderId ?? "",
403
+ senderAccountNumber: payload?.senderAccountNumber ?? "",
404
+ transactionReference: payload?.transactionReference ?? "",
405
+ },
406
+ ],
407
+ "onBankTransferSubmitted",
408
+ devMode
409
+ );
410
+ trackers.trackBankTransferSubmitted(orderId, publicKey, devMode, mockMode, checkoutUrl);
411
+ return;
412
+ }
413
+
414
+ // Handling pay via bank - bank connection successful event (informational, no status change)
415
+ if (messageType === "pay_via_bank.bank.connection_successful") {
416
+ const payload = messageData?.payload;
417
+ if (devMode) {
418
+ console.log("[Glomo-RN-SDK Standard] Pay via bank - bank connection successful:", payload);
419
+ }
420
+ safeCallback(
421
+ onPayViaBankBankConnectionSuccessful,
422
+ [
423
+ {
424
+ bankIdentifier: payload?.bankIdentifier ?? "",
425
+ cooldownPeriodInMinutes: payload?.cooldownPeriodInMinutes ?? 0,
426
+ bankName: payload?.bankName ?? "",
427
+ bankImageSrc: payload?.bankImageSrc ?? "",
428
+ bankImageAlt: payload?.bankImageAlt ?? "",
429
+ },
430
+ ],
431
+ "onPayViaBankBankConnectionSuccessful",
432
+ devMode
433
+ );
434
+ trackers.trackPayViaBankBankConnectionSuccessful(
435
+ orderId,
436
+ publicKey,
437
+ payload?.bankIdentifier ?? "",
438
+ devMode,
439
+ mockMode,
440
+ checkoutUrl
441
+ );
442
+ return;
443
+ }
444
+
445
+ /**
446
+ * Handling the generic glomoCheckoutJourneyTerminate event.
447
+ * This event is fired by the checkout page when the pay via bank (lean/open finance)
448
+ * flow completes. The deduplication ref prevents firing the callback twice when both
449
+ * main and flow WebViews emit this event.
450
+ */
451
+ if (messageType === "glomoCheckoutJourneyTerminate") {
452
+ // Deduplication - both main and flow WebViews can emit this event
453
+ if (payViaBankCompletedHandledRef.current) {
454
+ if (devMode) {
455
+ console.log(
456
+ "[Glomo-RN-SDK Standard] Pay via bank completed already handled, ignoring duplicate"
457
+ );
458
+ }
459
+ return;
460
+ }
461
+ payViaBankCompletedHandledRef.current = true;
462
+
463
+ const payload = messageData?.payload;
464
+ if (devMode) {
465
+ console.log("[Glomo-RN-SDK Standard] Pay via bank completed via MainWebView:", payload);
466
+ }
467
+ setShowFlowWebView(false);
468
+ setShowCheckout(false);
469
+ setFlowWebViewUrl("");
470
+ setStatus("pay_via_bank_completed");
471
+ safeCallback(
472
+ onPayViaBankCompleted,
473
+ [{ status: payload?.status ?? "unknown" }],
474
+ "onPayViaBankCompleted",
475
+ devMode
476
+ );
477
+ trackers.trackPayViaBankCompleted(
478
+ orderId,
479
+ publicKey,
480
+ payload?.status ?? "unknown",
481
+ devMode,
482
+ mockMode,
483
+ checkoutUrl
484
+ );
485
+ return;
486
+ }
487
+
488
+ // Handling checkout.closed events
489
+ if (messageType === "checkout.closed") {
490
+ if (devMode) {
491
+ console.log("[Glomo-RN-SDK Standard] Checkout closed event received");
492
+ }
493
+ setShowCheckout(false);
494
+ setShowFlowWebView(false);
495
+ setFlowWebViewUrl("");
496
+ safeCallback(onPaymentTerminate, [], "onPaymentTerminate", devMode);
497
+ // Tracking payment terminate callback
498
+ trackers.trackPaymentTerminate(
499
+ orderId,
500
+ "checkout_closed",
501
+ publicKey,
502
+ devMode,
503
+ mockMode,
504
+ checkoutUrl
505
+ );
506
+ return;
507
+ }
508
+ }
509
+ } catch (error) {
510
+ if (devMode) {
511
+ console.error("[Glomo-RN-SDK Standard] Error parsing MainWebView message:", error);
512
+ }
513
+ }
514
+ },
515
+ [
516
+ onPaymentSuccess,
517
+ onPaymentFailure,
518
+ onPaymentTerminate,
519
+ onBankTransferSubmitted,
520
+ onPayViaBankCompleted,
521
+ onPayViaBankBankConnectionSuccessful,
522
+ devMode,
523
+ mockMode,
524
+ orderId,
525
+ publicKey,
526
+ checkoutUrl,
527
+ trackers,
528
+ ]
529
+ );
530
+
531
+ /**
532
+ * Handler for the flow WebView messages.
533
+ * The flow WebView hosts child pages opened via window.open (e.g. 3DS, bank redirects).
534
+ * Message structure mirrors the main WebView handler but operates on the overlay WebView.
535
+ */
536
+ const handleFlowWebViewMessage = useCallback(
537
+ (event: WebViewMessageEvent) => {
538
+ try {
539
+ // Extracting and validating the raw message data from the flow WebView event
540
+ const data = event.nativeEvent?.data;
541
+ if (!data || typeof data !== "string") {
542
+ if (devMode) {
543
+ console.warn("[Glomo-RN-SDK Standard] Invalid message data from FlowWebView");
544
+ }
545
+ // Tracking invalid message received
546
+ trackers.trackInvalidMessageReceived(
547
+ "flow",
548
+ data,
549
+ flowWebViewUrl,
550
+ orderId,
551
+ publicKey,
552
+ devMode,
553
+ mockMode,
554
+ checkoutUrl
555
+ );
556
+ return;
557
+ }
558
+
559
+ const message = JSON.parse(data);
560
+ if (devMode) {
561
+ console.log("[Glomo-RN-SDK Standard] FlowWebView Message received:", message.type, message);
562
+ }
563
+
564
+ // Forwarding console messages from the flow WebView's injected script
565
+ if (message.type === "console") {
566
+ if (devMode) {
567
+ console.log(`[Glomo-RN-SDK Standard] [Flow] ${message.message}`);
568
+ }
569
+ return;
570
+ }
571
+
572
+ // Intercepting window.open calls from the flow WebView - navigating within the same overlay
573
+ if (message.type === "window.open") {
574
+ const url = message.url;
575
+ if (url && typeof url === "string" && isValidUrl(url)) {
576
+ if (devMode) {
577
+ console.log(`[Glomo-RN-SDK Standard] window.open called from FlowWebView: ${url}`);
578
+ }
579
+ setFlowWebViewUrl(url);
580
+ // Tracking window.open interception
581
+ trackers.trackWindowOpen("flow", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
582
+ } else {
583
+ if (devMode) {
584
+ console.log(
585
+ "[Glomo-RN-SDK Standard] Invalid URL in window.open from FlowWebView:",
586
+ url,
587
+ typeof url === "undefined" ? "undefined" : ""
588
+ );
589
+ }
590
+ }
591
+ return;
592
+ }
593
+
594
+ // Intercepting window.close calls from the flow WebView - closing the overlay
595
+ if (message.type === "window.close") {
596
+ if (devMode) {
597
+ console.log("[Glomo-RN-SDK Standard] FlowWebView close requested");
598
+ }
599
+ setShowFlowWebView(false);
600
+ setFlowWebViewUrl("");
601
+ // Tracking window.close interception
602
+ trackers.trackWindowClose("flow", orderId, publicKey, devMode, mockMode, checkoutUrl);
603
+ return;
604
+ }
605
+
606
+ // Handling merchant JS callbacks from the flow WebView (payment events, lifecycle events)
607
+ if (message.type === "message" && message.message) {
608
+ const messageData = message.message;
609
+ const messageType = messageData?.type;
610
+
611
+ // Handling payment success from the flow WebView
612
+ if (messageType === "payment.success" || messageType === "payment.complete") {
613
+ const paymentMessage = messageData;
614
+ const payload = paymentMessage?.payload;
615
+
616
+ if (isValidPaymentPayload(payload)) {
617
+ if (devMode) {
618
+ console.log("[Glomo-RN-SDK Standard] Payment successful via FlowWebView callback!");
619
+ }
620
+ setShowFlowWebView(false);
621
+ setShowCheckout(false);
622
+ setFlowWebViewUrl("");
623
+ setStatus("payment_successful");
624
+ safeCallback(onPaymentSuccess, [payload], "onPaymentSuccess", devMode);
625
+ // Tracking payment success callback
626
+ trackers.trackPaymentSuccess(
627
+ payload.orderId,
628
+ payload.paymentId,
629
+ publicKey,
630
+ devMode,
631
+ mockMode,
632
+ checkoutUrl
633
+ );
634
+ } else {
635
+ if (devMode) {
636
+ console.error(
637
+ "[Glomo-RN-SDK Standard] Invalid payment success payload from FlowWebView:",
638
+ payload
639
+ );
640
+ }
641
+ }
642
+ return;
643
+ }
644
+
645
+ // Handling payment failure from the flow WebView
646
+ if (messageType === "payment.failure" || messageType === "payment.error") {
647
+ const paymentMessage = messageData;
648
+ const payload = paymentMessage?.payload;
649
+
650
+ if (isValidPaymentPayload(payload)) {
651
+ if (devMode) {
652
+ console.error("[Glomo-RN-SDK Standard] Payment failed via FlowWebView callback");
653
+ }
654
+ setShowFlowWebView(false);
655
+ setShowCheckout(false);
656
+ setFlowWebViewUrl("");
657
+ setStatus("payment_failed");
658
+ safeCallback(onPaymentFailure, [payload], "onPaymentFailure", devMode);
659
+ // Tracking payment failure callback
660
+ trackers.trackPaymentFailure(
661
+ payload.orderId,
662
+ payload.paymentId,
663
+ publicKey,
664
+ devMode,
665
+ mockMode,
666
+ checkoutUrl
667
+ );
668
+ } else {
669
+ if (devMode) {
670
+ console.error(
671
+ "[Glomo-RN-SDK Standard] Invalid payment failure payload from FlowWebView:",
672
+ payload
673
+ );
674
+ }
675
+ }
676
+ return;
677
+ }
678
+
679
+ // Handling pay via bank - bank connection successful event from the flow WebView
680
+ if (messageType === "pay_via_bank.bank.connection_successful") {
681
+ const payload = messageData?.payload;
682
+ if (devMode) {
683
+ console.log(
684
+ "[Glomo-RN-SDK Standard] Pay via bank - bank connection successful via FlowWebView:",
685
+ payload
686
+ );
687
+ }
688
+ safeCallback(
689
+ onPayViaBankBankConnectionSuccessful,
690
+ [
691
+ {
692
+ bankIdentifier: payload?.bankIdentifier ?? "",
693
+ cooldownPeriodInMinutes: payload?.cooldownPeriodInMinutes ?? 0,
694
+ bankName: payload?.bankName ?? "",
695
+ bankImageSrc: payload?.bankImageSrc ?? "",
696
+ bankImageAlt: payload?.bankImageAlt ?? "",
697
+ },
698
+ ],
699
+ "onPayViaBankBankConnectionSuccessful",
700
+ devMode
701
+ );
702
+ trackers.trackPayViaBankBankConnectionSuccessful(
703
+ orderId,
704
+ publicKey,
705
+ payload?.bankIdentifier ?? "",
706
+ devMode,
707
+ mockMode,
708
+ checkoutUrl
709
+ );
710
+ return;
711
+ }
712
+
713
+ // Handling glomoCheckoutJourneyTerminate from the flow WebView (pay via bank completion)
714
+ if (messageType === "glomoCheckoutJourneyTerminate") {
715
+ // Deduplication - both main and flow WebViews can emit this event
716
+ if (payViaBankCompletedHandledRef.current) {
717
+ if (devMode) {
718
+ console.log(
719
+ "[Glomo-RN-SDK Standard] Pay via bank completed already handled, ignoring duplicate"
720
+ );
721
+ }
722
+ return;
723
+ }
724
+ payViaBankCompletedHandledRef.current = true;
725
+
726
+ const payload = messageData?.payload;
727
+ if (devMode) {
728
+ console.log("[Glomo-RN-SDK Standard] Pay via bank completed via FlowWebView:", payload);
729
+ }
730
+ setShowFlowWebView(false);
731
+ setShowCheckout(false);
732
+ setFlowWebViewUrl("");
733
+ setStatus("pay_via_bank_completed");
734
+ safeCallback(
735
+ onPayViaBankCompleted,
736
+ [{ status: payload?.status ?? "unknown" }],
737
+ "onPayViaBankCompleted",
738
+ devMode
739
+ );
740
+ trackers.trackPayViaBankCompleted(
741
+ orderId,
742
+ publicKey,
743
+ payload?.status ?? "unknown",
744
+ devMode,
745
+ mockMode,
746
+ checkoutUrl
747
+ );
748
+ return;
749
+ }
750
+ }
751
+ } catch (error) {
752
+ if (devMode) {
753
+ console.error("[Glomo-RN-SDK Standard] Error parsing FlowWebView message:", error);
754
+ }
755
+ }
756
+ },
757
+ [
758
+ onPaymentSuccess,
759
+ onPaymentFailure,
760
+ onPayViaBankCompleted,
761
+ onPayViaBankBankConnectionSuccessful,
762
+ devMode,
763
+ mockMode,
764
+ orderId,
765
+ publicKey,
766
+ flowWebViewUrl,
767
+ checkoutUrl,
768
+ trackers,
769
+ ]
770
+ );
771
+
772
+ // Handler for the navigation state changes
773
+ const handleNavigationStateChange = useCallback(
774
+ (navState: WebViewNavigation) => {
775
+ if (devMode) {
776
+ console.log("[Glomo-RN-SDK Standard] Navigation state:", navState);
777
+ }
778
+ },
779
+ [devMode]
780
+ );
781
+
782
+ // Handler for the modal back button press events
783
+ const handleModalBackButton = useCallback(() => {
784
+ try {
785
+ if (devMode) {
786
+ console.log("[Glomo-RN-SDK Standard] Modal dismissed/back button pressed");
787
+ }
788
+ if (showFlowWebView) {
789
+ setShowFlowWebView(false);
790
+ setFlowWebViewUrl("");
791
+ }
792
+ setShowCheckout(false);
793
+ if (status === "payment_in_progress") {
794
+ if (devMode) {
795
+ console.log("[Glomo-RN-SDK Standard] In progress payment cancelled");
796
+ }
797
+ setStatus("payment_cancelled");
798
+ } else {
799
+ if (devMode) {
800
+ console.log("[Glomo-RN-SDK Standard] Ignoring checkout status update for: " + status);
801
+ }
802
+ }
803
+ safeCallback(onPaymentTerminate, [], "onPaymentTerminate", devMode);
804
+ // Tracking payment terminate callback (user dismiss/back button)
805
+ const terminateSource = Platform.OS === "ios" ? "user_dismiss" : "back_button";
806
+ trackers.trackPaymentTerminate(orderId, terminateSource, publicKey, devMode, mockMode, checkoutUrl);
807
+ } catch (error) {
808
+ if (devMode) {
809
+ console.error("[Glomo-RN-SDK Standard] Error in handleModalBackButton:", error);
810
+ }
811
+ }
812
+ }, [showFlowWebView, devMode, onPaymentTerminate, status, orderId, publicKey, mockMode, checkoutUrl, trackers]);
813
+
814
+ /** Closes the flow WebView and returns to the main checkout */
815
+ const handleFlowBack = useCallback(() => {
816
+ if (devMode) {
817
+ console.log("[Glomo-RN-SDK Standard] Flow WebView back button pressed");
818
+ }
819
+ setShowFlowWebView(false);
820
+ setFlowWebViewUrl("");
821
+ trackers.trackWindowClose("flow", orderId, publicKey, devMode, mockMode, checkoutUrl);
822
+ }, [devMode, orderId, publicKey, mockMode, checkoutUrl, trackers]);
823
+
824
+ /**
825
+ * Handler for WebView permission requests (e.g. camera access for bank authentication).
826
+ * On Android, prompts the user for camera permission via PermissionsAndroid.
827
+ * On iOS, grants camera access automatically (iOS handles its own permission prompt).
828
+ * Non-camera permissions are denied by default.
829
+ */
830
+ const handlePermissionRequest = useCallback(
831
+ async (request: WebViewPermissionRequest) => {
832
+ try {
833
+ const { resources } = request.nativeEvent;
834
+ // Checking if the requested permission is camera-related
835
+ const isCameraPermissionRequest = resources.some(
836
+ (resource: string) => resource === "camera" || resource.toLowerCase().includes("camera")
837
+ );
838
+
839
+ if (!isCameraPermissionRequest) {
840
+ if (devMode) {
841
+ console.log("[Glomo-RN-SDK Standard] Non-camera permission requested, denying:", resources);
842
+ }
843
+ request.deny();
844
+ return;
845
+ }
846
+
847
+ if (devMode) {
848
+ console.log("[Glomo-RN-SDK Standard] Camera permission requested by webpage");
849
+ }
850
+
851
+ if (Platform.OS === "android") {
852
+ try {
853
+ const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA, {
854
+ title: "Camera Permission",
855
+ message: "This app needs camera access for identity verification during checkout",
856
+ buttonNeutral: "Ask Me Later",
857
+ buttonNegative: "Cancel",
858
+ buttonPositive: "OK",
859
+ });
860
+
861
+ if (granted === PermissionsAndroid.RESULTS.GRANTED) {
862
+ if (devMode) {
863
+ console.log("[Glomo-RN-SDK Standard] Camera permission granted");
864
+ }
865
+ request.grant(resources);
866
+ } else {
867
+ if (devMode) {
868
+ console.log("[Glomo-RN-SDK Standard] Camera permission denied by user");
869
+ }
870
+ setShowCheckout(false);
871
+ if (status === "payment_in_progress") {
872
+ setStatus("payment_cancelled");
873
+ }
874
+ safeCallback(onUserRefusedCameraPermissions, [], "onUserRefusedCameraPermissions", devMode);
875
+ request.deny();
876
+ }
877
+ } catch (err) {
878
+ if (devMode) {
879
+ console.error("[Glomo-RN-SDK Standard] Error requesting camera permission:", err);
880
+ }
881
+ setShowCheckout(false);
882
+ if (status === "payment_in_progress") {
883
+ setStatus("payment_cancelled");
884
+ }
885
+ safeCallback(onUserRefusedCameraPermissions, [], "onUserRefusedCameraPermissions", devMode);
886
+ request.deny();
887
+ }
888
+ } else if (Platform.OS === "ios") {
889
+ if (devMode) {
890
+ console.log("[Glomo-RN-SDK Standard] Granting camera permission request on iOS");
891
+ }
892
+ request.grant(resources);
893
+ } else {
894
+ if (devMode) {
895
+ console.warn("[Glomo-RN-SDK Standard] Unsupported platform for camera permissions");
896
+ }
897
+ request.deny();
898
+ }
899
+ } catch (error) {
900
+ if (devMode) {
901
+ console.error("[Glomo-RN-SDK Standard] Error in handlePermissionRequest:", error);
902
+ }
903
+ request.deny();
904
+ }
905
+ },
906
+ [devMode, status, onUserRefusedCameraPermissions]
907
+ );
908
+
909
+ /**
910
+ * Initiator method for the Standard Checkout flow.
911
+ * Using a ref, the standard flow can be started by calling this method.
912
+ * Example:
913
+ * const checkoutRef = useRef<GlomoStandardCheckoutRef>(null);
914
+ * checkoutRef.current?.start();
915
+ *
916
+ * The standard checkout can only be started if
917
+ * - The status is "ready", "payment_cancelled", or "payment_failed"
918
+ * - The status is NOT "payment_successful"
919
+ * - Input validation passes
920
+ */
921
+ const start = useCallback((): boolean => {
922
+ /**
923
+ * Checking device security compliance (if peerDependencies are available)
924
+ * A null value indicates that recommended security dependencies were not installed and compliance checks will be skipped in this case
925
+ */
926
+ const deviceComplianceCheck = checkDeviceCompliance(devMode);
927
+ const jailbreakDetectionLibraryInstalled = isComplianceCheckAvailable();
928
+ const jailbreakDetectionInfo = {
929
+ jailbreakDetectionLibraryInstalled,
930
+ jailbreakDetected: deviceComplianceCheck,
931
+ };
932
+
933
+ if (devMode) {
934
+ console.log(
935
+ `[Glomo-RN-SDK Standard] Attempting to start checkout of ${checkoutUrl} from status: ${status}`
936
+ );
937
+ }
938
+
939
+ // Tracking start attempt with current status
940
+ trackers.trackStartAttempt(status, orderId, publicKey, devMode, mockMode, checkoutUrl, jailbreakDetectionInfo);
941
+
942
+ if (deviceComplianceCheck === true) {
943
+ // Device detected as compromised
944
+ if (devMode) {
945
+ console.error("[Glomo-RN-SDK Standard] Cannot start checkout. Device is rooted or jailbroken.");
946
+ }
947
+ const errors: SdkError[] = [
948
+ {
949
+ type: "device_forbidden",
950
+ message: "Standard checkout is not allowed on rooted or jailbroken devices.",
951
+ },
952
+ ];
953
+ safeCallback(onSdkError, [errors], "onSdkError", devMode);
954
+ // Tracking start() failure
955
+ trackers.trackStartFailure(
956
+ "device_forbidden",
957
+ orderId,
958
+ publicKey,
959
+ devMode,
960
+ mockMode,
961
+ checkoutUrl,
962
+ jailbreakDetectionInfo
963
+ );
964
+ setShowCheckout(false);
965
+ return false;
966
+ } else if (deviceComplianceCheck === null) {
967
+ // 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.
968
+ console.warn(
969
+ "[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."
970
+ );
971
+ }
972
+
973
+ // Enforcing devMode requirement for development servers
974
+ const resolvedServer = server || "prod";
975
+ if (!devMode && resolvedServer !== "prod") {
976
+ const errors: SdkError[] = [
977
+ {
978
+ type: "validation_error",
979
+ message: "Development servers can only be used when devMode is enabled.",
980
+ },
981
+ ];
982
+ safeCallback(onSdkError, [errors], "onSdkError", devMode);
983
+ trackers.trackSdkError(errors, orderId, publicKey, devMode, mockMode, checkoutUrl);
984
+ trackers.trackStartFailure(
985
+ "validation_error",
986
+ orderId,
987
+ publicKey,
988
+ devMode,
989
+ mockMode,
990
+ checkoutUrl,
991
+ jailbreakDetectionInfo
992
+ );
993
+ setShowCheckout(false);
994
+ return false;
995
+ }
996
+
997
+ // Checking validation errors with detailed field-level messages
998
+ const errors: SdkError[] = [];
999
+
1000
+ if (!isValidPublicKey(publicKey)) {
1001
+ const error: SdkError = {
1002
+ type: "validation_error",
1003
+ message: "Invalid publicKey: must start with 'live_', 'mock_', or 'test_' and be a valid string",
1004
+ field: "publicKey",
1005
+ };
1006
+ errors.push(error);
1007
+ }
1008
+
1009
+ if (!internalOptions?._skipOrderIdValidation && !isValidOrderId(orderId)) {
1010
+ const error: SdkError = {
1011
+ type: "validation_error",
1012
+ message: "Invalid orderId: must start with 'order_' and be a valid string",
1013
+ field: "orderId",
1014
+ };
1015
+ errors.push(error);
1016
+ }
1017
+
1018
+ if (!isValidUrl(checkoutUrl)) {
1019
+ const error: SdkError = !isValidUrl(baseCheckoutUrl)
1020
+ ? {
1021
+ type: "validation_error",
1022
+ message: "Invalid baseCheckoutUrl: must be a valid HTTP or HTTPS URL",
1023
+ field: "baseCheckoutUrl",
1024
+ }
1025
+ : {
1026
+ type: "validation_error",
1027
+ message: "Invalid checkoutUrl: must be a valid HTTP or HTTPS URL",
1028
+ field: "generatedCheckoutUrl",
1029
+ };
1030
+ errors.push(error);
1031
+ }
1032
+
1033
+ if (errors.length > 0) {
1034
+ if (devMode) {
1035
+ console.error(`[Glomo-RN-SDK Standard] Cannot start checkout. Validation failed.`);
1036
+ }
1037
+ safeCallback(onSdkError, [errors], "onSdkError", devMode);
1038
+ // Tracking start() failure
1039
+ trackers.trackSdkError(errors, orderId, publicKey, devMode, mockMode, checkoutUrl);
1040
+ trackers.trackStartFailure(
1041
+ "validation_error",
1042
+ orderId,
1043
+ publicKey,
1044
+ devMode,
1045
+ mockMode,
1046
+ checkoutUrl,
1047
+ jailbreakDetectionInfo
1048
+ );
1049
+ setShowCheckout(false);
1050
+ return false;
1051
+ }
1052
+
1053
+ if (status === "payment_successful") {
1054
+ if (devMode) {
1055
+ console.log(
1056
+ `[Glomo-RN-SDK Standard] Cannot start checkout. Current status: ${status}. Payment already successful.`
1057
+ );
1058
+ }
1059
+ // Tracking start() failure
1060
+ trackers.trackStartFailure(
1061
+ "payment_successful",
1062
+ orderId,
1063
+ publicKey,
1064
+ devMode,
1065
+ mockMode,
1066
+ checkoutUrl,
1067
+ jailbreakDetectionInfo
1068
+ );
1069
+ setShowCheckout(false);
1070
+ return false;
1071
+ }
1072
+
1073
+ if (
1074
+ status !== "ready" &&
1075
+ status !== "payment_cancelled" &&
1076
+ status !== "payment_failed" &&
1077
+ status !== "payment_in_progress" &&
1078
+ status !== "bank_transfer_submitted" &&
1079
+ status !== "pay_via_bank_completed"
1080
+ ) {
1081
+ if (devMode) {
1082
+ console.log(`[Glomo-RN-SDK Standard] Cannot start checkout. Unknown status: ${status}.`);
1083
+ }
1084
+ // Tracking start() failure
1085
+ trackers.trackStartFailure(
1086
+ "invalid_status",
1087
+ orderId,
1088
+ publicKey,
1089
+ devMode,
1090
+ mockMode,
1091
+ checkoutUrl,
1092
+ jailbreakDetectionInfo
1093
+ );
1094
+ setShowCheckout(false);
1095
+ return false;
1096
+ }
1097
+
1098
+ if (!checkoutUrl) {
1099
+ if (devMode) {
1100
+ console.error("[Glomo-RN-SDK Standard] Cannot start checkout. Invalid checkout URL.");
1101
+ }
1102
+ // Tracking start() failure
1103
+ trackers.trackStartFailure(
1104
+ "invalid_url",
1105
+ orderId,
1106
+ publicKey,
1107
+ devMode,
1108
+ mockMode,
1109
+ checkoutUrl,
1110
+ jailbreakDetectionInfo
1111
+ );
1112
+ setShowCheckout(false);
1113
+ return false;
1114
+ }
1115
+
1116
+ if (devMode) {
1117
+ console.log("[Glomo-RN-SDK Standard] Starting checkout from previous state: " + status);
1118
+ }
1119
+
1120
+ // Resetting any stale state from a previous checkout attempt before starting a new one
1121
+ setShowFlowWebView(false);
1122
+ setFlowWebViewUrl("");
1123
+ payViaBankCompletedHandledRef.current = false;
1124
+ setStatus("payment_in_progress");
1125
+
1126
+ // Tracking successful start
1127
+ trackers.trackStartSuccess(orderId, publicKey, devMode, mockMode, checkoutUrl, jailbreakDetectionInfo);
1128
+
1129
+ // Opening the checkout modal
1130
+ setShowCheckout(true);
1131
+ return true;
1132
+ }, [
1133
+ devMode,
1134
+ status,
1135
+ publicKey,
1136
+ orderId,
1137
+ baseCheckoutUrl,
1138
+ checkoutUrl,
1139
+ mockMode,
1140
+ onSdkError,
1141
+ server,
1142
+ trackers,
1143
+ internalOptions,
1144
+ ]);
1145
+
1146
+ // Initializing Segment analytics
1147
+ useEffect(() => {
1148
+ initializeSegment(devMode);
1149
+ }, [devMode]);
1150
+
1151
+ // Resetting checkout flow if orderId or publicKey are changed
1152
+ useEffect(() => {
1153
+ if (orderId && publicKey) {
1154
+ // Only resetting the checkout flow if the current status is that of a terminal state
1155
+ if (
1156
+ status === "payment_successful" ||
1157
+ status === "payment_failed" ||
1158
+ status === "payment_cancelled" ||
1159
+ status === "bank_transfer_submitted" ||
1160
+ status === "pay_via_bank_completed"
1161
+ ) {
1162
+ if (devMode) {
1163
+ console.log(
1164
+ `[Glomo-RN-SDK Standard] Resetting checkout status ${status} for new orderId ${orderId} and publicKey ${publicKey}`
1165
+ );
1166
+ }
1167
+ setStatus("ready");
1168
+ }
1169
+ }
1170
+ }, [orderId, publicKey, status, devMode]);
1171
+
1172
+ // Cleaning up on unmount
1173
+ useEffect(() => {
1174
+ return () => {
1175
+ // Cleaning up - resetting state on unmount
1176
+ setStatus("ready");
1177
+ setShowCheckout(false);
1178
+ setShowFlowWebView(false);
1179
+ setFlowWebViewUrl("");
1180
+ };
1181
+ }, []);
1182
+
1183
+ return {
1184
+ start,
1185
+ status,
1186
+ showCheckout,
1187
+ flowWebViewUrl,
1188
+ showFlowWebView,
1189
+ checkoutUrl,
1190
+ mainWebViewRef,
1191
+ flowWebViewRef,
1192
+ handleMainWebViewMessage,
1193
+ handleFlowWebViewMessage,
1194
+ handleError,
1195
+ handleHttpError,
1196
+ handlePermissionRequest,
1197
+ handleNavigationStateChange,
1198
+ handleModalBackButton,
1199
+ handleFlowBack,
1200
+ injectedMain: InjectionScripts.standard,
1201
+ injectedFlow: InjectionScripts.flow,
1202
+ };
1203
+ }