@finspringinnovations/fdsdk 0.0.4 → 0.0.6

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.
@@ -20,7 +20,6 @@ import { useGetCustomerApplicationsMutation } from '../api/customerApi';
20
20
  import { useGetMasterDataQuery } from '../api/masterDataApi';
21
21
  import { useMasterData } from '../providers/MasterDataProvider';
22
22
  import { useTerminateWorkflowMutation } from '../api/workflowApi';
23
- import { usePaymentReverseFeedMutation } from '../api/fdApi';
24
23
  import type { ColorScheme, ThemeName } from '../theme';
25
24
  import SafeAreaWrapper from '../components/SafeAreaWrapper';
26
25
  import { getUserInfoForAPI, getAppData } from '../config/appDataConfig';
@@ -99,22 +98,9 @@ const FDList: React.FC<FDListProps> = ({
99
98
  isLoading: isTerminatingWorkflow,
100
99
  }] = useTerminateWorkflowMutation();
101
100
 
102
- // Payment Reverse Feed API
103
- const [paymentReverseFeed, {
104
- data: paymentReverseFeedResponse,
105
- error: paymentReverseFeedError,
106
- isLoading: isLoadingPaymentReverseFeed,
107
- }] = usePaymentReverseFeedMutation();
108
-
109
101
  const styles = createStyles(colors, typography, spacing, themeName);
110
102
  const { setMasterData } = useMasterData();
111
103
 
112
- // Redux selectors for workflow IDs
113
- const workflowInstanceId = useAppSelector((state: any) => state?.onboarding?.workflowInstanceId);
114
- const applicationId = useAppSelector((state: any) => state?.onboarding?.applicationId);
115
- const entityId = useAppSelector((state: any) => state?.onboarding?.entityid);
116
- const providerId = useAppSelector((state: any) => state?.onboarding?.providerId);
117
-
118
104
  // Helper function to check if customer applications is empty
