@finspringinnovations/fdsdk 0.0.5 → 0.0.7
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.
- package/lib/components/InterestRateCard.d.ts +4 -0
- package/lib/components/InterestRateCard.js +13 -3
- package/lib/config/appDataConfig.d.ts +5 -0
- package/lib/hooks/usePaymentSSE.js +277 -142
- package/lib/screens/FDCalculator.js +25 -1
- package/lib/screens/Payment.js +138 -20
- package/lib/screens/PaymentStatus.js +129 -13
- package/lib/utils/sseParser.js +19 -3
- package/package.json +1 -1
- package/src/components/InterestRateCard.tsx +33 -5
- package/src/config/appDataConfig.ts +5 -0
- package/src/hooks/usePaymentSSE.ts +283 -136
- package/src/screens/FDCalculator.tsx +23 -1
- package/src/screens/Payment.tsx +163 -30
- package/src/screens/PaymentStatus.tsx +146 -16
- package/src/utils/sseParser.ts +20 -4
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { useCallback, useRef } from 'react';
|
|
17
|
+
import { Platform } from 'react-native';
|
|
17
18
|
import { parseSSEBuffer } from '../utils/sseParser';
|
|
18
19
|
import { getEnvironmentData, getUserInfoForAPI } from '../config/appDataConfig';
|
|
19
20
|
import { getApiConfig, getSecureHeaders } from '../config/apiConfig';
|
|
@@ -40,6 +41,10 @@ export interface PaymentSSEExtraHeaders {
|
|
|
40
41
|
export function usePaymentSSE() {
|
|
41
42
|
// Store the XMLHttpRequest so we can abort it later
|
|
42
43
|
const xhrRef = useRef<XMLHttpRequest | null>(null);
|
|
44
|
+
const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
45
|
+
const connectWatchdogTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
46
|
+
const stopRequestedRef = useRef<boolean>(false);
|
|
47
|
+
const connectionIdRef = useRef<number>(0);
|
|
43
48
|
// Track how much of responseText we've already parsed
|
|
44
49
|
const lastIndexRef = useRef<number>(0);
|
|
45
50
|
// SSE text buffer (persists across onprogress calls)
|
|
@@ -47,11 +52,23 @@ export function usePaymentSSE() {
|
|
|
47
52
|
|
|
48
53
|
const start = useCallback(
|
|
49
54
|
(applicationId: string, callbacks: UsePaymentSSECallbacks, extraHeaders?: PaymentSSEExtraHeaders) => {
|
|
55
|
+
connectionIdRef.current += 1;
|
|
56
|
+
const connectionId = connectionIdRef.current;
|
|
57
|
+
stopRequestedRef.current = false;
|
|
58
|
+
|
|
50
59
|
// Clean up any existing connection first
|
|
51
60
|
if (xhrRef.current) {
|
|
52
61
|
xhrRef.current.abort();
|
|
53
62
|
xhrRef.current = null;
|
|
54
63
|
}
|
|
64
|
+
if (reconnectTimerRef.current) {
|
|
65
|
+
clearTimeout(reconnectTimerRef.current);
|
|
66
|
+
reconnectTimerRef.current = null;
|
|
67
|
+
}
|
|
68
|
+
if (connectWatchdogTimerRef.current) {
|
|
69
|
+
clearTimeout(connectWatchdogTimerRef.current);
|
|
70
|
+
connectWatchdogTimerRef.current = null;
|
|
71
|
+
}
|
|
55
72
|
|
|
56
73
|
// Reset parsing state
|
|
57
74
|
lastIndexRef.current = 0;
|
|
@@ -63,177 +80,307 @@ export function usePaymentSSE() {
|
|
|
63
80
|
const baseUrl = (envData?.apiBaseUrl || apiConfig?.baseUrl || '').replace(/\/$/, '');
|
|
64
81
|
const url = baseUrl ? `${baseUrl}/events?applicationId=${encodeURIComponent(applicationId)}` : '';
|
|
65
82
|
|
|
83
|
+
const minimalLogs = envData?.sseMinimalLogs === true;
|
|
84
|
+
const verboseSSE = envData?.enableVerboseSSELogging !== false;
|
|
85
|
+
const maxReconnectAttempts = Number(envData?.sseReconnectAttempts ?? 4);
|
|
86
|
+
const reconnectDelayMs = Number(envData?.sseReconnectDelayMs ?? 2000);
|
|
87
|
+
const connectWatchdogMs = Number(envData?.sseConnectTimeoutMs ?? 15000);
|
|
88
|
+
const rnVersion = (() => {
|
|
89
|
+
const v = (Platform as any)?.constants?.reactNativeVersion;
|
|
90
|
+
return v ? `${v.major}.${v.minor}.${v.patch}` : 'unknown';
|
|
91
|
+
})();
|
|
92
|
+
const jsEngine = (global as any)?.HermesInternal ? 'hermes' : 'jsc/other';
|
|
93
|
+
let reconnectAttempts = 0;
|
|
94
|
+
let connected = false;
|
|
95
|
+
|
|
66
96
|
if (!url) {
|
|
67
|
-
if (__DEV__) console.warn('[usePaymentSSE] No base URL, skipping SSE');
|
|
97
|
+
if (__DEV__ && verboseSSE) console.warn('[usePaymentSSE] No base URL, skipping SSE');
|
|
68
98
|
return;
|
|
69
99
|
}
|
|
70
100
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
101
|
+
const scheduleReconnect = (reason: string) => {
|
|
102
|
+
if (stopRequestedRef.current || connectionIdRef.current !== connectionId) return;
|
|
103
|
+
if (connectWatchdogTimerRef.current) {
|
|
104
|
+
clearTimeout(connectWatchdogTimerRef.current);
|
|
105
|
+
connectWatchdogTimerRef.current = null;
|
|
106
|
+
}
|
|
107
|
+
if (reconnectAttempts >= maxReconnectAttempts) {
|
|
108
|
+
callbacks.onError?.(new Error(reason));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
74
111
|
|
|
75
|
-
|
|
112
|
+
reconnectAttempts += 1;
|
|
113
|
+
const delay = reconnectDelayMs * reconnectAttempts;
|
|
76
114
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
xhr.setRequestHeader(key, value);
|
|
115
|
+
if (__DEV__ && !minimalLogs) {
|
|
116
|
+
console.warn(`[usePaymentSSE] Reconnecting (${reconnectAttempts}/${maxReconnectAttempts}) in ${delay}ms`);
|
|
117
|
+
console.log('[usePaymentSSE][runtime]', {
|
|
118
|
+
reason,
|
|
119
|
+
platform: Platform.OS,
|
|
120
|
+
osVersion: String(Platform.Version),
|
|
121
|
+
rnVersion,
|
|
122
|
+
jsEngine,
|
|
123
|
+
applicationId,
|
|
124
|
+
});
|
|
88
125
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
xhr.setRequestHeader('Accept', 'text/event-stream');
|
|
93
|
-
xhr.setRequestHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
94
|
-
xhr.setRequestHeader('Pragma', 'no-cache');
|
|
95
|
-
xhr.setRequestHeader('Expires', '0');
|
|
96
|
-
|
|
97
|
-
// Add userreferenceid header (required for authentication) - both cases
|
|
98
|
-
try {
|
|
99
|
-
const userInfo = getUserInfoForAPI();
|
|
100
|
-
const userRefId = userInfo?.userReferenceId || userInfo?.id;
|
|
101
|
-
if (userRefId) {
|
|
102
|
-
xhr.setRequestHeader('userreferenceid', userRefId);
|
|
103
|
-
xhr.setRequestHeader('userReferenceId', userRefId);
|
|
126
|
+
|
|
127
|
+
if (reconnectTimerRef.current) {
|
|
128
|
+
clearTimeout(reconnectTimerRef.current);
|
|
104
129
|
}
|
|
105
|
-
} catch {
|
|
106
|
-
if (__DEV__) console.warn('[usePaymentSSE] Could not get userReferenceId');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Add provider header (required for authentication)
|
|
110
|
-
if (extraHeaders?.providerId) {
|
|
111
|
-
xhr.setRequestHeader('provider', extraHeaders.providerId);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Add onboarding headers - both camelCase and lowercase variants
|
|
115
|
-
if (extraHeaders?.workflowInstanceId) {
|
|
116
|
-
xhr.setRequestHeader('workflowInstanceId', extraHeaders.workflowInstanceId);
|
|
117
|
-
}
|
|
118
|
-
if (extraHeaders?.applicationId) {
|
|
119
|
-
xhr.setRequestHeader('applicationId', extraHeaders.applicationId);
|
|
120
|
-
xhr.setRequestHeader('applicationid', extraHeaders.applicationId);
|
|
121
|
-
}
|
|
122
|
-
if (extraHeaders?.entityid) {
|
|
123
|
-
xhr.setRequestHeader('entityid', extraHeaders.entityid);
|
|
124
|
-
xhr.setRequestHeader('entityId', extraHeaders.entityid);
|
|
125
|
-
}
|
|
126
130
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
lastIndexRef.current = xhr.responseText.length;
|
|
131
|
+
reconnectTimerRef.current = setTimeout(() => {
|
|
132
|
+
if (stopRequestedRef.current || connectionIdRef.current !== connectionId) return;
|
|
133
|
+
openConnection();
|
|
134
|
+
}, delay);
|
|
135
|
+
};
|
|
133
136
|
|
|
134
|
-
|
|
137
|
+
const openConnection = () => {
|
|
138
|
+
if (stopRequestedRef.current || connectionIdRef.current !== connectionId) return;
|
|
139
|
+
connected = false;
|
|
135
140
|
|
|
136
|
-
//
|
|
137
|
-
const
|
|
141
|
+
// 2. Create XMLHttpRequest
|
|
142
|
+
const xhr = new XMLHttpRequest();
|
|
143
|
+
xhrRef.current = xhr;
|
|
138
144
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
+
xhr.open('GET', url);
|
|
146
|
+
|
|
147
|
+
// 3. Set headers (same headers your baseApi interceptor uses)
|
|
148
|
+
const apiKey = envData?.apiKey || apiConfig?.headers?.['X-API-Key'] || '';
|
|
149
|
+
if (apiKey) {
|
|
150
|
+
xhr.setRequestHeader('x-api-key', apiKey);
|
|
151
|
+
xhr.setRequestHeader('X-API-Key', apiKey);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Add secure headers (Content-Type, User-Agent, etc.)
|
|
155
|
+
const secureHeaders = getSecureHeaders();
|
|
156
|
+
Object.entries(secureHeaders).forEach(([key, value]) => {
|
|
157
|
+
if (key.toLowerCase() !== 'content-type') {
|
|
158
|
+
xhr.setRequestHeader(key, value);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// SSE-specific headers
|
|
163
|
+
xhr.setRequestHeader('Accept', 'text/event-stream');
|
|
164
|
+
xhr.setRequestHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
165
|
+
xhr.setRequestHeader('Pragma', 'no-cache');
|
|
166
|
+
xhr.setRequestHeader('Expires', '0');
|
|
167
|
+
|
|
168
|
+
// Add userreferenceid header (required for authentication) - both cases
|
|
169
|
+
try {
|
|
170
|
+
const userInfo = getUserInfoForAPI();
|
|
171
|
+
const userRefId = userInfo?.userReferenceId || userInfo?.id;
|
|
172
|
+
if (userRefId) {
|
|
173
|
+
xhr.setRequestHeader('userreferenceid', userRefId);
|
|
174
|
+
xhr.setRequestHeader('userReferenceId', userRefId);
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
if (__DEV__ && verboseSSE) console.warn('[usePaymentSSE] Could not get userReferenceId');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Add provider header variants
|
|
181
|
+
if (extraHeaders?.providerId) {
|
|
182
|
+
xhr.setRequestHeader('provider', extraHeaders.providerId);
|
|
183
|
+
xhr.setRequestHeader('providerid', extraHeaders.providerId);
|
|
184
|
+
xhr.setRequestHeader('providerId', extraHeaders.providerId);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Add onboarding headers - both camelCase and lowercase variants
|
|
188
|
+
if (extraHeaders?.workflowInstanceId) {
|
|
189
|
+
xhr.setRequestHeader('workflowInstanceId', extraHeaders.workflowInstanceId);
|
|
190
|
+
xhr.setRequestHeader('workflowinstanceid', extraHeaders.workflowInstanceId);
|
|
191
|
+
}
|
|
192
|
+
if (extraHeaders?.applicationId) {
|
|
193
|
+
xhr.setRequestHeader('applicationId', extraHeaders.applicationId);
|
|
194
|
+
xhr.setRequestHeader('applicationid', extraHeaders.applicationId);
|
|
195
|
+
}
|
|
196
|
+
if (extraHeaders?.entityid) {
|
|
197
|
+
xhr.setRequestHeader('entityid', extraHeaders.entityid);
|
|
198
|
+
xhr.setRequestHeader('entityId', extraHeaders.entityid);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const markConnected = (path: 'headers' | 'progress') => {
|
|
202
|
+
if (connected) return;
|
|
203
|
+
connected = true;
|
|
204
|
+
reconnectAttempts = 0;
|
|
205
|
+
if (connectWatchdogTimerRef.current) {
|
|
206
|
+
clearTimeout(connectWatchdogTimerRef.current);
|
|
207
|
+
connectWatchdogTimerRef.current = null;
|
|
208
|
+
}
|
|
209
|
+
callbacks.onConnected?.();
|
|
210
|
+
if (__DEV__ && !minimalLogs) {
|
|
211
|
+
console.log('[usePaymentSSE] Connected to SSE stream');
|
|
212
|
+
console.log('[usePaymentSSE][runtime]', {
|
|
213
|
+
path,
|
|
214
|
+
platform: Platform.OS,
|
|
215
|
+
osVersion: String(Platform.Version),
|
|
216
|
+
rnVersion,
|
|
217
|
+
jsEngine,
|
|
218
|
+
applicationId,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// 4. Handle incoming data
|
|
224
|
+
xhr.onprogress = () => {
|
|
225
|
+
if (xhr.responseText && xhr.responseText.length > 0) {
|
|
226
|
+
markConnected('progress');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// responseText contains ALL text received so far
|
|
230
|
+
// Only parse the NEW text since last call
|
|
231
|
+
const newText = xhr.responseText.slice(lastIndexRef.current);
|
|
232
|
+
lastIndexRef.current = xhr.responseText.length;
|
|
233
|
+
|
|
234
|
+
if (!newText) return;
|
|
235
|
+
|
|
236
|
+
// Parse new text into SSE events
|
|
237
|
+
const events = parseSSEBuffer(bufferRef.current, newText);
|
|
238
|
+
|
|
239
|
+
// Handle each event (support encrypted payload like web)
|
|
240
|
+
const handleEventData = async (rawData: string) => {
|
|
241
|
+
try {
|
|
242
|
+
const parsed = JSON.parse(rawData || '{}') as Record<string, unknown>;
|
|
243
|
+
let payload = parsed;
|
|
244
|
+
if (typeof parsed?.encryptedResponse === 'string' && parsed.encryptedResponse) {
|
|
245
|
+
try {
|
|
246
|
+
const config = getEncryptionConfig();
|
|
247
|
+
const decrypted = await decryptResponse(
|
|
248
|
+
{ encryptedResponse: parsed.encryptedResponse },
|
|
249
|
+
config
|
|
250
|
+
);
|
|
251
|
+
payload = (decrypted || {}) as Record<string, unknown>;
|
|
252
|
+
} catch {
|
|
253
|
+
if (__DEV__ && verboseSSE) console.warn('[usePaymentSSE] Decrypt failed, using raw');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const payloadData = (payload?.data as Record<string, unknown> | undefined) || {};
|
|
257
|
+
const status = String(
|
|
258
|
+
payload?.paymentStatus ??
|
|
259
|
+
payload?.payment_status ??
|
|
260
|
+
payloadData?.paymentStatus ??
|
|
261
|
+
payloadData?.payment_status ??
|
|
262
|
+
''
|
|
263
|
+
).toUpperCase();
|
|
264
|
+
if (status) callbacks.onPaymentStatus(status, payload);
|
|
265
|
+
} catch (parseError) {
|
|
266
|
+
if (__DEV__ && verboseSSE) console.error('[usePaymentSSE] Failed to parse event data:', parseError);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
for (const { eventType, data } of events) {
|
|
271
|
+
if (!data) continue;
|
|
272
|
+
const normalizedEventType = String(eventType || '').trim().toLowerCase();
|
|
273
|
+
if (normalizedEventType === 'fd_payment_status') {
|
|
274
|
+
handleEventData(data);
|
|
275
|
+
} else if (normalizedEventType === 'message') {
|
|
145
276
|
try {
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
config
|
|
150
|
-
);
|
|
151
|
-
payload = (decrypted || {}) as Record<string, unknown>;
|
|
277
|
+
const parsed = JSON.parse(data) as { event?: string; [k: string]: unknown };
|
|
278
|
+
const embeddedEvent = String(parsed?.event || '').trim().toLowerCase();
|
|
279
|
+
if (embeddedEvent === 'fd_payment_status') handleEventData(data);
|
|
152
280
|
} catch {
|
|
153
|
-
|
|
281
|
+
// ignore
|
|
154
282
|
}
|
|
155
283
|
}
|
|
156
|
-
const status = String(payload?.paymentStatus ?? payload?.payment_status ?? '').toUpperCase();
|
|
157
|
-
if (status) callbacks.onPaymentStatus(status, payload);
|
|
158
|
-
} catch (parseError) {
|
|
159
|
-
if (__DEV__) console.error('[usePaymentSSE] Failed to parse event data:', parseError);
|
|
160
284
|
}
|
|
161
285
|
};
|
|
162
286
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
287
|
+
// 5. Handle connection status
|
|
288
|
+
xhr.onreadystatechange = () => {
|
|
289
|
+
if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED || xhr.readyState === XMLHttpRequest.LOADING) {
|
|
290
|
+
if (xhr.status === 200) {
|
|
291
|
+
markConnected('headers');
|
|
292
|
+
} else if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED && xhr.status > 0) {
|
|
293
|
+
if (__DEV__) {
|
|
294
|
+
console.error('[usePaymentSSE] Bad status:', xhr.status);
|
|
295
|
+
}
|
|
296
|
+
scheduleReconnect(`SSE response status: ${xhr.status}`);
|
|
173
297
|
}
|
|
174
298
|
}
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
299
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (xhr.status === 200) {
|
|
182
|
-
callbacks.onConnected?.();
|
|
183
|
-
if (__DEV__) {
|
|
184
|
-
console.log('[usePaymentSSE] Connected to SSE stream');
|
|
300
|
+
if (xhr.readyState === XMLHttpRequest.DONE && !stopRequestedRef.current && connectionIdRef.current === connectionId) {
|
|
301
|
+
if (xhr.status !== 200) {
|
|
302
|
+
scheduleReconnect(`SSE closed with status: ${xhr.status}`);
|
|
185
303
|
}
|
|
186
|
-
} else {
|
|
187
|
-
if (__DEV__) {
|
|
188
|
-
console.error('[usePaymentSSE] Bad status:', xhr.status);
|
|
189
|
-
}
|
|
190
|
-
callbacks.onError?.(new Error(`SSE response status: ${xhr.status}`));
|
|
191
304
|
}
|
|
192
|
-
}
|
|
193
|
-
};
|
|
305
|
+
};
|
|
194
306
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
307
|
+
// 6. Handle errors
|
|
308
|
+
xhr.onerror = () => {
|
|
309
|
+
if (__DEV__ && verboseSSE) {
|
|
310
|
+
console.error('[usePaymentSSE] Connection error');
|
|
311
|
+
}
|
|
312
|
+
scheduleReconnect('SSE connection failed');
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// 7. Handle timeout
|
|
316
|
+
xhr.ontimeout = () => {
|
|
317
|
+
if (__DEV__) {
|
|
318
|
+
console.error('[usePaymentSSE] Connection timed out');
|
|
319
|
+
}
|
|
320
|
+
scheduleReconnect('SSE connection timed out');
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
// 8. Send the request (connection stays open)
|
|
324
|
+
xhr.send();
|
|
325
|
+
|
|
326
|
+
// If we never get connected callback/progress, restart connection.
|
|
327
|
+
if (connectWatchdogTimerRef.current) {
|
|
328
|
+
clearTimeout(connectWatchdogTimerRef.current);
|
|
199
329
|
}
|
|
200
|
-
|
|
201
|
-
|
|
330
|
+
connectWatchdogTimerRef.current = setTimeout(() => {
|
|
331
|
+
if (connected || stopRequestedRef.current || connectionIdRef.current !== connectionId) return;
|
|
332
|
+
try {
|
|
333
|
+
xhr.abort();
|
|
334
|
+
} catch {
|
|
335
|
+
// ignore
|
|
336
|
+
}
|
|
337
|
+
scheduleReconnect('SSE connect watchdog timeout');
|
|
338
|
+
}, connectWatchdogMs);
|
|
202
339
|
|
|
203
|
-
// 7. Handle timeout
|
|
204
|
-
xhr.ontimeout = () => {
|
|
205
340
|
if (__DEV__) {
|
|
206
|
-
console.
|
|
341
|
+
console.log('[usePaymentSSE] Starting SSE connection to:', url);
|
|
342
|
+
console.log('[usePaymentSSE][runtime]', {
|
|
343
|
+
phase: 'start',
|
|
344
|
+
platform: Platform.OS,
|
|
345
|
+
osVersion: String(Platform.Version),
|
|
346
|
+
rnVersion,
|
|
347
|
+
jsEngine,
|
|
348
|
+
applicationId,
|
|
349
|
+
});
|
|
350
|
+
console.log('[usePaymentSSE] Headers:', {
|
|
351
|
+
'x-api-key': apiKey ? '***' : 'missing',
|
|
352
|
+
'userreferenceid': (() => {
|
|
353
|
+
try {
|
|
354
|
+
const userInfo = getUserInfoForAPI();
|
|
355
|
+
return userInfo?.userReferenceId || userInfo?.id || 'missing';
|
|
356
|
+
} catch {
|
|
357
|
+
return 'missing';
|
|
358
|
+
}
|
|
359
|
+
})(),
|
|
360
|
+
'provider': extraHeaders?.providerId || 'missing',
|
|
361
|
+
'workflowInstanceId': extraHeaders?.workflowInstanceId || 'missing',
|
|
362
|
+
'applicationId': extraHeaders?.applicationId || 'missing',
|
|
363
|
+
'entityid': extraHeaders?.entityid || 'missing',
|
|
364
|
+
});
|
|
207
365
|
}
|
|
208
|
-
callbacks.onError?.(new Error('SSE connection timed out'));
|
|
209
366
|
};
|
|
210
367
|
|
|
211
|
-
|
|
212
|
-
xhr.send();
|
|
213
|
-
|
|
214
|
-
if (__DEV__) {
|
|
215
|
-
console.log('[usePaymentSSE] Starting SSE connection to:', url);
|
|
216
|
-
console.log('[usePaymentSSE] Headers:', {
|
|
217
|
-
'x-api-key': apiKey ? '***' : 'missing',
|
|
218
|
-
'userreferenceid': (() => {
|
|
219
|
-
try {
|
|
220
|
-
const userInfo = getUserInfoForAPI();
|
|
221
|
-
return userInfo?.userReferenceId || userInfo?.id || 'missing';
|
|
222
|
-
} catch {
|
|
223
|
-
return 'missing';
|
|
224
|
-
}
|
|
225
|
-
})(),
|
|
226
|
-
'provider': extraHeaders?.providerId || 'missing',
|
|
227
|
-
'workflowInstanceId': extraHeaders?.workflowInstanceId || 'missing',
|
|
228
|
-
'applicationId': extraHeaders?.applicationId || 'missing',
|
|
229
|
-
'entityid': extraHeaders?.entityid || 'missing',
|
|
230
|
-
});
|
|
231
|
-
}
|
|
368
|
+
openConnection();
|
|
232
369
|
},
|
|
233
370
|
[]
|
|
234
371
|
);
|
|
235
372
|
|
|
236
373
|
const stop = useCallback(() => {
|
|
374
|
+
stopRequestedRef.current = true;
|
|
375
|
+
connectionIdRef.current += 1;
|
|
376
|
+
if (reconnectTimerRef.current) {
|
|
377
|
+
clearTimeout(reconnectTimerRef.current);
|
|
378
|
+
reconnectTimerRef.current = null;
|
|
379
|
+
}
|
|
380
|
+
if (connectWatchdogTimerRef.current) {
|
|
381
|
+
clearTimeout(connectWatchdogTimerRef.current);
|
|
382
|
+
connectWatchdogTimerRef.current = null;
|
|
383
|
+
}
|
|
237
384
|
if (xhrRef.current) {
|
|
238
385
|
xhrRef.current.abort();
|
|
239
386
|
xhrRef.current = null;
|
|
@@ -246,4 +393,4 @@ export function usePaymentSSE() {
|
|
|
246
393
|
}, []);
|
|
247
394
|
|
|
248
395
|
return { start, stop };
|
|
249
|
-
}
|
|
396
|
+
}
|
|
@@ -757,7 +757,29 @@ const FDCalculator: React.FC<FDCalculatorProps> = ({ onGoBack, onExitSDK, onPanR
|
|
|
757
757
|
? data?.sdrCalc?.[0]
|
|
758
758
|
: data?.fdrCalc?.[0];
|
|
759
759
|
const maturityAmount = calcData?.maturityAmount || data?.maturityAmount || data?.totalAmount || data?.finalAmount || data?.amount;
|
|
760
|
-
return maturityAmount ? `₹${maturityAmount.toLocaleString()}` : "";
|
|
760
|
+
return maturityAmount ? `₹${Number(maturityAmount).toLocaleString()}` : "";
|
|
761
|
+
})()}
|
|
762
|
+
intPayAmt={(() => {
|
|
763
|
+
const data = calculationResult?.data;
|
|
764
|
+
const investmentType = payoutValue === 'On Maturity' ? 'SDR' : 'FDR';
|
|
765
|
+
const calcData = investmentType === 'SDR'
|
|
766
|
+
? data?.sdrCalc?.[0]
|
|
767
|
+
: data?.fdrCalc?.[0];
|
|
768
|
+
const val = calcData?.intPayAmt;
|
|
769
|
+
if (val == null) return undefined;
|
|
770
|
+
const num = Number(val);
|
|
771
|
+
return Number.isFinite(num) ? `₹ ${num.toLocaleString()}` : String(val);
|
|
772
|
+
})()}
|
|
773
|
+
totalInterestEarnings={(() => {
|
|
774
|
+
const data = calculationResult?.data;
|
|
775
|
+
const investmentType = payoutValue === 'On Maturity' ? 'SDR' : 'FDR';
|
|
776
|
+
const calcData = investmentType === 'SDR'
|
|
777
|
+
? data?.sdrCalc?.[0]
|
|
778
|
+
: data?.fdrCalc?.[0];
|
|
779
|
+
const val = calcData?.totalInterestEarnings;
|
|
780
|
+
if (val == null) return undefined;
|
|
781
|
+
const num = Number(val);
|
|
782
|
+
return Number.isFinite(num) ? `₹ ${num.toLocaleString()}` : String(val);
|
|
761
783
|
})()}
|
|
762
784
|
>
|
|
763
785
|
<CheckboxOption
|