@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.
@@ -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: PAYMENT_CONSTANTS.PAYMENT_CONFIG.ACQUIRER_ICA_NUMBER,
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: PAYMENT_CONSTANTS.PAYMENT_CONFIG.ACQUIRER_ICA_NUMBER,
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 (): Promise<ResponseHandlerResult> => {
197
+ export const handleResendOtp = async (
198
+ token?: string,
199
+ merchantId?: string
200
+ ): Promise<ResponseHandlerResult> => {
182
201
  try {
183
- const verifyService = new VerifyService();
184
- const response = await verifyService.resendOtp();
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\s]+$/, texts.cardholderNameInvalidText);
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: 'RTA Verification',
140
- cvvVerificationTitle: 'CVV Verification',
141
- rtaVerificationDescription:
142
- 'Please enter the 3-digit RTA verification code sent to your phone',
143
- cvvVerificationDescription: 'Please enter your card CVV code',
144
- otpVerificationDescription:
145
- 'Please enter the 6-digit verification code sent to your phone',
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
- if (paymentMethod === 'new_card') {
375
- const result = await processDirectPayment(tokenData);
376
-
377
- if (result.success) {
378
- dispatch(setError('Payment completed successfully!'));
379
- } else if (result.requires3D && result.redirectUrl) {
380
- window.location.assign(result.redirectUrl);
381
- } else if (result.requiresOTP && result.otpType) {
382
- updateModalState({
383
- showOTPModal: true,
384
- otpType: result.otpType
385
- });
386
- } else if (result.message) {
387
- dispatch(setError(result.message));
388
- }
389
- } else {
390
- await processPayment(tokenData);
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, tokenData, updateModalState, mergedTexts.paymentFailedText]);
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.otpHandlerNotAvailableText]);
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
  };