@akinon/pz-masterpass-rest 2.0.0-beta.12 → 2.0.0-beta.14
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 +23 -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
|
@@ -4,6 +4,16 @@ import type {
|
|
|
4
4
|
TransactionType,
|
|
5
5
|
DirectPaymentRequest
|
|
6
6
|
} from '../types/payment.types';
|
|
7
|
+
import type { MasterpassRestTokenData } from '../redux/reducer';
|
|
8
|
+
|
|
9
|
+
export const isTokenExpired = (
|
|
10
|
+
tokenData: MasterpassRestTokenData | null,
|
|
11
|
+
bufferSeconds = 60
|
|
12
|
+
): boolean => {
|
|
13
|
+
if (!tokenData?.exp) return true;
|
|
14
|
+
const bufferMs = bufferSeconds * 1000;
|
|
15
|
+
return Date.now() >= tokenData.exp * 1000 - bufferMs;
|
|
16
|
+
};
|
|
7
17
|
|
|
8
18
|
export const enhance3DSecureUrl = (originalUrl: string): string => {
|
|
9
19
|
try {
|
|
@@ -38,6 +48,8 @@ export const createPaymentRequest = (params: {
|
|
|
38
48
|
authenticationMethod: string;
|
|
39
49
|
secure3DModel: string;
|
|
40
50
|
terminalGroupId: string;
|
|
51
|
+
acquirerIcaNumber: string;
|
|
52
|
+
additionalFields?: any;
|
|
41
53
|
}): PaymentProcessRequest => {
|
|
42
54
|
return {
|
|
43
55
|
requestReferenceNo: params.orderNo,
|
|
@@ -48,11 +60,12 @@ export const createPaymentRequest = (params: {
|
|
|
48
60
|
orderNo: params.orderNo,
|
|
49
61
|
currencyCode: params.currencyCode,
|
|
50
62
|
paymentType: PAYMENT_CONSTANTS.PAYMENT_CONFIG.PAYMENT_TYPE,
|
|
51
|
-
acquirerIcaNumber:
|
|
63
|
+
acquirerIcaNumber: params.acquirerIcaNumber,
|
|
52
64
|
installmentCount: params.installmentCount,
|
|
53
65
|
authenticationMethod: params.authenticationMethod,
|
|
54
66
|
secure3DModel: params.secure3DModel,
|
|
55
|
-
terminalGroupId: params.terminalGroupId
|
|
67
|
+
terminalGroupId: params.terminalGroupId,
|
|
68
|
+
additionalFields: params.additionalFields || {}
|
|
56
69
|
};
|
|
57
70
|
};
|
|
58
71
|
|
|
@@ -70,6 +83,8 @@ export const createDirectPaymentRequest = (params: {
|
|
|
70
83
|
authenticationMethod: string;
|
|
71
84
|
secure3DModel: string;
|
|
72
85
|
terminalGroupId: string;
|
|
86
|
+
acquirerIcaNumber: string;
|
|
87
|
+
additionalFields?: any;
|
|
73
88
|
}): DirectPaymentRequest => {
|
|
74
89
|
const timestamp = Date.now().toString().slice(-10);
|
|
75
90
|
const random = Math.floor(Math.random() * 1000)
|
|
@@ -91,10 +106,11 @@ export const createDirectPaymentRequest = (params: {
|
|
|
91
106
|
orderNo: params.orderNo,
|
|
92
107
|
currencyCode: params.currencyCode,
|
|
93
108
|
paymentType: PAYMENT_CONSTANTS.PAYMENT_CONFIG.PAYMENT_TYPE,
|
|
94
|
-
acquirerIcaNumber:
|
|
109
|
+
acquirerIcaNumber: params.acquirerIcaNumber,
|
|
95
110
|
installmentCount: params.installmentCount,
|
|
96
111
|
authenticationMethod: params.authenticationMethod,
|
|
97
112
|
secure3DModel: params.secure3DModel,
|
|
98
|
-
terminalGroupId: params.terminalGroupId
|
|
113
|
+
terminalGroupId: params.terminalGroupId,
|
|
114
|
+
additionalFields: params.additionalFields || {}
|
|
99
115
|
};
|
|
100
116
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { VerifyService } from '../services/verify';
|
|
2
2
|
import { enhance3DSecureUrl } from './payment-utils';
|
|
3
3
|
import { PAYMENT_CONSTANTS } from './payment-constants';
|
|
4
|
+
import { store } from 'redux/store';
|
|
5
|
+
import { updateModalState } from '../redux/reducer';
|
|
4
6
|
|
|
5
7
|
export type ResponseHandlerResult = {
|
|
6
8
|
success: boolean;
|
|
@@ -8,6 +10,7 @@ export type ResponseHandlerResult = {
|
|
|
8
10
|
requires3D?: boolean;
|
|
9
11
|
requiresCVV?: boolean;
|
|
10
12
|
requiresRTA?: boolean;
|
|
13
|
+
sessionExpired?: boolean;
|
|
11
14
|
otpType?: 'OTP' | 'RTA' | 'CVV';
|
|
12
15
|
data?: any;
|
|
13
16
|
message?: string;
|
|
@@ -83,16 +86,20 @@ export const handleMasterpassResponse = async (
|
|
|
83
86
|
message:
|
|
84
87
|
response.exception?.message ||
|
|
85
88
|
response.error ||
|
|
89
|
+
response.description ||
|
|
90
|
+
response.result?.description ||
|
|
86
91
|
'Unknown error occurred'
|
|
87
92
|
};
|
|
88
93
|
}
|
|
89
94
|
};
|
|
90
95
|
|
|
91
96
|
export const handleVerification = async (
|
|
92
|
-
otp: string
|
|
97
|
+
otp: string,
|
|
98
|
+
token?: string,
|
|
99
|
+
merchantId?: string
|
|
93
100
|
): Promise<ResponseHandlerResult> => {
|
|
94
101
|
try {
|
|
95
|
-
const verifyService = new VerifyService();
|
|
102
|
+
const verifyService = new VerifyService(token, merchantId);
|
|
96
103
|
const response = await verifyService.verifyOtp({ otpCode: otp });
|
|
97
104
|
|
|
98
105
|
if (response.statusCode === 200) {
|
|
@@ -162,6 +169,15 @@ export const handleVerification = async (
|
|
|
162
169
|
}
|
|
163
170
|
}
|
|
164
171
|
|
|
172
|
+
const exceptionCode = response.exception?.code;
|
|
173
|
+
if (exceptionCode === PAYMENT_CONSTANTS.EXCEPTION_CODES.TOKEN_HAS_EXPIRED) {
|
|
174
|
+
return {
|
|
175
|
+
success: false,
|
|
176
|
+
sessionExpired: true,
|
|
177
|
+
message: response.exception?.message || 'Session has expired'
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
165
181
|
return {
|
|
166
182
|
success: false,
|
|
167
183
|
message:
|
|
@@ -178,12 +194,25 @@ export const handleVerification = async (
|
|
|
178
194
|
}
|
|
179
195
|
};
|
|
180
196
|
|
|
181
|
-
export const handleResendOtp = async (
|
|
197
|
+
export const handleResendOtp = async (
|
|
198
|
+
token?: string,
|
|
199
|
+
merchantId?: string
|
|
200
|
+
): Promise<ResponseHandlerResult> => {
|
|
182
201
|
try {
|
|
183
|
-
const verifyService = new VerifyService();
|
|
184
|
-
const
|
|
202
|
+
const verifyService = new VerifyService(token, merchantId);
|
|
203
|
+
const state = store.getState();
|
|
204
|
+
const otpToken = state.masterpassRest.modalState.verificationData?.result?.token;
|
|
205
|
+
|
|
206
|
+
const response = await verifyService.resendOtp(otpToken);
|
|
207
|
+
|
|
208
|
+
if (response.statusCode === 200 || response.statusCode === 202) {
|
|
209
|
+
const newToken = response.result?.token;
|
|
210
|
+
if (newToken) {
|
|
211
|
+
store.dispatch(updateModalState({
|
|
212
|
+
verificationData: response
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
185
215
|
|
|
186
|
-
if (response.statusCode === 200) {
|
|
187
216
|
return {
|
|
188
217
|
success: true,
|
|
189
218
|
data: response,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export const createSessionExpiredResponse = <T>(additionalProps: Partial<T> = {}): T => ({
|
|
2
|
+
statusCode: 401,
|
|
3
|
+
version: '',
|
|
4
|
+
buildId: '',
|
|
5
|
+
message: 'Session has expired',
|
|
6
|
+
correlationId: '',
|
|
7
|
+
requestId: '',
|
|
8
|
+
result: null as any,
|
|
9
|
+
exception: {
|
|
10
|
+
code: 'TOKEN_HAS_EXPIRED',
|
|
11
|
+
message: 'Session has expired',
|
|
12
|
+
level: 'error',
|
|
13
|
+
validationErrors: [],
|
|
14
|
+
details: []
|
|
15
|
+
},
|
|
16
|
+
...additionalProps
|
|
17
|
+
} as T);
|
|
18
|
+
|
|
19
|
+
export const withUnhandledRejectionHandler = <T>(
|
|
20
|
+
promiseFactory: () => Promise<T>,
|
|
21
|
+
fallbackValue: T
|
|
22
|
+
): Promise<T> => {
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
let resolved = false;
|
|
25
|
+
|
|
26
|
+
const handleRejection = (event: PromiseRejectionEvent) => {
|
|
27
|
+
if (!resolved && event.reason instanceof TypeError &&
|
|
28
|
+
event.reason.message === 'Failed to fetch') {
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
resolved = true;
|
|
31
|
+
resolve(fallbackValue);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
window.addEventListener('unhandledrejection', handleRejection);
|
|
36
|
+
|
|
37
|
+
promiseFactory()
|
|
38
|
+
.then((result) => {
|
|
39
|
+
if (!resolved) {
|
|
40
|
+
resolved = true;
|
|
41
|
+
resolve(result);
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
.catch((error) => {
|
|
45
|
+
if (!resolved) {
|
|
46
|
+
resolved = true;
|
|
47
|
+
if (error instanceof TypeError && error.message === 'Failed to fetch') {
|
|
48
|
+
resolve(fallbackValue);
|
|
49
|
+
} else {
|
|
50
|
+
resolve(fallbackValue);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
.finally(() => {
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
window.removeEventListener('unhandledrejection', handleRejection);
|
|
57
|
+
}, 100);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
};
|
|
@@ -63,7 +63,7 @@ export const createCreditCardFormSchema = (
|
|
|
63
63
|
.required(texts.cardholderNameRequiredText)
|
|
64
64
|
.min(2, texts.cardholderNameTooShortText)
|
|
65
65
|
.max(50, texts.cardholderNameTooLongText)
|
|
66
|
-
.matches(/^[a-zA-Z
|
|
66
|
+
.matches(/^[a-zA-ZçÇğĞıİöÖşŞüÜ\s]+$/, texts.cardholderNameInvalidText);
|
|
67
67
|
|
|
68
68
|
return yup.object({
|
|
69
69
|
cardNumber: creditCardNumberSchema,
|
|
@@ -28,6 +28,7 @@ import ConfirmationModal from '../components/confirmation-modal';
|
|
|
28
28
|
import InstallmentList from '../components/installment-list';
|
|
29
29
|
import CreditCardForm from '../components/credit-card-form';
|
|
30
30
|
import PaymentMethodSelector from '../components/payment-method-selector';
|
|
31
|
+
import InformationModal from '../components/information-modal';
|
|
31
32
|
import mpBlackLogo from '../assets/img/masterpass-black-logo.png';
|
|
32
33
|
|
|
33
34
|
interface MasterpassRestOptionProps {
|
|
@@ -136,17 +137,28 @@ const defaultTexts: MasterpassRestOptionTexts = {
|
|
|
136
137
|
linkAccountButton: 'Yes, I want it',
|
|
137
138
|
linkModalCancelButton: 'No, I don\'t want it',
|
|
138
139
|
whatIsMasterpassLink: 'What is Masterpass?',
|
|
139
|
-
rtaVerificationTitle: '
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
140
|
+
rtaVerificationTitle: 'Security Verification',
|
|
141
|
+
rtaVerificationDescription: 'To continue with your transaction, please enter the verification code sent to your phone.',
|
|
142
|
+
rtaVerificationPlaceholder: 'Verification Code',
|
|
143
|
+
bankOtpVerificationTitle: 'Bank Authorization Required',
|
|
144
|
+
bankOtpVerificationDescription: 'Please enter the one-time password sent by your bank to complete the verification.',
|
|
145
|
+
bankOtpVerificationPlaceholder: 'Bank OTP Code',
|
|
146
|
+
phoneVerificationTitle: 'Verify Your Phone Number',
|
|
147
|
+
phoneVerificationDescription: 'Enter the verification code sent to your phone to confirm your Masterpass account.',
|
|
148
|
+
phoneVerificationPlaceholder: 'SMS Verification Code',
|
|
149
|
+
cvvVerificationTitle: 'Card Security Code Required',
|
|
150
|
+
cvvVerificationDescription: 'Please enter the 3-digit security code (CVV) located on the back of your card to continue.',
|
|
151
|
+
cvvVerificationPlaceholder: 'CVV',
|
|
152
|
+
cvvVerificationHelperText: 'The CVV is the 3-digit number on the back of your card.',
|
|
153
|
+
otpVerificationDescription: 'Please enter the verification code sent to your phone.',
|
|
146
154
|
verifyButton: 'Verify',
|
|
147
155
|
otpModalCancelButton: 'Cancel',
|
|
148
156
|
otpModalErrorText: 'An unexpected error occurred. Please try again.',
|
|
149
|
-
otpModalResendOtpButton: 'Resend OTP'
|
|
157
|
+
otpModalResendOtpButton: 'Resend OTP',
|
|
158
|
+
sessionExpiredTitle: 'Session Expired',
|
|
159
|
+
sessionExpiredMessage: 'Your session has expired due to inactivity. Please restart the process to continue.',
|
|
160
|
+
sessionExpiredSecondaryMessage: 'For security reasons, sessions are only valid for a limited time.',
|
|
161
|
+
sessionExpiredButton: 'Start Again'
|
|
150
162
|
};
|
|
151
163
|
|
|
152
164
|
const mergeTexts = (
|
|
@@ -205,7 +217,6 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
205
217
|
} = useMasterpassPayment();
|
|
206
218
|
|
|
207
219
|
const {
|
|
208
|
-
tokenData,
|
|
209
220
|
accountData,
|
|
210
221
|
updateModalState,
|
|
211
222
|
handleLinkConfirm,
|
|
@@ -250,6 +261,8 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
250
261
|
setPaymentMethod('new_card');
|
|
251
262
|
} else if (hasCards) {
|
|
252
263
|
setPaymentMethod('stored_card');
|
|
264
|
+
} else {
|
|
265
|
+
setPaymentMethod('new_card');
|
|
253
266
|
}
|
|
254
267
|
}, [accountStatus?.isAccountNotLinked, accountStatus?.shouldShowDirectForm, accountData?.result?.cards]);
|
|
255
268
|
|
|
@@ -371,30 +384,32 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
371
384
|
const handleProceedToPayment = useCallback(async () => {
|
|
372
385
|
try {
|
|
373
386
|
dispatch(clearAllErrors());
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}
|
|
390
|
-
|
|
387
|
+
|
|
388
|
+
const result = paymentMethod === 'new_card'
|
|
389
|
+
? await processDirectPayment()
|
|
390
|
+
: await processPayment();
|
|
391
|
+
|
|
392
|
+
if (result.requiresRedirect) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (result.requiresOTP && result.otpType) {
|
|
397
|
+
updateModalState({
|
|
398
|
+
showOTPModal: true,
|
|
399
|
+
otpType: result.otpType
|
|
400
|
+
});
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (!result.success && result.message) {
|
|
405
|
+
dispatch(setError(result.message));
|
|
391
406
|
}
|
|
392
407
|
} catch (error) {
|
|
393
408
|
const errorMessage =
|
|
394
409
|
error instanceof Error ? error.message : mergedTexts.paymentFailedText;
|
|
395
410
|
dispatch(setError(errorMessage));
|
|
396
411
|
}
|
|
397
|
-
}, [dispatch, paymentMethod, processDirectPayment, processPayment,
|
|
412
|
+
}, [dispatch, paymentMethod, processDirectPayment, processPayment, updateModalState, mergedTexts.paymentFailedText]);
|
|
398
413
|
|
|
399
414
|
const handleOTPSubmitWithErrorHandling = useCallback(async (otp: string) => {
|
|
400
415
|
if (!handleOTPSubmit)
|
|
@@ -403,10 +418,10 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
403
418
|
message: mergedTexts.otpHandlerNotAvailableText
|
|
404
419
|
};
|
|
405
420
|
|
|
406
|
-
const result = await handleOTPSubmit(otp);
|
|
421
|
+
const result = await handleOTPSubmit(otp, mergedTexts);
|
|
407
422
|
|
|
408
423
|
return result;
|
|
409
|
-
}, [handleOTPSubmit, mergedTexts
|
|
424
|
+
}, [handleOTPSubmit, mergedTexts]);
|
|
410
425
|
|
|
411
426
|
const handleCardSelectWithErrorHandling = useCallback(async (card: any) => {
|
|
412
427
|
try {
|
|
@@ -501,6 +516,7 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
501
516
|
hasStoredCards,
|
|
502
517
|
shouldShowDirectForm,
|
|
503
518
|
cvc,
|
|
519
|
+
error,
|
|
504
520
|
|
|
505
521
|
accountData,
|
|
506
522
|
accountStatus,
|
|
@@ -518,7 +534,7 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
518
534
|
handleInstallmentSelect: handleInstallmentSelectWithErrorHandling,
|
|
519
535
|
handleProceedToPayment,
|
|
520
536
|
handleLinkConfirm,
|
|
521
|
-
handleOTPSubmit: handleOTPSubmit || fallbackOTPSubmit,
|
|
537
|
+
handleOTPSubmit: handleOTPSubmitWithErrorHandling || handleOTPSubmit || fallbackOTPSubmit,
|
|
522
538
|
onCloseError,
|
|
523
539
|
|
|
524
540
|
isCheckoutLoading,
|
|
@@ -534,6 +550,7 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
534
550
|
hasStoredCards,
|
|
535
551
|
shouldShowDirectForm,
|
|
536
552
|
cvc,
|
|
553
|
+
error,
|
|
537
554
|
accountData,
|
|
538
555
|
accountStatus,
|
|
539
556
|
modalState,
|
|
@@ -822,6 +839,7 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
822
839
|
onClose: () => updateModalState({ showOTPModal: false }),
|
|
823
840
|
onSubmit: handleOTPSubmitWithErrorHandling,
|
|
824
841
|
type: modalState?.otpType ?? 'OTP',
|
|
842
|
+
responseCode: modalState?.verificationData?.result?.responseCode,
|
|
825
843
|
description: modalState?.verificationData?.result?.description,
|
|
826
844
|
texts: mergedTexts
|
|
827
845
|
})
|
|
@@ -831,6 +849,7 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
831
849
|
onClose={() => updateModalState({ showOTPModal: false })}
|
|
832
850
|
onSubmit={handleOTPSubmitWithErrorHandling}
|
|
833
851
|
type={modalState?.otpType ?? 'OTP'}
|
|
852
|
+
responseCode={modalState?.verificationData?.result?.responseCode}
|
|
834
853
|
description={modalState?.verificationData?.result?.description}
|
|
835
854
|
texts={mergedTexts}
|
|
836
855
|
/>
|
|
@@ -869,6 +888,12 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
869
888
|
texts={mergedTexts}
|
|
870
889
|
/>
|
|
871
890
|
)}
|
|
891
|
+
|
|
892
|
+
<InformationModal
|
|
893
|
+
open={modalState?.showInformationModal ?? false}
|
|
894
|
+
onClose={() => updateModalState({ showInformationModal: false, informationModalData: null })}
|
|
895
|
+
data={modalState?.informationModalData}
|
|
896
|
+
/>
|
|
872
897
|
</div>
|
|
873
898
|
);
|
|
874
899
|
};
|