@finspringinnovations/fdsdk 0.0.1 → 0.0.3

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 (53) hide show
  1. package/lib/components/CompanyHeader.js +40 -36
  2. package/lib/components/InterestRateCard.js +3 -2
  3. package/lib/components/PaymentDetailsCard.js +8 -7
  4. package/lib/components/PendingFDBottomSheet.js +2 -2
  5. package/lib/components/TextFieldWithLabel.js +1 -1
  6. package/lib/components/TrustBox.js +2 -2
  7. package/lib/config/appDataConfig.d.ts +53 -2
  8. package/lib/config/appDataConfig.js +39 -6
  9. package/lib/hooks/usePaymentSSE.d.ts +24 -0
  10. package/lib/hooks/usePaymentSSE.js +135 -0
  11. package/lib/hooks/usePaymentStatusTimer.d.ts +25 -0
  12. package/lib/hooks/usePaymentStatusTimer.js +122 -0
  13. package/lib/index.d.ts +2 -2
  14. package/lib/index.js +5 -2
  15. package/lib/navigation/RootNavigator.js +6 -1
  16. package/lib/navigation/index.d.ts +2 -0
  17. package/lib/navigation/index.js +13 -11
  18. package/lib/screens/FDCalculator.d.ts +1 -0
  19. package/lib/screens/FDCalculator.js +64 -48
  20. package/lib/screens/FDList.js +10 -12
  21. package/lib/screens/NomineeDetail.js +21 -15
  22. package/lib/screens/PayNow.js +6 -6
  23. package/lib/screens/Payment.d.ts +1 -0
  24. package/lib/screens/Payment.js +34 -13
  25. package/lib/screens/PaymentStatus.js +33 -42
  26. package/lib/theme/ThemeContext.d.ts +2 -0
  27. package/lib/theme/ThemeContext.js +4 -2
  28. package/lib/theme/index.d.ts +6 -1
  29. package/lib/theme/index.js +24 -8
  30. package/lib/utils/sseParser.d.ts +7 -0
  31. package/lib/utils/sseParser.js +27 -0
  32. package/package.json +2 -2
  33. package/src/components/CompanyHeader.tsx +50 -44
  34. package/src/components/InterestRateCard.tsx +3 -2
  35. package/src/components/PaymentDetailsCard.tsx +45 -40
  36. package/src/components/PendingFDBottomSheet.tsx +2 -2
  37. package/src/components/TextFieldWithLabel.tsx +1 -1
  38. package/src/components/TrustBox.tsx +2 -2
  39. package/src/config/appDataConfig.ts +70 -5
  40. package/src/hooks/usePaymentSSE.ts +155 -0
  41. package/src/hooks/usePaymentStatusTimer.ts +169 -0
  42. package/src/index.tsx +4 -1
  43. package/src/navigation/RootNavigator.tsx +7 -0
  44. package/src/navigation/index.tsx +16 -17
  45. package/src/screens/FDCalculator.tsx +64 -40
  46. package/src/screens/FDList.tsx +11 -11
  47. package/src/screens/NomineeDetail.tsx +20 -14
  48. package/src/screens/PayNow.tsx +7 -7
  49. package/src/screens/Payment.tsx +45 -14
  50. package/src/screens/PaymentStatus.tsx +44 -57
  51. package/src/theme/ThemeContext.tsx +6 -1
  52. package/src/theme/index.ts +30 -8
  53. package/src/utils/sseParser.ts +40 -0
@@ -76,45 +76,50 @@ const PaymentDetailsCard: React.FC<PaymentDetailsCardProps> = ({
76
76
  );
77
77
  };
78
78
 
