@glomopay/react-native-sdk 1.3.4 → 2.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 (34) hide show
  1. package/README.md +9 -4
  2. package/lib/config/base.d.ts +14 -5
  3. package/lib/config/base.d.ts.map +1 -1
  4. package/lib/config/base.js +22 -6
  5. package/lib/glomo-lrs-checkout.d.ts.map +1 -1
  6. package/lib/glomo-lrs-checkout.js +156 -9
  7. package/lib/index.d.ts +1 -0
  8. package/lib/index.d.ts.map +1 -1
  9. package/lib/injections/index.d.ts +1 -0
  10. package/lib/injections/index.d.ts.map +1 -1
  11. package/lib/injections/index.js +2 -0
  12. package/lib/injections/webview-education-carousel.injection.d.ts +3 -0
  13. package/lib/injections/webview-education-carousel.injection.d.ts.map +1 -0
  14. package/lib/injections/webview-education-carousel.injection.js +157 -0
  15. package/lib/injections/webview-flow.injection.d.ts.map +1 -1
  16. package/lib/injections/webview-flow.injection.js +3 -5
  17. package/lib/injections/webview-main.injection.d.ts.map +1 -1
  18. package/lib/injections/webview-main.injection.js +3 -5
  19. package/lib/use-lrs-checkout.d.ts +14 -1
  20. package/lib/use-lrs-checkout.d.ts.map +1 -1
  21. package/lib/use-lrs-checkout.js +124 -8
  22. package/lib/utils/analytics.d.ts +11 -0
  23. package/lib/utils/analytics.d.ts.map +1 -1
  24. package/lib/utils/analytics.js +22 -0
  25. package/package.json +1 -1
  26. package/src/config/base.ts +35 -5
  27. package/src/glomo-lrs-checkout.tsx +206 -10
  28. package/src/index.ts +1 -0
  29. package/src/injections/index.ts +2 -0
  30. package/src/injections/webview-education-carousel.injection.ts +154 -0
  31. package/src/injections/webview-flow.injection.ts +3 -5
  32. package/src/injections/webview-main.injection.ts +3 -5
  33. package/src/use-lrs-checkout.tsx +161 -8
  34. package/src/utils/analytics.ts +29 -0
@@ -5,9 +5,9 @@ import { Platform, type NativeSyntheticEvent } from "react-native";
5
5
 
6
6
  import { WebViewNavigation, WebViewMessageEvent, type WebView } from "react-native-webview";
7
7
 
8
- import { InjectionScripts } from "./injections";
8
+ import { InjectionScripts } from "./injections/index";
9
9
  import { initializeSegment } from "./config/segment";
10
- import { DEFAULT_BASE_CHECKOUT_URL } from "./config/base";
10
+ import { resolveServerConfig, type GlomoLrsServer } from "./config/base";
11
11
  import { isValidPublicKey, isValidOrderId, isValidUrl, isValidPaymentPayload, safeCallback } from "./utils/validation";
12
12
  import { checkDeviceCompliance, isComplianceCheckAvailable } from "./utils/device-compliance";
