@finspringinnovations/fdsdk 0.0.3 → 0.0.5
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/api/baseApi.js +17 -6
- package/lib/api/masterDataApi.js +21 -1
- package/lib/api/workflowApi.js +46 -1
- package/lib/assets/images/images.d.ts +1 -0
- package/lib/assets/images/images.js +1 -0
- package/lib/components/FDCard.js +9 -0
- package/lib/hooks/usePaymentSSE.d.ts +7 -1
- package/lib/hooks/usePaymentSSE.js +101 -12
- package/lib/navigation/RootNavigator.d.ts +1 -0
- package/lib/navigation/RootNavigator.js +12 -4
- package/lib/navigation/index.d.ts +1 -0
- package/lib/navigation/index.js +2 -2
- package/lib/screens/AadhaarVerification.js +2 -2
- package/lib/screens/FDCalculator.d.ts +1 -0
- package/lib/screens/FDCalculator.js +11 -4
- package/lib/screens/FDList.js +37 -64
- package/lib/screens/Payment.js +42 -28
- package/lib/screens/PaymentStatus.js +24 -26
- package/lib/screens/ReviewKYC.js +45 -97
- package/package.json +1 -1
- package/src/api/baseApi.ts +19 -6
- package/src/api/masterDataApi.ts +21 -3
- package/src/api/workflowApi.ts +50 -1
- package/src/assets/images/images.js +1 -0
- package/src/assets/images/shriram.png +0 -0
- package/src/components/FDCard.tsx +15 -0
- package/src/hooks/usePaymentSSE.ts +109 -15
- package/src/navigation/RootNavigator.tsx +12 -3
- package/src/navigation/index.tsx +3 -1
- package/src/screens/AadhaarVerification.tsx +2 -2
- package/src/screens/FDCalculator.tsx +12 -4
- package/src/screens/FDList.tsx +37 -76
- package/src/screens/Payment.tsx +45 -35
- package/src/screens/PaymentStatus.tsx +25 -40
- package/src/screens/ReviewKYC.tsx +94 -170
package/src/api/masterDataApi.ts
CHANGED
|
@@ -12,17 +12,35 @@ export const masterDataApi = baseApi.injectEndpoints({
|
|
|
12
12
|
const request = {
|
|
13
13
|
url: 'masterdata',
|
|
14
14
|
method: 'GET' as const,
|
|
15
|
+
cache: 'no-store' as const,
|
|
15
16
|
headers: {
|
|
16
17
|
workflowInstanceId: '{{workflowInstanceId}}',
|
|
17
18
|
'x-api-key': '{{X-API-KEY}}',
|
|
18
19
|
encryptdecrypt: 'false',
|
|
20
|
+
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
21
|
+
Pragma: 'no-cache',
|
|
22
|
+
Expires: '0',
|
|
19
23
|
provider: providerId || '{{shriramprovider}}',
|
|
20
24
|
},
|
|
21
25
|
};
|
|
22
26
|
return request;
|
|
23
27
|
},
|
|
24
|
-
transformResponse: (response) => {
|
|
25
|
-
return response;
|
|
28
|
+
transformResponse: (response: any) => {
|
|
29
|
+
if (response == null) return response;
|
|
30
|
+
// If API returns a JSON string, parse it
|
|
31
|
+
let data = response;
|
|
32
|
+
if (typeof response === 'string') {
|
|
33
|
+
try {
|
|
34
|
+
data = JSON.parse(response);
|
|
35
|
+
} catch {
|
|
36
|
+
return response;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Unwrap { data: ... } so consumers get a consistent shape
|
|
40
|
+
if (typeof data === 'object' && data !== null && 'data' in data && Object.keys(data).length === 1) {
|
|
41
|
+
return data.data;
|
|
42
|
+
}
|
|
43
|
+
return data;
|
|
26
44
|
},
|
|
27
45
|
}),
|
|
28
46
|
}),
|
|
@@ -31,4 +49,4 @@ export const masterDataApi = baseApi.injectEndpoints({
|
|
|
31
49
|
|
|
32
50
|
export const {
|
|
33
51
|
useGetMasterDataQuery
|
|
34
|
-
} = masterDataApi;
|
|
52
|
+
} = masterDataApi;
|
package/src/api/workflowApi.ts
CHANGED
|
@@ -9,6 +9,38 @@ export interface TerminateWorkflowRequest {
|
|
|
9
9
|
|
|
10
10
|
export type TerminateWorkflowResponse = any; // Replace with concrete type when available
|
|
11
11
|
|
|
12
|
+
const TASK_ID_TO_CAPTION: Record<string, string> = {
|
|
13
|
+
'11042': 'SHRIRAM_V1_S1_T1_START',
|
|
14
|
+
'11043': 'SHRIRAM_V1_S1_T2_GET_PERSONAL_DETAILS_AND_BASIC_FD_INFO',
|
|
15
|
+
'11044': 'SHRIRAM_V1_S1_T3_END',
|
|
16
|
+
'11050': 'SHRIRAM_V1_S2_T1_KYC_START',
|
|
17
|
+
'11051': 'SHRIRAM_V1_S2_T2_CHECK_FOR_PAN_RAPID_STATUS',
|
|
18
|
+
'11052': 'SHRIRAM_V1_S2_T3_CHECK_IF_AADHAAR_ALREADY_VERIFIED',
|
|
19
|
+
'11045': 'SHRIRAM_V1_S2_T4_SEND_AADHAR_OTP',
|
|
20
|
+
'11046': 'SHRIRAM_V1_S2_T5_CHECK_FOR_OTP',
|
|
21
|
+
'11047': 'SHRIRAM_V1_S2_T6_VALIDATE_AADHAR_OTP',
|
|
22
|
+
'11053': 'SHRIRAM_V1_S2_T7_AADHAAR_VERIFIED',
|
|
23
|
+
'11049': 'SHRIRAM_V1_S2_T8_TERMINATE_TASK_AND_WORKFLOW_AFTER_KYC_RETRIES',
|
|
24
|
+
'11048': 'SHRIRAM_V1_S2_T9_KYC_END',
|
|
25
|
+
'11054': 'SHRIRAM_V1_S3_T1_OCCUPATION_START',
|
|
26
|
+
'11055': 'SHRIRAM_V1_S3_T2_CAPTURE_OCCUPATION_DETAILS',
|
|
27
|
+
'11056': 'SHRIRAM_V1_S3_T3_OCCUPATION_END',
|
|
28
|
+
'11057': 'SHRIRAM_V1_S4_T1_NOMINEE_START',
|
|
29
|
+
'11058': 'SHRIRAM_V1_S4_T2_CAPTURE_NOMINEE_DETAILS',
|
|
30
|
+
'11059': 'SHRIRAM_V1_S4_T3_NOMINEE_END',
|
|
31
|
+
'11060': 'SHRIRAM_V1_S5_T1_BANK_DETAILS_START',
|
|
32
|
+
'11061': 'SHRIRAM_V1_S5_T2_CAPTURE_BANK_DETAILS',
|
|
33
|
+
'11062': 'SHRIRAM_V1_S5_T3_BANK_DETAILS_END',
|
|
34
|
+
'11063': 'SHRIRAM_V1_S6_T1_FD_CREATION_START',
|
|
35
|
+
'11064': 'SHRIRAM_V1_S6_T2_CREATE_FD',
|
|
36
|
+
'11065': 'SHRIRAM_V1_S6_T3_FD_CREATION_END',
|
|
37
|
+
'11069': 'SHRIRAM_V1_S7_T1_PAYMENT_START',
|
|
38
|
+
'11068': 'SHRIRAM_V1_S7_T2_PAYMENT',
|
|
39
|
+
'11067': 'SHRIRAM_V1_S7_T3_CHECK_FOR_PAYMENT_STATUS',
|
|
40
|
+
'11070': 'SHRIRAM_V1_S7_T4_TERMINATE_TASK_AND_WORKFLOW_AFTER_PAYMENT_RETRIES',
|
|
41
|
+
'11066': 'SHRIRAM_V1_S7_T5_PAYMENT_END',
|
|
42
|
+
};
|
|
43
|
+
|
|
12
44
|
export const workflowApi = baseApi.injectEndpoints({
|
|
13
45
|
endpoints: (builder) => ({
|
|
14
46
|
terminateWorkflow: builder.mutation<
|
|
@@ -37,11 +69,28 @@ export const workflowApi = baseApi.injectEndpoints({
|
|
|
37
69
|
updateTask: builder.mutation<any, any>({
|
|
38
70
|
query: (body) => {
|
|
39
71
|
const { providerId, workflowInstanceId, userreferenceid, applicationid, entityid, ...requestBody } = body;
|
|
72
|
+
const normalizedRequestBody = { ...requestBody } as Record<string, any>;
|
|
73
|
+
|
|
74
|
+
// Backend now expects caption-based field name.
|
|
75
|
+
// Keep compatibility if any caller still passes targetTaskId.
|
|
76
|
+
if (!normalizedRequestBody.targetTaskCaption && normalizedRequestBody.targetTaskId) {
|
|
77
|
+
normalizedRequestBody.targetTaskCaption = normalizedRequestBody.targetTaskId;
|
|
78
|
+
delete normalizedRequestBody.targetTaskId;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Normalize numeric task IDs to caption values if provided.
|
|
82
|
+
if (normalizedRequestBody.targetTaskCaption !== undefined && normalizedRequestBody.targetTaskCaption !== null) {
|
|
83
|
+
const key = String(normalizedRequestBody.targetTaskCaption).trim();
|
|
84
|
+
if (TASK_ID_TO_CAPTION[key]) {
|
|
85
|
+
normalizedRequestBody.targetTaskCaption = TASK_ID_TO_CAPTION[key];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
40
89
|
return {
|
|
41
90
|
url: 'taskflow/update',
|
|
42
91
|
method: 'POST',
|
|
43
92
|
body: {
|
|
44
|
-
...
|
|
93
|
+
...normalizedRequestBody,
|
|
45
94
|
workflowInstanceId: workflowInstanceId,
|
|
46
95
|
},
|
|
47
96
|
headers: {
|
|
Binary file
|
|
@@ -5,10 +5,12 @@ import {
|
|
|
5
5
|
StyleSheet,
|
|
6
6
|
TouchableOpacity,
|
|
7
7
|
Platform,
|
|
8
|
+
Image,
|
|
8
9
|
} from 'react-native';
|
|
9
10
|
import { useColors, useTypography, useSpacing, useShadows, useTheme } from '../theme/ThemeContext';
|
|
10
11
|
import { getBorderColor } from '../theme';
|
|
11
12
|
import type { ColorScheme, ThemeName } from '../theme';
|
|
13
|
+
import { base64Images } from '../constants/strings/base64Images';
|
|
12
14
|
|
|
13
15
|
interface FDCardProps {
|
|
14
16
|
id: string;
|
|
@@ -74,6 +76,13 @@ const FDCard: React.FC<FDCardProps> = ({
|
|
|
74
76
|
activeOpacity={0.8}
|
|
75
77
|
>
|
|
76
78
|
<View style={styles.fdCardHeader}>
|
|
79
|
+
<View>
|
|
80
|
+
<Image
|
|
81
|
+
source={{ uri: base64Images.shriramLogo }}
|
|
82
|
+
style={styles.logo}
|
|
83
|
+
resizeMode="contain"
|
|
84
|
+
/>
|
|
85
|
+
</View>
|
|
77
86
|
<Text style={[styles.fdName, customStyles.name]}>{name}</Text>
|
|
78
87
|
</View>
|
|
79
88
|
|
|
@@ -160,6 +169,12 @@ const createStyles = (colors: ColorScheme, typography: any, spacing: any, shadow
|
|
|
160
169
|
roiText: {
|
|
161
170
|
color: colors.success,
|
|
162
171
|
},
|
|
172
|
+
logo: {
|
|
173
|
+
width: 25,
|
|
174
|
+
height: 25,
|
|
175
|
+
marginRight: 5,
|
|
176
|
+
borderRadius: 15,
|
|
177
|
+
},
|
|
163
178
|
});
|
|
164
179
|
|
|
165
180
|
export default FDCard;
|
|
@@ -14,9 +14,11 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { useCallback, useRef } from 'react';
|
|
17
|
-
import { parseSSEBuffer
|
|
18
|
-
import { getEnvironmentData } from '../config/appDataConfig';
|
|
19
|
-
import { getApiConfig } from '../config/apiConfig';
|
|
17
|
+
import { parseSSEBuffer } from '../utils/sseParser';
|
|
18
|
+
import { getEnvironmentData, getUserInfoForAPI } from '../config/appDataConfig';
|
|
19
|
+
import { getApiConfig, getSecureHeaders } from '../config/apiConfig';
|
|
20
|
+
import { decryptResponse } from '../utils/encryption';
|
|
21
|
+
import { getEncryptionConfig } from '../config/encryptionConfig';
|
|
20
22
|
|
|
21
23
|
// -- Types --
|
|
22
24
|
|
|
@@ -26,6 +28,13 @@ interface UsePaymentSSECallbacks {
|
|
|
26
28
|
onConnected?: () => void;
|
|
27
29
|
}
|
|
28
30
|
|
|
31
|
+
export interface PaymentSSEExtraHeaders {
|
|
32
|
+
workflowInstanceId?: string;
|
|
33
|
+
applicationId?: string;
|
|
34
|
+
entityid?: string;
|
|
35
|
+
providerId?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
29
38
|
// -- Hook --
|
|
30
39
|
|
|
31
40
|
export function usePaymentSSE() {
|
|
@@ -37,7 +46,7 @@ export function usePaymentSSE() {
|
|
|
37
46
|
const bufferRef = useRef<{ value: string }>({ value: '' });
|
|
38
47
|
|
|
39
48
|
const start = useCallback(
|
|
40
|
-
(applicationId: string, callbacks: UsePaymentSSECallbacks) => {
|
|
49
|
+
(applicationId: string, callbacks: UsePaymentSSECallbacks, extraHeaders?: PaymentSSEExtraHeaders) => {
|
|
41
50
|
// Clean up any existing connection first
|
|
42
51
|
if (xhrRef.current) {
|
|
43
52
|
xhrRef.current.abort();
|
|
@@ -51,8 +60,13 @@ export function usePaymentSSE() {
|
|
|
51
60
|
// 1. Build the SSE endpoint URL
|
|
52
61
|
const envData = getEnvironmentData();
|
|
53
62
|
const apiConfig = getApiConfig();
|
|
54
|
-
const baseUrl = envData?.apiBaseUrl || apiConfig?.baseUrl || '';
|
|
55
|
-
const url = `${baseUrl}/events?applicationId=${encodeURIComponent(applicationId)}
|
|
63
|
+
const baseUrl = (envData?.apiBaseUrl || apiConfig?.baseUrl || '').replace(/\/$/, '');
|
|
64
|
+
const url = baseUrl ? `${baseUrl}/events?applicationId=${encodeURIComponent(applicationId)}` : '';
|
|
65
|
+
|
|
66
|
+
if (!url) {
|
|
67
|
+
if (__DEV__) console.warn('[usePaymentSSE] No base URL, skipping SSE');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
56
70
|
|
|
57
71
|
// 2. Create XMLHttpRequest
|
|
58
72
|
const xhr = new XMLHttpRequest();
|
|
@@ -65,8 +79,50 @@ export function usePaymentSSE() {
|
|
|
65
79
|
if (apiKey) {
|
|
66
80
|
xhr.setRequestHeader('x-api-key', apiKey);
|
|
67
81
|
}
|
|
82
|
+
|
|
83
|
+
// Add secure headers (Content-Type, User-Agent, etc.)
|
|
84
|
+
const secureHeaders = getSecureHeaders();
|
|
85
|
+
Object.entries(secureHeaders).forEach(([key, value]) => {
|
|
86
|
+
if (key.toLowerCase() !== 'content-type') {
|
|
87
|
+
xhr.setRequestHeader(key, value);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// SSE-specific headers
|
|
68
92
|
xhr.setRequestHeader('Accept', 'text/event-stream');
|
|
69
|
-
xhr.setRequestHeader('Cache-Control', 'no-cache');
|
|
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);
|
|
104
|
+
}
|
|
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
|
+
}
|
|
70
126
|
|
|
71
127
|
// 4. Handle incoming data
|
|
72
128
|
xhr.onprogress = () => {
|
|
@@ -80,17 +136,40 @@ export function usePaymentSSE() {
|
|
|
80
136
|
// Parse new text into SSE events
|
|
81
137
|
const events = parseSSEBuffer(bufferRef.current, newText);
|
|
82
138
|
|
|
83
|
-
// Handle each event
|
|
139
|
+
// Handle each event (support encrypted payload like web)
|
|
140
|
+
const handleEventData = async (rawData: string) => {
|
|
141
|
+
try {
|
|
142
|
+
const parsed = JSON.parse(rawData || '{}') as Record<string, unknown>;
|
|
143
|
+
let payload = parsed;
|
|
144
|
+
if (typeof parsed?.encryptedResponse === 'string' && parsed.encryptedResponse) {
|
|
145
|
+
try {
|
|
146
|
+
const config = getEncryptionConfig();
|
|
147
|
+
const decrypted = await decryptResponse(
|
|
148
|
+
{ encryptedResponse: parsed.encryptedResponse },
|
|
149
|
+
config
|
|
150
|
+
);
|
|
151
|
+
payload = (decrypted || {}) as Record<string, unknown>;
|
|
152
|
+
} catch {
|
|
153
|
+
if (__DEV__) console.warn('[usePaymentSSE] Decrypt failed, using raw');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
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
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
84
163
|
for (const { eventType, data } of events) {
|
|
164
|
+
if (!data) continue;
|
|
85
165
|
if (eventType === 'fd_payment_status') {
|
|
166
|
+
handleEventData(data);
|
|
167
|
+
} else if (eventType === 'message') {
|
|
86
168
|
try {
|
|
87
|
-
const parsed = JSON.parse(data);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (__DEV__) {
|
|
92
|
-
console.error('[usePaymentSSE] Failed to parse event data:', parseError);
|
|
93
|
-
}
|
|
169
|
+
const parsed = JSON.parse(data) as { event?: string; [k: string]: unknown };
|
|
170
|
+
if (parsed?.event === 'fd_payment_status') handleEventData(data);
|
|
171
|
+
} catch {
|
|
172
|
+
// ignore
|
|
94
173
|
}
|
|
95
174
|
}
|
|
96
175
|
}
|
|
@@ -134,6 +213,21 @@ export function usePaymentSSE() {
|
|
|
134
213
|
|
|
135
214
|
if (__DEV__) {
|
|
136
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
|
+
});
|
|
137
231
|
}
|
|
138
232
|
},
|
|
139
233
|
[]
|
|
@@ -27,11 +27,13 @@ const Stack = createStackNavigator<RootStackParamList>();
|
|
|
27
27
|
interface RootNavigatorProps {
|
|
28
28
|
config?: SDKNavigationConfig;
|
|
29
29
|
onExit?: (fdDetails?: any) => void;
|
|
30
|
+
onPanRequired?: () => void;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
const RootNavigator: React.FC<RootNavigatorProps> = ({
|
|
33
34
|
config = {},
|
|
34
|
-
onExit
|
|
35
|
+
onExit,
|
|
36
|
+
onPanRequired,
|
|
35
37
|
}) => {
|
|
36
38
|
// Handle Android hardware back button
|
|
37
39
|
useEffect(() => {
|
|
@@ -151,6 +153,7 @@ const RootNavigator: React.FC<RootNavigatorProps> = ({
|
|
|
151
153
|
<FDCalculatorScreen
|
|
152
154
|
onGoBack={() => goBack()}
|
|
153
155
|
onExitSDK={() => onExit?.()}
|
|
156
|
+
onPanRequired={onPanRequired} // <── add this
|
|
154
157
|
onNavigateToReviewKYC={() => navigate('ReviewKYC', { fdData: props.route.params?.fdData })}
|
|
155
158
|
fdData={(props.route.params as any)?.fdData}
|
|
156
159
|
{...props}
|
|
@@ -309,15 +312,21 @@ const RootNavigator: React.FC<RootNavigatorProps> = ({
|
|
|
309
312
|
onGoBack={() => goBack()}
|
|
310
313
|
paymentUrl={(getPaymentSession().paymentUrl) || ''}
|
|
311
314
|
onPaymentSuccess={(data) => {
|
|
315
|
+
const payload = data && typeof data === 'object' ? data : {};
|
|
316
|
+
const transactionId = (payload as any)?.transactionId ?? (payload as any)?.transaction_id ?? (payload as any)?.TransactionId;
|
|
312
317
|
navigate('PaymentStatus', {
|
|
313
318
|
status: 'success',
|
|
314
|
-
paymentData: data
|
|
319
|
+
paymentData: data,
|
|
320
|
+
transactionId,
|
|
315
321
|
} as any);
|
|
316
322
|
}}
|
|
317
323
|
onPaymentFailure={(error) => {
|
|
324
|
+
const payload = error && typeof error === 'object' ? error : {};
|
|
325
|
+
const transactionId = (payload as any)?.transactionId ?? (payload as any)?.transaction_id ?? (payload as any)?.TransactionId;
|
|
318
326
|
navigate('PaymentStatus', {
|
|
319
327
|
status: 'failed',
|
|
320
|
-
paymentData: error
|
|
328
|
+
paymentData: error,
|
|
329
|
+
transactionId,
|
|
321
330
|
} as any);
|
|
322
331
|
}}
|
|
323
332
|
onPaymentPending={(info) => {
|
package/src/navigation/index.tsx
CHANGED
|
@@ -14,6 +14,7 @@ import { getSDKColors, type CustomColors } from '../config/appDataConfig';
|
|
|
14
14
|
interface SDKNavigationContainerProps {
|
|
15
15
|
config?: SDKNavigationConfig;
|
|
16
16
|
onExit?: (fdDetails?: any) => void;
|
|
17
|
+
onPanRequired?: () => void;
|
|
17
18
|
children?: React.ReactNode;
|
|
18
19
|
theme?: ThemeName | Theme;
|
|
19
20
|
colors?: CustomColors; // Custom color overrides (prop-level, highest priority)
|
|
@@ -24,6 +25,7 @@ interface SDKNavigationContainerProps {
|
|
|
24
25
|
export const SDKNavigationContainer: React.FC<SDKNavigationContainerProps> = ({
|
|
25
26
|
config,
|
|
26
27
|
onExit,
|
|
28
|
+
onPanRequired,
|
|
27
29
|
children,
|
|
28
30
|
theme,
|
|
29
31
|
colors: propColors,
|
|
@@ -37,7 +39,7 @@ export const SDKNavigationContainer: React.FC<SDKNavigationContainerProps> = ({
|
|
|
37
39
|
|
|
38
40
|
// Choose navigator based on flag
|
|
39
41
|
const navigator = useReactNavigation
|
|
40
|
-
? <RootNavigator config={config} onExit={onExit} />
|
|
42
|
+
? <RootNavigator config={config} onExit={onExit} onPanRequired={onPanRequired} />
|
|
41
43
|
: <SimpleNavigator config={config as SimpleNavigationConfig} onExit={onExit} />;
|
|
42
44
|
|
|
43
45
|
const content = children || (
|
|
@@ -295,7 +295,7 @@ const AadhaarVerification: React.FC<AadhaarVerificationProps> = ({
|
|
|
295
295
|
applicationid: applicationId,
|
|
296
296
|
entityid: entityId,
|
|
297
297
|
// Request params/body
|
|
298
|
-
|
|
298
|
+
targetTaskCaption: WORKFLOW_TASKS.VALIDATE_OTP,
|
|
299
299
|
}).unwrap();
|
|
300
300
|
setIsValidateOtpTaskCalled(true);
|
|
301
301
|
} else {
|
|
@@ -357,7 +357,7 @@ const AadhaarVerification: React.FC<AadhaarVerificationProps> = ({
|
|
|
357
357
|
applicationid: applicationId,
|
|
358
358
|
entityid: entityId,
|
|
359
359
|
// Request params/body
|
|
360
|
-
|
|
360
|
+
targetTaskCaption: WORKFLOW_TASKS.RESEND_OTP,
|
|
361
361
|
}).unwrap();
|
|
362
362
|
|
|
363
363
|
|
|
@@ -42,6 +42,7 @@ import { base64Images } from '../constants/strings/base64Images';
|
|
|
42
42
|
export interface FDCalculatorProps {
|
|
43
43
|
onGoBack?: () => void;
|
|
44
44
|
onExitSDK?: () => void;
|
|
45
|
+
onPanRequired?: () => void;
|
|
45
46
|
onNavigateToReviewKYC?: () => void;
|
|
46
47
|
fdData?: {
|
|
47
48
|
id: string;
|
|
@@ -56,7 +57,7 @@ export interface FDCalculatorProps {
|
|
|
56
57
|
};
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
const FDCalculator: React.FC<FDCalculatorProps> = ({ onGoBack, onExitSDK, onNavigateToReviewKYC, fdData }) => {
|
|
60
|
+
const FDCalculator: React.FC<FDCalculatorProps> = ({ onGoBack, onExitSDK, onPanRequired, onNavigateToReviewKYC, fdData }) => {
|
|
60
61
|
const typography = useTypography();
|
|
61
62
|
const colors = useColors();
|
|
62
63
|
const { themeName } = useTheme();
|
|
@@ -139,7 +140,12 @@ const FDCalculator: React.FC<FDCalculatorProps> = ({ onGoBack, onExitSDK, onNavi
|
|
|
139
140
|
|
|
140
141
|
const { data: fallbackMasterData, isLoading: isLoadingFallback, error: fallbackError, refetch: refetchMasterData } = useGetMasterDataQuery(
|
|
141
142
|
{ providerId: defaultProviderId }, // Use default provider ID
|
|
142
|
-
{
|
|
143
|
+
{
|
|
144
|
+
skip: !!masterData || !defaultProviderId,
|
|
145
|
+
refetchOnMountOrArgChange: true,
|
|
146
|
+
refetchOnFocus: true,
|
|
147
|
+
refetchOnReconnect: true,
|
|
148
|
+
}
|
|
143
149
|
);
|
|
144
150
|
|
|
145
151
|
// Use fallback master data if global master data is not available
|
|
@@ -376,7 +382,9 @@ const FDCalculator: React.FC<FDCalculatorProps> = ({ onGoBack, onExitSDK, onNavi
|
|
|
376
382
|
{
|
|
377
383
|
text: 'OK',
|
|
378
384
|
onPress: () => {
|
|
379
|
-
if (
|
|
385
|
+
if (onPanRequired) {
|
|
386
|
+
onPanRequired();
|
|
387
|
+
} else if (onExitSDK) {
|
|
380
388
|
onExitSDK();
|
|
381
389
|
}
|
|
382
390
|
},
|
|
@@ -1008,4 +1016,4 @@ const createStyles = (typography: any, colors: any, themeName: string) => StyleS
|
|
|
1008
1016
|
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
|
1009
1017
|
zIndex: 1000,
|
|
1010
1018
|
},
|
|
1011
|
-
});
|
|
1019
|
+
});
|
package/src/screens/FDList.tsx
CHANGED
|
@@ -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
|
-
{
|
|
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
|
|
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 (
|
|
500
|
-
const activeOnly =
|
|
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
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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,
|
|
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
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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;
|