79
- const createStyles = (colors: any, typography: any, themeName: string) => StyleSheet.create({
80
- card: {
81
- backgroundColor: themeName === 'dark' ? '#0B2940' : '#0B2940',
82
- borderRadius: 16,
83
- padding: 20,
84
- alignItems: 'flex-start',
85
- marginHorizontal: 16,
86
- borderWidth: 1,
87
- borderColor: '#1F4C73',
88
- },
89
- companyName: {
90
- ...typography.styles.h3,
91
- color: '#FFFFFF',
92
- marginBottom: 12,
93
- },
94
- companyLine: {
95
- height: 1,
96
- backgroundColor: '#1F4C73',
97
- marginBottom: 12,
98
- width: '100%',
99
- },
100
- detailsContainer: {
101
- width: '100%',
102
- },
103
- detailRow: {
104
- flexDirection: 'row',
105
- justifyContent: 'space-between',
106
- alignItems: 'center',
107
- paddingVertical: 8,
108
- },
109
- detailLabel: {
110
- ...typography.styles.text12Regular,
111
- color: '#BFD5F6',
112
- },
113
- detailValue: {
114
- ...typography.styles.text14Medium,
115
- color: '#FFFFFF',
116
- lineHeight: (typography.styles.text14Medium?.lineHeight ?? typography.styles.text14Medium?.fontSize ?? 14) + 3,
117
- },
118
- });
79
+ const createStyles = (colors: any, typography: any, themeName: string) => {
80
+ const isDark = themeName === 'dark';
81
+
82
+ return StyleSheet.create({
83
+ card: {
84
+ backgroundColor: isDark ? colors.headerBg : 'rgba(0,235,180,0.1)',
85
+ borderRadius: 16,
86
+ padding: 20,
87
+ alignItems: 'flex-start',
88
+ marginHorizontal: 16,
89
+ borderWidth: isDark ? 1 : 0,
90
+ borderColor: isDark ? colors.headerBg + 'AA' : 'transparent',
91
+ },
92
+ companyName: {
93
+ ...typography.styles.h3,
94
+ color: isDark ? colors.headerText : colors.text,
95
+ marginBottom: 12,
96
+ },
97
+ companyLine: {
98
+ height: 1,
99
+ backgroundColor: isDark ? colors.headerText + '33' : colors.border + '40',
100
+ marginBottom: 12,
101
+ width: '100%',
102
+ },
103
+ detailsContainer: {
104
+ width: '100%',
105
+ },
106
+ detailRow: {
107
+ flexDirection: 'row',
108
+ justifyContent: 'space-between',
109
+ alignItems: 'center',
110
+ paddingVertical: 8,
111
+ },
112
+ detailLabel: {
113
+ ...typography.styles.text12Regular,
114
+ color: isDark ? colors.headerText : colors.text,
115
+ opacity: isDark ? 0.7 : 1,
116
+ },
117
+ detailValue: {
118
+ ...typography.styles.text14Medium,
119
+ color: isDark ? colors.headerText : colors.text,
120
+ lineHeight: (typography.styles.text14Medium?.lineHeight ?? typography.styles.text14Medium?.fontSize ?? 14) + 3,
121
+ },
122
+ });
123
+ };
119
124
 
120
125
  export default PaymentDetailsCard;
@@ -218,7 +218,7 @@ const createStyles = (colors: ColorScheme, typography: any, spacing: any, themeN
218
218
  },
219
219
  continueButton: {
220
220
  height: 50,
221
- backgroundColor: '#007AFF',
221
+ backgroundColor: (colors as any).buttonBackground || (colors as any).headerBg || '#007AFF',
222
222
  borderRadius: 25,
223
223
  paddingHorizontal: spacing.lg,
224
224
  alignItems: 'center',
@@ -227,7 +227,7 @@ const createStyles = (colors: ColorScheme, typography: any, spacing: any, themeN
227
227
  },
228
228
  continueButtonText: {
229
229
  ...typography.styles.button,
230
- color: 'white',
230
+ color: (colors as any).buttonTextColor || (colors as any).headerText || '#FFFFFF',
231
231
  fontWeight: '600',
232
232
  },
233
233
  });
@@ -409,7 +409,7 @@ const createStyles = (colors: any, typography: any, themeName: string) => StyleS
409
409
  height: '100%',
410
410
  },
411
411
  disabledTextInput: {
412
- color: '#000000',
412
+ color: colors.text,
413
413
  },
414
414
  leftIcon: {
415
415
  marginRight: 12,
@@ -50,12 +50,12 @@ const createStyles = (colors: any, typography: any, themeName: string) => StyleS
50
50
  },