13
13
  import {
@@ -20,6 +20,7 @@ import {
20
20
  trackPaymentFailure,
21
21
  trackPaymentTerminate,
22
22
  trackConnectionError,
23
+ trackEducationStepsShown,
23
24
  trackSdkError,
24
25
  trackInvalidMessageReceived,
25
26
  type SdkError,
@@ -42,6 +43,13 @@ export type LrsCheckoutStatus =
42
43
  // LRS checkout flow aborted midway. User can retry the flow on the same orderId
43
44
  | "payment_cancelled";
44
45
 
46
+ /**
47
+ * Tracks whether the education carousel WebView has signalled that it has content to show.
48
+ * - "pending": waiting for the carousel to report its state (default/reset value)
49
+ * - "hasContent": carousel sent lrs.has_education_steps and should be shown to the user
50
+ */
51
+ export type EducationCarouselState = "pending" | "hasContent";
52
+
45
53
  /** The payload for a successful or failed payment */
46
54
  export interface GlomoLrsCheckoutPayload {
47
55
  orderId: string;
@@ -51,7 +59,7 @@ export interface GlomoLrsCheckoutPayload {
51
59
 
52
60
  /** The options for the useLrsCheckout hook */
53
61
  export interface UseLrsCheckoutOptions {
54
- server?: string;
62
+ server?: GlomoLrsServer;
55
63
  publicKey: string;
56
64
  orderId: string;
57
65
  onPaymentSuccess: (payload: GlomoLrsCheckoutPayload) => void;
@@ -69,19 +77,25 @@ export interface UseLrsCheckoutReturn {
69
77
  showCheckout: boolean;
70
78
  flowWebViewUrl: string;
71
79
  showFlowWebView: boolean;
80
+ educationCarouselUrl: string;
81
+ educationCarouselState: EducationCarouselState;
72
82
  checkoutUrl: string;
73
83
  mainWebViewRef: React.RefObject<WebView>;
74
84
  flowWebViewRef: React.RefObject<WebView>;
85
+ educationCarouselWebViewRef: React.RefObject<WebView>;
75
86
  handleMainWebViewMessage: (event: WebViewMessageEvent) => void;
76
87
  handleFlowWebViewMessage: (event: WebViewMessageEvent) => void;
88
+ handleEducationCarouselWebViewMessage: (event: WebViewMessageEvent) => void;
77
89
  handleError: (
78
90
  syntheticEvent: NativeSyntheticEvent<{ code?: number; domain?: string; description?: string }>
79
91
  ) => void;
80
92
  handleHttpError: (syntheticEvent: NativeSyntheticEvent<{ statusCode: number; description?: string }>) => void;
81
93
  handleNavigationStateChange: (navState: WebViewNavigation) => void;
82
94
  handleModalBackButton: () => void;
95
+ handleFlowBack: () => void;
83
96
  injectedMain?: string;
84
97
  injectedFlow?: string;
98
+ injectedEducationCarousel?: string;
85
99
  }
86
100
 
87
101
  /** The main SDK hook for the GlomoPay LRS Checkout flow */
@@ -102,9 +116,11 @@ export function useLrsCheckout(
102
116
  // Refs for the WebViews
103
117
  const mainWebViewRef = useRef<WebView>(null);
104
118
  const flowWebViewRef = useRef<WebView>(null);
119
+ const educationCarouselWebViewRef = useRef<WebView>(null);
105
120
 
106
- // Using the server prop if provided, otherwise using the default production URL
107
- const baseCheckoutUrl = useMemo(() => server ?? DEFAULT_BASE_CHECKOUT_URL, [server]);
121
+ // Resolving checkout and carousel URLs from the selected SDK server
122
+ const serverConfig = useMemo(() => resolveServerConfig(server), [server]);
123
+ const baseCheckoutUrl = serverConfig.checkoutBaseUrl;
108
124
 
109
125
  /**
110
126
  * The main checkout URL for the LRS flow
@@ -168,6 +184,58 @@ export function useLrsCheckout(
168
184
  }
169
185
  }, [baseCheckoutUrl, orderId, publicKey, mockMode, devMode, onSdkError]);
170
186
 
187
+ /**
188
+ * The education carousel URL, built from the server config with the current orderId and publicKey.
189
+ * Loaded in a hidden WebView alongside the flow WebView; shown only once the carousel signals it has content.
190
+ */
191
+ const educationCarouselUrl = useMemo(() => {
192
+ try {
193
+ const baseEducationCarouselUrl = serverConfig.educationCarouselBaseUrl;
194
+ const separator = baseEducationCarouselUrl.includes("?") ? "&" : "?";
195
+ const finalUrl =
196
+ `${baseEducationCarouselUrl}${separator}orderId=${encodeURIComponent(orderId)}` +
197
+ `&publicKey=${encodeURIComponent(publicKey)}`;
198
+
199
+ if (!isValidUrl(finalUrl)) {
200
+ if (devMode) {
201
+ console.error("[GlomoPay RN SDK] Generated invalid education carousel URL:", finalUrl);
202
+ }
203
+ const errors: SdkError[] = [
204
+ {
205
+ type: "validation_error",
206
+ message: "Generated invalid education carousel URL",
207
+ },
208
+ ];
209
+ trackSdkError(
210
+ errors,
211
+ orderId,
212
+ publicKey,
213
+ devMode,
214
+ mockMode,
215
+ finalUrl ? finalUrl : finalUrl === null ? "null" : "undefined"
216
+ );
217
+ return "";
218
+ }
219
+
220
+ if (devMode) {
221
+ console.log("[GlomoPay RN SDK] Education Carousel URL:", finalUrl);
222
+ }
223
+ return finalUrl;
224
+ } catch (error) {
225
+ if (devMode) {
226
+ console.error("[GlomoPay RN SDK] Error building education carousel URL:", error);
227
+ }
228
+ const errors: SdkError[] = [
229
+ {
230
+ type: "validation_error",
231
+ message: `Error building education carousel URL: ${error instanceof Error ? error.message : "Unknown error"}`,
232
+ },
233
+ ];
234
+ trackSdkError(errors, orderId, publicKey, devMode, mockMode, "");
235
+ return "";
236
+ }
237
+ }, [serverConfig, orderId, publicKey, devMode, mockMode]);
238
+
171
239
  // Checkout status state
172
240
  const [status, setStatus] = useState<LrsCheckoutStatus>("ready");
173
241
 
@@ -176,6 +244,15 @@ export function useLrsCheckout(
176
244
 
177
245
  const [showFlowWebView, setShowFlowWebView] = useState(false);
178
246
  const [flowWebViewUrl, setFlowWebViewUrl] = useState<string>("");
247
+ const [educationCarouselState, setEducationCarouselState] = useState<EducationCarouselState>("pending");
248
+
249
+ useEffect(() => {
250
+ if (devMode) {
251
+ console.log(
252
+ `[GlomoPay RN SDK] Carousel state snapshot: status=${status}, showCheckout=${showCheckout}, showFlowWebView=${showFlowWebView}, flowWebViewUrl=${flowWebViewUrl || "<empty>"}, educationCarouselState=${educationCarouselState}, educationCarouselUrl=${educationCarouselUrl || "<empty>"}`
253
+ );
254
+ }
255
+ }, [devMode, status, showCheckout, showFlowWebView, flowWebViewUrl, educationCarouselState, educationCarouselUrl]);
179
256
 
180
257
  // Handler for the WebView errors
181
258
  const handleError = useCallback(
@@ -213,6 +290,7 @@ export function useLrsCheckout(
213
290
  setShowCheckout(false);
214
291
  setShowFlowWebView(false);
215
292
  setFlowWebViewUrl("");
293
+ setEducationCarouselState("pending");
216
294
 
217
295
  // Calling onConnectionError callback if provided
218
296
  safeCallback(onConnectionError, [nativeEvent], "onConnectionError", devMode);
@@ -229,7 +307,7 @@ export function useLrsCheckout(
229
307
  }
230
308
  }
231
309
  },
232
- [devMode, onConnectionError, checkoutUrl]
310
+ [devMode, onConnectionError, checkoutUrl, orderId, publicKey, mockMode]
233
311
  );
234
312
 
235
313
  // Handler for the WebView HTTP errors
@@ -293,6 +371,7 @@ export function useLrsCheckout(
293
371
  }
294
372
  setFlowWebViewUrl(url);
295
373
  setShowFlowWebView(true);
374
+ setEducationCarouselState("pending");
296
375
  // Tracking window.open interception
297
376
  trackWindowOpen("main", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
298
377
  } else {
@@ -313,6 +392,7 @@ export function useLrsCheckout(
313
392
  }
314
393
  setShowFlowWebView(false);
315
394
  setFlowWebViewUrl("");
395
+ setEducationCarouselState("pending");
316
396
  // Tracking window.close interception
317
397
  trackWindowClose("main", orderId, publicKey, devMode, mockMode, checkoutUrl);
318
398
  return;
@@ -334,6 +414,7 @@ export function useLrsCheckout(
334
414
  setShowCheckout(false);
335
415
  setShowFlowWebView(false);
336
416
  setFlowWebViewUrl("");
417
+ setEducationCarouselState("pending");
337
418
  setStatus("payment_successful");
338
419
  safeCallback(onPaymentSuccess, [payload], "onPaymentSuccess", devMode);
339
420
  // Tracking payment success callback
@@ -364,6 +445,7 @@ export function useLrsCheckout(
364
445
  setShowCheckout(false);
365
446
  setShowFlowWebView(false);
366
447
  setFlowWebViewUrl("");
448
+ setEducationCarouselState("pending");
367
449
  setStatus("payment_failed");
368
450
  safeCallback(onPaymentFailure, [payload], "onPaymentFailure", devMode);
369
451
  // Tracking payment failure callback
@@ -391,6 +473,7 @@ export function useLrsCheckout(
391
473
  setShowCheckout(false);
392
474
  setShowFlowWebView(false);
393
475
  setFlowWebViewUrl("");
476
+ setEducationCarouselState("pending");
394
477
  safeCallback(onPaymentTerminate, [], "onPaymentTerminate", devMode);
395
478
  // Tracking payment terminate callback
396
479
  trackPaymentTerminate(orderId, "checkout_closed", publicKey, devMode, mockMode, checkoutUrl);
@@ -448,6 +531,7 @@ export function useLrsCheckout(
448
531
  console.log(`[GlomoPay RN SDK] window.open called from FlowWebView: ${url}`);
449
532
  }
450
533
  setFlowWebViewUrl(url);
534
+ setEducationCarouselState("pending");
451
535
  // Tracking window.open interception
452
536
  trackWindowOpen("flow", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
453
537
  } else {
@@ -468,6 +552,7 @@ export function useLrsCheckout(
468
552
  }
469
553
  setShowFlowWebView(false);
470
554
  setFlowWebViewUrl("");
555
+ setEducationCarouselState("pending");
471
556
  // Tracking window.close interception
472
557
  trackWindowClose("flow", orderId, publicKey, devMode, mockMode, checkoutUrl);
473
558
  return;
@@ -488,6 +573,7 @@ export function useLrsCheckout(
488
573
  setShowFlowWebView(false);
489
574
  setShowCheckout(false);
490
575
  setFlowWebViewUrl("");
576
+ setEducationCarouselState("pending");
491
577
  setStatus("payment_successful");
492
578
  safeCallback(onPaymentSuccess, [payload], "onPaymentSuccess", devMode);
493
579
  // Tracking payment success callback
@@ -521,6 +607,7 @@ export function useLrsCheckout(
521
607
  setShowFlowWebView(false);
522
608
  setShowCheckout(false);
523
609
  setFlowWebViewUrl("");
610
+ setEducationCarouselState("pending");
524
611
  setStatus("payment_failed");
525
612
  safeCallback(onPaymentFailure, [payload], "onPaymentFailure", devMode);
526
613
  // Tracking payment failure callback
@@ -552,6 +639,52 @@ export function useLrsCheckout(
552
639
  [onPaymentSuccess, onPaymentFailure, devMode, mockMode, orderId, publicKey, flowWebViewUrl, checkoutUrl]
553
640
  );
554
641
 
642
+ // Handler for the education carousel WebView messages
643
+ const handleEducationCarouselWebViewMessage = useCallback(
644
+ (event: WebViewMessageEvent) => {
645
+ try {
646
+ const data = event.nativeEvent?.data;
647
+ if (!data || typeof data !== "string") {
648
+ if (devMode) {
649
+ console.warn("[GlomoPay RN SDK] Invalid message data from EducationCarouselWebView");
650
+ }
651
+ return;
652
+ }
653
+
654
+ const message = JSON.parse(data);
655
+ if (devMode) {
656
+ console.log("[GlomoPay RN SDK] EducationCarouselWebView Message received:", message.type, message);
657
+ }
658
+
659
+ if (message.type === "console") {
660
+ if (devMode) {
661
+ console.log(`[GlomoPay RN SDK] [EducationCarousel] ${message.message}`);
662
+ }
663
+ return;
664
+ }
665
+
666
+ if (message.type === "lrs.has_education_steps") {
667
+ const nextState: EducationCarouselState = "hasContent";
668
+
669
+ if (devMode) {
670
+ console.log(
671
+ `[GlomoPay RN SDK] Education carousel signal received: value=${String(message.value)} source=${message.source || "unknown"} payload=${JSON.stringify(message.payload || {})}`
672
+ );
673
+ }
674
+
675
+ trackEducationStepsShown(orderId, publicKey, devMode, mockMode, checkoutUrl, message.source);
676
+ setEducationCarouselState(nextState);
677
+ return;
678
+ }
679
+ } catch (error) {
680
+ if (devMode) {
681
+ console.error("[GlomoPay RN SDK] Error parsing EducationCarouselWebView message:", error);
682
+ }
683
+ }
684
+ },
685
+ [checkoutUrl, devMode, mockMode, orderId, publicKey]
686
+ );
687
+
555
688
  // Handler for the navigation state changes
556
689
  const handleNavigationStateChange = useCallback(
557
690
  (navState: WebViewNavigation) => {
@@ -571,6 +704,7 @@ export function useLrsCheckout(
571
704
  if (showFlowWebView) {
572
705
  setShowFlowWebView(false);
573
706
  setFlowWebViewUrl("");
707
+ setEducationCarouselState("pending");
574
708
  }
575
709
  setShowCheckout(false);
576
710
  if (status === "payment_in_progress") {
@@ -592,7 +726,18 @@ export function useLrsCheckout(
592
726
  console.error("[GlomoPay RN SDK] Error in handleModalBackButton:", error);
593
727
  }
594
728
  }
595
- }, [showFlowWebView, devMode, onPaymentTerminate, status, orderId, mockMode, checkoutUrl]);
729
+ }, [showFlowWebView, devMode, onPaymentTerminate, status, orderId, publicKey, mockMode, checkoutUrl]);
730
+
731
+ /** Closes the flow WebView and returns to the main checkout */
732
+ const handleFlowBack = useCallback(() => {
733
+ if (devMode) {
734
+ console.log("[GlomoPay RN SDK] Flow WebView back button pressed");
735
+ }
736
+ setShowFlowWebView(false);
737
+ setFlowWebViewUrl("");
738
+ setEducationCarouselState("pending");
739
+ trackWindowClose("flow", orderId, publicKey, devMode, mockMode, checkoutUrl);
740
+ }, [devMode, orderId, publicKey, mockMode, checkoutUrl]);
596
741
 
597
742
  /**
598
743
  * Initiator method for the LRS Checkout flow.
@@ -779,6 +924,7 @@ export function useLrsCheckout(
779
924
 
780
925
  setShowFlowWebView(false);
781
926
  setFlowWebViewUrl("");
927
+ setEducationCarouselState("pending");
782
928
  setStatus("payment_in_progress");
783
929
 
784
930
  // Tracking successful start
@@ -796,7 +942,7 @@ export function useLrsCheckout(
796
942
  // Resetting checkout flow if orderId or publicKey are changed
797
943
  useEffect(() => {
798
944
  if (orderId && publicKey) {
799
- // Only resetting the checkout flow if the current status is that ofa terminal state
945
+ // Only resetting the checkout flow if the current status is that of a terminal state
800
946
  if (status === "payment_successful" || status === "payment_failed" || status === "payment_cancelled") {
801
947
  if (devMode) {
802
948
  console.log(
@@ -816,6 +962,7 @@ export function useLrsCheckout(
816
962
  setShowCheckout(false);
817
963
  setShowFlowWebView(false);
818
964
  setFlowWebViewUrl("");
965
+ setEducationCarouselState("pending");
819
966
  };
820
967
  }, []);
821
968
 
@@ -825,16 +972,22 @@ export function useLrsCheckout(
825
972
  showCheckout,
826
973
  flowWebViewUrl,
827
974
  showFlowWebView,
975
+ educationCarouselUrl,
976
+ educationCarouselState,
828
977
  checkoutUrl,
829
978
  mainWebViewRef,
830
979
  flowWebViewRef,
980
+ educationCarouselWebViewRef,
831
981
  handleMainWebViewMessage,
832
982
  handleFlowWebViewMessage,
983
+ handleEducationCarouselWebViewMessage,
833
984
  handleError,
834
985
  handleHttpError,
835
986
  handleNavigationStateChange,
836
987
  handleModalBackButton,
988
+ handleFlowBack,
837
989
  injectedMain: InjectionScripts.main,
838
990
  injectedFlow: InjectionScripts.flow,
991
+ injectedEducationCarousel: InjectionScripts.educationCarousel,
839
992
  };
840
993
  }
@@ -502,6 +502,35 @@ export function trackConnectionError(
502
502
  });
503
503
  }
504
504
 
505
+ /**
506
+ * Tracks when the education carousel reports that it has visible education steps
507
+ *
508
+ * @param orderId - The order ID
509
+ * @param publicKey - The public key of the merchant
510
+ * @param devMode - Whether dev mode is enabled
511
+ * @param mockMode - Whether mock mode is enabled
512
+ * @param lrsUrl - The LRS checkout URL
513
+ * @param source - Where the carousel event originated
514
+ */
515
+ export function trackEducationStepsShown(
516
+ orderId: string,
517
+ publicKey: string,
518
+ devMode: boolean = false,
519
+ mockMode: boolean = false,
520
+ lrsUrl?: string,
521
+ source?: string
522
+ ): void {
523
+ const properties = {
524
+ ...buildCommonProperties(orderId, publicKey, devMode, mockMode, lrsUrl),
525
+ ...(source && { source }),
526
+ };
527
+ trackEvent("LRS Has Education Steps", properties, devMode).catch((error) => {
528
+ if (devMode) {
529
+ console.error(`[GlomoPay RN SDK] Event tracking error on education steps shown event:`, error);
530
+ }
531
+ });
532
+ }
533
+
505
534
  /**
506
535
  * Tracks when onSdkError callback is invoked
507
536
  *