@glomopay/react-native-sdk 1.3.5 → 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 +137 -11
  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 +13 -1
  20. package/lib/use-lrs-checkout.d.ts.map +1 -1
  21. package/lib/use-lrs-checkout.js +114 -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 +186 -12
  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 +149 -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,11 +77,15 @@ 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;
@@ -83,6 +95,7 @@ export interface UseLrsCheckoutReturn {
83
95
  handleFlowBack: () => void;
84
96
  injectedMain?: string;
85
97
  injectedFlow?: string;
98
+ injectedEducationCarousel?: string;
86
99
  }
87
100
 
88
101
  /** The main SDK hook for the GlomoPay LRS Checkout flow */
@@ -103,9 +116,11 @@ export function useLrsCheckout(
103
116
  // Refs for the WebViews
104
117
  const mainWebViewRef = useRef<WebView>(null);
105
118
  const flowWebViewRef = useRef<WebView>(null);
119
+ const educationCarouselWebViewRef = useRef<WebView>(null);
106
120
 
107
- // Using the server prop if provided, otherwise using the default production URL
108
- 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;
109
124
 
110
125
  /**
111
126
  * The main checkout URL for the LRS flow
@@ -169,6 +184,58 @@ export function useLrsCheckout(
169
184
  }
170
185
  }, [baseCheckoutUrl, orderId, publicKey, mockMode, devMode, onSdkError]);
171
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
+
172
239
  // Checkout status state
173
240
  const [status, setStatus] = useState<LrsCheckoutStatus>("ready");
174
241
 
@@ -177,6 +244,15 @@ export function useLrsCheckout(
177
244
 
178
245
  const [showFlowWebView, setShowFlowWebView] = useState(false);
179
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]);
180
256
 
181
257
  // Handler for the WebView errors
182
258
  const handleError = useCallback(
@@ -214,6 +290,7 @@ export function useLrsCheckout(
214
290
  setShowCheckout(false);
215
291
  setShowFlowWebView(false);
216
292
  setFlowWebViewUrl("");
293
+ setEducationCarouselState("pending");
217
294
 
218
295
  // Calling onConnectionError callback if provided
219
296
  safeCallback(onConnectionError, [nativeEvent], "onConnectionError", devMode);
@@ -230,7 +307,7 @@ export function useLrsCheckout(
230
307
  }
231
308
  }
232
309
  },
233
- [devMode, onConnectionError, checkoutUrl]
310
+ [devMode, onConnectionError, checkoutUrl, orderId, publicKey, mockMode]
234
311
  );
235
312
 
236
313
  // Handler for the WebView HTTP errors
@@ -294,6 +371,7 @@ export function useLrsCheckout(
294
371
  }
295
372
  setFlowWebViewUrl(url);
296
373
  setShowFlowWebView(true);
374
+ setEducationCarouselState("pending");
297
375
  // Tracking window.open interception
298
376
  trackWindowOpen("main", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
299
377
  } else {
@@ -314,6 +392,7 @@ export function useLrsCheckout(
314
392
  }
315
393
  setShowFlowWebView(false);
316
394
  setFlowWebViewUrl("");
395
+ setEducationCarouselState("pending");
317
396
  // Tracking window.close interception
318
397
  trackWindowClose("main", orderId, publicKey, devMode, mockMode, checkoutUrl);
319
398
  return;
@@ -335,6 +414,7 @@ export function useLrsCheckout(
335
414
  setShowCheckout(false);
336
415
  setShowFlowWebView(false);
337
416
  setFlowWebViewUrl("");
417
+ setEducationCarouselState("pending");
338
418
  setStatus("payment_successful");
339
419
  safeCallback(onPaymentSuccess, [payload], "onPaymentSuccess", devMode);
340
420
  // Tracking payment success callback
@@ -365,6 +445,7 @@ export function useLrsCheckout(
365
445
  setShowCheckout(false);
366
446
  setShowFlowWebView(false);
367
447
  setFlowWebViewUrl("");
448
+ setEducationCarouselState("pending");
368
449
  setStatus("payment_failed");
369
450
  safeCallback(onPaymentFailure, [payload], "onPaymentFailure", devMode);
370
451
  // Tracking payment failure callback
@@ -392,6 +473,7 @@ export function useLrsCheckout(
392
473
  setShowCheckout(false);
393
474
  setShowFlowWebView(false);
394
475
  setFlowWebViewUrl("");
476
+ setEducationCarouselState("pending");
395
477
  safeCallback(onPaymentTerminate, [], "onPaymentTerminate", devMode);
396
478
  // Tracking payment terminate callback
397
479
  trackPaymentTerminate(orderId, "checkout_closed", publicKey, devMode, mockMode, checkoutUrl);
@@ -449,6 +531,7 @@ export function useLrsCheckout(
449
531
  console.log(`[GlomoPay RN SDK] window.open called from FlowWebView: ${url}`);
450
532
  }
451
533
  setFlowWebViewUrl(url);
534
+ setEducationCarouselState("pending");
452
535
  // Tracking window.open interception
453
536
  trackWindowOpen("flow", url, orderId, publicKey, devMode, mockMode, checkoutUrl);
454
537
  } else {
@@ -469,6 +552,7 @@ export function useLrsCheckout(
469
552
  }
470
553
  setShowFlowWebView(false);
471
554
  setFlowWebViewUrl("");
555
+ setEducationCarouselState("pending");
472
556
  // Tracking window.close interception
473
557
  trackWindowClose("flow", orderId, publicKey, devMode, mockMode, checkoutUrl);
474
558
  return;
@@ -489,6 +573,7 @@ export function useLrsCheckout(
489
573
  setShowFlowWebView(false);
490
574
  setShowCheckout(false);
491
575
  setFlowWebViewUrl("");
576
+ setEducationCarouselState("pending");
492
577
  setStatus("payment_successful");
493
578
  safeCallback(onPaymentSuccess, [payload], "onPaymentSuccess", devMode);
494
579
  // Tracking payment success callback
@@ -522,6 +607,7 @@ export function useLrsCheckout(
522
607
  setShowFlowWebView(false);
523
608
  setShowCheckout(false);
524
609
  setFlowWebViewUrl("");
610
+ setEducationCarouselState("pending");
525
611
  setStatus("payment_failed");
526
612
  safeCallback(onPaymentFailure, [payload], "onPaymentFailure", devMode);
527
613
  // Tracking payment failure callback
@@ -553,6 +639,52 @@ export function useLrsCheckout(
553
639
  [onPaymentSuccess, onPaymentFailure, devMode, mockMode, orderId, publicKey, flowWebViewUrl, checkoutUrl]
554
640
  );
555
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
+
556
688
  // Handler for the navigation state changes
557
689
  const handleNavigationStateChange = useCallback(
558
690
  (navState: WebViewNavigation) => {
@@ -572,6 +704,7 @@ export function useLrsCheckout(
572
704
  if (showFlowWebView) {
573
705
  setShowFlowWebView(false);
574
706
  setFlowWebViewUrl("");
707
+ setEducationCarouselState("pending");
575
708
  }
576
709
  setShowCheckout(false);
577
710
  if (status === "payment_in_progress") {
@@ -593,7 +726,7 @@ export function useLrsCheckout(
593
726
  console.error("[GlomoPay RN SDK] Error in handleModalBackButton:", error);
594
727
  }
595
728
  }
596
- }, [showFlowWebView, devMode, onPaymentTerminate, status, orderId, mockMode, checkoutUrl]);
729
+ }, [showFlowWebView, devMode, onPaymentTerminate, status, orderId, publicKey, mockMode, checkoutUrl]);
597
730
 
598
731
  /** Closes the flow WebView and returns to the main checkout */
599
732
  const handleFlowBack = useCallback(() => {
@@ -602,6 +735,7 @@ export function useLrsCheckout(
602
735
  }
603
736
  setShowFlowWebView(false);
604
737
  setFlowWebViewUrl("");
738
+ setEducationCarouselState("pending");
605
739
  trackWindowClose("flow", orderId, publicKey, devMode, mockMode, checkoutUrl);
606
740
  }, [devMode, orderId, publicKey, mockMode, checkoutUrl]);
607
741
 
@@ -790,6 +924,7 @@ export function useLrsCheckout(
790
924
 
791
925
  setShowFlowWebView(false);
792
926
  setFlowWebViewUrl("");
927
+ setEducationCarouselState("pending");
793
928
  setStatus("payment_in_progress");
794
929
 
795
930
  // Tracking successful start
@@ -807,7 +942,7 @@ export function useLrsCheckout(
807
942
  // Resetting checkout flow if orderId or publicKey are changed
808
943
  useEffect(() => {
809
944
  if (orderId && publicKey) {
810
- // 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
811
946
  if (status === "payment_successful" || status === "payment_failed" || status === "payment_cancelled") {
812
947
  if (devMode) {
813
948
  console.log(
@@ -827,6 +962,7 @@ export function useLrsCheckout(
827
962
  setShowCheckout(false);
828
963
  setShowFlowWebView(false);
829
964
  setFlowWebViewUrl("");
965
+ setEducationCarouselState("pending");
830
966
  };
831
967
  }, []);
832
968
 
@@ -836,11 +972,15 @@ export function useLrsCheckout(
836
972
  showCheckout,
837
973
  flowWebViewUrl,
838
974
  showFlowWebView,
975
+ educationCarouselUrl,
976
+ educationCarouselState,
839
977
  checkoutUrl,
840
978
  mainWebViewRef,
841
979
  flowWebViewRef,
980
+ educationCarouselWebViewRef,
842
981
  handleMainWebViewMessage,
843
982
  handleFlowWebViewMessage,
983
+ handleEducationCarouselWebViewMessage,
844
984
  handleError,
845
985
  handleHttpError,
846
986
  handleNavigationStateChange,
@@ -848,5 +988,6 @@ export function useLrsCheckout(
848
988
  handleFlowBack,
849
989
  injectedMain: InjectionScripts.main,
850
990
  injectedFlow: InjectionScripts.flow,
991
+ injectedEducationCarousel: InjectionScripts.educationCarousel,
851
992
  };
852
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
  *