@akinon/pz-masterpass-rest 2.0.53-rc.0 → 2.0.54-beta.0
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 +2 -8
- package/package.json +2 -2
- package/src/components/credit-card-form.tsx +1 -1
- package/src/components/installment-list.tsx +1 -1
- package/src/components/otp-modal.tsx +4 -12
- package/src/hooks/useMasterpassAccount.ts +6 -26
- package/src/hooks/useMasterpassPayment.ts +8 -8
- package/src/hooks/useMasterpassToken.ts +13 -50
- package/src/index.ts +0 -1
- package/src/utils/card-utils.ts +1 -1
- package/src/utils/wait-for-utils-ready.ts +0 -64
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
# @akinon/pz-masterpass-rest
|
|
2
2
|
|
|
3
|
-
## 2.0.
|
|
3
|
+
## 2.0.54-beta.0
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- 119f0c11: ZERO-4629: Refactor token decoding to use decodeMasterpassToken utility and add wait-for-utils-ready functionality
|
|
8
|
-
- 823a4449a: ZERO-4193: Upgrade TypeScript to version 5.2.2 across multiple packages
|
|
9
|
-
- 5da13fff: ZERO-4358: fix masterpass-rest guest checkout flow
|
|
10
|
-
- fb4aa9b4: ZERO-4333: update maxLength for cardNumber input and enhance button styling in installment list
|
|
11
|
-
- 63a720003: ZERO-3895: enhance payment processing with additional fields support
|
|
5
|
+
## 2.0.53
|
|
12
6
|
|
|
13
7
|
## 2.0.52
|
|
14
8
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/pz-masterpass-rest",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.54-beta.0",
|
|
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",
|
|
@@ -31,6 +31,6 @@
|
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/react": "18.2.27",
|
|
33
33
|
"@types/react-dom": "18.2.12",
|
|
34
|
-
"typescript": "
|
|
34
|
+
"typescript": "5.2.2"
|
|
35
35
|
}
|
|
36
36
|
}
|
|
@@ -184,7 +184,7 @@ const CreditCardForm: React.FC<CreditCardFormProps> = ({
|
|
|
184
184
|
setValue('cardNumber', formatted, { shouldValidate: true });
|
|
185
185
|
}}
|
|
186
186
|
error={errors.cardNumber}
|
|
187
|
-
maxLength={
|
|
187
|
+
maxLength={23}
|
|
188
188
|
className="pr-20"
|
|
189
189
|
/>
|
|
190
190
|
{cardType !== 'unknown' && (
|
|
@@ -118,7 +118,7 @@ const InstallmentList: React.FC<InstallmentListProps> = ({
|
|
|
118
118
|
<Button
|
|
119
119
|
onClick={onProceedToPayment}
|
|
120
120
|
disabled={isLoading || paymentLoading}
|
|
121
|
-
className="w-full cursor-pointer
|
|
121
|
+
className="w-full cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
|
|
122
122
|
>
|
|
123
123
|
{paymentLoading ? (
|
|
124
124
|
<div className="flex items-center justify-center">
|
|
@@ -204,21 +204,15 @@ const OTPModal: React.FC<OTPModalProps> = ({
|
|
|
204
204
|
>
|
|
205
205
|
<div className="px-6">
|
|
206
206
|
{getTitle() && (
|
|
207
|
-
<h3 className="text-center mt-4 text-lg font-semibold">
|
|
208
|
-
{getTitle()}
|
|
209
|
-
</h3>
|
|
207
|
+
<h3 className="text-center mt-4 text-lg font-semibold">{getTitle()}</h3>
|
|
210
208
|
)}
|
|
211
|
-
<p className="text-center mt-2 text-sm text-gray-600">
|
|
212
|
-
{getDescription()}
|
|
213
|
-
</p>
|
|
209
|
+
<p className="text-center mt-2 text-sm text-gray-600">{getDescription()}</p>
|
|
214
210
|
<div className="flex flex-col gap-3 p-5 w-3/4 m-auto">
|
|
215
211
|
<Input
|
|
216
212
|
type="text"
|
|
217
213
|
value={otp}
|
|
218
214
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
219
|
-
setOtp(
|
|
220
|
-
e.target.value.replace(/\D/g, '').slice(0, getMaxLength())
|
|
221
|
-
);
|
|
215
|
+
setOtp(e.target.value.replace(/\D/g, '').slice(0, getMaxLength()));
|
|
222
216
|
setError(null);
|
|
223
217
|
}}
|
|
224
218
|
maxLength={getMaxLength()}
|
|
@@ -228,9 +222,7 @@ const OTPModal: React.FC<OTPModalProps> = ({
|
|
|
228
222
|
placeholder={getPlaceholder()}
|
|
229
223
|
/>
|
|
230
224
|
{getHelperText() && (
|
|
231
|
-
<p className="text-xs text-gray-500 text-center">
|
|
232
|
-
{getHelperText()}
|
|
233
|
-
</p>
|
|
225
|
+
<p className="text-xs text-gray-500 text-center">{getHelperText()}</p>
|
|
234
226
|
)}
|
|
235
227
|
{error && (
|
|
236
228
|
<p className="text-[#d72b01] text-xs text-center">{error}</p>
|
|
@@ -24,7 +24,6 @@ import type {
|
|
|
24
24
|
CardModel
|
|
25
25
|
} from '../types/account.types';
|
|
26
26
|
import { useMasterpassToken } from './useMasterpassToken';
|
|
27
|
-
import { decodeMasterpassToken } from '../utils/wait-for-utils-ready';
|
|
28
27
|
|
|
29
28
|
export const useMasterpassAccount = () => {
|
|
30
29
|
const dispatch = useDispatch();
|
|
@@ -41,16 +40,7 @@ export const useMasterpassAccount = () => {
|
|
|
41
40
|
|
|
42
41
|
const initializeAccount = useCallback(
|
|
43
42
|
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
|
-
}
|
|
43
|
+
if (!newTokenData?.AccountKey) return;
|
|
54
44
|
|
|
55
45
|
const accountService = new AccountService(rawToken || token, newTokenData?.MerchantId);
|
|
56
46
|
|
|
@@ -136,19 +126,13 @@ export const useMasterpassAccount = () => {
|
|
|
136
126
|
dispatch(resetState());
|
|
137
127
|
}, [dispatch]);
|
|
138
128
|
|
|
139
|
-
const refreshToken = async (options?: {
|
|
140
|
-
three_d?: boolean;
|
|
141
|
-
skipReset?: boolean;
|
|
142
|
-
}) => {
|
|
129
|
+
const refreshToken = async (options?: { three_d?: boolean; skipReset?: boolean }) => {
|
|
143
130
|
if (options?.three_d !== false && !options?.skipReset) {
|
|
144
131
|
resetData();
|
|
145
132
|
}
|
|
146
133
|
const result = await refetch(options);
|
|
147
134
|
if (result?.data?.token) {
|
|
148
|
-
const decodedToken =
|
|
149
|
-
if (!decodedToken) {
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
135
|
+
const decodedToken = window.Utils.decodeJwt(result.data.token);
|
|
152
136
|
return {
|
|
153
137
|
tokenData: decodedToken,
|
|
154
138
|
token: result.data.token,
|
|
@@ -234,12 +218,8 @@ export const useMasterpassAccount = () => {
|
|
|
234
218
|
informationModalData: {
|
|
235
219
|
type: 'warning',
|
|
236
220
|
title: texts?.sessionExpiredTitle || 'Session Expired',
|
|
237
|
-
message:
|
|
238
|
-
|
|
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.',
|
|
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.',
|
|
243
223
|
buttonText: texts?.sessionExpiredButton || 'Start Again'
|
|
244
224
|
}
|
|
245
225
|
})
|
|
@@ -355,7 +335,7 @@ export const useMasterpassAccount = () => {
|
|
|
355
335
|
const activeMerchantId = freshResult?.merchantId || tokenData?.MerchantId;
|
|
356
336
|
|
|
357
337
|
if (!activeTokenData?.AccountKey) {
|
|
358
|
-
|
|
338
|
+
throw new Error('Token data not available');
|
|
359
339
|
}
|
|
360
340
|
|
|
361
341
|
const accountService = new AccountService(activeToken, activeMerchantId);
|
|
@@ -25,7 +25,6 @@ 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,
|
|
@@ -94,10 +93,7 @@ export const useMasterpassPayment = (
|
|
|
94
93
|
const refreshTokenForPayment = useCallback(async () => {
|
|
95
94
|
const result = await refetchToken({ skipReset: true });
|
|
96
95
|
if (result?.data?.token) {
|
|
97
|
-
const decodedToken =
|
|
98
|
-
if (!decodedToken) {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
96
|
+
const decodedToken = window.Utils.decodeJwt(result.data.token);
|
|
101
97
|
return {
|
|
102
98
|
tokenData: decodedToken,
|
|
103
99
|
token: result.data.token,
|
|
@@ -351,6 +347,10 @@ export const useMasterpassPayment = (
|
|
|
351
347
|
const activeToken = freshResult?.token;
|
|
352
348
|
const activeMerchantId = freshResult?.merchantId || tokenData?.MerchantId;
|
|
353
349
|
|
|
350
|
+
if (!activeTokenData?.AccountKey) {
|
|
351
|
+
return { success: false, message: 'Token data not available' };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
354
|
const prepareResult = await prepareMasterpassOrder({
|
|
355
355
|
use_three_d: paymentState.useThreeD
|
|
356
356
|
}).unwrap();
|
|
@@ -364,13 +364,13 @@ export const useMasterpassPayment = (
|
|
|
364
364
|
cardHolderName: newCardFormData.cardholderName || '',
|
|
365
365
|
cardAlias: newCardFormData.cardAlias || '',
|
|
366
366
|
expiryDate: newCardFormData.expiryDate,
|
|
367
|
-
accountKey: activeTokenData
|
|
367
|
+
accountKey: activeTokenData.AccountKey,
|
|
368
368
|
amount: prepareResult.pre_order?.total_amount_with_interest,
|
|
369
369
|
orderNo: prepareResult.context_list?.[0]?.page_context?.order_no,
|
|
370
370
|
currencyCode: currency,
|
|
371
371
|
installmentCount: selectedInstallment.installment_count,
|
|
372
|
-
authenticationMethod: activeTokenData
|
|
373
|
-
secure3DModel: activeTokenData
|
|
372
|
+
authenticationMethod: activeTokenData.AuthenticationMethod,
|
|
373
|
+
secure3DModel: activeTokenData.Secure3dType,
|
|
374
374
|
terminalGroupId,
|
|
375
375
|
acquirerIcaNumber: bankIca,
|
|
376
376
|
additionalFields:
|
|
@@ -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 (
|
|
73
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (decodedToken) {
|
|
134
|
-
dispatch(setMasterpassRestTokenData(decodedToken));
|
|
97
|
+
const decodedToken = window.Utils.decodeJwt(result.data.token);
|
|
98
|
+
dispatch(setMasterpassRestTokenData(decodedToken));
|
|
135
99
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
100
|
+
if (decodedToken?.AuthenticationMethod?.toLowerCase() === 'none') {
|
|
101
|
+
dispatch(updatePaymentState({ useThreeD: false }));
|
|
102
|
+
}
|
|
139
103
|
|
|
140
|
-
|
|
141
|
-
|
|
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';
|
package/src/utils/card-utils.ts
CHANGED
|
@@ -9,7 +9,7 @@ export const formatCardNumber = (value: string): string => {
|
|
|
9
9
|
|
|
10
10
|
const formatted = cleanValue.replace(/(\d{4})(?=\d)/g, '$1 ');
|
|
11
11
|
|
|
12
|
-
return formatted.substring(0,
|
|
12
|
+
return formatted.substring(0, 23);
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export const formatExpiryDate = (value: string): string => {
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
const DEFAULT_TIMEOUT_MS = 10000;
|
|
2
|
-
const POLL_INTERVAL_MS = 100;
|
|
3
|
-
|
|
4
|
-
interface MasterpassUtils {
|
|
5
|
-
decodeJwt: (token: string) => any;
|
|
6
|
-
[key: string]: any;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// `window.Utils` is attached by the Masterpass REST SDK and is undefined until
|
|
10
|
-
// that script finishes loading, so it is resolved through this guarded getter
|
|
11
|
-
// rather than accessed directly.
|
|
12
|
-
const getMasterpassUtils = (): MasterpassUtils | undefined =>
|
|
13
|
-
(window as unknown as { Utils?: MasterpassUtils }).Utils;
|
|
14
|
-
|
|
15
|
-
// The Masterpass REST SDK (`/masterpass-javascript-sdk-web.min.js`) attaches
|
|
16
|
-
// both `window.Masterpass` and `window.Utils` when it finishes loading. That
|
|
17
|
-
// script is loaded asynchronously (see `useMasterpassScript`), so on slow
|
|
18
|
-
// networks the `/orders/masterpass-token` response can arrive before the SDK
|
|
19
|
-
// is ready. Calling `window.Utils.decodeJwt(...)` at that point crashes the
|
|
20
|
-
// checkout with `TypeError: Cannot read properties of undefined (reading
|
|
21
|
-
// 'decodeJwt')` (ZERO-4362 — same class of bug already fixed in the non-rest
|
|
22
|
-
// `@akinon/pz-masterpass` package).
|
|
23
|
-
//
|
|
24
|
-
// This helper polls until the SDK has exposed `window.Utils.decodeJwt`, so
|
|
25
|
-
// callers can await readiness before decoding instead of crashing.
|
|
26
|
-
export const waitForUtilsReady = (
|
|
27
|
-
timeoutMs: number = DEFAULT_TIMEOUT_MS
|
|
28
|
-
): Promise<boolean> =>
|
|
29
|
-
new Promise((resolve) => {
|
|
30
|
-
const start = Date.now();
|
|
31
|
-
|
|
32
|
-
const check = () => {
|
|
33
|
-
if (typeof getMasterpassUtils()?.decodeJwt === 'function') {
|
|
34
|
-
resolve(true);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (Date.now() - start >= timeoutMs) {
|
|
39
|
-
resolve(false);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
setTimeout(check, POLL_INTERVAL_MS);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
check();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// Guarded `decodeJwt` wrapper. Returns `undefined` (instead of throwing) when
|
|
50
|
-
// the SDK is not yet available, so every callsite degrades gracefully.
|
|
51
|
-
export const decodeMasterpassToken = <T = any>(
|
|
52
|
-
token: string
|
|
53
|
-
): T | undefined => {
|
|
54
|
-
const utils = getMasterpassUtils();
|
|
55
|
-
|
|
56
|
-
if (typeof utils?.decodeJwt !== 'function') {
|
|
57
|
-
console.error(
|
|
58
|
-
'[pz-masterpass-rest] window.Utils.decodeJwt is not available'
|
|
59
|
-
);
|
|
60
|
-
return undefined;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return utils.decodeJwt(token);
|
|
64
|
-
};
|