51
51
  titleLight: {
52
52
  ...typography.styles.bodyMedium,
53
- color: themeName === 'dark' ? '#C4CDD3' : colors.textLight,
53
+ color: colors.textLight,
54
54
  marginBottom: 0,
55
55
  },
56
56
  title: {
57
57
  ...typography.styles.bodyMedium,
58
- color: themeName === 'dark' ? colors.labelColor : colors.textLight,
58
+ color: colors.text,
59
59
  marginBottom: 0,
60
60
  },
61
61
  });
@@ -8,6 +8,36 @@ export interface EnvironmentData {
8
8
  enableLogging?: boolean;
9
9
  }
10
10
 
11
+ /**
12
+ * Custom color overrides that can be passed during SDK initialization.
13
+ * Any color defined here will override the corresponding color from the selected theme.
14
+ */
15
+ export interface CustomColors {
16
+ primary?: string;
17
+ tabSelected?: string;
18
+ headerBg?: string;
19
+ headerText?: string;
20
+ success?: string;
21
+ textSecondary?: string;
22
+ border?: string;
23
+ shadow?: string;
24
+ background?: string;
25
+ surface?: string;
26
+ text?: string;
27
+ textLight?: string;
28
+ error?: string;
29
+ warning?: string;
30
+ info?: string;
31
+ muted?: string;
32
+ inputBackground?: string;
33
+ inputBorder?: string;
34
+ placeholderColor?: string;
35
+ labelColor?: string;
36
+ buttonBackground?: string;
37
+ buttonTextColor?: string;
38
+ cancelButtonBg?: string;
39
+ }
40
+
11
41
  export interface AppData {
12
42
  // Required user identification fields
13
43
  id: string; // Required: User ID from main app
@@ -16,8 +46,7 @@ export interface AppData {
16
46
  gender: string; // Required: Male, Female, Other
17
47
  mobNo: string; // Required: Mobile number (10 digits)
18
48
  email: string; // Required: Email address
19
- panNumber: string; // Required: PAN number from main app
20
-
49
+ panNumber?: string; // <-- change from required to optional
21
50
  // Optional fields - can be collected in SDK if not provided
22
51
  address?: string;
23
52
  area?: string;
@@ -32,6 +61,7 @@ export interface AppData {
32
61
  maritalStatus?: string; // Optional: Single, Married, Divorced, Widowed
33
62
  userReferenceId?: string; // Optional: User reference ID from main app
34
63
  eventNotifyUrl?: string; // Optional webhook/callback URL for SDK events
64
+ startFDAlertMessage?: string; // ADD THIS LINE
35
65
  }
36
66
 
37
67
  // Global app data storage
@@ -40,6 +70,9 @@ let globalAppData: AppData | null = null;
40
70
  // Global environment data storage
41
71
  let globalEnvironmentData: EnvironmentData | null = null;
42
72
 
73
+ // Global custom color overrides
74
+ let globalCustomColors: CustomColors | null = null;
75
+
43
76
  /**
44
77
  * Initialize the SDK with app data from the main application
45
78
  * This should be called when the SDK starts
@@ -53,7 +86,7 @@ export const initializeSDK = (appData: AppData, onValidationError?: (errors: str
53
86
  { field: 'gender', label: 'Gender' },
54
87
  { field: 'mobNo', label: 'Mobile Number' },
55
88
  { field: 'email', label: 'Email' },
56
- { field: 'panNumber', label: 'PAN Number' },
89
+ // { field: 'panNumber', label: 'PAN Number' },
57
90
  ];
58
91
 
59
92
  const missingFields: string[] = [];
@@ -227,14 +260,15 @@ export const validateAppData = (appData: AppData): { isValid: boolean; errors: s
227
260
  if (!appData.gender) errors.push('Gender is required');
228
261
  if (!appData.mobNo?.trim()) errors.push('Mobile number is required');
229
262
  if (!appData.email?.trim()) errors.push('Email is required');
230
- if (!appData.panNumber?.trim()) errors.push('PAN number is required');
263
+ // PAN is optional — validated later on FD Calculator screen
264
+ // if (!appData.panNumber?.trim()) errors.push('PAN number is required');
231
265
 
232
266
  // Format validation (only if fields are provided)
233
267
  if (appData.dob && !/^\d{4}-\d{2}-\d{2}$/.test(appData.dob)) {
234
268
  errors.push('Date of birth must be in YYYY-MM-DD format');
235
269
  }
236
270
 
237
- if (appData.panNumber && !/^[A-Z]{5}[0-9]{4}[A-Z]{1}$/.test(appData.panNumber)) {
271
+ if (appData.panNumber && appData.panNumber.trim() && !/^[A-Z]{5}[0-9]{4}[A-Z]{1}$/.test(appData.panNumber)) {
238
272
  errors.push('PAN number must be in valid format (e.g., ABCDE1234F)');
239
273
  }
240
274
 
@@ -256,6 +290,36 @@ export const validateAppData = (appData: AppData): { isValid: boolean; errors: s
256
290
  };
257
291
  };
258
292
 
293
+ /**
294
+ * Set custom color overrides for the SDK theme.
295
+ * Colors defined here will override the corresponding color from the selected theme (primary/dark/corporate).
296
+ * Call this before rendering the SDK navigator.
297
+ *
298
+ * @example
299
+ * setSDKColors({
300
+ * primary: '#FF5722',
301
+ * headerBg: '#1976D2',
302
+ * buttonBackground: '#1976D2',
303
+ * });
304
+ */
305
+ export const setSDKColors = (customColors: CustomColors): void => {
306
+ globalCustomColors = customColors;
307
+ };
308
+
309
+ /**
310
+ * Get the current custom color overrides
311
+ */
312
+ export const getSDKColors = (): CustomColors | null => {
313
+ return globalCustomColors;
314
+ };
315
+
316
+ /**
317
+ * Clear custom color overrides
318
+ */
319
+ export const clearSDKColors = (): void => {
320
+ globalCustomColors = null;
321
+ };
322
+
259
323
  /**
260
324
  * Clear app data (for testing or logout scenarios)
261
325
  */
@@ -276,4 +340,5 @@ export const clearEnvironmentData = (): void => {
276
340
  export const clearAllData = (): void => {
277
341
  globalAppData = null;
278
342
  globalEnvironmentData = null;
343
+ globalCustomColors = null;
279
344
  };
@@ -0,0 +1,155 @@
1
+ /**
2
+ * usePaymentSSE - React Native SSE hook for payment status tracking.
3
+ *
4
+ * Uses XMLHttpRequest instead of fetch streams because
5
+ * React Native does not support ReadableStream / getReader().
6
+ *
7
+ * Usage:
8
+ * const { start, stop } = usePaymentSSE();
9
+ * start(applicationId, {
10
+ * onPaymentStatus: (status, payload) => { ... },
11
+ * onError: (error) => { ... },
12
+ * });
13
+ * stop(); // to cancel
14
+ */
15
+
16
+ import { useCallback, useRef } from 'react';
17
+ import { parseSSEBuffer, type SSEEvent } from '../utils/sseParser';
18
+ import { getEnvironmentData } from '../config/appDataConfig';
19
+ import { getApiConfig } from '../config/apiConfig';
20
+
21
+ // -- Types --
22
+
23
+ interface UsePaymentSSECallbacks {
24
+ onPaymentStatus: (status: string, payload: Record<string, unknown>) => void;
25
+ onError?: (error: any) => void;
26
+ onConnected?: () => void;
27
+ }
28
+
29
+ // -- Hook --
30
+
31
+ export function usePaymentSSE() {
32
+ // Store the XMLHttpRequest so we can abort it later
33
+ const xhrRef = useRef<XMLHttpRequest | null>(null);
34
+ // Track how much of responseText we've already parsed
35
+ const lastIndexRef = useRef<number>(0);
36
+ // SSE text buffer (persists across onprogress calls)
37
+ const bufferRef = useRef<{ value: string }>({ value: '' });
38
+
39
+ const start = useCallback(
40
+ (applicationId: string, callbacks: UsePaymentSSECallbacks) => {
41
+ // Clean up any existing connection first
42
+ if (xhrRef.current) {
43
+ xhrRef.current.abort();
44
+ xhrRef.current = null;
45
+ }
46
+
47
+ // Reset parsing state
48
+ lastIndexRef.current = 0;
49
+ bufferRef.current = { value: '' };
50
+
51
+ // 1. Build the SSE endpoint URL
52
+ const envData = getEnvironmentData();
53
+ const apiConfig = getApiConfig();
54
+ const baseUrl = envData?.apiBaseUrl || apiConfig?.baseUrl || '';
55
+ const url = `${baseUrl}/events?applicationId=${encodeURIComponent(applicationId)}`;
56
+
57
+ // 2. Create XMLHttpRequest
58
+ const xhr = new XMLHttpRequest();
59
+ xhrRef.current = xhr;
60
+
61
+ xhr.open('GET', url);
62
+
63
+ // 3. Set headers (same headers your baseApi interceptor uses)
64
+ const apiKey = envData?.apiKey || apiConfig?.headers?.['X-API-Key'] || '';
65
+ if (apiKey) {
66
+ xhr.setRequestHeader('x-api-key', apiKey);
67
+ }
68
+ xhr.setRequestHeader('Accept', 'text/event-stream');
69
+ xhr.setRequestHeader('Cache-Control', 'no-cache');
70
+
71
+ // 4. Handle incoming data
72
+ xhr.onprogress = () => {
73
+ // responseText contains ALL text received so far
74
+ // Only parse the NEW text since last call
75
+ const newText = xhr.responseText.slice(lastIndexRef.current);
76
+ lastIndexRef.current = xhr.responseText.length;
77
+
78
+ if (!newText) return;
79
+
80
+ // Parse new text into SSE events
81
+ const events = parseSSEBuffer(bufferRef.current, newText);
82
+
83
+ // Handle each event
84
+ for (const { eventType, data } of events) {
85
+ if (eventType === 'fd_payment_status') {
86
+ try {
87
+ const parsed = JSON.parse(data);
88
+ const status = (parsed.paymentStatus ?? '').toUpperCase();
89
+ callbacks.onPaymentStatus(status, parsed);
90
+ } catch (parseError) {
91
+ if (__DEV__) {
92
+ console.error('[usePaymentSSE] Failed to parse event data:', parseError);
93
+ }
94
+ }
95
+ }
96
+ }
97
+ };
98
+
99
+ // 5. Handle successful connection
100
+ xhr.onreadystatechange = () => {
101
+ if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
102
+ if (xhr.status === 200) {
103
+ callbacks.onConnected?.();
104
+ if (__DEV__) {
105
+ console.log('[usePaymentSSE] Connected to SSE stream');
106
+ }
107
+ } else {
108
+ if (__DEV__) {
109
+ console.error('[usePaymentSSE] Bad status:', xhr.status);
110
+ }
111
+ callbacks.onError?.(new Error(`SSE response status: ${xhr.status}`));
112
+ }
113
+ }
114
+ };
115
+
116
+ // 6. Handle errors
117
+ xhr.onerror = () => {
118
+ if (__DEV__) {
119
+ console.error('[usePaymentSSE] Connection error');
120
+ }
121
+ callbacks.onError?.(new Error('SSE connection failed'));
122
+ };
123
+
124
+ // 7. Handle timeout
125
+ xhr.ontimeout = () => {
126
+ if (__DEV__) {
127
+ console.error('[usePaymentSSE] Connection timed out');
128
+ }
129
+ callbacks.onError?.(new Error('SSE connection timed out'));
130
+ };
131
+
132
+ // 8. Send the request (connection stays open)
133
+ xhr.send();
134
+
135
+ if (__DEV__) {
136
+ console.log('[usePaymentSSE] Starting SSE connection to:', url);
137
+ }
138
+ },
139
+ []
140
+ );
141
+
142
+ const stop = useCallback(() => {
143
+ if (xhrRef.current) {
144
+ xhrRef.current.abort();
145
+ xhrRef.current = null;
146
+ if (__DEV__) {
147
+ console.log('[usePaymentSSE] SSE connection stopped');
148
+ }
149
+ }
150
+ lastIndexRef.current = 0;
151
+ bufferRef.current = { value: '' };
152
+ }, []);
153
+
154
+ return { start, stop };
155
+ }
@@ -0,0 +1,169 @@
1
+ import { useCallback, useEffect, useMemo, useRef } from 'react';
2
+ import { usePaymentReverseFeedMutation } from '../api/fdApi';
3
+ import { useAppSelector } from '../store';
4
+ import { getUserInfoForAPI } from '../config/appDataConfig';
5
+ import { getPaymentSession } from '../state/paymentSession';
6
+
7
+ export type PaymentStatus = 'success' | 'failed' | 'pending';
8
+
9
+ interface ReverseFeedOverrides {
10
+ providerId?: string;
11
+ workflowInstanceId?: string;
12
+ userreferenceid?: string;
13
+ applicationid?: string;
14
+ entityid?: string;
15
+ transactionId?: string;
16
+ }
17
+
18
+ interface UsePaymentStatusTimerConfig {
19
+ transactionId?: string;
20
+ overrides?: ReverseFeedOverrides;
21
+ initialDelayMs?: number; // default 60_000
22
+ repeatDelayMs?: number; // default 15_000
23
+ maxDurationMs?: number; // default 15 * 60_000
24
+ onStatusUpdate?: (status: PaymentStatus, response?: any) => void;
25
+ }
26
+
27
+ export const usePaymentStatusTimer = ({
28
+ transactionId,
29
+ overrides,
30
+ initialDelayMs = 60_000,
31
+ repeatDelayMs = 15_000,
32
+ maxDurationMs = 15 * 60_000,
33
+ onStatusUpdate,
34
+ }: UsePaymentStatusTimerConfig) => {
35
+ const workflowInstanceId = useAppSelector((state: any) => state?.onboarding?.workflowInstanceId);
36
+ const applicationId = useAppSelector((state: any) => state?.onboarding?.applicationId);
37
+ const entityId = useAppSelector((state: any) => state?.onboarding?.entityid);
38
+ const providerId = useAppSelector((state: any) => state?.onboarding?.providerId);
39
+
40
+ const { transactionId: sessionTransactionId } = getPaymentSession();
41
+ const userInfo = getUserInfoForAPI();
42
+
43
+ const resolvedTransactionId = overrides?.transactionId || transactionId || sessionTransactionId || '';
44
+
45
+ const requestPayload = useMemo(() => ({
46
+ providerId: overrides?.providerId || providerId,
47
+ workflowInstanceId: overrides?.workflowInstanceId || workflowInstanceId,
48
+ userreferenceid: overrides?.userreferenceid || userInfo?.id,
49
+ applicationid: overrides?.applicationid || applicationId,
50
+ entityid: overrides?.entityid || entityId,
51
+ transactionId: resolvedTransactionId,
52
+ }), [
53
+ applicationId,
54
+ entityId,
55
+ overrides?.applicationid,
56
+ overrides?.entityid,
57
+ overrides?.providerId,
58
+ overrides?.transactionId,
59
+ overrides?.userreferenceid,
60
+ overrides?.workflowInstanceId,
61
+ providerId,
62
+ resolvedTransactionId,
63
+ userInfo?.id,
64
+ workflowInstanceId,
65
+ ]);
66
+
67
+ const [paymentReverseFeed, { isLoading: isCheckingStatus }] = usePaymentReverseFeedMutation();
68
+
69
+ const timeoutRef = useRef<NodeJS.Timeout | null>(null);
70
+ const startTimestampRef = useRef(0);
71
+ const isRunningRef = useRef(false);
72
+ const latestStatusRef = useRef<PaymentStatus>('pending');
73
+
74
+ const clearTimer = useCallback(() => {
75
+ if (timeoutRef.current) {
76
+ clearTimeout(timeoutRef.current);
77
+ timeoutRef.current = null;
78
+ }
79
+ }, []);
80
+
81
+ const stopTimer = useCallback(() => {
82
+ isRunningRef.current = false;
83
+ clearTimer();
84
+ }, [clearTimer]);
85
+
86
+ const triggerStatusCheck = useCallback(async () => {
87
+ if (!requestPayload.transactionId) {
88
+ return latestStatusRef.current;
89
+ }
90
+
91
+ try {
92
+ const response = await paymentReverseFeed(requestPayload).unwrap();
93
+
94
+ const status = (response?.data?.paymentStatus || '').toUpperCase();
95
+
96
+ if (status === 'SUCCESS') {
97
+ latestStatusRef.current = 'success';
98
+ onStatusUpdate?.('success', response);
99
+ return 'success';
100
+ }
101
+
102
+ if (status === 'FAILED') {
103
+ latestStatusRef.current = 'failed';
104
+ onStatusUpdate?.('failed', response);
105
+ return 'failed';
106
+ }
107
+
108
+ // IN_PROGRESS OR ANY OTHER STATUS → do nothing
109
+ latestStatusRef.current = 'pending';
110
+ return 'pending';
111
+
112
+ } catch (error) {
113
+ // // ANY error → Pending
114
+ // latestStatusRef.current = 'pending';
115
+
116
+ // onStatusUpdate?.('pending', {
117
+ // status: 'pending',
118
+ // message: 'Reverse feed API failed or backend unreachable',
119
+ // error
120
+ // });
121
+
122
+ // return 'pending';
123
+ }
124
+ }, [onStatusUpdate, paymentReverseFeed, requestPayload]);
125
+
126
+
127
+ const scheduleNext = useCallback((delay: number) => {
128
+ clearTimer();
129
+ timeoutRef.current = setTimeout(async () => {
130
+ if (!isRunningRef.current) {
131
+ clearTimer();
132
+ return;
133
+ }
134
+
135
+ if (Date.now() - startTimestampRef.current >= maxDurationMs) {
136
+ stopTimer();
137
+ return;
138
+ }
139
+
140
+ const status = await triggerStatusCheck();
141
+
142
+ if (status === 'success' || status === 'failed') {
143
+ stopTimer();
144
+ return;
145
+ }
146
+
147
+ scheduleNext(repeatDelayMs);
148
+ }, delay);
149
+ }, [clearTimer, maxDurationMs, repeatDelayMs, stopTimer, triggerStatusCheck]);
150
+
151
+ const startTimer = useCallback(() => {
152
+ if (isRunningRef.current) {
153
+ return;
154
+ }
155
+ isRunningRef.current = true;
156
+ startTimestampRef.current = Date.now();
157
+ scheduleNext(initialDelayMs);
158
+ }, [initialDelayMs, scheduleNext]);
159
+
160
+ useEffect(() => stopTimer, [stopTimer]);
161
+
162
+ return {
163
+ startTimer,
164
+ stopTimer,
165
+ triggerStatusCheck,
166
+ isCheckingStatus,
167
+ latestStatusRef,
168
+ };
169
+ };
package/src/index.tsx CHANGED
@@ -92,8 +92,11 @@ export {
92
92
  isEnvironmentInitialized,
93
93
  clearEnvironmentData,
94
94
  clearAllData,
95
+ setSDKColors,
96
+ getSDKColors,
97
+ clearSDKColors,
95
98
  } from './config/appDataConfig';
96
- export type { AppData, EnvironmentData } from './config/appDataConfig';
99
+ export type { AppData, EnvironmentData, CustomColors } from './config/appDataConfig';
97
100
 
98
101
  // Export validation utilities
99
102
  export { showValidationErrorAlert, validateAndProceed } from './components/ValidationErrorAlert';
@@ -150,6 +150,7 @@ const RootNavigator: React.FC<RootNavigatorProps> = ({
150
150
  {(props) => (
151
151
  <FDCalculatorScreen
152
152
  onGoBack={() => goBack()}
153
+ onExitSDK={() => onExit?.()}
153
154
  onNavigateToReviewKYC={() => navigate('ReviewKYC', { fdData: props.route.params?.fdData })}
154
155
  fdData={(props.route.params as any)?.fdData}
155
156
  {...props}
@@ -319,6 +320,12 @@ const RootNavigator: React.FC<RootNavigatorProps> = ({
319
320
  paymentData: error
320
321
  } as any);
321
322
  }}
323
+ onPaymentPending={(info) => {
324
+ navigate('PaymentStatus', {
325
+ status: 'pending',
326
+ paymentData: info
327
+ } as any);
328
+ }}
322
329
  {...props}
323
330
  />
324
331
  )}