@akinon/pz-masterpass-rest 2.0.0-beta.12 → 2.0.0-beta.13
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/CHANGELOG.md +21 -0
- package/README.md +11 -1
- package/assets/masterpass-javascript-sdk-web.min.js +1 -0
- package/docs/USAGE.md +1176 -0
- package/package.json +3 -3
- package/src/components/credit-card-form.tsx +1 -1
- package/src/components/information-modal.tsx +139 -0
- package/src/components/otp-modal.tsx +94 -12
- package/src/hooks/useMasterpassAccount.ts +77 -37
- package/src/hooks/useMasterpassPayment.ts +248 -123
- package/src/hooks/useMasterpassToken.ts +61 -16
- package/src/redux/api.ts +16 -7
- package/src/redux/reducer.ts +14 -0
- package/src/services/account.ts +73 -64
- package/src/services/payment.ts +131 -93
- package/src/services/verify.ts +44 -44
- package/src/types/custom-render.types.ts +22 -2
- package/src/types/payment.types.ts +14 -0
- package/src/utils/payment-constants.ts +3 -3
- package/src/utils/payment-utils.ts +20 -4
- package/src/utils/response-handler.ts +35 -6
- package/src/utils/session-handler.ts +60 -0
- package/src/utils/validation-schemas.ts +1 -1
- package/src/views/masterpass-rest-option.tsx +55 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/pz-masterpass-rest",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.13",
|
|
4
4
|
"main": "src/index.ts",
|
|
5
5
|
"types": "src/index.d.ts",
|
|
6
6
|
"description": "Modern React-based Masterpass REST API integration package for ProjectZero e-commerce platform",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"yup": "0.32.11"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"react": "^18.2.0",
|
|
27
|
-
"react-dom": "^18.2.0",
|
|
26
|
+
"react": "^18.2.0 || ^19.0.0",
|
|
27
|
+
"react-dom": "^18.2.0 || ^19.0.0",
|
|
28
28
|
"react-redux": "^8.1.3",
|
|
29
29
|
"@reduxjs/toolkit": "^1.9.7"
|
|
30
30
|
},
|
|
@@ -50,7 +50,7 @@ const CreditCardForm: React.FC<CreditCardFormProps> = ({
|
|
|
50
50
|
setValue,
|
|
51
51
|
formState: { errors, isValid }
|
|
52
52
|
} = useForm<CreditCardFormData>({
|
|
53
|
-
resolver: yupResolver(validationSchema),
|
|
53
|
+
resolver: yupResolver(validationSchema) as any,
|
|
54
54
|
defaultValues: {
|
|
55
55
|
cardNumber: '',
|
|
56
56
|
expiryDate: '',
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Modal, Button } from '@akinon/next/components';
|
|
3
|
+
import mpBlackLogo from '../assets/img/masterpass-black-logo.png';
|
|
4
|
+
import type { InformationModalType, InformationModalData } from '../types/payment.types';
|
|
5
|
+
|
|
6
|
+
export interface InformationModalProps {
|
|
7
|
+
open: boolean;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
data: InformationModalData | null;
|
|
10
|
+
onButtonClick?: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const iconMap: Record<InformationModalType, React.ReactNode> = {
|
|
14
|
+
warning: (
|
|
15
|
+
<svg
|
|
16
|
+
className="w-8 h-8 text-amber-600"
|
|
17
|
+
fill="none"
|
|
18
|
+
stroke="currentColor"
|
|
19
|
+
viewBox="0 0 24 24"
|
|
20
|
+
>
|
|
21
|
+
<path
|
|
22
|
+
strokeLinecap="round"
|
|
23
|
+
strokeLinejoin="round"
|
|
24
|
+
strokeWidth={2}
|
|
25
|
+
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
26
|
+
/>
|
|
27
|
+
</svg>
|
|
28
|
+
),
|
|
29
|
+
error: (
|
|
30
|
+
<svg
|
|
31
|
+
className="w-8 h-8 text-red-600"
|
|
32
|
+
fill="none"
|
|
33
|
+
stroke="currentColor"
|
|
34
|
+
viewBox="0 0 24 24"
|
|
35
|
+
>
|
|
36
|
+
<path
|
|
37
|
+
strokeLinecap="round"
|
|
38
|
+
strokeLinejoin="round"
|
|
39
|
+
strokeWidth={2}
|
|
40
|
+
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
41
|
+
/>
|
|
42
|
+
</svg>
|
|
43
|
+
),
|
|
44
|
+
success: (
|
|
45
|
+
<svg
|
|
46
|
+
className="w-8 h-8 text-green-600"
|
|
47
|
+
fill="none"
|
|
48
|
+
stroke="currentColor"
|
|
49
|
+
viewBox="0 0 24 24"
|
|
50
|
+
>
|
|
51
|
+
<path
|
|
52
|
+
strokeLinecap="round"
|
|
53
|
+
strokeLinejoin="round"
|
|
54
|
+
strokeWidth={2}
|
|
55
|
+
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
56
|
+
/>
|
|
57
|
+
</svg>
|
|
58
|
+
),
|
|
59
|
+
info: (
|
|
60
|
+
<svg
|
|
61
|
+
className="w-8 h-8 text-blue-600"
|
|
62
|
+
fill="none"
|
|
63
|
+
stroke="currentColor"
|
|
64
|
+
viewBox="0 0 24 24"
|
|
65
|
+
>
|
|
66
|
+
<path
|
|
67
|
+
strokeLinecap="round"
|
|
68
|
+
strokeLinejoin="round"
|
|
69
|
+
strokeWidth={2}
|
|
70
|
+
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
71
|
+
/>
|
|
72
|
+
</svg>
|
|
73
|
+
)
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const bgColorMap: Record<InformationModalType, string> = {
|
|
77
|
+
warning: 'bg-amber-100',
|
|
78
|
+
error: 'bg-red-100',
|
|
79
|
+
success: 'bg-green-100',
|
|
80
|
+
info: 'bg-blue-100'
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const InformationModal: React.FC<InformationModalProps> = ({
|
|
84
|
+
open,
|
|
85
|
+
onClose,
|
|
86
|
+
data,
|
|
87
|
+
onButtonClick
|
|
88
|
+
}) => {
|
|
89
|
+
const handleButtonClick = () => {
|
|
90
|
+
if (onButtonClick) {
|
|
91
|
+
onButtonClick();
|
|
92
|
+
} else {
|
|
93
|
+
onClose();
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (!data) return null;
|
|
98
|
+
|
|
99
|
+
const { type, title, message, secondaryMessage, buttonText } = data;
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<Modal
|
|
103
|
+
portalId="masterpass-information-modal"
|
|
104
|
+
open={open}
|
|
105
|
+
setOpen={onClose}
|
|
106
|
+
title={
|
|
107
|
+
<img src={mpBlackLogo.src} alt="Masterpass" className="w-36 h-auto" />
|
|
108
|
+
}
|
|
109
|
+
className="w-full sm:w-[28rem] max-h-[90vh] overflow-y-auto"
|
|
110
|
+
>
|
|
111
|
+
<div className="px-6 py-4">
|
|
112
|
+
<div className="flex flex-col items-center text-center">
|
|
113
|
+
<div className={`w-16 h-16 rounded-full ${bgColorMap[type]} flex items-center justify-center mb-4`}>
|
|
114
|
+
{iconMap[type]}
|
|
115
|
+
</div>
|
|
116
|
+
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
|
117
|
+
{title}
|
|
118
|
+
</h3>
|
|
119
|
+
<p className="text-sm text-gray-600 mb-3">
|
|
120
|
+
{message}
|
|
121
|
+
</p>
|
|
122
|
+
{secondaryMessage && (
|
|
123
|
+
<p className="text-xs text-gray-500 mb-6">
|
|
124
|
+
{secondaryMessage}
|
|
125
|
+
</p>
|
|
126
|
+
)}
|
|
127
|
+
<Button
|
|
128
|
+
className="w-full py-3 h-auto"
|
|
129
|
+
onClick={handleButtonClick}
|
|
130
|
+
>
|
|
131
|
+
{buttonText}
|
|
132
|
+
</Button>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</Modal>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export default InformationModal;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
2
|
import { Modal, Button, Input } from '@akinon/next/components';
|
|
3
3
|
import { LoaderSpinner } from '@akinon/next/components';
|
|
4
|
+
import { useAppSelector } from '@akinon/next/redux/hooks';
|
|
4
5
|
import type { OTPModalProps } from '../types/custom-render.types';
|
|
5
6
|
import mpBlackLogo from '../assets/img/masterpass-black-logo.png';
|
|
6
7
|
import { handleResendOtp } from '../utils/response-handler';
|
|
@@ -11,9 +12,11 @@ const OTPModal: React.FC<OTPModalProps> = ({
|
|
|
11
12
|
onClose,
|
|
12
13
|
onSubmit,
|
|
13
14
|
type,
|
|
15
|
+
responseCode,
|
|
14
16
|
description,
|
|
15
17
|
texts
|
|
16
18
|
}) => {
|
|
19
|
+
const { token, tokenData } = useAppSelector((state) => state.masterpassRest);
|
|
17
20
|
const [otp, setOtp] = useState('');
|
|
18
21
|
const [isLoading, setIsLoading] = useState(false);
|
|
19
22
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -21,6 +24,13 @@ const OTPModal: React.FC<OTPModalProps> = ({
|
|
|
21
24
|
const [canResend, setCanResend] = useState(false);
|
|
22
25
|
const [remainingTime, setRemainingTime] = useState(90);
|
|
23
26
|
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!open) {
|
|
29
|
+
setError(null);
|
|
30
|
+
setOtp('');
|
|
31
|
+
}
|
|
32
|
+
}, [open]);
|
|
33
|
+
|
|
24
34
|
useEffect(() => {
|
|
25
35
|
if (open && type === 'OTP') {
|
|
26
36
|
setRemainingTime(90);
|
|
@@ -38,14 +48,16 @@ const OTPModal: React.FC<OTPModalProps> = ({
|
|
|
38
48
|
|
|
39
49
|
return () => clearInterval(timer);
|
|
40
50
|
}
|
|
41
|
-
}, [open, type]);
|
|
51
|
+
}, [open, type, responseCode]);
|
|
42
52
|
|
|
43
53
|
const handleSubmit = async () => {
|
|
44
54
|
setError(null);
|
|
45
55
|
setIsLoading(true);
|
|
46
56
|
try {
|
|
47
57
|
const result = await onSubmit(otp);
|
|
48
|
-
if (result?.
|
|
58
|
+
if (result?.requiresOTP) {
|
|
59
|
+
setOtp('');
|
|
60
|
+
} else if (result?.message) {
|
|
49
61
|
setError(result.message);
|
|
50
62
|
}
|
|
51
63
|
} catch (error) {
|
|
@@ -59,7 +71,7 @@ const OTPModal: React.FC<OTPModalProps> = ({
|
|
|
59
71
|
setError(null);
|
|
60
72
|
setIsResending(true);
|
|
61
73
|
try {
|
|
62
|
-
const result = await handleResendOtp();
|
|
74
|
+
const result = await handleResendOtp(token, tokenData?.MerchantId);
|
|
63
75
|
if (result.success) {
|
|
64
76
|
setRemainingTime(90);
|
|
65
77
|
setCanResend(false);
|
|
@@ -90,7 +102,47 @@ const OTPModal: React.FC<OTPModalProps> = ({
|
|
|
90
102
|
return `${minutes}:${secs.toString().padStart(2, '0')}`;
|
|
91
103
|
};
|
|
92
104
|
|
|
105
|
+
const getContentByResponseCode = () => {
|
|
106
|
+
switch (responseCode) {
|
|
107
|
+
case '5000':
|
|
108
|
+
return {
|
|
109
|
+
title: texts.rtaVerificationTitle,
|
|
110
|
+
description: texts.rtaVerificationDescription,
|
|
111
|
+
placeholder: texts.rtaVerificationPlaceholder,
|
|
112
|
+
helperText: null
|
|
113
|
+
};
|
|
114
|
+
case '5001':
|
|
115
|
+
return {
|
|
116
|
+
title: texts.bankOtpVerificationTitle,
|
|
117
|
+
description: texts.bankOtpVerificationDescription,
|
|
118
|
+
placeholder: texts.bankOtpVerificationPlaceholder,
|
|
119
|
+
helperText: null
|
|
120
|
+
};
|
|
121
|
+
case '5008':
|
|
122
|
+
return {
|
|
123
|
+
title: texts.phoneVerificationTitle,
|
|
124
|
+
description: texts.phoneVerificationDescription,
|
|
125
|
+
placeholder: texts.phoneVerificationPlaceholder,
|
|
126
|
+
helperText: null
|
|
127
|
+
};
|
|
128
|
+
case '5013':
|
|
129
|
+
return {
|
|
130
|
+
title: texts.cvvVerificationTitle,
|
|
131
|
+
description: texts.cvvVerificationDescription,
|
|
132
|
+
placeholder: texts.cvvVerificationPlaceholder,
|
|
133
|
+
helperText: texts.cvvVerificationHelperText
|
|
134
|
+
};
|
|
135
|
+
default:
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
93
140
|
const getDescription = () => {
|
|
141
|
+
const responseCodeContent = getContentByResponseCode();
|
|
142
|
+
if (responseCodeContent) {
|
|
143
|
+
return responseCodeContent.description;
|
|
144
|
+
}
|
|
145
|
+
|
|
94
146
|
if (description) {
|
|
95
147
|
return description;
|
|
96
148
|
}
|
|
@@ -104,15 +156,40 @@ const OTPModal: React.FC<OTPModalProps> = ({
|
|
|
104
156
|
}
|
|
105
157
|
};
|
|
106
158
|
|
|
107
|
-
const
|
|
159
|
+
const getTitle = () => {
|
|
160
|
+
const responseCodeContent = getContentByResponseCode();
|
|
161
|
+
if (responseCodeContent) {
|
|
162
|
+
return responseCodeContent.title;
|
|
163
|
+
}
|
|
164
|
+
|
|
108
165
|
switch (type) {
|
|
109
166
|
case 'RTA':
|
|
110
|
-
return
|
|
167
|
+
return texts.rtaVerificationTitle;
|
|
111
168
|
case 'CVV':
|
|
112
|
-
return
|
|
169
|
+
return texts.cvvVerificationTitle;
|
|
113
170
|
default:
|
|
114
|
-
return
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const getPlaceholder = () => {
|
|
176
|
+
const responseCodeContent = getContentByResponseCode();
|
|
177
|
+
if (responseCodeContent) {
|
|
178
|
+
return responseCodeContent.placeholder;
|
|
115
179
|
}
|
|
180
|
+
return '';
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const getHelperText = () => {
|
|
184
|
+
const responseCodeContent = getContentByResponseCode();
|
|
185
|
+
return responseCodeContent?.helperText || null;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const getMaxLength = () => {
|
|
189
|
+
if (responseCode === '5013' || type === 'CVV') {
|
|
190
|
+
return 4;
|
|
191
|
+
}
|
|
192
|
+
return 6;
|
|
116
193
|
};
|
|
117
194
|
|
|
118
195
|
return (
|
|
@@ -126,27 +203,32 @@ const OTPModal: React.FC<OTPModalProps> = ({
|
|
|
126
203
|
className="w-full sm:w-[28rem] max-h-[90vh] overflow-y-auto"
|
|
127
204
|
>
|
|
128
205
|
<div className="px-6">
|
|
129
|
-
|
|
206
|
+
{getTitle() && (
|
|
207
|
+
<h3 className="text-center mt-4 text-lg font-semibold">{getTitle()}</h3>
|
|
208
|
+
)}
|
|
209
|
+
<p className="text-center mt-2 text-sm text-gray-600">{getDescription()}</p>
|
|
130
210
|
<div className="flex flex-col gap-3 p-5 w-3/4 m-auto">
|
|
131
211
|
<Input
|
|
132
212
|
type="text"
|
|
133
213
|
value={otp}
|
|
134
214
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
135
|
-
setOtp(
|
|
136
|
-
e.target.value.replace(/\D/g, '').slice(0, getMaxLength())
|
|
137
|
-
);
|
|
215
|
+
setOtp(e.target.value.replace(/\D/g, '').slice(0, getMaxLength()));
|
|
138
216
|
setError(null);
|
|
139
217
|
}}
|
|
140
218
|
maxLength={getMaxLength()}
|
|
141
219
|
pattern="[0-9]*"
|
|
142
220
|
inputMode="numeric"
|
|
143
221
|
className="text-center"
|
|
222
|
+
placeholder={getPlaceholder()}
|
|
144
223
|
/>
|
|
224
|
+
{getHelperText() && (
|
|
225
|
+
<p className="text-xs text-gray-500 text-center">{getHelperText()}</p>
|
|
226
|
+
)}
|
|
145
227
|
{error && <p className="text-error text-xs text-center">{error}</p>}
|
|
146
228
|
<Button
|
|
147
229
|
className="py-3 h-auto"
|
|
148
230
|
onClick={handleSubmit}
|
|
149
|
-
disabled={isLoading || otp.length
|
|
231
|
+
disabled={isLoading || otp.length === 0}
|
|
150
232
|
>
|
|
151
233
|
{isLoading ? (
|
|
152
234
|
<LoaderSpinner className="w-4 h-4" />
|
|
@@ -39,16 +39,16 @@ export const useMasterpassAccount = () => {
|
|
|
39
39
|
} = useAppSelector((state) => state.masterpassRest);
|
|
40
40
|
|
|
41
41
|
const initializeAccount = useCallback(
|
|
42
|
-
async (
|
|
43
|
-
if (!
|
|
42
|
+
async (newTokenData: any, rawToken?: string) => {
|
|
43
|
+
if (!newTokenData?.AccountKey) return;
|
|
44
44
|
|
|
45
|
-
const accountService = new AccountService();
|
|
45
|
+
const accountService = new AccountService(rawToken || token, newTokenData?.MerchantId);
|
|
46
46
|
|
|
47
47
|
try {
|
|
48
48
|
const response = await accountService.accountAccess({
|
|
49
|
-
accountKey:
|
|
49
|
+
accountKey: newTokenData.AccountKey,
|
|
50
50
|
accountKeyType: 'Msisdn',
|
|
51
|
-
userId:
|
|
51
|
+
userId: newTokenData.UserId
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
if (response.statusCode !== 200 && response.exception) {
|
|
@@ -99,12 +99,12 @@ export const useMasterpassAccount = () => {
|
|
|
99
99
|
);
|
|
100
100
|
}
|
|
101
101
|
},
|
|
102
|
-
[dispatch]
|
|
102
|
+
[dispatch, token]
|
|
103
103
|
);
|
|
104
104
|
|
|
105
105
|
const onTokenReady = useCallback(
|
|
106
|
-
(newTokenData: any) => {
|
|
107
|
-
initializeAccount(newTokenData);
|
|
106
|
+
(newTokenData: any, rawToken?: string) => {
|
|
107
|
+
initializeAccount(newTokenData, rawToken);
|
|
108
108
|
},
|
|
109
109
|
[initializeAccount]
|
|
110
110
|
);
|
|
@@ -126,15 +126,26 @@ export const useMasterpassAccount = () => {
|
|
|
126
126
|
dispatch(resetState());
|
|
127
127
|
}, [dispatch]);
|
|
128
128
|
|
|
129
|
-
const refreshToken = async () => {
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
const refreshToken = async (options?: { three_d?: boolean; skipReset?: boolean }) => {
|
|
130
|
+
if (options?.three_d !== false && !options?.skipReset) {
|
|
131
|
+
resetData();
|
|
132
|
+
}
|
|
133
|
+
const result = await refetch(options);
|
|
134
|
+
if (result?.data?.token) {
|
|
135
|
+
const decodedToken = window.Utils.decodeJwt(result.data.token);
|
|
136
|
+
return {
|
|
137
|
+
tokenData: decodedToken,
|
|
138
|
+
token: result.data.token,
|
|
139
|
+
merchantId: decodedToken?.MerchantId
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
132
143
|
};
|
|
133
144
|
|
|
134
145
|
const refreshAccountData = useCallback(async () => {
|
|
135
146
|
if (!tokenData?.AccountKey) return;
|
|
136
147
|
|
|
137
|
-
const accountService = new AccountService();
|
|
148
|
+
const accountService = new AccountService(token, tokenData?.MerchantId);
|
|
138
149
|
const response = await accountService.accountAccess({
|
|
139
150
|
accountKey: tokenData.AccountKey,
|
|
140
151
|
accountKeyType: 'Msisdn',
|
|
@@ -153,15 +164,20 @@ export const useMasterpassAccount = () => {
|
|
|
153
164
|
})
|
|
154
165
|
);
|
|
155
166
|
}
|
|
156
|
-
}, [tokenData?.AccountKey, tokenData?.UserId, dispatch]);
|
|
167
|
+
}, [tokenData?.AccountKey, tokenData?.UserId, tokenData?.MerchantId, token, dispatch]);
|
|
157
168
|
|
|
158
169
|
const handleLinkConfirm = useCallback(async () => {
|
|
159
|
-
if (!tokenData?.AccountKey) return;
|
|
160
|
-
|
|
161
170
|
try {
|
|
162
|
-
const
|
|
171
|
+
const freshResult = await refreshToken({ three_d: false });
|
|
172
|
+
const activeTokenData = freshResult?.tokenData || tokenData;
|
|
173
|
+
const activeToken = freshResult?.token || token;
|
|
174
|
+
const activeMerchantId = freshResult?.merchantId || tokenData?.MerchantId;
|
|
175
|
+
|
|
176
|
+
if (!activeTokenData?.AccountKey) return;
|
|
177
|
+
|
|
178
|
+
const accountService = new AccountService(activeToken, activeMerchantId);
|
|
163
179
|
const response = await accountService.linkToMerchant({
|
|
164
|
-
accountKey:
|
|
180
|
+
accountKey: activeTokenData.AccountKey
|
|
165
181
|
});
|
|
166
182
|
|
|
167
183
|
const result = await handleMasterpassResponse(response);
|
|
@@ -177,7 +193,6 @@ export const useMasterpassAccount = () => {
|
|
|
177
193
|
);
|
|
178
194
|
} else if (result.success) {
|
|
179
195
|
dispatch(setShowLinkModal(false));
|
|
180
|
-
await refreshToken();
|
|
181
196
|
await refreshAccountData();
|
|
182
197
|
} else {
|
|
183
198
|
dispatch(setShowLinkModal(false));
|
|
@@ -185,17 +200,34 @@ export const useMasterpassAccount = () => {
|
|
|
185
200
|
} catch (error) {
|
|
186
201
|
dispatch(setShowLinkModal(false));
|
|
187
202
|
}
|
|
188
|
-
}, [tokenData
|
|
203
|
+
}, [tokenData, token, dispatch, refreshToken, refreshAccountData]);
|
|
189
204
|
|
|
190
205
|
const handleOTPSubmit = useCallback(
|
|
191
|
-
async (otp: string) => {
|
|
192
|
-
const result = await handleVerification(otp);
|
|
206
|
+
async (otp: string, texts?: any) => {
|
|
207
|
+
const result = await handleVerification(otp, token, tokenData?.MerchantId);
|
|
193
208
|
|
|
194
209
|
if (result.success) {
|
|
195
210
|
dispatch(setShowOTPModal(false));
|
|
196
|
-
await refreshToken();
|
|
197
211
|
await refreshAccountData();
|
|
198
212
|
return { success: true };
|
|
213
|
+
} else if (result.sessionExpired) {
|
|
214
|
+
dispatch(setShowOTPModal(false));
|
|
215
|
+
dispatch(
|
|
216
|
+
updateModalState({
|
|
217
|
+
showInformationModal: true,
|
|
218
|
+
informationModalData: {
|
|
219
|
+
type: 'warning',
|
|
220
|
+
title: texts?.sessionExpiredTitle || 'Session Expired',
|
|
221
|
+
message: texts?.sessionExpiredMessage || 'Your session has expired due to inactivity. Please restart the process to continue.',
|
|
222
|
+
secondaryMessage: texts?.sessionExpiredSecondaryMessage || 'For security reasons, verification codes are only valid for a limited time.',
|
|
223
|
+
buttonText: texts?.sessionExpiredButton || 'Start Again'
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
);
|
|
227
|
+
return {
|
|
228
|
+
success: false,
|
|
229
|
+
sessionExpired: true
|
|
230
|
+
};
|
|
199
231
|
} else if (result.requires3D && result.redirectUrl) {
|
|
200
232
|
dispatch(setShowOTPModal(false));
|
|
201
233
|
window.location.href = result.redirectUrl;
|
|
@@ -216,10 +248,7 @@ export const useMasterpassAccount = () => {
|
|
|
216
248
|
return {
|
|
217
249
|
success: false,
|
|
218
250
|
requiresOTP: true,
|
|
219
|
-
otpType: result.otpType
|
|
220
|
-
message:
|
|
221
|
-
result.message ||
|
|
222
|
-
'Additional verification required. Please enter the new OTP code.'
|
|
251
|
+
otpType: result.otpType
|
|
223
252
|
};
|
|
224
253
|
} else {
|
|
225
254
|
return {
|
|
@@ -228,7 +257,7 @@ export const useMasterpassAccount = () => {
|
|
|
228
257
|
};
|
|
229
258
|
}
|
|
230
259
|
},
|
|
231
|
-
[dispatch, refreshAccountData,
|
|
260
|
+
[dispatch, refreshAccountData, token, tokenData?.MerchantId]
|
|
232
261
|
);
|
|
233
262
|
|
|
234
263
|
const handleRemoveCard = useCallback(
|
|
@@ -245,7 +274,7 @@ export const useMasterpassAccount = () => {
|
|
|
245
274
|
dispatch(setRemovingCardId(cardToDelete.uniqueCardNumber));
|
|
246
275
|
|
|
247
276
|
try {
|
|
248
|
-
const accountService = new AccountService();
|
|
277
|
+
const accountService = new AccountService(token, tokenData?.MerchantId);
|
|
249
278
|
const response = await accountService.removeCard({
|
|
250
279
|
accountKey: tokenData.AccountKey,
|
|
251
280
|
cardAlias: cardToDelete.cardAlias
|
|
@@ -276,6 +305,8 @@ export const useMasterpassAccount = () => {
|
|
|
276
305
|
}, [
|
|
277
306
|
modalState.cardToDelete,
|
|
278
307
|
tokenData?.AccountKey,
|
|
308
|
+
tokenData?.MerchantId,
|
|
309
|
+
token,
|
|
279
310
|
dispatch,
|
|
280
311
|
refreshAccountData
|
|
281
312
|
]);
|
|
@@ -288,10 +319,6 @@ export const useMasterpassAccount = () => {
|
|
|
288
319
|
cvv: string;
|
|
289
320
|
cardAlias: string;
|
|
290
321
|
}) => {
|
|
291
|
-
if (!tokenData?.AccountKey) {
|
|
292
|
-
throw new Error('Account key not available');
|
|
293
|
-
}
|
|
294
|
-
|
|
295
322
|
const finalCardData = cardData || {
|
|
296
323
|
cardNumber: newCardFormData.cardNumber || '',
|
|
297
324
|
cardholderName: newCardFormData.cardholderName || '',
|
|
@@ -301,7 +328,17 @@ export const useMasterpassAccount = () => {
|
|
|
301
328
|
};
|
|
302
329
|
|
|
303
330
|
try {
|
|
304
|
-
const
|
|
331
|
+
const freshResult = await refreshToken({ three_d: false });
|
|
332
|
+
|
|
333
|
+
const activeTokenData = freshResult?.tokenData || tokenData;
|
|
334
|
+
const activeToken = freshResult?.token || token;
|
|
335
|
+
const activeMerchantId = freshResult?.merchantId || tokenData?.MerchantId;
|
|
336
|
+
|
|
337
|
+
if (!activeTokenData?.AccountKey) {
|
|
338
|
+
throw new Error('Token data not available');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const accountService = new AccountService(activeToken, activeMerchantId);
|
|
305
342
|
|
|
306
343
|
const timestamp = Date.now().toString().slice(-10);
|
|
307
344
|
const random = Math.floor(Math.random() * 1000)
|
|
@@ -314,16 +351,16 @@ export const useMasterpassAccount = () => {
|
|
|
314
351
|
|
|
315
352
|
const response = await accountService.addCard({
|
|
316
353
|
requestReferenceNumber,
|
|
317
|
-
accountKey:
|
|
354
|
+
accountKey: activeTokenData.AccountKey,
|
|
318
355
|
accountKeyType: 'Msisdn',
|
|
319
356
|
cardNumber: finalCardData.cardNumber.replace(/\D/g, ''),
|
|
320
357
|
cardHolderName: finalCardData.cardholderName,
|
|
321
358
|
expiryDate: formattedExpiryDate,
|
|
322
359
|
cvv: finalCardData.cvv,
|
|
323
360
|
accountAliasName: finalCardData.cardAlias,
|
|
324
|
-
userId:
|
|
325
|
-
isMsisdnValidatedByMerchant:
|
|
326
|
-
authenticationMethod:
|
|
361
|
+
userId: activeTokenData.UserId,
|
|
362
|
+
isMsisdnValidatedByMerchant: activeTokenData.IsMsisdnValidated === 'True',
|
|
363
|
+
authenticationMethod: activeTokenData.AuthenticationMethod
|
|
327
364
|
});
|
|
328
365
|
|
|
329
366
|
const result = await handleMasterpassResponse(response);
|
|
@@ -344,6 +381,7 @@ export const useMasterpassAccount = () => {
|
|
|
344
381
|
|
|
345
382
|
return result;
|
|
346
383
|
} else if (result.success) {
|
|
384
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
347
385
|
await refreshAccountData();
|
|
348
386
|
return { success: true, data: result.data };
|
|
349
387
|
}
|
|
@@ -372,6 +410,7 @@ export const useMasterpassAccount = () => {
|
|
|
372
410
|
tokenData?.AuthenticationMethod,
|
|
373
411
|
newCardFormData,
|
|
374
412
|
refreshAccountData,
|
|
413
|
+
refreshToken,
|
|
375
414
|
dispatch
|
|
376
415
|
]
|
|
377
416
|
);
|
|
@@ -388,6 +427,7 @@ export const useMasterpassAccount = () => {
|
|
|
388
427
|
updateModalState: updateModalStateAction,
|
|
389
428
|
initializeAccount,
|
|
390
429
|
refreshAccountData,
|
|
430
|
+
refreshToken,
|
|
391
431
|
handleLinkConfirm,
|
|
392
432
|
handleOTPSubmit,
|
|
393
433
|
handleRemoveCard,
|