@akinon/pz-masterpass-rest 2.0.56-rc.0 → 2.0.56

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.
@@ -0,0 +1,255 @@
1
+ import React from 'react';
2
+ import { renderHook, act } from '@testing-library/react';
3
+ import { Provider } from 'react-redux';
4
+ import { configureStore } from '@reduxjs/toolkit';
5
+
6
+ import masterpassRestReducer from '../redux/reducer';
7
+ import { getPaymentPending, setPaymentPending } from '../utils/payment-utils';
8
+
9
+ const prepareMock = jest.fn();
10
+ const finalizeMock = jest.fn();
11
+ const binNumberMock = jest.fn();
12
+ const installmentMock = jest.fn();
13
+ const processPaymentMock = jest.fn();
14
+ const directPaymentMock = jest.fn();
15
+ const handleResponseMock = jest.fn();
16
+
17
+ const asMutation = (fn: jest.Mock) => () =>
18
+ [(arg: unknown) => ({ unwrap: () => fn(arg) }), { isLoading: false }];
19
+
20
+ jest.mock('../redux/api', () => ({
21
+ usePrepareMasterpassOrderMutation: asMutation(prepareMock),
22
+ useFinalizeMasterpassOrderMutation: asMutation(finalizeMock),
23
+ useSetMasterpassRestBinNumberMutation: asMutation(binNumberMock),
24
+ useCheckoutMasterpassInstallmentMutation: asMutation(installmentMock),
25
+ useQueryMasterpassRewardsMutation: asMutation(jest.fn()),
26
+ useSelectMasterpassRewardsMutation: asMutation(jest.fn())
27
+ }));
28
+
29
+ jest.mock('../services/payment', () => ({
30
+ PaymentService: class {
31
+ processPayment = processPaymentMock;
32
+ directPayment = directPaymentMock;
33
+ }
34
+ }));
35
+
36
+ jest.mock('../utils/response-handler', () => ({
37
+ handleMasterpassResponse: (...args: unknown[]) => handleResponseMock(...args)
38
+ }));
39
+
40
+ jest.mock('./useMasterpassToken', () => ({
41
+ useMasterpassToken: () => ({
42
+ refetch: jest.fn(async () => ({ data: { token: 'fresh-token' } }))
43
+ })
44
+ }));
45
+
46
+ // Imported after the mocks so the hook picks them up.
47
+ import { useMasterpassPayment } from './useMasterpassPayment';
48
+
49
+ const PREPARED_ORDER = {
50
+ pre_order: { total_amount_with_interest: '100.00' },
51
+ context_list: [{ page_context: { order_no: 'ORDER-1', extras: {} } }]
52
+ };
53
+
54
+ const SELECTED_CARD = { cardAlias: 'my card', cardBin: '454671' };
55
+ const SELECTED_INSTALLMENT = { pk: 1, installment_count: 1 };
56
+
57
+ const NEW_CARD_FORM = {
58
+ cardNumber: '4546 7112 3456 7890',
59
+ cardholderName: 'Test User',
60
+ expiryDate: '12/30',
61
+ cvv: '123'
62
+ };
63
+
64
+ const renderPaymentHook = ({ withNewCardForm = false } = {}) => {
65
+ const store = configureStore({
66
+ reducer: { masterpassRest: masterpassRestReducer },
67
+ middleware: (getDefault) => getDefault({ serializableCheck: false })
68
+ });
69
+
70
+ store.dispatch({
71
+ type: 'masterpassRest/updatePaymentState',
72
+ payload: {
73
+ selectedCard: SELECTED_CARD,
74
+ selectedInstallment: SELECTED_INSTALLMENT,
75
+ cvc: '123',
76
+ useThreeD: true
77
+ }
78
+ });
79
+
80
+ if (withNewCardForm) {
81
+ store.dispatch({
82
+ type: 'masterpassRest/setNewCardFormData',
83
+ payload: NEW_CARD_FORM
84
+ });
85
+ }
86
+
87
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
88
+ <Provider store={store}>{children}</Provider>
89
+ );
90
+
91
+ return { ...renderHook(() => useMasterpassPayment(), { wrapper }), store };
92
+ };
93
+
94
+ beforeEach(() => {
95
+ prepareMock.mockResolvedValue(PREPARED_ORDER);
96
+ finalizeMock.mockResolvedValue({ errors: null });
97
+ processPaymentMock.mockResolvedValue({ success: true, data: { result: {} } });
98
+ directPaymentMock.mockResolvedValue({ success: true, data: { result: {} } });
99
+ handleResponseMock.mockResolvedValue({ success: true });
100
+ });
101
+
102
+ describe('order number lifecycle during payment', () => {
103
+ it('marks the order number pending as soon as it is bound to a transaction', async () => {
104
+ // The SDK call never resolves, so the assertion sees the state the user is
105
+ // in while the bank page is open.
106
+ processPaymentMock.mockImplementation(() => new Promise(() => {}));
107
+
108
+ const { result } = renderPaymentHook();
109
+
110
+ act(() => {
111
+ void result.current.processPayment();
112
+ });
113
+
114
+ await act(async () => {
115
+ await Promise.resolve();
116
+ });
117
+
118
+ expect(getPaymentPending()?.orderNo).toBe('ORDER-1');
119
+ });
120
+
121
+ it('releases the order number once the backend has completed the order', async () => {
122
+ const { result } = renderPaymentHook();
123
+
124
+ await act(async () => {
125
+ await result.current.processPayment();
126
+ });
127
+
128
+ expect(finalizeMock).toHaveBeenCalled();
129
+ expect(getPaymentPending()).toBeNull();
130
+ });
131
+
132
+ it('keeps the order number pending while the shopper is redirected to 3D', async () => {
133
+ handleResponseMock.mockResolvedValue({
134
+ requires3D: true,
135
+ redirectUrl: 'https://bank.example/3d'
136
+ });
137
+
138
+ // jsdom refuses real navigation; the assignment itself is not under test.
139
+ const location = window.location;
140
+ delete (window as any).location;
141
+ (window as any).location = { ...location, href: '' };
142
+
143
+ const { result } = renderPaymentHook();
144
+
145
+ let outcome: any;
146
+ await act(async () => {
147
+ outcome = await result.current.processPayment();
148
+ });
149
+
150
+ expect(outcome.requiresRedirect).toBe(true);
151
+ expect(getPaymentPending()?.orderNo).toBe('ORDER-1');
152
+
153
+ (window as any).location = location;
154
+ });
155
+
156
+ it('keeps the order number pending while a verification step is open', async () => {
157
+ handleResponseMock.mockResolvedValue({
158
+ success: false,
159
+ requiresOTP: true,
160
+ otpType: 'OTP'
161
+ });
162
+
163
+ const { result } = renderPaymentHook();
164
+
165
+ await act(async () => {
166
+ await result.current.processPayment();
167
+ });
168
+
169
+ expect(getPaymentPending()?.orderNo).toBe('ORDER-1');
170
+ });
171
+
172
+ it('releases the order number when the payment is rejected outright', async () => {
173
+ handleResponseMock.mockResolvedValue({
174
+ success: false,
175
+ message: 'Insufficient funds'
176
+ });
177
+
178
+ const { result } = renderPaymentHook();
179
+
180
+ await act(async () => {
181
+ await result.current.processPayment();
182
+ });
183
+
184
+ expect(getPaymentPending()).toBeNull();
185
+ });
186
+
187
+ it('releases the order number when the flow throws', async () => {
188
+ prepareMock.mockRejectedValue(new Error('network down'));
189
+ setPaymentPending('STALE-ORDER');
190
+
191
+ const { result } = renderPaymentHook();
192
+
193
+ await act(async () => {
194
+ await result.current.processPayment();
195
+ });
196
+
197
+ expect(getPaymentPending()).toBeNull();
198
+ });
199
+
200
+ it('marks the order number pending on the new-card flow too', async () => {
201
+ directPaymentMock.mockImplementation(() => new Promise(() => {}));
202
+
203
+ const { result } = renderPaymentHook({ withNewCardForm: true });
204
+
205
+ act(() => {
206
+ void result.current.processDirectPayment();
207
+ });
208
+
209
+ await act(async () => {
210
+ await Promise.resolve();
211
+ });
212
+
213
+ expect(getPaymentPending()?.orderNo).toBe('ORDER-1');
214
+ });
215
+ });
216
+
217
+ describe('completing a payment after a verification step', () => {
218
+ it('sends the verification result to the backend and releases the order number', async () => {
219
+ setPaymentPending('ORDER-1');
220
+
221
+ const { result } = renderPaymentHook();
222
+
223
+ let outcome: any;
224
+ await act(async () => {
225
+ outcome = await result.current.finalizePaymentAfterVerification({
226
+ result: { responseCode: '0000', token: 'verified-token' }
227
+ });
228
+ });
229
+
230
+ expect(finalizeMock).toHaveBeenCalledWith({
231
+ responseCode: '0000',
232
+ token: 'verified-token',
233
+ three_d_secure: true,
234
+ transactionType: 'PURCHASE_3D'
235
+ });
236
+ expect(outcome.success).toBe(true);
237
+ expect(getPaymentPending()).toBeNull();
238
+ });
239
+
240
+ it('surfaces backend errors instead of reporting success', async () => {
241
+ finalizeMock.mockResolvedValue({ errors: { non_field_errors: 'ORDER_NO_MISMATCH' } });
242
+
243
+ const { result } = renderPaymentHook();
244
+
245
+ let outcome: any;
246
+ await act(async () => {
247
+ outcome = await result.current.finalizePaymentAfterVerification({
248
+ result: { responseCode: '0000', token: 'verified-token' }
249
+ });
250
+ });
251
+
252
+ expect(outcome.success).toBe(false);
253
+ expect(outcome.message).toEqual({ non_field_errors: 'ORDER_NO_MISMATCH' });
254
+ });
255
+ });
@@ -19,12 +19,12 @@ import {
19
19
  handleMasterpassResponse,
20
20
  handleVerification
21
21
  } from '../utils/response-handler';