119
105
  const isCustomerApplicationsEmpty = () => {
120
106
  if (!customerApplications) {
@@ -223,7 +209,12 @@ const FDList: React.FC<FDListProps> = ({
223
209
 
224
210
  const { data: masterData, isLoading: isLoadingMaster } = useGetMasterDataQuery(
225
211
  { providerId: inferredProviderId },
226
- { skip: !inferredProviderId }
212
+ {
213
+ skip: !inferredProviderId,
214
+ refetchOnMountOrArgChange: true,
215
+ refetchOnFocus: true,
216
+ refetchOnReconnect: true,
217
+ }
227
218
  );
228
219
 
229
220
  // Only render once all three API calls have completed (success or error)
@@ -491,26 +482,34 @@ const FDList: React.FC<FDListProps> = ({
491
482
 
492
483
  // Persist onboarding identifiers globally for subsequent API calls (like FDCalculator)
493
484
  try {
494
- // Extract workflow parameters from response
485
+ // Extract workflow parameters from response (support array / data[] / applications[])
495
486
  const responseData = customerApplications?.data || customerApplications;
496
- let applicationData = Array.isArray(responseData) ? responseData[0] : responseData;
487
+ let applicationsData: any[] = [];
488
+ if (Array.isArray(responseData)) {
489
+ applicationsData = responseData;
490
+ } else if (responseData?.applications && Array.isArray(responseData.applications)) {
491
+ applicationsData = responseData.applications;
492
+ } else if (Array.isArray(customerApplications?.applications)) {
493
+ applicationsData = customerApplications.applications;
494
+ }
495
+
496
+ let applicationData = applicationsData.length > 0 ? applicationsData[0] : (Array.isArray(responseData) ? responseData[0] : responseData);
497
497
 
498
498
  // Prefer the application with wf_status === 'Active'
499
- if (Array.isArray(responseData)) {
500
- const activeOnly = responseData.find((app: any) => {
499
+ if (applicationsData.length > 0) {
500
+ const activeOnly = applicationsData.find((app: any) => {
501
501
  const status = (app.wf_status || app.status || '').toString();
502
502
  return status === 'Active';
503
503
  });
504
504
  if (activeOnly) applicationData = activeOnly;
505
505
  }
506
506
 
507
- if (Array.isArray(responseData)) {
508
- completeFDData = responseData.find((app: any) => {
509
- const status = (app.wf_status || app.status || '').toString();
510
- return status === 'Completed';
511
- });
512
- setGlobalData({ completeFDData: !!completeFDData });
513
- }
507
+ // Set completed flag robustly for downstream ReviewKYC/Aadhaar flow decisions
508
+ completeFDData = applicationsData.find((app: any) => {
509
+ const status = (app.wf_status || app.status || '').toString().toLowerCase();
510
+ return status === 'completed';
511
+ });
512
+ setGlobalData({ completeFDData: !!completeFDData });
514
513
  if (applicationData) {
515
514
  const ids = {
516
515
  workflowInstanceId: applicationData.workflow_instance_id,
@@ -593,56 +592,18 @@ const FDList: React.FC<FDListProps> = ({
593
592
  // Handle error silently
594
593
  }
595
594
 
596
- // If current state is payment and transactionId is available, call payment reverse feed API
595
+ // If current state is payment and transactionId is available,
596
+ // open PaymentStatus and let SSE (/events) drive status updates.
597
597
  if (currentState === WORKFLOW_STATES.PAYMENT && transactionId) {
598
- try {
599
- // Get user info and required IDs
600
- const userInfo = getUserInfoForAPI();
601
-
602
- // Prepare payment reverse feed request
603
- const paymentReverseFeedRequest = {
604
- // Headers
605
- providerId: providerId,
606
- workflowInstanceId: workflowInstanceId,
607
- userreferenceid: userInfo.id,
608
- applicationid: applicationId,
609
- entityid: entityId,
610
- // Body
611
- transactionId: transactionId,
612
- };
613
-
614
- const response = await paymentReverseFeed(paymentReverseFeedRequest).unwrap();
615
-
616
- // Handle the response based on payment status
617
- const paymentStatus = (response?.data?.paymentStatus || '').toLowerCase();
618
- const statusParam = paymentStatus === 'success' ? 'success' : paymentStatus === 'failed' ? 'failed' : 'pending';
619
-
620
- // Build fdData for display
621
- const fdDataParam = activeFD ? {
622
- companyName: activeFD.name,
623
- amount: Number(activeFD.invested) || 0,
624
- fdRate: `${activeFD.returns}% p.a.`,
625
- tenure: activeFD.maturityDate ? `${activeFD.maturityDate}` : '-',
626
- interestPayout: activeFD.interestPayout || 'Yearly',
627
- } : undefined;
628
-
629
- navigate('PaymentStatus', { status: statusParam, transactionId, fdData: fdDataParam } as any);
630
- return;
631
-
632
- } catch (error) {
633
- // Handle error silently
634
-
635
- // On error, navigate with pending status as fallback
636
- const fdDataParam = activeFD ? {
637
- companyName: activeFD.name,
638
- amount: Number(activeFD.invested) || 0,
639
- fdRate: `${activeFD.returns}% p.a.`,
640
- tenure: activeFD.maturityDate ? `${activeFD.maturityDate}` : '-',
641
- interestPayout: activeFD.interestPayout || 'Yearly',
642
- } : undefined;
643
- navigate('PaymentStatus', { status: 'pending', transactionId, fdData: fdDataParam } as any);
644
- return;
645
- }
598
+ const fdDataParam = activeFD ? {
599
+ companyName: activeFD.name,
600
+ amount: Number(activeFD.invested) || 0,
601
+ fdRate: `${activeFD.returns}% p.a.`,
602
+ tenure: activeFD.maturityDate ? `${activeFD.maturityDate}` : '-',
603
+ interestPayout: activeFD.interestPayout || 'Yearly',
604
+ } : undefined;
605
+ navigate('PaymentStatus', { status: 'pending', transactionId, fdData: fdDataParam } as any);
606
+ return;
646
607
  }
647
608
 
648
609
  // Use workflow navigation based on current state (non-payment states or no transactionId)
@@ -1281,4 +1242,4 @@ const createStyles = (colors: ColorScheme, typography: any, spacing: any, themeN
1281
1242
  },
1282
1243
  });
1283
1244
 
1284
- export default FDList;
1245
+ export default FDList;
@@ -1,13 +1,14 @@
1
- import React, { useRef, useState, useEffect } from 'react';
1
+ import React, { useRef, useState, useEffect, useCallback } from 'react';
2
2
  import { View, StyleSheet, ActivityIndicator, BackHandler, Platform, StatusBar } from 'react-native';
3
3
  import { WebView } from 'react-native-webview';
4
4
  import SafeAreaWrapper from '../components/SafeAreaWrapper';
5
5
  import { useColors } from '../theme/ThemeContext';
6
6
  import { decryptResponse } from '../utils/encryption';
7
+ import { parseSSEBuffer } from '../utils/sseParser';
7
8
  import { getEncryptionConfig } from '../config/encryptionConfig';
8
- import { useFocusEffect } from '@react-navigation/native';
9
- import { getPaymentSession } from '../state/paymentSession';
10
- import { usePaymentStatusTimer, PaymentStatus } from '../hooks/usePaymentStatusTimer';
9
+ import { getEnvironmentData, getUserInfoForAPI } from '../config/appDataConfig';
10
+ import { getApiConfig, getSecureHeaders } from '../config/apiConfig';
11
+ import { useAppSelector } from '../store';
11
12
 
12
13
  export interface PaymentProps {
13
14
  onGoBack?: () => void;
@@ -32,36 +33,27 @@ const Payment: React.FC<PaymentProps> = ({
32
33
  const styles = createStyles(colors);
33
34
  const webViewRef = useRef<WebView>(null);
34
35
  const [loading, setLoading] = useState(true);
35
- const currentStatusRef = useRef<PaymentStatus>('pending');
36
- const { transactionId } = getPaymentSession();
37
36
 
38
- const { startTimer, stopTimer } = usePaymentStatusTimer({
39
- transactionId,
40
- onStatusUpdate: (status, response) => {
41
- currentStatusRef.current = status;
42
-
43
- if (status === 'success') {
44
- onPaymentSuccess?.(response);
45
- }
46
- else if (status === 'failed') {
47
- onPaymentFailure?.(response);
48
- }
49
- else if (status === 'pending') {
50
- onPaymentPending?.(response);
51
- }
37
+ const applicationId = useAppSelector((state: any) => state?.onboarding?.applicationId);
38
+ const workflowInstanceId = useAppSelector((state: any) => state?.onboarding?.workflowInstanceId);
39
+ const entityId = useAppSelector((state: any) => state?.onboarding?.entityid);
40
+ const providerId = useAppSelector((state: any) => state?.onboarding?.providerId);
41
+ const sseXhrRef = useRef<XMLHttpRequest | null>(null);
42
+ const sseLastIndexRef = useRef<number>(0);
43
+ const sseBufferRef = useRef<{ value: string }>({ value: '' });
44
+ const onSuccessRef = useRef(onPaymentSuccess);
45
+ const onFailureRef = useRef(onPaymentFailure);
46
+ onSuccessRef.current = onPaymentSuccess;
47
+ onFailureRef.current = onPaymentFailure;
48
+
49
+ const stopPaymentSSE = useCallback(() => {
50
+ if (sseXhrRef.current) {
51
+ sseXhrRef.current.abort();
52
+ sseXhrRef.current = null;
52
53
  }
53
-
54
- });
55
-
56
- useFocusEffect(
57
- React.useCallback(() => {
58
- startTimer();
59
-
60
- }, [startTimer, stopTimer])
61
- );
62
-
63
-
64
-
54
+ sseLastIndexRef.current = 0;
55
+ sseBufferRef.current = { value: '' };
56
+ }, []);
65
57
 
66
58
  const handleNavigationStateChange = async (navState: any) => {
67
59
  const { url } = navState;
@@ -89,6 +81,163 @@ const Payment: React.FC<PaymentProps> = ({
89
81
  setLoading(false);
90
82
  };
91
83
 
84
+ // SSE: listen for payment status while user is on Payment WebView (same as web integration)
85
+ useEffect(() => {
86
+ if (!applicationId || !paymentUrl?.trim()) {
87
+ stopPaymentSSE();
88
+ return;
89
+ }
90
+
91
+ const envData = getEnvironmentData();
92
+ const apiConfig = getApiConfig();
93
+ const baseUrl = (envData?.apiBaseUrl || apiConfig?.baseUrl || '').replace(/\/$/, '');
94
+ const url = baseUrl ? `${baseUrl}/events?applicationId=${encodeURIComponent(applicationId)}` : '';
95
+
96
+ if (!url) return;
97
+
98
+ stopPaymentSSE();
99
+
100
+ const xhr = new XMLHttpRequest();
101
+ sseXhrRef.current = xhr;
102
+ xhr.open('GET', url);
103
+
104
+ const apiKey = envData?.apiKey || apiConfig?.headers?.['X-API-Key'] || '';
105
+ if (apiKey) {
106
+ xhr.setRequestHeader('x-api-key', apiKey);
107
+ xhr.setRequestHeader('X-API-Key', apiKey);
108
+ }
109
+
110
+ const secureHeaders = getSecureHeaders();
111
+ Object.entries(secureHeaders).forEach(([key, value]) => {
112
+ if (key.toLowerCase() !== 'content-type') {
113
+ xhr.setRequestHeader(key, value);
114
+ }
115
+ });
116
+
117
+ xhr.setRequestHeader('Accept', 'text/event-stream');
118
+ xhr.setRequestHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
119
+ xhr.setRequestHeader('Pragma', 'no-cache');
120
+ xhr.setRequestHeader('Expires', '0');
121
+
122
+ try {
123
+ const userInfo = getUserInfoForAPI();
124
+ const userRefId = userInfo?.userReferenceId || userInfo?.id;
125
+ if (userRefId) {
126
+ xhr.setRequestHeader('userreferenceid', userRefId);
127
+ xhr.setRequestHeader('userReferenceId', userRefId);
128
+ }
129
+ } catch {
130
+ // ignore
131
+ }
132
+
133
+ if (providerId) {
134
+ xhr.setRequestHeader('provider', providerId);
135
+ xhr.setRequestHeader('providerid', providerId);
136
+ xhr.setRequestHeader('providerId', providerId);
137
+ }
138
+ if (workflowInstanceId) {
139
+ xhr.setRequestHeader('workflowInstanceId', workflowInstanceId);
140
+ xhr.setRequestHeader('workflowinstanceid', workflowInstanceId);
141
+ }
142
+ if (applicationId) {
143
+ xhr.setRequestHeader('applicationId', applicationId);
144
+ xhr.setRequestHeader('applicationid', applicationId);
145
+ }
146
+ if (entityId) {
147
+ xhr.setRequestHeader('entityid', entityId);
148
+ xhr.setRequestHeader('entityId', entityId);
149
+ }
150
+
151
+ const handleEventData = async (rawData: string) => {
152
+ try {
153
+ const parsed = JSON.parse(rawData || '{}') as Record<string, unknown>;
154
+ let payload = parsed;
155
+ if (typeof parsed?.encryptedResponse === 'string' && parsed.encryptedResponse) {
156
+ try {
157
+ const config = getEncryptionConfig();
158
+ const decrypted = await decryptResponse(
159
+ { encryptedResponse: parsed.encryptedResponse },
160
+ config
161
+ );
162
+ payload = (decrypted || {}) as Record<string, unknown>;
163
+ } catch {
164
+ // ignore and use raw payload
165
+ }
166
+ }
167
+
168
+ const payloadData = (payload?.data as Record<string, unknown> | undefined) || {};
169
+ const status = String(
170
+ payload?.paymentStatus ??
171
+ payload?.payment_status ??
172
+ payloadData?.paymentStatus ??
173
+ payloadData?.payment_status ??
174
+ ''
175
+ ).toUpperCase();
176
+
177
+ if (status === 'SUCCESS') {
178
+ stopPaymentSSE();
179
+ onSuccessRef.current?.(payload);
180
+ return;
181
+ }
182
+
183
+ if (status === 'FAILED') {
184
+ stopPaymentSSE();
185
+ onFailureRef.current?.(payload);
186
+ }
187
+ } catch {
188
+ // ignore parse errors
189
+ }
190
+ };
191
+
192
+ xhr.onprogress = () => {
193
+ setLoading(false);
194
+
195
+ const newText = xhr.responseText.slice(sseLastIndexRef.current);
196
+ sseLastIndexRef.current = xhr.responseText.length;
197
+ if (!newText) return;
198
+
199
+ const events = parseSSEBuffer(sseBufferRef.current, newText);
200
+ for (const { eventType, data } of events) {
201
+ if (!data) continue;
202
+ const normalizedEventType = String(eventType || '').trim().toLowerCase();
203
+ if (normalizedEventType === 'fd_payment_status') {
204
+ handleEventData(data);
205
+ } else if (normalizedEventType === 'message') {
206
+ try {
207
+ const parsed = JSON.parse(data) as { event?: string; [k: string]: unknown };
208
+ const embeddedEvent = String(parsed?.event || '').trim().toLowerCase();
209
+ if (embeddedEvent === 'fd_payment_status') {
210
+ handleEventData(data);
211
+ }
212
+ } catch {
213
+ // ignore
214
+ }
215
+ }
216
+ }
217
+ };
218
+
219
+ xhr.onreadystatechange = () => {
220
+ if (
221
+ (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED ||
222
+ xhr.readyState === XMLHttpRequest.LOADING) &&
223
+ xhr.status === 200
224
+ ) {
225
+ setLoading(false);
226
+ if (__DEV__) console.log('[Payment] SSE connected for payment status');
227
+ }
228
+ };
229
+
230
+ xhr.onerror = () => {
231
+ if (__DEV__) console.warn('[Payment] SSE connection error');
232
+ };
233
+
234
+ xhr.send();
235
+
236
+ return () => {
237
+ stopPaymentSSE();
238
+ };
239
+ }, [applicationId, paymentUrl, workflowInstanceId, entityId, providerId, stopPaymentSSE]);
240
+
92
241
  const handleMessage = async (event: any) => {
93
242
  try {
94
243
  const dataString = event.nativeEvent.data;
@@ -133,18 +282,13 @@ const Payment: React.FC<PaymentProps> = ({
133
282
  const paymentStatus = response.data?.paymentStatus?.toLowerCase();
134
283
 
135
284
  if (paymentStatus === 'success') {
136
- currentStatusRef.current = "success";
137
- stopTimer();
138
285
  onPaymentSuccess?.(response);
139
286
  } else if (paymentStatus === 'failed') {
140
- currentStatusRef.current = "failed";
141
- stopTimer();
142
287
  onPaymentFailure?.(response);
143
288
  } else {
144
289
  onPaymentPending?.(response);
145
290
  }
146
291
  } else {
147
- currentStatusRef.current = "pending";
148
292
  onPaymentPending?.(response);
149
293
  }
150
294
 
@@ -252,4 +396,3 @@ const createStyles = (colors: any) => StyleSheet.create({
252
396
  });
253
397
 
254
398
  export default Payment;
255
-
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect } from 'react';
1
+ import React, { useState, useEffect, useRef, useCallback } from 'react';
2
2
  import { View, Text, StyleSheet, ScrollView, Alert, Image, BackHandler, Platform, StatusBar } from 'react-native';
3
3
  import Icon from 'react-native-vector-icons/Ionicons';
4
4
  import { base64Images } from '../constants/strings/base64Images';
@@ -9,14 +9,16 @@ import { useColors, useTypography, useTheme } from '../theme/ThemeContext';
9
9
  import { usePaymentRetryMutation } from '../api/fdApi';
10
10
  import { useGetCustomerApplicationsMutation } from '../api/customerApi';
11
11
  import { useAppSelector } from '../store';
12
- import { getUserInfoForAPI } from '../config/appDataConfig';
12
+ import { getEnvironmentData, getUserInfoForAPI } from '../config/appDataConfig';
13
+ import { getApiConfig, getSecureHeaders } from '../config/apiConfig';
13
14
  import { navigate } from '../navigation/helpers';
14
15
  import { setPaymentSession, getPaymentSession } from '../state/paymentSession';
15
16
  import { useRoute } from '@react-navigation/native';
16
17
  import { BANK_STRINGS } from '../constants/strings/bank';
17
18
  import { COMMON_STRINGS } from '../constants/strings/common';
18
- import { getApiConfig } from '../config/apiConfig';
19
- import { usePaymentStatusTimer } from '../hooks/usePaymentStatusTimer';
19
+ import { parseSSEBuffer } from '../utils/sseParser';
20
+ import { decryptResponse } from '../utils/encryption';
21
+ import { getEncryptionConfig } from '../config/encryptionConfig';
20
22
 
21
23
  export interface PaymentStatusProps {
22
24
  onRetry?: () => void;
@@ -63,24 +65,9 @@ const PaymentStatus: React.FC<PaymentStatusProps> = ({
63
65
  const applicationId = useAppSelector((state: any) => state?.onboarding?.applicationId);
64
66
  const entityId = useAppSelector((state: any) => state?.onboarding?.entityid);
65
67
  const providerId = useAppSelector((state: any) => state?.onboarding?.providerId);
66
-
67
- const {
68
- startTimer,
69
- stopTimer,
70
- triggerStatusCheck,
71
- isCheckingStatus,
72
- } = usePaymentStatusTimer({
73
- transactionId: finalTransactionId,
74
- overrides: {
75
- providerId,
76
- workflowInstanceId,
77
- applicationid: applicationId,
78
- entityid: entityId,
79
- },
80
- onStatusUpdate: (nextStatus) => {
81
- setCurrentStatus(nextStatus);
82
- },
83
- });
68
+ const sseXhrRef = useRef<XMLHttpRequest | null>(null);
69
+ const sseLastIndexRef = useRef<number>(0);
70
+ const sseBufferRef = useRef<{ value: string }>({ value: '' });
84
71
 
85
72
  // Payment Retry API
86
73
  const [paymentRetry, {
@@ -117,21 +104,160 @@ const PaymentStatus: React.FC<PaymentStatusProps> = ({
117
104
  return amount.toLocaleString('en-IN');
118
105
  };
119
106
 
107
+ const stopPaymentSSE = useCallback(() => {
108
+ if (sseXhrRef.current) {
109
+ sseXhrRef.current.abort();
110
+ sseXhrRef.current = null;
111
+ }
112
+ sseLastIndexRef.current = 0;
113
+ sseBufferRef.current = { value: '' };
114
+ }, []);
115
+
120
116
  useEffect(() => {
121
- if (currentStatus === 'pending') {
122
- startTimer();
123
- } else {
124
- stopTimer();
117
+ if (currentStatus !== 'pending' || !applicationId) {
118
+ stopPaymentSSE();
119
+ return;
125
120
  }
126
121
 
127
- return () => {
128
- stopTimer();
122
+ const envData = getEnvironmentData();
123
+ const apiConfig = getApiConfig();
124
+ const baseUrl = (envData?.apiBaseUrl || apiConfig?.baseUrl || '').replace(/\/$/, '');
125
+ const url = baseUrl ? `${baseUrl}/events?applicationId=${encodeURIComponent(applicationId)}` : '';
126
+
127
+ if (!url) return;
128
+
129
+ stopPaymentSSE();
130
+
131
+ const xhr = new XMLHttpRequest();
132
+ sseXhrRef.current = xhr;
133
+ xhr.open('GET', url);
134
+
135
+ const apiKey = envData?.apiKey || apiConfig?.headers?.['X-API-Key'] || '';
136
+ if (apiKey) {
137
+ xhr.setRequestHeader('x-api-key', apiKey);
138
+ xhr.setRequestHeader('X-API-Key', apiKey);
139
+ }
140
+
141
+ const secureHeaders = getSecureHeaders();
142
+ Object.entries(secureHeaders).forEach(([key, value]) => {
143
+ if (key.toLowerCase() !== 'content-type') {
144
+ xhr.setRequestHeader(key, value);
145
+ }
146
+ });
147
+
148
+ xhr.setRequestHeader('Accept', 'text/event-stream');
149
+ xhr.setRequestHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
150
+ xhr.setRequestHeader('Pragma', 'no-cache');
151
+ xhr.setRequestHeader('Expires', '0');
152
+
153
+ try {
154
+ const userInfo = getUserInfoForAPI();
155
+ const userRefId = userInfo?.userReferenceId || userInfo?.id;
156
+ if (userRefId) {
157
+ xhr.setRequestHeader('userreferenceid', userRefId);
158
+ xhr.setRequestHeader('userReferenceId', userRefId);
159
+ }
160
+ } catch {
161
+ // ignore
162
+ }
163
+
164
+ if (providerId) {
165
+ xhr.setRequestHeader('provider', providerId);
166
+ xhr.setRequestHeader('providerid', providerId);
167
+ xhr.setRequestHeader('providerId', providerId);
168
+ }
169
+ if (workflowInstanceId) {
170
+ xhr.setRequestHeader('workflowInstanceId', workflowInstanceId);
171
+ xhr.setRequestHeader('workflowinstanceid', workflowInstanceId);
172
+ }
173
+ if (applicationId) {
174
+ xhr.setRequestHeader('applicationId', applicationId);
175
+ xhr.setRequestHeader('applicationid', applicationId);
176
+ }
177
+ if (entityId) {
178
+ xhr.setRequestHeader('entityid', entityId);
179
+ xhr.setRequestHeader('entityId', entityId);
180
+ }
181
+
182
+ const handleEventData = async (rawData: string) => {
183
+ try {
184
+ const parsed = JSON.parse(rawData || '{}') as Record<string, unknown>;
185
+ let payload = parsed;
186
+ if (typeof parsed?.encryptedResponse === 'string' && parsed.encryptedResponse) {
187
+ try {
188
+ const config = getEncryptionConfig();
189
+ const decrypted = await decryptResponse(
190
+ { encryptedResponse: parsed.encryptedResponse },
191
+ config
192
+ );
193
+ payload = (decrypted || {}) as Record<string, unknown>;
194
+ } catch {
195
+ // ignore and use raw payload
196
+ }
197
+ }
198
+
199
+ const payloadData = (payload?.data as Record<string, unknown> | undefined) || {};
200
+ const status = String(
201
+ payload?.paymentStatus ??
202
+ payload?.payment_status ??
203
+ payloadData?.paymentStatus ??
204
+ payloadData?.payment_status ??
205
+ ''
206
+ ).toUpperCase();
207
+
208
+ if (status === 'SUCCESS') {
209
+ setCurrentStatus('success');
210
+ stopPaymentSSE();
211
+ return;
212
+ }
213
+
214
+ if (status === 'FAILED') {
215
+ setCurrentStatus('failed');
216
+ stopPaymentSSE();
217
+ }
218
+ } catch {
219
+ // ignore parse errors
220
+ }
129
221
  };
130
- }, [currentStatus, startTimer, stopTimer]);
131
222
 
132
- const handlePaymentReverseFeed = async () => {
133
- await triggerStatusCheck();
134
- };
223
+ xhr.onprogress = () => {
224
+ const newText = xhr.responseText.slice(sseLastIndexRef.current);
225
+ sseLastIndexRef.current = xhr.responseText.length;
226
+ if (!newText) return;
227
+
228
+ const events = parseSSEBuffer(sseBufferRef.current, newText);
229
+ for (const { eventType, data } of events) {
230
+ if (!data) continue;
231
+ const normalizedEventType = String(eventType || '').trim().toLowerCase();
232
+ if (normalizedEventType === 'fd_payment_status') {
233
+ handleEventData(data);
234
+ } else if (normalizedEventType === 'message') {
235
+ try {
236
+ const parsed = JSON.parse(data) as { event?: string; [k: string]: unknown };
237
+ const embeddedEvent = String(parsed?.event || '').trim().toLowerCase();
238
+ if (embeddedEvent === 'fd_payment_status') {
239
+ handleEventData(data);
240
+ }
241
+ } catch {
242
+ // ignore
243
+ }
244
+ }
245
+ }
246
+ };
247
+
248
+ xhr.send();
249
+
250
+ return () => {
251
+ stopPaymentSSE();
252
+ };
253
+ }, [
254
+ applicationId,
255
+ currentStatus,
256
+ entityId,
257
+ providerId,
258
+ stopPaymentSSE,
259
+ workflowInstanceId
260
+ ]);
135
261
 
136
262
  // Handle payment retry when status is failed
137
263
  const handlePaymentRetry = async () => {
@@ -181,9 +307,6 @@ const PaymentStatus: React.FC<PaymentStatusProps> = ({
181
307
  // Get user info from app data
182
308
  const userInfo = getUserInfoForAPI();
183
309
 
184
- // Get API configuration
185
- const apiConfig = getApiConfig();
186
-
187
310
  // Prepare request payload
188
311
  const requestPayload = {
189
312
  userReferenceId: userInfo.id
@@ -412,13 +535,6 @@ const PaymentStatus: React.FC<PaymentStatusProps> = ({
412
535
  disabled={isLoadingPaymentRetry}
413
536
  />
414
537
  )}
415
- {/* {currentStatus === 'pending' && (
416
- <ActionButton
417
- title={isCheckingStatus ? COMMON_STRINGS.CHECKING : BANK_STRINGS.REFRESH_STATUS_BUTTON}
418
- onPress={handlePaymentReverseFeed}
419
- disabled={isCheckingStatus}
420
- />
421
- )} */}
422
538
  </View>
423
539
  </View>
424
540
  </SafeAreaWrapper>
@@ -545,4 +661,3 @@ const createStyles = (colors: any, typography: any, status: 'success' | 'failed'
545
661
  };
546
662
 
547
663
  export default PaymentStatus;
548
-