@akinon/pz-masterpass-rest 2.0.17 → 2.0.18-rc.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 +10 -0
- package/docs/USAGE.md +332 -61
- package/package.json +2 -2
- package/src/components/card-list.tsx +72 -2
- package/src/components/credit-card-form.tsx +1 -1
- package/src/components/installment-list.tsx +1 -1
- package/src/components/otp-modal.tsx +12 -4
- package/src/components/reward-selection-modal.tsx +194 -0
- package/src/hooks/useMasterpassAccount.ts +21 -5
- package/src/hooks/useMasterpassPayment.ts +112 -13
- package/src/index.ts +2 -0
- package/src/redux/api.ts +82 -1
- package/src/redux/reducer.ts +35 -3
- package/src/types/custom-render.types.ts +47 -1
- package/src/types/payment.types.ts +13 -0
- package/src/utils/card-utils.ts +1 -1
- package/src/utils/reward-utils.ts +41 -0
- package/src/views/masterpass-rest-option.tsx +133 -9
package/src/redux/api.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { api } from '@akinon/next/data/client/api';
|
|
2
2
|
import { buildClientRequestUrl } from '@akinon/next/utils';
|
|
3
3
|
import { setPaymentStepBusy } from '@akinon/next/redux/reducers/checkout';
|
|
4
|
+
import type { RewardItem } from '../types/payment.types';
|
|
4
5
|
|
|
5
6
|
export interface MasterpassRestTokenRequest {
|
|
6
7
|
three_d?: boolean;
|
|
@@ -97,6 +98,39 @@ interface CardType {
|
|
|
97
98
|
logo: string;
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
export interface QueryMasterpassRewardsRequest {
|
|
102
|
+
card_alias: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface QueryMasterpassRewardsResponse {
|
|
106
|
+
context_list: Array<{
|
|
107
|
+
page_context: {
|
|
108
|
+
rewards: RewardItem[];
|
|
109
|
+
};
|
|
110
|
+
page_name: string;
|
|
111
|
+
page_slug: string;
|
|
112
|
+
}>;
|
|
113
|
+
template_name: string;
|
|
114
|
+
errors: any;
|
|
115
|
+
pre_order: any;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface SelectMasterpassRewardsRequest {
|
|
119
|
+
special: string;
|
|
120
|
+
general: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface SelectMasterpassRewardsResponse {
|
|
124
|
+
context_list: Array<{
|
|
125
|
+
page_context: any;
|
|
126
|
+
page_name: string;
|
|
127
|
+
page_slug: string;
|
|
128
|
+
}>;
|
|
129
|
+
template_name: string;
|
|
130
|
+
errors: any;
|
|
131
|
+
pre_order: any;
|
|
132
|
+
}
|
|
133
|
+
|
|
100
134
|
export interface SetMasterpassRestBinNumberResponse {
|
|
101
135
|
context_list: Array<{
|
|
102
136
|
page_context: {
|
|
@@ -202,6 +236,51 @@ export const masterpassRestApi = api.injectEndpoints({
|
|
|
202
236
|
dispatch(setPaymentStepBusy(false));
|
|
203
237
|
}
|
|
204
238
|
}),
|
|
239
|
+
queryMasterpassRewards: build.mutation<
|
|
240
|
+
QueryMasterpassRewardsResponse,
|
|
241
|
+
QueryMasterpassRewardsRequest
|
|
242
|
+
>({
|
|
243
|
+
query: (arg) => ({
|
|
244
|
+
url: buildClientRequestUrl('/orders/checkout/', {
|
|
245
|
+
useFormData: true
|
|
246
|
+
}),
|
|
247
|
+
method: 'POST',
|
|
248
|
+
params: {
|
|
249
|
+
page: 'MasterpassRestRewardListPage'
|
|
250
|
+
},
|
|
251
|
+
body: {
|
|
252
|
+
card_alias: arg.card_alias
|
|
253
|
+
}
|
|
254
|
+
}),
|
|
255
|
+
async onQueryStarted(_, { dispatch, queryFulfilled }) {
|
|
256
|
+
dispatch(setPaymentStepBusy(true));
|
|
257
|
+
await queryFulfilled;
|
|
258
|
+
dispatch(setPaymentStepBusy(false));
|
|
259
|
+
}
|
|
260
|
+
}),
|
|
261
|
+
selectMasterpassRewards: build.mutation<
|
|
262
|
+
SelectMasterpassRewardsResponse,
|
|
263
|
+
SelectMasterpassRewardsRequest
|
|
264
|
+
>({
|
|
265
|
+
query: (arg) => ({
|
|
266
|
+
url: buildClientRequestUrl('/orders/checkout/', {
|
|
267
|
+
useFormData: true
|
|
268
|
+
}),
|
|
269
|
+
method: 'POST',
|
|
270
|
+
params: {
|
|
271
|
+
page: 'MasterpassRestRewardSelectionPage'
|
|
272
|
+
},
|
|
273
|
+
body: {
|
|
274
|
+
special: arg.special,
|
|
275
|
+
general: arg.general
|
|
276
|
+
}
|
|
277
|
+
}),
|
|
278
|
+
async onQueryStarted(_, { dispatch, queryFulfilled }) {
|
|
279
|
+
dispatch(setPaymentStepBusy(true));
|
|
280
|
+
await queryFulfilled;
|
|
281
|
+
dispatch(setPaymentStepBusy(false));
|
|
282
|
+
}
|
|
283
|
+
}),
|
|
205
284
|
finalizeMasterpassOrder: build.mutation<
|
|
206
285
|
FinalizeMasterpassOrderResponse,
|
|
207
286
|
FinalizeMasterpassOrderRequest
|
|
@@ -239,5 +318,7 @@ export const {
|
|
|
239
318
|
useSetMasterpassRestBinNumberMutation,
|
|
240
319
|
useCheckoutMasterpassInstallmentMutation,
|
|
241
320
|
usePrepareMasterpassOrderMutation,
|
|
242
|
-
useFinalizeMasterpassOrderMutation
|
|
321
|
+
useFinalizeMasterpassOrderMutation,
|
|
322
|
+
useQueryMasterpassRewardsMutation,
|
|
323
|
+
useSelectMasterpassRewardsMutation
|
|
243
324
|
} = masterpassRestApi;
|
package/src/redux/reducer.ts
CHANGED
|
@@ -5,7 +5,8 @@ import {
|
|
|
5
5
|
ModalState,
|
|
6
6
|
Installment,
|
|
7
7
|
CardType,
|
|
8
|
-
OrderData
|
|
8
|
+
OrderData,
|
|
9
|
+
RewardItem
|
|
9
10
|
} from '../types/payment.types';
|
|
10
11
|
import { CreditCardFormData } from '../utils/validation-schemas';
|
|
11
12
|
|
|
@@ -68,13 +69,17 @@ const initialState: MasterpassRestState = {
|
|
|
68
69
|
orderData: null,
|
|
69
70
|
orderCompleted: false,
|
|
70
71
|
cvc: '',
|
|
71
|
-
useThreeD: true
|
|
72
|
+
useThreeD: true,
|
|
73
|
+
availableRewards: [],
|
|
74
|
+
selectedRewards: [],
|
|
75
|
+
isLoadingRewards: false
|
|
72
76
|
},
|
|
73
77
|
modalState: {
|
|
74
78
|
showLinkModal: false,
|
|
75
79
|
showOTPModal: false,
|
|
76
80
|
show3DSecureModal: false,
|
|
77
81
|
showInformationModal: false,
|
|
82
|
+
showRewardModal: false,
|
|
78
83
|
informationModalData: null,
|
|
79
84
|
otpType: 'OTP',
|
|
80
85
|
verificationData: null,
|
|
@@ -169,6 +174,24 @@ const masterpassRestSlice = createSlice({
|
|
|
169
174
|
setOrderCompleted: (state, action: PayloadAction<boolean>) => {
|
|
170
175
|
state.paymentState.orderCompleted = action.payload;
|
|
171
176
|
},
|
|
177
|
+
setAvailableRewards: (state, action: PayloadAction<RewardItem[]>) => {
|
|
178
|
+
state.paymentState.availableRewards = action.payload;
|
|
179
|
+
},
|
|
180
|
+
setSelectedRewards: (state, action: PayloadAction<RewardItem[]>) => {
|
|
181
|
+
state.paymentState.selectedRewards = action.payload;
|
|
182
|
+
},
|
|
183
|
+
setLoadingRewards: (state, action: PayloadAction<boolean>) => {
|
|
184
|
+
state.paymentState.isLoadingRewards = action.payload;
|
|
185
|
+
},
|
|
186
|
+
setShowRewardModal: (state, action: PayloadAction<boolean>) => {
|
|
187
|
+
state.modalState.showRewardModal = action.payload;
|
|
188
|
+
},
|
|
189
|
+
clearRewards: (state) => {
|
|
190
|
+
state.paymentState.availableRewards = [];
|
|
191
|
+
state.paymentState.selectedRewards = [];
|
|
192
|
+
state.paymentState.isLoadingRewards = false;
|
|
193
|
+
state.modalState.showRewardModal = false;
|
|
194
|
+
},
|
|
172
195
|
updateModalState: (state, action: PayloadAction<Partial<ModalState>>) => {
|
|
173
196
|
state.modalState = { ...state.modalState, ...action.payload };
|
|
174
197
|
},
|
|
@@ -234,13 +257,17 @@ const masterpassRestSlice = createSlice({
|
|
|
234
257
|
orderData: null,
|
|
235
258
|
orderCompleted: false,
|
|
236
259
|
cvc: '',
|
|
237
|
-
useThreeD: true
|
|
260
|
+
useThreeD: true,
|
|
261
|
+
availableRewards: [],
|
|
262
|
+
selectedRewards: [],
|
|
263
|
+
isLoadingRewards: false
|
|
238
264
|
};
|
|
239
265
|
state.modalState = {
|
|
240
266
|
showLinkModal: false,
|
|
241
267
|
showOTPModal: false,
|
|
242
268
|
show3DSecureModal: false,
|
|
243
269
|
showInformationModal: false,
|
|
270
|
+
showRewardModal: false,
|
|
244
271
|
informationModalData: null,
|
|
245
272
|
otpType: 'OTP',
|
|
246
273
|
verificationData: null,
|
|
@@ -276,6 +303,11 @@ export const {
|
|
|
276
303
|
setOrderData,
|
|
277
304
|
setCvc,
|
|
278
305
|
setOrderCompleted,
|
|
306
|
+
setAvailableRewards,
|
|
307
|
+
setSelectedRewards,
|
|
308
|
+
setLoadingRewards,
|
|
309
|
+
setShowRewardModal,
|
|
310
|
+
clearRewards,
|
|
279
311
|
updateModalState,
|
|
280
312
|
setShowLinkModal,
|
|
281
313
|
setShowOTPModal,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReactElement } from 'react';
|
|
2
2
|
import type { CreditCardFormData } from '../utils/validation-schemas';
|
|
3
|
-
import { CardType, Installment } from './payment.types';
|
|
3
|
+
import { CardType, Installment, RewardItem } from './payment.types';
|
|
4
4
|
|
|
5
5
|
export type PaymentMethodSelectorProps = {
|
|
6
6
|
cards: any[];
|
|
@@ -18,6 +18,14 @@ export type CardListProps = {
|
|
|
18
18
|
cvc?: string;
|
|
19
19
|
onCvcChange?: (cvc: string) => void;
|
|
20
20
|
cvcRequired?: boolean;
|
|
21
|
+
availableRewards?: RewardItem[];
|
|
22
|
+
selectedRewards?: RewardItem[];
|
|
23
|
+
isLoadingRewards?: boolean;
|
|
24
|
+
isConfirmingRewards?: boolean;
|
|
25
|
+
onOpenRewardModal?: () => void;
|
|
26
|
+
onConfirmRewards?: (selected: RewardItem[]) => Promise<void> | void;
|
|
27
|
+
rewardCurrency?: string;
|
|
28
|
+
rewardPayableAmount?: string | number | null;
|
|
21
29
|
texts: MasterpassRestOptionTexts;
|
|
22
30
|
};
|
|
23
31
|
|
|
@@ -58,6 +66,18 @@ export type OTPModalProps = {
|
|
|
58
66
|
texts: MasterpassRestOptionTexts;
|
|
59
67
|
};
|
|
60
68
|
|
|
69
|
+
export type RewardSelectionModalProps = {
|
|
70
|
+
open: boolean;
|
|
71
|
+
onClose: () => void;
|
|
72
|
+
onConfirm: (selected: RewardItem[]) => Promise<void> | void;
|
|
73
|
+
rewards: RewardItem[];
|
|
74
|
+
selectedRewards: RewardItem[];
|
|
75
|
+
isLoading?: boolean;
|
|
76
|
+
currency?: string;
|
|
77
|
+
payableAmount?: string | number | null;
|
|
78
|
+
texts: MasterpassRestOptionTexts;
|
|
79
|
+
};
|
|
80
|
+
|
|
61
81
|
export type ConfirmationModalProps = {
|
|
62
82
|
open: boolean;
|
|
63
83
|
onClose: () => void;
|
|
@@ -119,6 +139,14 @@ export type MasterpassRestOptionRenderProps = {
|
|
|
119
139
|
isInstallmentLoading: boolean;
|
|
120
140
|
isPrepareLoading: boolean;
|
|
121
141
|
isFinalizeLoading: boolean;
|
|
142
|
+
isProcessingPayment: boolean;
|
|
143
|
+
isRewardsQueryLoading: boolean;
|
|
144
|
+
isRewardsSelectLoading: boolean;
|
|
145
|
+
|
|
146
|
+
openRewardModal: () => void;
|
|
147
|
+
closeRewardModal: () => void;
|
|
148
|
+
handleConfirmRewards: (selected: RewardItem[]) => Promise<void>;
|
|
149
|
+
payableAmount: string | null;
|
|
122
150
|
|
|
123
151
|
texts: MasterpassRestOptionTexts;
|
|
124
152
|
|
|
@@ -130,6 +158,7 @@ export type MasterpassRestOptionRenderProps = {
|
|
|
130
158
|
LinkModal: React.ComponentType<LinkModalProps>;
|
|
131
159
|
OTPModal: React.ComponentType<OTPModalProps>;
|
|
132
160
|
ConfirmationModal: React.ComponentType<ConfirmationModalProps>;
|
|
161
|
+
RewardSelectionModal: React.ComponentType<RewardSelectionModalProps>;
|
|
133
162
|
};
|
|
134
163
|
};
|
|
135
164
|
|
|
@@ -141,6 +170,7 @@ export type MasterpassRestOptionCustomRender = {
|
|
|
141
170
|
linkModal?: (props: LinkModalProps) => ReactElement;
|
|
142
171
|
otpModal?: (props: OTPModalProps) => ReactElement;
|
|
143
172
|
confirmationModal?: (props: ConfirmationModalProps) => ReactElement;
|
|
173
|
+
rewardSelectionModal?: (props: RewardSelectionModalProps) => ReactElement;
|
|
144
174
|
errorDisplay?: (props: ErrorDisplayProps) => ReactElement;
|
|
145
175
|
loadingState?: (props: LoadingStateProps) => ReactElement;
|
|
146
176
|
emptyState?: (props: EmptyStateProps) => ReactElement;
|
|
@@ -277,4 +307,20 @@ export type MasterpassRestOptionTexts = {
|
|
|
277
307
|
sessionExpiredMessage?: string;
|
|
278
308
|
sessionExpiredSecondaryMessage?: string;
|
|
279
309
|
sessionExpiredButton?: string;
|
|
310
|
+
|
|
311
|
+
rewardModalTitle?: string;
|
|
312
|
+
rewardModalDescription?: string;
|
|
313
|
+
rewardModalEmptyMessage?: string;
|
|
314
|
+
rewardModalConfirmText?: string;
|
|
315
|
+
rewardModalCancelText?: string;
|
|
316
|
+
rewardModalLoadingText?: string;
|
|
317
|
+
rewardOpenButtonText?: string;
|
|
318
|
+
rewardSelectedSummaryText?: string;
|
|
319
|
+
rewardAvailableSummaryText?: string;
|
|
320
|
+
rewardClearText?: string;
|
|
321
|
+
rewardCategorySpecialText?: string;
|
|
322
|
+
rewardCategoryGeneralText?: string;
|
|
323
|
+
rewardFailedToLoadText?: string;
|
|
324
|
+
rewardFailedToSelectText?: string;
|
|
325
|
+
rewardCappedNoticeText?: string;
|
|
280
326
|
};
|
|
@@ -69,6 +69,15 @@ export type TransactionType =
|
|
|
69
69
|
| 'DIRECT_PURCHASE'
|
|
70
70
|
| 'DIRECT_PURCHASE_3D';
|
|
71
71
|
|
|
72
|
+
export type RewardName = 'FBB' | 'BNS';
|
|
73
|
+
export type RewardCategory = 'special' | 'general';
|
|
74
|
+
|
|
75
|
+
export interface RewardItem {
|
|
76
|
+
type: RewardCategory;
|
|
77
|
+
amount: number | string;
|
|
78
|
+
name?: RewardName;
|
|
79
|
+
}
|
|
80
|
+
|
|
72
81
|
export interface PaymentState {
|
|
73
82
|
selectedCard: any | null;
|
|
74
83
|
selectedInstallment: Installment | null;
|
|
@@ -78,6 +87,9 @@ export interface PaymentState {
|
|
|
78
87
|
orderCompleted: boolean;
|
|
79
88
|
cvc: string;
|
|
80
89
|
useThreeD: boolean;
|
|
90
|
+
availableRewards: RewardItem[];
|
|
91
|
+
selectedRewards: RewardItem[];
|
|
92
|
+
isLoadingRewards: boolean;
|
|
81
93
|
}
|
|
82
94
|
|
|
83
95
|
export type InformationModalType = 'warning' | 'error' | 'success' | 'info';
|
|
@@ -95,6 +107,7 @@ export interface ModalState {
|
|
|
95
107
|
showOTPModal: boolean;
|
|
96
108
|
show3DSecureModal: boolean;
|
|
97
109
|
showInformationModal: boolean;
|
|
110
|
+
showRewardModal: boolean;
|
|
98
111
|
informationModalData: InformationModalData | null;
|
|
99
112
|
otpType: OTPType;
|
|
100
113
|
verificationData: any | null;
|
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, 19);
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export const formatExpiryDate = (value: string): string => {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { RewardItem, RewardCategory } from '../types/payment.types';
|
|
2
|
+
|
|
3
|
+
export const REWARD_PRIORITY: RewardCategory[] = ['special', 'general'];
|
|
4
|
+
|
|
5
|
+
export const parseRewardAmount = (amount: number | string): number => {
|
|
6
|
+
const value = typeof amount === 'string' ? parseFloat(amount) : amount;
|
|
7
|
+
return Number.isFinite(value) ? value : 0;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const getCappedRewardAmounts = (
|
|
11
|
+
selected: RewardItem[],
|
|
12
|
+
payableAmount?: string | number | null
|
|
13
|
+
): Record<RewardCategory, number> => {
|
|
14
|
+
const parsedPayable =
|
|
15
|
+
payableAmount != null ? parseRewardAmount(payableAmount) : NaN;
|
|
16
|
+
let remaining = Number.isFinite(parsedPayable) ? parsedPayable : Infinity;
|
|
17
|
+
|
|
18
|
+
const result: Record<RewardCategory, number> = { special: 0, general: 0 };
|
|
19
|
+
|
|
20
|
+
REWARD_PRIORITY.forEach((type) => {
|
|
21
|
+
const picked = selected.find((item) => item.type === type);
|
|
22
|
+
if (!picked) return;
|
|
23
|
+
|
|
24
|
+
const value = parseRewardAmount(picked.amount);
|
|
25
|
+
if (value <= 0) return;
|
|
26
|
+
|
|
27
|
+
const used = Math.min(value, remaining);
|
|
28
|
+
remaining = Math.max(remaining - used, 0);
|
|
29
|
+
result[type] = used;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const getCappedRewardTotal = (
|
|
36
|
+
selected: RewardItem[],
|
|
37
|
+
payableAmount?: string | number | null
|
|
38
|
+
): number => {
|
|
39
|
+
const amounts = getCappedRewardAmounts(selected, payableAmount);
|
|
40
|
+
return REWARD_PRIORITY.reduce((sum, type) => sum + amounts[type], 0);
|
|
41
|
+
};
|
|
@@ -29,6 +29,8 @@ 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
31
|
import InformationModal from '../components/information-modal';
|
|
32
|
+
import RewardSelectionModal from '../components/reward-selection-modal';
|
|
33
|
+
import type { RewardItem } from '../types/payment.types';
|
|
32
34
|
import mpBlackLogo from '../assets/img/masterpass-black-logo.png';
|
|
33
35
|
|
|
34
36
|
interface MasterpassRestOptionProps {
|
|
@@ -38,6 +40,7 @@ interface MasterpassRestOptionProps {
|
|
|
38
40
|
environment?: MasterpassEnvironment;
|
|
39
41
|
texts?: MasterpassRestOptionTexts;
|
|
40
42
|
customRender?: MasterpassRestOptionCustomRender;
|
|
43
|
+
enableRewards?: boolean;
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
const defaultTexts: MasterpassRestOptionTexts = {
|
|
@@ -158,7 +161,23 @@ const defaultTexts: MasterpassRestOptionTexts = {
|
|
|
158
161
|
sessionExpiredTitle: 'Session Expired',
|
|
159
162
|
sessionExpiredMessage: 'Your session has expired due to inactivity. Please restart the process to continue.',
|
|
160
163
|
sessionExpiredSecondaryMessage: 'For security reasons, sessions are only valid for a limited time.',
|
|
161
|
-
sessionExpiredButton: 'Start Again'
|
|
164
|
+
sessionExpiredButton: 'Start Again',
|
|
165
|
+
|
|
166
|
+
rewardModalTitle: 'Use Card Rewards',
|
|
167
|
+
rewardModalDescription: 'Select the rewards you want to apply to this order.',
|
|
168
|
+
rewardModalEmptyMessage: 'No rewards available for this card.',
|
|
169
|
+
rewardModalConfirmText: 'Apply',
|
|
170
|
+
rewardModalCancelText: 'Cancel',
|
|
171
|
+
rewardModalLoadingText: 'Applying...',
|
|
172
|
+
rewardOpenButtonText: 'Use Rewards',
|
|
173
|
+
rewardSelectedSummaryText: '{count} reward(s) applied',
|
|
174
|
+
rewardAvailableSummaryText: '{count} reward(s) available',
|
|
175
|
+
rewardClearText: 'Clear',
|
|
176
|
+
rewardCategorySpecialText: 'Special Rewards',
|
|
177
|
+
rewardCategoryGeneralText: 'General Rewards',
|
|
178
|
+
rewardFailedToLoadText: 'Failed to load rewards',
|
|
179
|
+
rewardFailedToSelectText: 'Failed to apply rewards',
|
|
180
|
+
rewardCappedNoticeText: '{amount} will be applied'
|
|
162
181
|
};
|
|
163
182
|
|
|
164
183
|
const mergeTexts = (
|
|
@@ -177,7 +196,8 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
177
196
|
sdkUrl,
|
|
178
197
|
environment,
|
|
179
198
|
texts = defaultTexts,
|
|
180
|
-
customRender
|
|
199
|
+
customRender,
|
|
200
|
+
enableRewards = false
|
|
181
201
|
}) => {
|
|
182
202
|
const mergedTexts = React.useMemo(
|
|
183
203
|
() => mergeTexts(texts, defaultTexts),
|
|
@@ -201,6 +221,7 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
201
221
|
'stored_card' | 'new_card'
|
|
202
222
|
>('stored_card');
|
|
203
223
|
const [cvc, setCvc] = useState<string>('');
|
|
224
|
+
const [isProcessingPayment, setIsProcessingPayment] = useState(false);
|
|
204
225
|
|
|
205
226
|
const [setMasterpassRestBinNumber] = useSetMasterpassRestBinNumberMutation();
|
|
206
227
|
|
|
@@ -209,12 +230,18 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
209
230
|
isInstallmentLoading,
|
|
210
231
|
isPrepareLoading,
|
|
211
232
|
isFinalizeLoading,
|
|
233
|
+
isRewardsQueryLoading,
|
|
234
|
+
isRewardsSelectLoading,
|
|
212
235
|
updatePaymentState,
|
|
213
236
|
handleCardSelect,
|
|
214
237
|
handleInstallmentSelect,
|
|
215
238
|
processPayment,
|
|
216
|
-
processDirectPayment
|
|
217
|
-
|
|
239
|
+
processDirectPayment,
|
|
240
|
+
openRewardModal,
|
|
241
|
+
closeRewardModal,
|
|
242
|
+
confirmRewards,
|
|
243
|
+
payableAmount
|
|
244
|
+
} = useMasterpassPayment({ enableRewards });
|
|
218
245
|
|
|
219
246
|
const {
|
|
220
247
|
accountData,
|
|
@@ -382,6 +409,10 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
382
409
|
}, [dispatch, handleAddCard, mergedTexts.failedToSaveCardText]);
|
|
383
410
|
|
|
384
411
|
const handleProceedToPayment = useCallback(async () => {
|
|
412
|
+
if (isProcessingPayment) return;
|
|
413
|
+
|
|
414
|
+
setIsProcessingPayment(true);
|
|
415
|
+
|
|
385
416
|
try {
|
|
386
417
|
dispatch(clearAllErrors());
|
|
387
418
|
|
|
@@ -408,8 +439,10 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
408
439
|
const errorMessage =
|
|
409
440
|
error instanceof Error ? error.message : mergedTexts.paymentFailedText;
|
|
410
441
|
dispatch(setError(errorMessage));
|
|
442
|
+
} finally {
|
|
443
|
+
setIsProcessingPayment(false);
|
|
411
444
|
}
|
|
412
|
-
}, [dispatch, paymentMethod, processDirectPayment, processPayment, updateModalState, mergedTexts.paymentFailedText]);
|
|
445
|
+
}, [isProcessingPayment, dispatch, paymentMethod, processDirectPayment, processPayment, updateModalState, mergedTexts.paymentFailedText]);
|
|
413
446
|
|
|
414
447
|
const handleOTPSubmitWithErrorHandling = useCallback(async (otp: string) => {
|
|
415
448
|
if (!handleOTPSubmit)
|
|
@@ -449,6 +482,18 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
449
482
|
}
|
|
450
483
|
}, [dispatch, handleInstallmentSelect, mergedTexts.failedToSelectInstallmentText]);
|
|
451
484
|
|
|
485
|
+
const handleConfirmRewards = useCallback(
|
|
486
|
+
async (selected: RewardItem[]) => {
|
|
487
|
+
const result = await confirmRewards(selected);
|
|
488
|
+
if (!result.success) {
|
|
489
|
+
dispatch(
|
|
490
|
+
setError(result.message || mergedTexts.rewardFailedToSelectText)
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
[confirmRewards, dispatch, mergedTexts.rewardFailedToSelectText]
|
|
495
|
+
);
|
|
496
|
+
|
|
452
497
|
const handleCvcChange = useCallback((newCvc: string) => {
|
|
453
498
|
setCvc(newCvc);
|
|
454
499
|
updatePaymentState({ cvc: newCvc });
|
|
@@ -486,7 +531,8 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
486
531
|
InstallmentList: wrapRenderFunction(customRenderRef.current?.installmentList) || InstallmentList,
|
|
487
532
|
LinkModal: wrapRenderFunction(customRenderRef.current?.linkModal) || LinkModal,
|
|
488
533
|
OTPModal: wrapRenderFunction(customRenderRef.current?.otpModal) || OTPModal,
|
|
489
|
-
ConfirmationModal: wrapRenderFunction(customRenderRef.current?.confirmationModal) || ConfirmationModal
|
|
534
|
+
ConfirmationModal: wrapRenderFunction(customRenderRef.current?.confirmationModal) || ConfirmationModal,
|
|
535
|
+
RewardSelectionModal: wrapRenderFunction(customRenderRef.current?.rewardSelectionModal) || RewardSelectionModal
|
|
490
536
|
};
|
|
491
537
|
}, [
|
|
492
538
|
customRender?.paymentMethodSelector,
|
|
@@ -495,7 +541,8 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
495
541
|
customRender?.installmentList,
|
|
496
542
|
customRender?.linkModal,
|
|
497
543
|
customRender?.otpModal,
|
|
498
|
-
customRender?.confirmationModal
|
|
544
|
+
customRender?.confirmationModal,
|
|
545
|
+
customRender?.rewardSelectionModal
|
|
499
546
|
]);
|
|
500
547
|
|
|
501
548
|
const hasStoredCards =
|
|
@@ -541,6 +588,14 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
541
588
|
isInstallmentLoading,
|
|
542
589
|
isPrepareLoading,
|
|
543
590
|
isFinalizeLoading,
|
|
591
|
+
isProcessingPayment,
|
|
592
|
+
isRewardsQueryLoading,
|
|
593
|
+
isRewardsSelectLoading,
|
|
594
|
+
|
|
595
|
+
openRewardModal,
|
|
596
|
+
closeRewardModal,
|
|
597
|
+
handleConfirmRewards,
|
|
598
|
+
payableAmount,
|
|
544
599
|
|
|
545
600
|
texts: mergedTexts,
|
|
546
601
|
|
|
@@ -573,6 +628,13 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
573
628
|
isInstallmentLoading,
|
|
574
629
|
isPrepareLoading,
|
|
575
630
|
isFinalizeLoading,
|
|
631
|
+
isProcessingPayment,
|
|
632
|
+
isRewardsQueryLoading,
|
|
633
|
+
isRewardsSelectLoading,
|
|
634
|
+
openRewardModal,
|
|
635
|
+
closeRewardModal,
|
|
636
|
+
handleConfirmRewards,
|
|
637
|
+
payableAmount,
|
|
576
638
|
mergedTexts,
|
|
577
639
|
memoizedComponents
|
|
578
640
|
]);
|
|
@@ -651,6 +713,20 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
651
713
|
removingCardId: modalState?.removingCardId,
|
|
652
714
|
cvc: cvc,
|
|
653
715
|
onCvcChange: handleCvcChange,
|
|
716
|
+
...(enableRewards
|
|
717
|
+
? {
|
|
718
|
+
availableRewards: paymentState.availableRewards,
|
|
719
|
+
selectedRewards: paymentState.selectedRewards,
|
|
720
|
+
isLoadingRewards:
|
|
721
|
+
paymentState.isLoadingRewards ||
|
|
722
|
+
isRewardsQueryLoading,
|
|
723
|
+
isConfirmingRewards: isRewardsSelectLoading,
|
|
724
|
+
onOpenRewardModal: openRewardModal,
|
|
725
|
+
onConfirmRewards: handleConfirmRewards,
|
|
726
|
+
rewardCurrency: stateCurrency || currency,
|
|
727
|
+
rewardPayableAmount: payableAmount
|
|
728
|
+
}
|
|
729
|
+
: {}),
|
|
654
730
|
texts: mergedTexts
|
|
655
731
|
})
|
|
656
732
|
) : (
|
|
@@ -662,6 +738,20 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
662
738
|
removingCardId={modalState?.removingCardId}
|
|
663
739
|
cvc={cvc}
|
|
664
740
|
onCvcChange={handleCvcChange}
|
|
741
|
+
{...(enableRewards
|
|
742
|
+
? {
|
|
743
|
+
availableRewards: paymentState.availableRewards,
|
|
744
|
+
selectedRewards: paymentState.selectedRewards,
|
|
745
|
+
isLoadingRewards:
|
|
746
|
+
paymentState.isLoadingRewards ||
|
|
747
|
+
isRewardsQueryLoading,
|
|
748
|
+
isConfirmingRewards: isRewardsSelectLoading,
|
|
749
|
+
onOpenRewardModal: openRewardModal,
|
|
750
|
+
onConfirmRewards: handleConfirmRewards,
|
|
751
|
+
rewardCurrency: stateCurrency || currency,
|
|
752
|
+
rewardPayableAmount: payableAmount
|
|
753
|
+
}
|
|
754
|
+
: {})}
|
|
665
755
|
texts={mergedTexts}
|
|
666
756
|
/>
|
|
667
757
|
)}
|
|
@@ -707,7 +797,10 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
707
797
|
selectedInstallment: paymentState.selectedInstallment,
|
|
708
798
|
isLoading: isInstallmentLoading || isPrepareLoading,
|
|
709
799
|
onProceedToPayment: handleProceedToPayment,
|
|
710
|
-
paymentLoading:
|
|
800
|
+
paymentLoading:
|
|
801
|
+
isProcessingPayment ||
|
|
802
|
+
isPrepareLoading ||
|
|
803
|
+
isFinalizeLoading,
|
|
711
804
|
texts: mergedTexts
|
|
712
805
|
})
|
|
713
806
|
) : (
|
|
@@ -718,7 +811,11 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
718
811
|
selectedInstallment={paymentState.selectedInstallment}
|
|
719
812
|
isLoading={isInstallmentLoading || isPrepareLoading}
|
|
720
813
|
onProceedToPayment={handleProceedToPayment}
|
|
721
|
-
paymentLoading={
|
|
814
|
+
paymentLoading={
|
|
815
|
+
isProcessingPayment ||
|
|
816
|
+
isPrepareLoading ||
|
|
817
|
+
isFinalizeLoading
|
|
818
|
+
}
|
|
722
819
|
texts={mergedTexts}
|
|
723
820
|
/>
|
|
724
821
|
)
|
|
@@ -894,6 +991,33 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
|
|
|
894
991
|
onClose={() => updateModalState({ showInformationModal: false, informationModalData: null })}
|
|
895
992
|
data={modalState?.informationModalData}
|
|
896
993
|
/>
|
|
994
|
+
|
|
995
|
+
{enableRewards &&
|
|
996
|
+
(customRender?.rewardSelectionModal ? (
|
|
997
|
+
customRender.rewardSelectionModal({
|
|
998
|
+
open: modalState?.showRewardModal ?? false,
|
|
999
|
+
onClose: closeRewardModal,
|
|
1000
|
+
onConfirm: handleConfirmRewards,
|
|
1001
|
+
rewards: paymentState.availableRewards,
|
|
1002
|
+
selectedRewards: paymentState.selectedRewards,
|
|
1003
|
+
isLoading: isRewardsSelectLoading,
|
|
1004
|
+
currency: stateCurrency || currency,
|
|
1005
|
+
payableAmount,
|
|
1006
|
+
texts: mergedTexts
|
|
1007
|
+
})
|
|
1008
|
+
) : (
|
|
1009
|
+
<RewardSelectionModal
|
|
1010
|
+
open={modalState?.showRewardModal ?? false}
|
|
1011
|
+
onClose={closeRewardModal}
|
|
1012
|
+
onConfirm={handleConfirmRewards}
|
|
1013
|
+
rewards={paymentState.availableRewards}
|
|
1014
|
+
selectedRewards={paymentState.selectedRewards}
|
|
1015
|
+
isLoading={isRewardsSelectLoading}
|
|
1016
|
+
currency={stateCurrency || currency}
|
|
1017
|
+
payableAmount={payableAmount}
|
|
1018
|
+
texts={mergedTexts}
|
|
1019
|
+
/>
|
|
1020
|
+
))}
|
|
897
1021
|
</div>
|
|
898
1022
|
);
|
|
899
1023
|
};
|