@akinon/next 1.18.0 → 1.19.1
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 +8 -0
- package/components/plugin-module.tsx +30 -4
- package/components/selected-payment-option-view.tsx +4 -0
- package/data/client/checkout.ts +149 -12
- package/data/urls.ts +2 -0
- package/hooks/use-payment-options.ts +1 -0
- package/middlewares/complete-masterpass.ts +159 -0
- package/middlewares/default.ts +105 -93
- package/middlewares/index.ts +3 -1
- package/package.json +2 -2
- package/plugins.d.ts +3 -0
- package/plugins.js +1 -0
- package/redux/reducers/index.ts +6 -2
- package/types/commerce/checkout.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,7 @@ enum Plugin {
|
|
|
15
15
|
Otp = 'pz-otp',
|
|
16
16
|
BKMExpress = 'pz-bkm',
|
|
17
17
|
CreditPayment = 'pz-credit-payment',
|
|
18
|
+
Masterpass = 'pz-masterpass',
|
|
18
19
|
B2B = 'pz-b2b'
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -28,6 +29,12 @@ export enum Component {
|
|
|
28
29
|
Otp = 'Otp',
|
|
29
30
|
BKMExpress = 'BKMOption',
|
|
30
31
|
CreditPayment = 'CreditPayment',
|
|
32
|
+
MasterpassProvider = 'MasterpassProvider',
|
|
33
|
+
MasterpassCardList = 'MasterpassCardList',
|
|
34
|
+
MasterpassCardRegistration = 'MasterpassCardRegistration',
|
|
35
|
+
MasterpassDeleteConfirmationModal = 'MasterpassDeleteConfirmationModal',
|
|
36
|
+
MasterpassOtpModal = 'MasterpassOtpModal',
|
|
37
|
+
MasterpassLinkModal = 'MasterpassLinkModal',
|
|
31
38
|
MyQuotationsB2B = 'AccountMyQuotations',
|
|
32
39
|
BasketB2B = 'BasketB2b'
|
|
33
40
|
}
|
|
@@ -42,7 +49,18 @@ const PluginComponents = new Map([
|
|
|
42
49
|
[Plugin.Otp, [Component.Otp]],
|
|
43
50
|
[Plugin.BKMExpress, [Component.BKMExpress]],
|
|
44
51
|
[Plugin.CreditPayment, [Component.CreditPayment]],
|
|
45
|
-
[
|
|
52
|
+
[
|
|
53
|
+
Plugin.Masterpass,
|
|
54
|
+
[
|
|
55
|
+
Component.MasterpassCardList,
|
|
56
|
+
Component.MasterpassProvider,
|
|
57
|
+
Component.MasterpassCardRegistration,
|
|
58
|
+
Component.MasterpassDeleteConfirmationModal,
|
|
59
|
+
Component.MasterpassOtpModal,
|
|
60
|
+
Component.MasterpassLinkModal
|
|
61
|
+
]
|
|
62
|
+
],
|
|
63
|
+
[Plugin.B2B, [Component.MyQuotationsB2B, Component.BasketB2B]]
|
|
46
64
|
]);
|
|
47
65
|
|
|
48
66
|
const getPlugin = (component: Component) => {
|
|
@@ -55,10 +73,12 @@ const getPlugin = (component: Component) => {
|
|
|
55
73
|
|
|
56
74
|
export default function PluginModule({
|
|
57
75
|
component,
|
|
58
|
-
props
|
|
76
|
+
props,
|
|
77
|
+
children
|
|
59
78
|
}: {
|
|
60
79
|
component: Component;
|
|
61
80
|
props?: any;
|
|
81
|
+
children?: React.ReactNode;
|
|
62
82
|
}) {
|
|
63
83
|
const plugin = getPlugin(component);
|
|
64
84
|
|
|
@@ -87,6 +107,8 @@ export default function PluginModule({
|
|
|
87
107
|
promise = import(`${'@akinon/pz-bkm'}`);
|
|
88
108
|
} else if (plugin === Plugin.CreditPayment) {
|
|
89
109
|
promise = import(`${'@akinon/pz-credit-payment'}`);
|
|
110
|
+
} else if (plugin === Plugin.Masterpass) {
|
|
111
|
+
promise = import(`${'@akinon/pz-masterpass'}`);
|
|
90
112
|
} else if (plugin === Plugin.B2B) {
|
|
91
113
|
promise = import(`${'@akinon/pz-b2b'}`);
|
|
92
114
|
}
|
|
@@ -100,11 +122,15 @@ export default function PluginModule({
|
|
|
100
122
|
},
|
|
101
123
|
{ ssr: false }
|
|
102
124
|
),
|
|
103
|
-
[plugin]
|
|
125
|
+
[plugin, component]
|
|
104
126
|
);
|
|
105
127
|
|
|
106
128
|
if (!(plugins as string[]).includes(plugin)) {
|
|
107
|
-
return
|
|
129
|
+
return <>{children}</>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (children) {
|
|
133
|
+
return <Component {...props}>{children}</Component>;
|
|
108
134
|
}
|
|
109
135
|
|
|
110
136
|
return <Component {...props} />;
|
|
@@ -50,6 +50,10 @@ export default function SelectedPaymentOptionView() {
|
|
|
50
50
|
);
|
|
51
51
|
} else if (payment_option.payment_type === 'loyalty_money') {
|
|
52
52
|
promise = import(`views/checkout/steps/payment/options/loyalty`);
|
|
53
|
+
} else if (payment_option.payment_type === 'masterpass') {
|
|
54
|
+
promise = import(
|
|
55
|
+
`views/checkout/steps/payment/options/credit-card`
|
|
56
|
+
);
|
|
53
57
|
}
|
|
54
58
|
// else if (payment_option.payment_type === 'credit_payment') {
|
|
55
59
|
// promise = import(`views/checkout/steps/payment/options/credit-payment`);
|
package/data/client/checkout.ts
CHANGED
|
@@ -16,7 +16,17 @@ import {
|
|
|
16
16
|
import { buildClientRequestUrl } from '../../utils';
|
|
17
17
|
import { api } from './api';
|
|
18
18
|
import { checkout } from '../urls';
|
|
19
|
-
import { store } from 'redux/store';
|
|
19
|
+
import { AppDispatch, AppStore, store } from 'redux/store';
|
|
20
|
+
import settings from 'settings';
|
|
21
|
+
import { showMobile3dIframe } from '../../utils/mobile-3d-iframe';
|
|
22
|
+
import {
|
|
23
|
+
setError,
|
|
24
|
+
setOtpModalVisible
|
|
25
|
+
} from '@akinon/pz-masterpass/src/redux/reducer';
|
|
26
|
+
import {
|
|
27
|
+
buildDirectPurchaseForm,
|
|
28
|
+
buildPurchaseForm
|
|
29
|
+
} from '@akinon/pz-masterpass/src/utils';
|
|
20
30
|
|
|
21
31
|
interface CheckoutResponse {
|
|
22
32
|
pre_order?: PreOrder;
|
|
@@ -38,7 +48,7 @@ interface SetRetailStoreParams {
|
|
|
38
48
|
billingAddressPk: number;
|
|
39
49
|
}
|
|
40
50
|
|
|
41
|
-
interface CompleteCreditCardParams {
|
|
51
|
+
export interface CompleteCreditCardParams {
|
|
42
52
|
card_holder: string;
|
|
43
53
|
card_cvv: string;
|
|
44
54
|
card_number: string;
|
|
@@ -66,6 +76,83 @@ export interface PayOnDeliveryParams {
|
|
|
66
76
|
paymentType: string;
|
|
67
77
|
}
|
|
68
78
|
|
|
79
|
+
const completeMasterpassPayment = async (
|
|
80
|
+
params: CompleteCreditCardParams,
|
|
81
|
+
dispatch: AppDispatch,
|
|
82
|
+
context: CheckoutContext,
|
|
83
|
+
reduxStore: AppStore
|
|
84
|
+
) => {
|
|
85
|
+
const state = reduxStore.getState();
|
|
86
|
+
const { token, order_no } = context.page_context;
|
|
87
|
+
const { endpoints: apiEndpoints } = checkoutApi;
|
|
88
|
+
const { preOrder } = state.checkout ?? {};
|
|
89
|
+
|
|
90
|
+
const { isDirectPurchase, referenceNo, credentials, msisdn, selectedCard } =
|
|
91
|
+
state.masterpass ?? {};
|
|
92
|
+
|
|
93
|
+
const commonFormValues = {
|
|
94
|
+
token,
|
|
95
|
+
orderNo: order_no,
|
|
96
|
+
referenceNo,
|
|
97
|
+
merchantId: credentials?.merchant_id ?? null,
|
|
98
|
+
msisdn,
|
|
99
|
+
amount: preOrder?.unpaid_amount?.replace('.', '') ?? ''
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const form = isDirectPurchase
|
|
103
|
+
? await buildDirectPurchaseForm(commonFormValues, params)
|
|
104
|
+
: await buildPurchaseForm({ ...commonFormValues, selectedCard });
|
|
105
|
+
|
|
106
|
+
window.MFS?.[
|
|
107
|
+
state.masterpass.isDirectPurchase ? 'directPurchase' : 'purchase'
|
|
108
|
+
]($(form), async (statusCode, response) => {
|
|
109
|
+
if (response.responseCode == '5009') {
|
|
110
|
+
// TODO: Handle Digital Loan
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (response.responseCode == '5010') {
|
|
114
|
+
const { isMobileApp } = state.root;
|
|
115
|
+
const returnUrl = state.masterpass.credentials?.s3d_return_url;
|
|
116
|
+
const url =
|
|
117
|
+
response.url3D + '&returnUrl=' + encodeURIComponent(returnUrl);
|
|
118
|
+
const urlObj = new URL(url, window.location.origin);
|
|
119
|
+
urlObj.searchParams.set('t', new Date().getTime().toString());
|
|
120
|
+
|
|
121
|
+
if (
|
|
122
|
+
(isMobileApp ||
|
|
123
|
+
/iPad|iPhone|iPod|Android/i.test(navigator.userAgent)) &&
|
|
124
|
+
!settings.checkout?.iframeExcludedPaymentOptions?.includes(
|
|
125
|
+
preOrder?.payment_option?.slug
|
|
126
|
+
)
|
|
127
|
+
) {
|
|
128
|
+
showMobile3dIframe(urlObj.toString());
|
|
129
|
+
} else {
|
|
130
|
+
window.location.href = urlObj.toString();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (response.responseCode == '5001') {
|
|
137
|
+
dispatch(setOtpModalVisible(true));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (
|
|
142
|
+
response.token &&
|
|
143
|
+
(response.responseCode == '0000' || response.responseCode == '')
|
|
144
|
+
) {
|
|
145
|
+
dispatch(
|
|
146
|
+
apiEndpoints.completeMasterpassPayment.initiate({ token }) as any
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (response.responseDescription.length) {
|
|
151
|
+
dispatch(setError(response.responseDescription));
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
|
|
69
156
|
export const checkoutApi = api.injectEndpoints({
|
|
70
157
|
endpoints: (build) => ({
|
|
71
158
|
fetchCheckout: build.query<CheckoutResponse, void>({
|
|
@@ -101,19 +188,69 @@ export const checkoutApi = api.injectEndpoints({
|
|
|
101
188
|
card_month,
|
|
102
189
|
card_year,
|
|
103
190
|
use_three_d = true
|
|
104
|
-
}) =>
|
|
105
|
-
|
|
191
|
+
}) => {
|
|
192
|
+
const paymentOption =
|
|
193
|
+
store.getState().checkout?.preOrder?.payment_option;
|
|
194
|
+
|
|
195
|
+
if (paymentOption?.payment_type === 'masterpass') {
|
|
196
|
+
return {
|
|
197
|
+
url: buildClientRequestUrl(checkout.getMasterpassOrderNo, {
|
|
198
|
+
useFormData: true
|
|
199
|
+
}),
|
|
200
|
+
method: 'POST'
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
url: buildClientRequestUrl(checkout.completeCreditCardPayment, {
|
|
206
|
+
useFormData: true
|
|
207
|
+
}),
|
|
208
|
+
method: 'POST',
|
|
209
|
+
body: {
|
|
210
|
+
agreement: '1',
|
|
211
|
+
use_three_d: use_three_d ? '1' : '0',
|
|
212
|
+
card_cvv,
|
|
213
|
+
card_holder,
|
|
214
|
+
card_month,
|
|
215
|
+
card_number,
|
|
216
|
+
card_year
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
},
|
|
220
|
+
async onQueryStarted(args, { dispatch, queryFulfilled }) {
|
|
221
|
+
dispatch(setPaymentStepBusy(true));
|
|
222
|
+
const { data } = await queryFulfilled;
|
|
223
|
+
const reduxStore = (await import('redux/store')).store;
|
|
224
|
+
const completePaymentContext = data?.context_list?.find(
|
|
225
|
+
(context) => context?.page_name === 'MasterpassCompletePage'
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
if (completePaymentContext) {
|
|
229
|
+
await completeMasterpassPayment(
|
|
230
|
+
args,
|
|
231
|
+
dispatch,
|
|
232
|
+
completePaymentContext,
|
|
233
|
+
reduxStore
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
dispatch(setPaymentStepBusy(false));
|
|
238
|
+
},
|
|
239
|
+
invalidatesTags: ['Basket']
|
|
240
|
+
}),
|
|
241
|
+
completeMasterpassPayment: build.mutation<
|
|
242
|
+
CheckoutResponse,
|
|
243
|
+
{
|
|
244
|
+
token: string;
|
|
245
|
+
}
|
|
246
|
+
>({
|
|
247
|
+
query: ({ token }) => ({
|
|
248
|
+
url: buildClientRequestUrl(checkout.completeMasterpassPayment, {
|
|
106
249
|
useFormData: true
|
|
107
250
|
}),
|
|
108
251
|
method: 'POST',
|
|
109
252
|
body: {
|
|
110
|
-
|
|
111
|
-
use_three_d: use_three_d ? '1' : '0',
|
|
112
|
-
card_cvv,
|
|
113
|
-
card_holder,
|
|
114
|
-
card_month,
|
|
115
|
-
card_number,
|
|
116
|
-
card_year
|
|
253
|
+
token
|
|
117
254
|
}
|
|
118
255
|
}),
|
|
119
256
|
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
|
|
@@ -328,7 +465,7 @@ export const checkoutApi = api.injectEndpoints({
|
|
|
328
465
|
}),
|
|
329
466
|
method: 'POST',
|
|
330
467
|
body: {
|
|
331
|
-
agreement: true
|
|
468
|
+
agreement: true
|
|
332
469
|
}
|
|
333
470
|
}),
|
|
334
471
|
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
|
package/data/urls.ts
CHANGED
|
@@ -79,6 +79,8 @@ export const checkout = {
|
|
|
79
79
|
setPaymentOption: '/orders/checkout/?page=PaymentOptionSelectionPage',
|
|
80
80
|
setBinNumber: '/orders/checkout/?page=BinNumberPage',
|
|
81
81
|
setMasterpassBinNumber: '/orders/checkout/?page=MasterpassBinNumberPage',
|
|
82
|
+
getMasterpassOrderNo: '/orders/checkout/?page=MasterpassOrderNoPage',
|
|
83
|
+
completeMasterpassPayment: '/orders/checkout/?page=MasterpassCompletePage',
|
|
82
84
|
setInstallmentOption: '/orders/checkout/?page=InstallmentSelectionPage',
|
|
83
85
|
setMasterPassInstallmentOption:
|
|
84
86
|
'/orders/checkout/?page=MasterpassInstallmentPage',
|
|
@@ -18,6 +18,7 @@ export const usePaymentOptions = () => {
|
|
|
18
18
|
pay_on_delivery: 'pz-pay-on-delivery',
|
|
19
19
|
bkm_express: 'pz-bkm',
|
|
20
20
|
credit_payment: 'pz-credit-payment',
|
|
21
|
+
masterpass: 'pz-masterpass'
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
const isInitialTypeIncluded = (type: string) => initialTypes.has(type);
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { NextFetchEvent, NextMiddleware, NextResponse } from 'next/server';
|
|
2
|
+
import Settings from 'settings';
|
|
3
|
+
import { Buffer } from 'buffer';
|
|
4
|
+
import logger from '../utils/log';
|
|
5
|
+
import { getUrlPathWithLocale } from '../utils/localization';
|
|
6
|
+
import { PzNextRequest } from '.';
|
|
7
|
+
|
|
8
|
+
const streamToString = async (stream: ReadableStream<Uint8Array> | null) => {
|
|
9
|
+
if (stream) {
|
|
10
|
+
const chunks = [];
|
|
11
|
+
let result = '';
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
for await (const chunk of stream as any) {
|
|
15
|
+
chunks.push(Buffer.from(chunk));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
result = Buffer.concat(chunks).toString('utf-8');
|
|
19
|
+
} catch (error) {
|
|
20
|
+
logger.error('Error while reading body stream', {
|
|
21
|
+
middleware: 'complete-masterpass',
|
|
22
|
+
error
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const withCompleteMasterpass =
|
|
32
|
+
(middleware: NextMiddleware) =>
|
|
33
|
+
async (req: PzNextRequest, event: NextFetchEvent) => {
|
|
34
|
+
const url = req.nextUrl.clone();
|
|
35
|
+
const ip = req.headers.get('x-forwarded-for') ?? '';
|
|
36
|
+
|
|
37
|
+
if (url.search.indexOf('MasterpassCompletePage') === -1) {
|
|
38
|
+
return middleware(req, event);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const requestUrl = `${Settings.commerceUrl}/orders/checkout/${url.search}`;
|
|
42
|
+
const requestHeaders = {
|
|
43
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
44
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
45
|
+
Cookie: `osessionid=${req.cookies.get('osessionid')?.value ?? ''}`,
|
|
46
|
+
'x-currency': req.cookies.get('pz-currency')?.value ?? ''
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const body = await streamToString(req.body);
|
|
51
|
+
|
|
52
|
+
const request = await fetch(requestUrl, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: requestHeaders,
|
|
55
|
+
body
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
logger.info('Complete Masterpass payment request', {
|
|
59
|
+
requestUrl,
|
|
60
|
+
status: request.status,
|
|
61
|
+
requestHeaders,
|
|
62
|
+
ip
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const response = await request.json();
|
|
66
|
+
|
|
67
|
+
const { context_list: contextList, errors } = response;
|
|
68
|
+
const redirectionContext = contextList?.find(
|
|
69
|
+
(context) => context.page_context?.redirect_url
|
|
70
|
+
);
|
|
71
|
+
const redirectUrl = redirectionContext?.page_context?.redirect_url;
|
|
72
|
+
|
|
73
|
+
if (errors && Object.keys(errors).length) {
|
|
74
|
+
logger.error('Error while completing Masterpass payment', {
|
|
75
|
+
middleware: 'complete-masterpass',
|
|
76
|
+
errors,
|
|
77
|
+
requestHeaders,
|
|
78
|
+
ip
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return NextResponse.redirect(
|
|
82
|
+
`${url.origin}${getUrlPathWithLocale(
|
|
83
|
+
'/orders/checkout/',
|
|
84
|
+
req.cookies.get('pz-locale')?.value
|
|
85
|
+
)}`,
|
|
86
|
+
{
|
|
87
|
+
status: 303,
|
|
88
|
+
headers: {
|
|
89
|
+
'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
logger.info('Order success page context list', {
|
|
96
|
+
middleware: 'complete-masterpass',
|
|
97
|
+
contextList,
|
|
98
|
+
ip
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (!redirectUrl) {
|
|
102
|
+
logger.warn(
|
|
103
|
+
'No redirection url for order success page found in page_context. Redirecting to checkout page.',
|
|
104
|
+
{
|
|
105
|
+
middleware: 'complete-masterpass',
|
|
106
|
+
requestHeaders,
|
|
107
|
+
response: JSON.stringify(response),
|
|
108
|
+
ip
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
|
|
113
|
+
'/orders/checkout/',
|
|
114
|
+
req.cookies.get('pz-locale')?.value
|
|
115
|
+
)}`;
|
|
116
|
+
|
|
117
|
+
return NextResponse.redirect(redirectUrlWithLocale, 303);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
|
|
121
|
+
redirectUrl,
|
|
122
|
+
req.cookies.get('pz-locale')?.value
|
|
123
|
+
)}`;
|
|
124
|
+
|
|
125
|
+
logger.info('Redirecting to order success page', {
|
|
126
|
+
middleware: 'complete-masterpass',
|
|
127
|
+
redirectUrlWithLocale,
|
|
128
|
+
ip
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Using POST method while redirecting causes an error,
|
|
132
|
+
// So we use 303 status code to change the method to GET
|
|
133
|
+
const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
|
|
134
|
+
|
|
135
|
+
nextResponse.headers.set(
|
|
136
|
+
'Set-Cookie',
|
|
137
|
+
request.headers.get('set-cookie') ?? ''
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
return nextResponse;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
logger.error('Error while completing Masterpass payment', {
|
|
143
|
+
middleware: 'complete-masterpass',
|
|
144
|
+
error,
|
|
145
|
+
requestHeaders,
|
|
146
|
+
ip
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return NextResponse.redirect(
|
|
150
|
+
`${url.origin}${getUrlPathWithLocale(
|
|
151
|
+
'/orders/checkout/',
|
|
152
|
+
req.cookies.get('pz-locale')?.value
|
|
153
|
+
)}`,
|
|
154
|
+
303
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export default withCompleteMasterpass;
|
package/middlewares/default.ts
CHANGED
|
@@ -3,6 +3,7 @@ import Settings from 'settings';
|
|
|
3
3
|
import {
|
|
4
4
|
PzNextRequest,
|
|
5
5
|
withCompleteGpay,
|
|
6
|
+
withCompleteMasterpass,
|
|
6
7
|
withOauthLogin,
|
|
7
8
|
withPrettyUrl,
|
|
8
9
|
withRedirectionPayment,
|
|
@@ -124,61 +125,74 @@ const withPzDefault =
|
|
|
124
125
|
withThreeDRedirection(
|
|
125
126
|
withUrlRedirection(
|
|
126
127
|
withCompleteGpay(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
NextResponse
|
|
128
|
+
withCompleteMasterpass(
|
|
129
|
+
async (req: PzNextRequest, event: NextFetchEvent) => {
|
|
130
|
+
let middlewareResult: NextResponse | void =
|
|
131
|
+
NextResponse.next();
|
|
130
132
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
url.basePath = `/${commerceUrl}`;
|
|
142
|
-
url.pathname = `/${
|
|
143
|
-
locale.length ? `${locale}/` : ''
|
|
144
|
-
}${currency}${prettyUrl ?? pathnameWithoutLocale}`;
|
|
145
|
-
|
|
146
|
-
Settings.rewrites.forEach((rewrite) => {
|
|
147
|
-
url.pathname = url.pathname.replace(
|
|
148
|
-
rewrite.source,
|
|
149
|
-
rewrite.destination
|
|
133
|
+
try {
|
|
134
|
+
const { locale, prettyUrl, currency } =
|
|
135
|
+
req.middlewareParams.rewrites;
|
|
136
|
+
const { defaultLocaleValue } = Settings.localization;
|
|
137
|
+
const url = req.nextUrl.clone();
|
|
138
|
+
const pathnameWithoutLocale = url.pathname.replace(
|
|
139
|
+
urlLocaleMatcherRegex,
|
|
140
|
+
''
|
|
150
141
|
);
|
|
151
|
-
});
|
|
152
142
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
143
|
+
url.basePath = `/${commerceUrl}`;
|
|
144
|
+
url.pathname = `/${
|
|
145
|
+
locale.length ? `${locale}/` : ''
|
|
146
|
+
}${currency}${prettyUrl ?? pathnameWithoutLocale}`;
|
|
157
147
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
middlewareResult.headers.get(
|
|
163
|
-
'pz-override-response'
|
|
164
|
-
) !== 'true'
|
|
165
|
-
) {
|
|
166
|
-
middlewareResult.headers.set(
|
|
167
|
-
'x-middleware-rewrite',
|
|
168
|
-
url.href
|
|
148
|
+
Settings.rewrites.forEach((rewrite) => {
|
|
149
|
+
url.pathname = url.pathname.replace(
|
|
150
|
+
rewrite.source,
|
|
151
|
+
rewrite.destination
|
|
169
152
|
);
|
|
170
|
-
}
|
|
171
|
-
} else {
|
|
172
|
-
// if middleware.ts doesn't have a return value.
|
|
173
|
-
// e.g. NextResponse.next() doesn't exist in middleware.ts
|
|
153
|
+
});
|
|
174
154
|
|
|
175
|
-
middlewareResult =
|
|
176
|
-
|
|
155
|
+
middlewareResult = (await middleware(
|
|
156
|
+
req,
|
|
157
|
+
event
|
|
158
|
+
)) as NextResponse | void;
|
|
177
159
|
|
|
178
|
-
|
|
160
|
+
// if middleware.ts has a return value for current url
|
|
161
|
+
if (middlewareResult instanceof NextResponse) {
|
|
162
|
+
// pz-override-response header is used to prevent 404 page for custom responses.
|
|
163
|
+
if (
|
|
164
|
+
middlewareResult.headers.get(
|
|
165
|
+
'pz-override-response'
|
|
166
|
+
) !== 'true'
|
|
167
|
+
) {
|
|
168
|
+
middlewareResult.headers.set(
|
|
169
|
+
'x-middleware-rewrite',
|
|
170
|
+
url.href
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
// if middleware.ts doesn't have a return value.
|
|
175
|
+
// e.g. NextResponse.next() doesn't exist in middleware.ts
|
|
176
|
+
|
|
177
|
+
middlewareResult = NextResponse.rewrite(url);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!url.pathname.startsWith(`/${currency}/orders`)) {
|
|
181
|
+
middlewareResult.cookies.set(
|
|
182
|
+
'pz-locale',
|
|
183
|
+
locale?.length > 0 ? locale : defaultLocaleValue,
|
|
184
|
+
{
|
|
185
|
+
sameSite: 'none',
|
|
186
|
+
secure: true,
|
|
187
|
+
expires: new Date(
|
|
188
|
+
Date.now() + 1000 * 60 * 60 * 24 * 7
|
|
189
|
+
) // 7 days
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
}
|
|
179
193
|
middlewareResult.cookies.set(
|
|
180
|
-
'pz-
|
|
181
|
-
|
|
194
|
+
'pz-currency',
|
|
195
|
+
currency,
|
|
182
196
|
{
|
|
183
197
|
sameSite: 'none',
|
|
184
198
|
secure: true,
|
|
@@ -187,65 +201,63 @@ const withPzDefault =
|
|
|
187
201
|
) // 7 days
|
|
188
202
|
}
|
|
189
203
|
);
|
|
190
|
-
}
|
|
191
|
-
middlewareResult.cookies.set('pz-currency', currency, {
|
|
192
|
-
sameSite: 'none',
|
|
193
|
-
secure: true,
|
|
194
|
-
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7) // 7 days
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
if (
|
|
198
|
-
req.cookies.get('pz-locale') &&
|
|
199
|
-
req.cookies.get('pz-locale').value !== locale
|
|
200
|
-
) {
|
|
201
|
-
logger.debug('Locale changed', {
|
|
202
|
-
locale,
|
|
203
|
-
oldLocale: req.cookies.get('pz-locale')?.value,
|
|
204
|
-
ip
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
middlewareResult.headers.set(
|
|
209
|
-
'pz-url',
|
|
210
|
-
req.nextUrl.toString()
|
|
211
|
-
);
|
|
212
204
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
205
|
+
if (
|
|
206
|
+
req.cookies.get('pz-locale') &&
|
|
207
|
+
req.cookies.get('pz-locale').value !== locale
|
|
208
|
+
) {
|
|
209
|
+
logger.debug('Locale changed', {
|
|
210
|
+
locale,
|
|
211
|
+
oldLocale: req.cookies.get('pz-locale')?.value,
|
|
212
|
+
ip
|
|
213
|
+
});
|
|
214
|
+
}
|
|
216
215
|
|
|
217
|
-
if (process.env.ACC_APP_VERSION) {
|
|
218
216
|
middlewareResult.headers.set(
|
|
219
|
-
'
|
|
220
|
-
|
|
217
|
+
'pz-url',
|
|
218
|
+
req.nextUrl.toString()
|
|
221
219
|
);
|
|
222
|
-
}
|
|
223
220
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
221
|
+
if (req.cookies.get('pz-set-currency')) {
|
|
222
|
+
middlewareResult.cookies.delete('pz-set-currency');
|
|
223
|
+
}
|
|
227
224
|
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
225
|
+
if (process.env.ACC_APP_VERSION) {
|
|
226
|
+
middlewareResult.headers.set(
|
|
227
|
+
'acc-app-version',
|
|
228
|
+
process.env.ACC_APP_VERSION
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Set CSRF token if not set
|
|
233
|
+
try {
|
|
234
|
+
const url = `${Settings.commerceUrl}${user.csrfToken}`;
|
|
235
|
+
|
|
236
|
+
if (!req.cookies.get('csrftoken')) {
|
|
237
|
+
const { csrf_token } = await (
|
|
238
|
+
await fetch(url)
|
|
239
|
+
).json();
|
|
240
|
+
middlewareResult.cookies.set(
|
|
241
|
+
'csrftoken',
|
|
242
|
+
csrf_token
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
} catch (error) {
|
|
246
|
+
logger.error('CSRF Error', {
|
|
247
|
+
error,
|
|
248
|
+
ip
|
|
249
|
+
});
|
|
233
250
|
}
|
|
234
251
|
} catch (error) {
|
|
235
|
-
logger.error('
|
|
252
|
+
logger.error('withPzDefault Error', {
|
|
236
253
|
error,
|
|
237
254
|
ip
|
|
238
255
|
});
|
|
239
256
|
}
|
|
240
|
-
} catch (error) {
|
|
241
|
-
logger.error('withPzDefault Error', {
|
|
242
|
-
error,
|
|
243
|
-
ip
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
257
|
|
|
247
|
-
|
|
248
|
-
|
|
258
|
+
return middlewareResult;
|
|
259
|
+
}
|
|
260
|
+
)
|
|
249
261
|
)
|
|
250
262
|
)
|
|
251
263
|
)
|
package/middlewares/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import withLocale from './locale';
|
|
|
6
6
|
import withOauthLogin from './oauth-login';
|
|
7
7
|
import withUrlRedirection from './url-redirection';
|
|
8
8
|
import withCompleteGpay from './complete-gpay';
|
|
9
|
+
import withCompleteMasterpass from './complete-masterpass';
|
|
9
10
|
import { NextRequest } from 'next/server';
|
|
10
11
|
|
|
11
12
|
export {
|
|
@@ -16,7 +17,8 @@ export {
|
|
|
16
17
|
withLocale,
|
|
17
18
|
withOauthLogin,
|
|
18
19
|
withUrlRedirection,
|
|
19
|
-
withCompleteGpay
|
|
20
|
+
withCompleteGpay,
|
|
21
|
+
withCompleteMasterpass
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
export interface PzNextRequest extends NextRequest {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/next",
|
|
3
3
|
"description": "Core package for Project Zero Next",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.19.1",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@typescript-eslint/eslint-plugin": "6.7.4",
|
|
32
32
|
"@typescript-eslint/parser": "6.7.4",
|
|
33
33
|
"eslint": "^8.14.0",
|
|
34
|
-
"@akinon/eslint-plugin-projectzero": "1.
|
|
34
|
+
"@akinon/eslint-plugin-projectzero": "1.19.1",
|
|
35
35
|
"eslint-config-prettier": "8.5.0"
|
|
36
36
|
}
|
|
37
37
|
}
|
package/plugins.d.ts
ADDED
package/plugins.js
CHANGED
package/redux/reducers/index.ts
CHANGED
|
@@ -4,12 +4,16 @@ import configReducer from './config';
|
|
|
4
4
|
import headerReducer from './header';
|
|
5
5
|
import { api } from '../../data/client/api';
|
|
6
6
|
|
|
7
|
+
// Plugin reducers
|
|
8
|
+
import { masterpassReducer } from '@akinon/pz-masterpass';
|
|
9
|
+
|
|
7
10
|
const reducers = {
|
|
8
11
|
[api.reducerPath]: api.reducer,
|
|
9
12
|
root: rootReducer,
|
|
10
13
|
checkout: checkoutReducer,
|
|
11
14
|
config: configReducer,
|
|
12
|
-
header: headerReducer
|
|
15
|
+
header: headerReducer,
|
|
16
|
+
masterpass: masterpassReducer
|
|
13
17
|
};
|
|
14
18
|
|
|
15
|
-
export default reducers;
|
|
19
|
+
export default reducers;
|