22
+ import { getPaymentPending, setPaymentPending } from '../utils/payment-utils';
22
23
  import type {
23
24
  AccountAccessSuccessResponse,
24
25
  CardModel
25
26
  } from '../types/account.types';
26
27
  import { useMasterpassToken } from './useMasterpassToken';
27
- import { decodeMasterpassToken } from '../utils/wait-for-utils-ready';
28
28
 
29
29
  export const useMasterpassAccount = () => {
30
30
  const dispatch = useDispatch();
@@ -41,16 +41,7 @@ export const useMasterpassAccount = () => {
41
41
 
42
42
  const initializeAccount = useCallback(
43
43
  async (newTokenData: any, rawToken?: string) => {
44
- if (!newTokenData?.AccountKey) {
45
- dispatch(
46
- setAccountStatus({
47
- isAccountNotFound: false,
48
- isAccountNotLinked: true,
49
- shouldShowDirectForm: true
50
- })
51
- );
52
- return;
53
- }
44
+ if (!newTokenData?.AccountKey) return;
54
45
 
55
46
  const accountService = new AccountService(rawToken || token, newTokenData?.MerchantId);
56
47
 
@@ -136,19 +127,13 @@ export const useMasterpassAccount = () => {
136
127
  dispatch(resetState());
137
128
  }, [dispatch]);
138
129
 
139
- const refreshToken = async (options?: {
140
- three_d?: boolean;
141
- skipReset?: boolean;
142
- }) => {
130
+ const refreshToken = async (options?: { three_d?: boolean; skipReset?: boolean }) => {
143
131
  if (options?.three_d !== false && !options?.skipReset) {
144
132
  resetData();
145
133
  }
146
134
  const result = await refetch(options);
147
135
  if (result?.data?.token) {
148
- const decodedToken = decodeMasterpassToken(result.data.token);
149
- if (!decodedToken) {
150
- return null;
151
- }
136
+ const decodedToken = window.Utils.decodeJwt(result.data.token);
152
137
  return {
153
138
  tokenData: decodedToken,
154
139
  token: result.data.token,
@@ -202,6 +187,7 @@ export const useMasterpassAccount = () => {
202
187
  dispatch(
203
188
  updateModalState({
204
189
  otpType: result.otpType,
190
+ otpContext: 'account',
205
191
  verificationData: result.data,
206
192
  showLinkModal: false,
207
193
  showOTPModal: true
@@ -221,9 +207,22 @@ export const useMasterpassAccount = () => {
221
207
  const handleOTPSubmit = useCallback(
222
208
  async (otp: string, texts?: any) => {
223
209
  const result = await handleVerification(otp, token, tokenData?.MerchantId);
210
+ const isPaymentContext = modalState.otpContext === 'payment';
224
211
 
225
212
  if (result.success) {
226
213
  dispatch(setShowOTPModal(false));
214
+
215
+ // A payment-context OTP still has an open Masterpass transaction. The
216
+ // caller has to complete it against the backend; refreshing the account
217
+ // here would leave the order unfinalized.
218
+ if (isPaymentContext) {
219
+ return {
220
+ success: true,
221
+ isPaymentContext: true,
222
+ verificationData: result.data
223
+ };
224
+ }
225
+
227
226
  await refreshAccountData();
228
227
  return { success: true };
229
228
  } else if (result.sessionExpired) {
@@ -234,12 +233,8 @@ export const useMasterpassAccount = () => {
234
233
  informationModalData: {
235
234
  type: 'warning',
236
235
  title: texts?.sessionExpiredTitle || 'Session Expired',
237
- message:
238
- texts?.sessionExpiredMessage ||
239
- 'Your session has expired due to inactivity. Please restart the process to continue.',
240
- secondaryMessage:
241
- texts?.sessionExpiredSecondaryMessage ||
242
- 'For security reasons, verification codes are only valid for a limited time.',
236
+ message: texts?.sessionExpiredMessage || 'Your session has expired due to inactivity. Please restart the process to continue.',
237
+ secondaryMessage: texts?.sessionExpiredSecondaryMessage || 'For security reasons, verification codes are only valid for a limited time.',
243
238
  buttonText: texts?.sessionExpiredButton || 'Start Again'
244
239
  }
245
240
  })
@@ -250,6 +245,13 @@ export const useMasterpassAccount = () => {
250
245
  };
251
246
  } else if (result.requires3D && result.redirectUrl) {
252
247
  dispatch(setShowOTPModal(false));
248
+
249
+ // Leaving the tab for the bank restarts the wait, so refresh the record
250
+ // without losing the order number it was opened with.
251
+ if (isPaymentContext) {
252
+ setPaymentPending(getPaymentPending()?.orderNo ?? null);
253
+ }
254
+
253
255
  window.location.href = result.redirectUrl;
254
256
  return {
255
257
  success: false,
@@ -277,7 +279,13 @@ export const useMasterpassAccount = () => {
277
279
  };
278
280
  }
279
281
  },
280
- [dispatch, refreshAccountData, token, tokenData?.MerchantId]
282
+ [
283
+ dispatch,
284
+ refreshAccountData,
285
+ token,
286
+ tokenData?.MerchantId,
287
+ modalState.otpContext
288
+ ]
281
289
  );
282
290
 
283
291
  const handleRemoveCard = useCallback(
@@ -355,7 +363,7 @@ export const useMasterpassAccount = () => {
355
363
  const activeMerchantId = freshResult?.merchantId || tokenData?.MerchantId;
356
364
 
357
365
  if (!activeTokenData?.AccountKey) {
358
- return { success: false, message: '' };
366
+ throw new Error('Token data not available');
359
367
  }
360
368
 
361
369
  const accountService = new AccountService(activeToken, activeMerchantId);
@@ -388,6 +396,7 @@ export const useMasterpassAccount = () => {
388
396
  dispatch(
389
397
  updateModalState({
390
398
  otpType: result.otpType,
399
+ otpContext: 'account',
391
400
  verificationData: result.data,
392
401
  showLinkModal: false,
393
402
  showOTPModal: true
@@ -25,11 +25,12 @@ import {
25
25
  } from '../redux/reducer';
26
26
  import { useMasterpassToken } from './useMasterpassToken';
27
27
  import { PaymentService } from '../services/payment';
28
- import { decodeMasterpassToken } from '../utils/wait-for-utils-ready';
29
28
  import {
30
29
  createPaymentRequest,
31
30
  createDirectPaymentRequest,
32
- getTransactionType
31
+ getTransactionType,
32
+ setPaymentPending,
33
+ clearPaymentPending
33
34
  } from '../utils/payment-utils';
34
35
  import { handleMasterpassResponse } from '../utils/response-handler';
35
36
  import type { Installment, RewardItem } from '../types/payment.types';
@@ -94,10 +95,7 @@ export const useMasterpassPayment = (
94
95
  const refreshTokenForPayment = useCallback(async () => {
95
96
  const result = await refetchToken({ skipReset: true });
96
97
  if (result?.data?.token) {
97
- const decodedToken = decodeMasterpassToken(result.data.token);
98
- if (!decodedToken) {
99
- return null;
100
- }
98
+ const decodedToken = window.Utils.decodeJwt(result.data.token);
101
99
  return {
102
100
  tokenData: decodedToken,
103
101
  token: result.data.token,
@@ -252,13 +250,20 @@ export const useMasterpassPayment = (
252
250
 
253
251
  dispatch(setOrderData(prepareResult));
254
252
 
253
+ const orderNo = prepareResult.context_list?.[0]?.page_context?.order_no;
254
+
255
+ // From here on the order number is bound to a Masterpass transaction.
256
+ // Regenerating it before that transaction settles makes the backend
257
+ // reject the completion with ORDER_NO_MISMATCH.
258
+ setPaymentPending(orderNo);
259
+
255
260
  const paymentService = new PaymentService(activeToken, activeMerchantId);
256
261
  const paymentRequest = createPaymentRequest({
257
262
  cvc,
258
263
  cardAlias: selectedCard.cardAlias,
259
264
  accountKey: activeTokenData.AccountKey,
260
265
  amount: prepareResult.pre_order?.total_amount_with_interest,
261
- orderNo: prepareResult.context_list?.[0]?.page_context?.order_no,
266
+ orderNo,
262
267
  currencyCode: currency,
263
268
  installmentCount: selectedInstallment.installment_count,
264
269
  authenticationMethod: activeTokenData.AuthenticationMethod,
@@ -288,6 +293,7 @@ export const useMasterpassPayment = (
288
293
  }).unwrap();
289
294
 
290
295
  dispatch(setOrderData(finalizeResult));
296
+ clearPaymentPending();
291
297
 
292
298
  if (finalizeResult.errors) {
293
299
  return { success: false, message: finalizeResult.errors };
@@ -297,6 +303,10 @@ export const useMasterpassPayment = (
297
303
  return { success: true };
298
304
  }
299
305
 
306
+ if (!response.requiresOTP) {
307
+ clearPaymentPending();
308
+ }
309
+
300
310
  return {
301
311
  success: response.success,
302
312
  requiresOTP: response.requiresOTP,
@@ -304,6 +314,7 @@ export const useMasterpassPayment = (
304
314
  message: response.message
305
315
  };
306
316
  } catch (error) {
317
+ clearPaymentPending();
307
318
  return {
308
319
  success: false,
309
320
  message:
@@ -351,12 +362,23 @@ export const useMasterpassPayment = (
351
362
  const activeToken = freshResult?.token;
352
363
  const activeMerchantId = freshResult?.merchantId || tokenData?.MerchantId;
353
364
 
365
+ if (!activeTokenData?.AccountKey) {
366
+ return { success: false, message: 'Token data not available' };
367
+ }
368
+
354
369
  const prepareResult = await prepareMasterpassOrder({
355
370
  use_three_d: paymentState.useThreeD
356
371
  }).unwrap();
357
372
 
358
373
  dispatch(setOrderData(prepareResult));
359
374
 
375
+ const orderNo = prepareResult.context_list?.[0]?.page_context?.order_no;
376
+
377
+ // From here on the order number is bound to a Masterpass transaction.
378
+ // Regenerating it before that transaction settles makes the backend
379
+ // reject the completion with ORDER_NO_MISMATCH.
380
+ setPaymentPending(orderNo);
381
+
360
382
  const paymentService = new PaymentService(activeToken, activeMerchantId);
361
383
  const directPaymentRequest = createDirectPaymentRequest({
362
384
  cvc: newCardFormData.cvv,
@@ -364,13 +386,13 @@ export const useMasterpassPayment = (
364
386
  cardHolderName: newCardFormData.cardholderName || '',
365
387
  cardAlias: newCardFormData.cardAlias || '',
366
388
  expiryDate: newCardFormData.expiryDate,
367
- accountKey: activeTokenData?.AccountKey || '',
389
+ accountKey: activeTokenData.AccountKey,
368
390
  amount: prepareResult.pre_order?.total_amount_with_interest,
369
- orderNo: prepareResult.context_list?.[0]?.page_context?.order_no,
391
+ orderNo,
370
392
  currencyCode: currency,
371
393
  installmentCount: selectedInstallment.installment_count,
372
- authenticationMethod: activeTokenData?.AuthenticationMethod,
373
- secure3DModel: activeTokenData?.Secure3dType,
394
+ authenticationMethod: activeTokenData.AuthenticationMethod,
395
+ secure3DModel: activeTokenData.Secure3dType,
374
396
  terminalGroupId,
375
397
  acquirerIcaNumber: bankIca,
376
398
  additionalFields:
@@ -398,6 +420,7 @@ export const useMasterpassPayment = (
398
420
  }).unwrap();
399
421
 
400
422
  dispatch(setOrderData(finalizeResult));
423
+ clearPaymentPending();
401
424
 
402
425
  if (finalizeResult.errors) {
403
426
  return { success: false, message: finalizeResult.errors };
@@ -407,6 +430,10 @@ export const useMasterpassPayment = (
407
430
  return { success: true };
408
431
  }
409
432
 
433
+ if (!response.requiresOTP) {
434
+ clearPaymentPending();
435
+ }
436
+
410
437
  return {
411
438
  success: response.success,
412
439
  requiresOTP: response.requiresOTP,
@@ -414,6 +441,7 @@ export const useMasterpassPayment = (
414
441
  message: response.message
415
442
  };
416
443
  } catch (error) {
444
+ clearPaymentPending();
417
445
  return {
418
446
  success: false,
419
447
  message:
@@ -435,6 +463,41 @@ export const useMasterpassPayment = (
435
463
  dispatch
436
464
  ]);
437
465
 
466
+ const finalizePaymentAfterVerification = useCallback(
467
+ async (verificationResponse: any): Promise<PaymentResult> => {
468
+ const { useThreeD } = paymentState;
469
+
470
+ try {
471
+ const finalizeResult = await finalizeMasterpassOrder({
472
+ responseCode: verificationResponse?.result?.responseCode,
473
+ token: verificationResponse?.result?.token,
474
+ three_d_secure: useThreeD,
475
+ transactionType: getTransactionType(useThreeD)
476
+ }).unwrap();
477
+
478
+ dispatch(setOrderData(finalizeResult));
479
+ clearPaymentPending();
480
+
481
+ if (finalizeResult.errors) {
482
+ return { success: false, message: finalizeResult.errors };
483
+ }
484
+
485
+ dispatch(setOrderCompleted(true));
486
+ return { success: true };
487
+ } catch (error) {
488
+ clearPaymentPending();
489
+ return {
490
+ success: false,
491
+ message:
492
+ error instanceof Error
493
+ ? error.message
494
+ : 'An unexpected error occurred'
495
+ };
496
+ }
497
+ },
498
+ [paymentState, finalizeMasterpassOrder, dispatch]
499
+ );
500
+
438
501
  return {
439
502
  paymentState,
440
503
 
@@ -451,6 +514,7 @@ export const useMasterpassPayment = (
451
514
  handleInstallmentSelect,
452
515
  processPayment,
453
516
  processDirectPayment,
517
+ finalizePaymentAfterVerification,
454
518
  fetchRewardsForCard,
455
519
  openRewardModal,
456
520
  closeRewardModal,
@@ -14,10 +14,6 @@ import {
14
14
  updatePaymentState
15
15
  } from '../redux/reducer';
16
16
  import { isTokenExpired } from '../utils/payment-utils';
17
- import {
18
- waitForUtilsReady,
19
- decodeMasterpassToken
20
- } from '../utils/wait-for-utils-ready';
21
17
 
22
18
  interface UseMasterpassTokenProps {
23
19
  useThreeD: boolean;
@@ -69,37 +65,12 @@ export const useMasterpassToken = ({
69
65
  return;
70
66
  }
71
67
 
72
- if (!(data?.token && data.token !== reduxToken)) {
73
- return;
74
- }
75
-
76
- const token = data.token;
77
- const extras = data.extras;
78
- let cancelled = false;
79
-
80
- // The token request can resolve before the Masterpass SDK script has
81
- // loaded (common on slow/3G networks). Wait for `window.Utils.decodeJwt`
82
- // to exist before decoding, otherwise the checkout crashes with
83
- // `Cannot read properties of undefined (reading 'decodeJwt')`.
84
- //
85
- // Redux writes are deferred until after the wait resolves: dispatching
86
- // `setMasterpassRestToken` synchronously would change the `reduxToken`
87
- // dependency, re-run this effect and cancel the in-flight decode.
88
- (async () => {
89
- const ready = await waitForUtilsReady();
90
- if (cancelled) {
91
- return;
92
- }
93
-
94
- dispatch(setMasterpassRestToken(token));
95
- dispatch(setMasterpassTerminalGroupId(extras.terminal_group_id));
96
- dispatch(setMasterpassBankIca(extras.bank_ica));
97
-
98
- const decodedToken = ready ? decodeMasterpassToken(token) : undefined;
99
- if (!decodedToken) {
100
- return;
101
- }
68
+ if (data?.token && data.token !== reduxToken) {
69
+ dispatch(setMasterpassRestToken(data.token));
70
+ dispatch(setMasterpassTerminalGroupId(data.extras.terminal_group_id));
71
+ dispatch(setMasterpassBankIca(data.extras.bank_ica));
102
72
 
73
+ const decodedToken = window.Utils.decodeJwt(data.token);
103
74
  dispatch(setMasterpassRestTokenData(decodedToken));
104
75
 
105
76
  if (decodedToken?.AuthenticationMethod?.toLowerCase() === 'none') {
@@ -107,11 +78,7 @@ export const useMasterpassToken = ({
107
78
  }
108
79
 
109
80
  stableOnTokenReady(decodedToken);
110
- })();
111
-
112
- return () => {
113
- cancelled = true;
114
- };
81
+ }
115
82
  }, [data?.token, reduxToken, dispatch, stableOnTokenReady]);
116
83
 
117
84
  const refetchToken = useCallback(
@@ -127,19 +94,15 @@ export const useMasterpassToken = ({
127
94
  );
128
95
  dispatch(setMasterpassBankIca(result.data.extras.bank_ica));
129
96
 
130
- await waitForUtilsReady();
131
- const decodedToken = decodeMasterpassToken(result.data.token);
132
-
133
- if (decodedToken) {
134
- dispatch(setMasterpassRestTokenData(decodedToken));
97
+ const decodedToken = window.Utils.decodeJwt(result.data.token);
98
+ dispatch(setMasterpassRestTokenData(decodedToken));
135
99
 
136
- if (decodedToken?.AuthenticationMethod?.toLowerCase() === 'none') {
137
- dispatch(updatePaymentState({ useThreeD: false }));
138
- }
100
+ if (decodedToken?.AuthenticationMethod?.toLowerCase() === 'none') {
101
+ dispatch(updatePaymentState({ useThreeD: false }));
102
+ }
139
103
 
140
- if (options?.three_d !== false && !options?.skipReset) {
141
- stableOnTokenReady(decodedToken);
142
- }
104
+ if (options?.three_d !== false && !options?.skipReset) {
105
+ stableOnTokenReady(decodedToken);
143
106
  }
144
107
  }
145
108
 
package/src/index.ts CHANGED
@@ -19,7 +19,6 @@ export * from './utils/reward-utils';
19
19
  export * from './utils/card-utils';
20
20
  export * from './utils/validation-schemas';
21
21
  export * from './utils/masterpass-sdk';
22
- export * from './utils/wait-for-utils-ready';
23
22
  export type { MasterpassEnvironment } from './utils/constants';
24
23
 
25
24
  export * from './types/payment.types';