@glomopay/react-native-sdk 2.0.2 → 3.0.1

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