@akinon/next 1.111.0-rc.16 → 1.111.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 CHANGED
@@ -1,16 +1,11 @@
1
1
  # @akinon/next
2
2
 
3
- ## 1.111.0-rc.16
3
+ ## 1.111.0
4
4
 
5
5
  ### Minor Changes
6
6
 
7
- - d2c0e75: ZERO-3684: Handle cross-origin iframe access in iframeURLChange function
8
- - b55acb76: ZERO-2577: Fix pagination bug and update usePagination hook and ensure pagination controls rendering correctly
9
- - 143be2b: ZERO-3457: Crop styles are customizable and logic improved for rendering similar products modal
10
- - 9f8cd3bc: ZERO-3449: AI Search Active Filters & Crop Style changes have been implemented
11
- - d99a6a7: ZERO-3457_1: Fixed the settings prop and made sure everything is customizable.
12
- - 4de5303c: ZERO-2504: add cookie filter to api client request
13
- - 95b139d: ZERO-3795: Remove duplicate entry for SavedCard in PluginComponents map
7
+ - c026300: ZERO-3833: fix masterpass rest conflicts
8
+ - b47b9d8: ZERO-3414: create masterpass-rest package
14
9
 
15
10
  ## 1.110.0
16
11
 
@@ -24,7 +24,7 @@ enum Plugin {
24
24
  FlowPayment = 'pz-flow-payment',
25
25
  VirtualTryOn = 'pz-virtual-try-on',
26
26
  Hepsipay = 'pz-hepsipay',
27
- SimilarProducts = 'pz-similar-products'
27
+ MasterpassRest = 'pz-masterpass-rest'
28
28
  }
29
29
 
30
30
  export enum Component {
@@ -51,16 +51,10 @@ export enum Component {
51
51
  MultiBasket = 'MultiBasket',
52
52
  SavedCard = 'SavedCardOption',
53
53
  VirtualTryOnPlugin = 'VirtualTryOnPlugin',
54
- SimilarProductsModal = 'SimilarProductsModal',
55
- SimilarProductsFilterSidebar = 'SimilarProductsFilterSidebar',
56
- SimilarProductsResultsGrid = 'SimilarProductsResultsGrid',
57
- SimilarProductsPlugin = 'SimilarProductsPlugin',
58
- ProductImageSearchFeature = 'ProductImageSearchFeature',
59
- ImageSearchButton = 'ImageSearchButton',
60
- HeaderImageSearchFeature = 'HeaderImageSearchFeature',
61
54
  IyzicoSavedCard = 'IyzicoSavedCardOption',
62
55
  Hepsipay = 'Hepsipay',
63
- FlowPayment = 'FlowPayment'
56
+ FlowPayment = 'FlowPayment',
57
+ MasterpassRest = 'MasterpassRestOption'
64
58
  }
65
59
 
66
60
  const PluginComponents = new Map([
@@ -93,22 +87,12 @@ const PluginComponents = new Map([
93
87
  [Component.AkifastQuickLoginButton, Component.AkifastCheckoutButton]
94
88
  ],
95
89
  [Plugin.MultiBasket, [Component.MultiBasket]],
96
- [
97
- Plugin.SimilarProducts,
98
- [
99
- Component.SimilarProductsModal,
100
- Component.SimilarProductsFilterSidebar,
101
- Component.SimilarProductsResultsGrid,
102
- Component.SimilarProductsPlugin,
103
- Component.ProductImageSearchFeature,
104
- Component.ImageSearchButton,
105
- Component.HeaderImageSearchFeature
106
- ]
107
- ],
108
90
  [Plugin.SavedCard, [Component.SavedCard, Component.IyzicoSavedCard]],
91
+ [Plugin.SavedCard, [Component.SavedCard]],
109
92
  [Plugin.FlowPayment, [Component.FlowPayment]],
110
93
  [Plugin.VirtualTryOn, [Component.VirtualTryOnPlugin]],
111
- [Plugin.Hepsipay, [Component.Hepsipay]]
94
+ [Plugin.Hepsipay, [Component.Hepsipay]],
95
+ [Plugin.MasterpassRest, [Component.MasterpassRest]]
112
96
  ]);
113
97
 
114
98
  const getPlugin = (component: Component) => {
@@ -120,10 +104,10 @@ const getPlugin = (component: Component) => {
120
104
  };
121
105
 
122
106
  export default function PluginModule({
123
- component,
124
- props,
125
- children
126
- }: {
107
+ component,
108
+ props,
109
+ children
110
+ }: {
127
111
  component: Component;
128
112
  props?: any;
129
113
  children?: React.ReactNode;
@@ -173,14 +157,14 @@ export default function PluginModule({
173
157
  promise = import(`${'@akinon/pz-multi-basket'}`);
174
158
  } else if (plugin === Plugin.SavedCard) {
175
159
  promise = import(`${'@akinon/pz-saved-card'}`);
176
- } else if (plugin === Plugin.SimilarProducts) {
177
- promise = import(`${'@akinon/pz-similar-products'}`);
178
160
  } else if (plugin === Plugin.Hepsipay) {
179
161
  promise = import(`${'@akinon/pz-hepsipay'}`);
180
162
  } else if (plugin === Plugin.FlowPayment) {
181
163
  promise = import(`${'@akinon/pz-flow-payment'}`);
182
164
  } else if (plugin === Plugin.VirtualTryOn) {
183
165
  promise = import(`${'@akinon/pz-virtual-try-on'}`);
166
+ } else if (plugin === Plugin.MasterpassRest) {
167
+ promise = import(`${'@akinon/pz-masterpass-rest'}`);
184
168
  }
185
169
  } catch (error) {
186
170
  logger.error(error);
@@ -16,6 +16,7 @@ const paymentTypeToView = {
16
16
  gpay: 'gpay',
17
17
  loyalty_money: 'loyalty',
18
18
  masterpass: 'credit-card',
19
+ masterpass_rest: 'masterpass-rest',
19
20
  pay_on_delivery: 'pay-on-delivery',
20
21
  redirection: 'redirection',
21
22
  saved_card: 'saved-card'
package/data/urls.ts CHANGED
@@ -183,11 +183,7 @@ export const product = {
183
183
  breadcrumbUrl: (menuitemmodel: string) =>
184
184
  `/menus/generate_breadcrumb/?item=${menuitemmodel}&generator_name=menu_item`,
185
185
  bundleProduct: (productPk: string, queryString: string) =>
186
- `/bundle-product/${productPk}/?${queryString}`,
187
- similarProducts: (params?: string) =>
188
- `/similar-products${params ? `?${params}` : ''}`,
189
- similarProductsList: (params?: string) =>
190
- `/similar-product-list${params ? `?${params}` : ''}`
186
+ `/bundle-product/${productPk}/?${queryString}`
191
187
  };
192
188
 
193
189
  export const wishlist = {
@@ -21,7 +21,8 @@ export const usePaymentOptions = () => {
21
21
  credit_payment: 'pz-credit-payment',
22
22
  masterpass: 'pz-masterpass',
23
23
  saved_card: 'pz-saved-card',
24
- gpay: 'pz-gpay'
24
+ gpay: 'pz-gpay',
25
+ masterpass_rest: 'pz-masterpass-rest'
25
26
  };
26
27
 
27
28
  const isInitialTypeIncluded = (type: string) => initialTypes.has(type);
@@ -13,7 +13,8 @@ import {
13
13
  withThreeDRedirection,
14
14
  withUrlRedirection,
15
15
  withCompleteWallet,
16
- withWalletCompleteRedirection
16
+ withWalletCompleteRedirection,
17
+ withMasterpassRestCallback
17
18
  } from '.';
18
19
  import { urlLocaleMatcherRegex } from '../utils';
19
20
  import withCurrency from './currency';
@@ -230,226 +231,235 @@ const withPzDefault =
230
231
  withCompleteMasterpass(
231
232
  withSavedCardRedirection(
232
233
  withCompleteWallet(
233
- withWalletCompleteRedirection(
234
- async (
235
- req: PzNextRequest,
236
- event: NextFetchEvent
237
- ) => {
238
- let middlewareResult: NextResponse | void =
239
- NextResponse.next();
240
-
241
- try {
242
- const { locale, prettyUrl, currency } =
243
- req.middlewareParams.rewrites;
244
- const { defaultLocaleValue } =
245
- Settings.localization;
246
- const url = req.nextUrl.clone();
247
- const pathnameWithoutLocale =
248
- url.pathname.replace(
249
- urlLocaleMatcherRegex,
250
- ''
251
- );
252
-
253
- middlewareResult = (await middleware(
254
- req,
255
- event
256
- )) as NextResponse | void;
257
-
258
- let customRewriteUrlDiff = '';
259
-
260
- if (
261
- middlewareResult instanceof NextResponse &&
262
- middlewareResult.headers.get(
263
- 'pz-override-response'
264
- ) &&
265
- middlewareResult.headers.get(
266
- 'x-middleware-rewrite'
267
- )
268
- ) {
269
- const rewriteUrl = new URL(
270
- middlewareResult.headers.get(
271
- 'x-middleware-rewrite'
272
- )
273
- );
274
- const originalUrl = new URL(req.url);
275
- customRewriteUrlDiff =
276
- rewriteUrl.pathname.replace(
277
- originalUrl.pathname,
278
- ''
279
- );
280
- }
234
+ withWalletCompleteRedirection(
235
+ withMasterpassRestCallback(
236
+ async (
237
+ req: PzNextRequest,
238
+ event: NextFetchEvent
239
+ ) => {
240
+ let middlewareResult: NextResponse | void =
241
+ NextResponse.next();
242
+
243
+ try {
244
+ const { locale, prettyUrl, currency } =
245
+ req.middlewareParams.rewrites;
246
+ const { defaultLocaleValue } =
247
+ Settings.localization;
248
+ const url = req.nextUrl.clone();
249
+ const pathnameWithoutLocale =
250
+ url.pathname.replace(
251
+ urlLocaleMatcherRegex,
252
+ ''
253
+ );
254
+
255
+ middlewareResult = (await middleware(
256
+ req,
257
+ event
258
+ )) as NextResponse | void;
259
+
260
+ let customRewriteUrlDiff = '';
261
+
262
+ if (
263
+ middlewareResult instanceof
264
+ NextResponse &&
265
+ middlewareResult.headers.get(
266
+ 'pz-override-response'
267
+ ) &&
268
+ middlewareResult.headers.get(
269
+ 'x-middleware-rewrite'
270
+ )
271
+ ) {
272
+ const rewriteUrl = new URL(
273
+ middlewareResult.headers.get(
274
+ 'x-middleware-rewrite'
275
+ )
276
+ );
277
+ const originalUrl = new URL(req.url);
278
+ customRewriteUrlDiff =
279
+ rewriteUrl.pathname.replace(
280
+ originalUrl.pathname,
281
+ ''
282
+ );
283
+ }
281
284
 
282
- url.basePath = `/${commerceUrl}`;
283
- url.pathname = `/${
284
- locale.length ? `${locale}/` : ''
285
- }${currency}/${customRewriteUrlDiff}${
286
- prettyUrl ?? pathnameWithoutLocale
287
- }`.replace(/\/+/g, '/');
288
-
289
- if (
290
- Settings.usePrettyUrlRoute &&
291
- url.searchParams.toString().length > 0 &&
292
- !Object.entries(ROUTES).find(([, value]) =>
293
- new RegExp(`^${value}/?$`).test(
294
- pathnameWithoutLocale
295
- )
296
- )
297
- ) {
298
- url.pathname =
299
- url.pathname +
300
- (/\/$/.test(url.pathname) ? '' : '/') +
301
- `searchparams|${encodeURIComponent(
302
- url.searchParams.toString()
303
- )}`;
304
- }
285
+ url.basePath = `/${commerceUrl}`;
286
+ url.pathname = `/${
287
+ locale.length ? `${locale}/` : ''
288
+ }${currency}/${customRewriteUrlDiff}${
289
+ prettyUrl ?? pathnameWithoutLocale
290
+ }`.replace(/\/+/g, '/');
291
+
292
+ if (
293
+ Settings.usePrettyUrlRoute &&
294
+ url.searchParams.toString().length > 0 &&
295
+ !Object.entries(ROUTES).find(
296
+ ([, value]) =>
297
+ new RegExp(`^${value}/?$`).test(
298
+ pathnameWithoutLocale
299
+ )
300
+ )
301
+ ) {
302
+ url.pathname =
303
+ url.pathname +
304
+ (/\/$/.test(url.pathname) ? '' : '/') +
305
+ `searchparams|${encodeURIComponent(
306
+ url.searchParams.toString()
307
+ )}`;
308
+ }
305
309
 
306
- Settings.rewrites.forEach((rewrite) => {
307
- url.pathname = url.pathname.replace(
308
- rewrite.source,
309
- rewrite.destination
310
- );
311
- });
312
-
313
- // if middleware.ts has a return value for current url
314
- if (middlewareResult instanceof NextResponse) {
315
- // pz-override-response header is used to prevent 404 page for custom responses.
316
- if (
317
- middlewareResult.headers.get(
318
- 'pz-override-response'
319
- ) !== 'true'
320
- ) {
321
- middlewareResult.headers.set(
322
- 'x-middleware-rewrite',
323
- url.href
324
- );
325
- } else if (
326
- middlewareResult.headers.get(
327
- 'x-middleware-rewrite'
328
- ) &&
329
- middlewareResult.headers.get(
330
- 'pz-override-response'
331
- ) === 'true'
332
- ) {
333
- middlewareResult =
334
- NextResponse.rewrite(url);
335
- }
336
- } else {
337
- // if middleware.ts doesn't have a return value.
338
- // e.g. NextResponse.next() doesn't exist in middleware.ts
310
+ Settings.rewrites.forEach((rewrite) => {
311
+ url.pathname = url.pathname.replace(
312
+ rewrite.source,
313
+ rewrite.destination
314
+ );
315
+ });
316
+
317
+ // if middleware.ts has a return value for current url
318
+ if (
319
+ middlewareResult instanceof NextResponse
320
+ ) {
321
+ // pz-override-response header is used to prevent 404 page for custom responses.
322
+ if (
323
+ middlewareResult.headers.get(
324
+ 'pz-override-response'
325
+ ) !== 'true'
326
+ ) {
327
+ middlewareResult.headers.set(
328
+ 'x-middleware-rewrite',
329
+ url.href
330
+ );
331
+ } else if (
332
+ middlewareResult.headers.get(
333
+ 'x-middleware-rewrite'
334
+ ) &&
335
+ middlewareResult.headers.get(
336
+ 'pz-override-response'
337
+ ) === 'true'
338
+ ) {
339
+ middlewareResult =
340
+ NextResponse.rewrite(url);
341
+ }
342
+ } else {
343
+ // if middleware.ts doesn't have a return value.
344
+ // e.g. NextResponse.next() doesn't exist in middleware.ts
339
345
 
340
- middlewareResult = NextResponse.rewrite(url);
341
- }
346
+ middlewareResult =
347
+ NextResponse.rewrite(url);
348
+ }
342
349
 
343
- const { localeUrlStrategy } =
344
- Settings.localization;
345
-
346
- const fallbackHost =
347
- req.headers.get('x-forwarded-host') ||
348
- req.headers.get('host');
349
- const hostname =
350
- process.env.NEXT_PUBLIC_URL ||
351
- `https://${fallbackHost}`;
352
- const rootHostname =
353
- localeUrlStrategy ===
354
- LocaleUrlStrategy.Subdomain
355
- ? getRootHostname(hostname)
356
- : null;
357
-
358
- if (
359
- !url.pathname.startsWith(
360
- `/${currency}/orders`
361
- )
362
- ) {
363
- middlewareResult.cookies.set(
364
- 'pz-locale',
365
- locale?.length > 0
366
- ? locale
367
- : defaultLocaleValue,
368
- {
369
- domain: rootHostname,
370
- sameSite: 'none',
371
- secure: true,
372
- expires: new Date(
373
- Date.now() + 1000 * 60 * 60 * 24 * 7
374
- ) // 7 days
350
+ const { localeUrlStrategy } =
351
+ Settings.localization;
352
+
353
+ const fallbackHost =
354
+ req.headers.get('x-forwarded-host') ||
355
+ req.headers.get('host');
356
+ const hostname =
357
+ process.env.NEXT_PUBLIC_URL ||
358
+ `https://${fallbackHost}`;
359
+ const rootHostname =
360
+ localeUrlStrategy ===
361
+ LocaleUrlStrategy.Subdomain
362
+ ? getRootHostname(hostname)
363
+ : null;
364
+
365
+ if (
366
+ !url.pathname.startsWith(
367
+ `/${currency}/orders`
368
+ )
369
+ ) {
370
+ middlewareResult.cookies.set(
371
+ 'pz-locale',
372
+ locale?.length > 0
373
+ ? locale
374
+ : defaultLocaleValue,
375
+ {
376
+ domain: rootHostname,
377
+ sameSite: 'none',
378
+ secure: true,
379
+ expires: new Date(
380
+ Date.now() + 1000 * 60 * 60 * 24 * 7
381
+ ) // 7 days
382
+ }
383
+ );
375
384
  }
376
- );
377
- }
378
385
 
379
- middlewareResult.cookies.set(
380
- 'pz-currency',
381
- currency,
382
- {
383
- domain: rootHostname,
384
- sameSite: 'none',
385
- secure: true,
386
- expires: new Date(
387
- Date.now() + 1000 * 60 * 60 * 24 * 7
388
- ) // 7 days
389
- }
390
- );
391
-
392
- if (
393
- req.cookies.get('pz-locale') &&
394
- req.cookies.get('pz-locale').value !== locale
395
- ) {
396
- logger.debug('Locale changed', {
397
- locale,
398
- oldLocale:
399
- req.cookies.get('pz-locale')?.value,
400
- ip
401
- });
402
- }
386
+ middlewareResult.cookies.set(
387
+ 'pz-currency',
388
+ currency,
389
+ {
390
+ domain: rootHostname,
391
+ sameSite: 'none',
392
+ secure: true,
393
+ expires: new Date(
394
+ Date.now() + 1000 * 60 * 60 * 24 * 7
395
+ ) // 7 days
396
+ }
397
+ );
403
398
 
404
- middlewareResult.headers.set(
405
- 'pz-url',
406
- req.nextUrl.toString()
407
- );
399
+ if (
400
+ req.cookies.get('pz-locale') &&
401
+ req.cookies.get('pz-locale').value !==
402
+ locale
403
+ ) {
404
+ logger.debug('Locale changed', {
405
+ locale,
406
+ oldLocale:
407
+ req.cookies.get('pz-locale')?.value,
408
+ ip
409
+ });
410
+ }
408
411
 
409
- if (req.cookies.get('pz-set-currency')) {
410
- middlewareResult.cookies.delete(
411
- 'pz-set-currency'
412
- );
413
- }
412
+ middlewareResult.headers.set(
413
+ 'pz-url',
414
+ req.nextUrl.toString()
415
+ );
414
416
 
415
- if (process.env.ACC_APP_VERSION) {
416
- middlewareResult.headers.set(
417
- 'acc-app-version',
418
- process.env.ACC_APP_VERSION
419
- );
420
- }
417
+ if (req.cookies.get('pz-set-currency')) {
418
+ middlewareResult.cookies.delete(
419
+ 'pz-set-currency'
420
+ );
421
+ }
421
422
 
422
- // Set CSRF token if not set
423
- try {
424
- const url = `${Settings.commerceUrl}${user.csrfToken}`;
423
+ if (process.env.ACC_APP_VERSION) {
424
+ middlewareResult.headers.set(
425
+ 'acc-app-version',
426
+ process.env.ACC_APP_VERSION
427
+ );
428
+ }
425
429
 
426
- if (!req.cookies.get('csrftoken')) {
427
- const { csrf_token } = await (
428
- await fetch(url)
429
- ).json();
430
- middlewareResult.cookies.set(
431
- 'csrftoken',
432
- csrf_token,
433
- {
434
- domain: rootHostname
430
+ // Set CSRF token if not set
431
+ try {
432
+ const url = `${Settings.commerceUrl}${user.csrfToken}`;
433
+
434
+ if (!req.cookies.get('csrftoken')) {
435
+ const { csrf_token } = await (
436
+ await fetch(url)
437
+ ).json();
438
+ middlewareResult.cookies.set(
439
+ 'csrftoken',
440
+ csrf_token,
441
+ {
442
+ domain: rootHostname
443
+ }
444
+ );
435
445
  }
436
- );
446
+ } catch (error) {
447
+ logger.error('CSRF Error', {
448
+ error,
449
+ ip
450
+ });
451
+ }
452
+ } catch (error) {
453
+ logger.error('withPzDefault Error', {
454
+ error,
455
+ ip
456
+ });
437
457
  }
438
- } catch (error) {
439
- logger.error('CSRF Error', {
440
- error,
441
- ip
442
- });
458
+
459
+ return middlewareResult;
443
460
  }
444
- } catch (error) {
445
- logger.error('withPzDefault Error', {
446
- error,
447
- ip
448
- });
449
- }
450
-
451
- return middlewareResult;
452
- }
461
+ )
462
+ )
453
463
  )
454
464
  )
455
465
  )
@@ -461,7 +471,6 @@ const withPzDefault =
461
471
  )
462
472
  )
463
473
  )
464
- )
465
474
  )(req, event);
466
475
  };
467
476
 
@@ -11,6 +11,7 @@ import withCheckoutProvider from './checkout-provider';
11
11
  import withSavedCardRedirection from './saved-card-redirection';
12
12
  import withCompleteWallet from './complete-wallet';
13
13
  import withWalletCompleteRedirection from './wallet-complete-redirection';
14
+ import withMasterpassRestCallback from './masterpass-rest-callback';
14
15
  import { NextRequest } from 'next/server';
15
16
 
16
17
  export {
@@ -26,7 +27,8 @@ export {
26
27
  withCheckoutProvider,
27
28
  withSavedCardRedirection,
28
29
  withCompleteWallet,
29
- withWalletCompleteRedirection
30
+ withWalletCompleteRedirection,
31
+ withMasterpassRestCallback
30
32
  };
31
33
 
32
34
  export interface PzNextRequest extends NextRequest {
@@ -0,0 +1,218 @@
1
+ import { NextFetchEvent, NextMiddleware, NextResponse } from 'next/server';
2
+ import Settings from 'settings';
3
+ import logger from '../utils/log';
4
+ import { getUrlPathWithLocale } from '../utils/localization';
5
+ import { PzNextRequest } from '.';
6
+
7
+ const withMasterpassRestCallback =
8
+ (middleware: NextMiddleware) =>
9
+ async (req: PzNextRequest, event: NextFetchEvent) => {
10
+ const url = req.nextUrl.clone();
11
+ const ip = req.headers.get('x-forwarded-for') ?? '';
12
+ const sessionId = req.cookies.get('osessionid');
13
+
14
+ if (!url.pathname.includes('/orders/masterpass-rest-callback')) {
15
+ return middleware(req, event);
16
+ }
17
+
18
+ if (req.method !== 'POST') {
19
+ logger.warn('Invalid request method for masterpass REST callback', {
20
+ middleware: 'masterpass-rest-callback',
21
+ method: req.method,
22
+ ip
23
+ });
24
+
25
+ return NextResponse.redirect(
26
+ `${url.origin}${getUrlPathWithLocale(
27
+ '/orders/checkout/',
28
+ req.cookies.get('pz-locale')?.value
29
+ )}`,
30
+ 303
31
+ );
32
+ }
33
+
34
+ const responseCode = url.searchParams.get('responseCode');
35
+ const token = url.searchParams.get('token');
36
+
37
+ if (!responseCode || !token) {
38
+ logger.warn('Missing required parameters for masterpass REST callback', {
39
+ middleware: 'masterpass-rest-callback',
40
+ responseCode,
41
+ token,
42
+ ip
43
+ });
44
+
45
+ return NextResponse.redirect(
46
+ `${url.origin}${getUrlPathWithLocale(
47
+ '/orders/checkout/',
48
+ req.cookies.get('pz-locale')?.value
49
+ )}`,
50
+ 303
51
+ );
52
+ }
53
+
54
+ try {
55
+ const formData = await req.formData();
56
+ const body: Record<string, string> = {};
57
+
58
+ Array.from(formData.entries()).forEach(([key, value]) => {
59
+ body[key] = value.toString();
60
+ });
61
+
62
+ if (!sessionId) {
63
+ logger.warn(
64
+ 'Make sure that the SESSION_COOKIE_SAMESITE environment variable is set to None in Commerce.',
65
+ {
66
+ middleware: 'masterpass-rest-callback',
67
+ ip
68
+ }
69
+ );
70
+
71
+ return NextResponse.redirect(
72
+ `${url.origin}${getUrlPathWithLocale(
73
+ '/orders/checkout/',
74
+ req.cookies.get('pz-locale')?.value
75
+ )}`,
76
+ 303
77
+ );
78
+ }
79
+
80
+ const requestUrl = new URL('/orders/checkout/', Settings.commerceUrl);
81
+ requestUrl.searchParams.set('page', 'MasterpassRestCompletePage');
82
+ requestUrl.searchParams.set('responseCode', responseCode);
83
+ requestUrl.searchParams.set('token', token);
84
+ requestUrl.searchParams.set(
85
+ 'three_d_secure',
86
+ body.transactionType?.includes('3D') ? 'true' : 'false'
87
+ );
88
+ requestUrl.searchParams.set(
89
+ 'transactionType',
90
+ body.transactionType || ''
91
+ );
92
+
93
+ const requestHeaders = {
94
+ 'Content-Type': 'application/x-www-form-urlencoded',
95
+ 'X-Requested-With': 'XMLHttpRequest',
96
+ Cookie: req.headers.get('cookie') ?? '',
97
+ 'x-currency': req.cookies.get('pz-currency')?.value ?? '',
98
+ 'x-forwarded-for': ip,
99
+ 'User-Agent': req.headers.get('user-agent') ?? ''
100
+ };
101
+
102
+ const request = await fetch(requestUrl.toString(), {
103
+ method: 'POST',
104
+ headers: requestHeaders,
105
+ body: new URLSearchParams(body)
106
+ });
107
+
108
+ logger.info('Masterpass REST callback request', {
109
+ requestUrl: requestUrl.toString(),
110
+ status: request.status,
111
+ requestHeaders,
112
+ ip
113
+ });
114
+
115
+ const response = await request.json();
116
+
117
+ const { context_list: contextList, errors } = response;
118
+
119
+ let redirectUrl = response.redirect_url;
120
+
121
+ if (!redirectUrl && contextList && contextList.length > 0) {
122
+ for (const context of contextList) {
123
+ if (context.page_context && context.page_context.redirect_url) {
124
+ redirectUrl = context.page_context.redirect_url;
125
+ break;
126
+ }
127
+ }
128
+ }
129
+
130
+ if (errors && Object.keys(errors).length) {
131
+ logger.error('Error while processing masterpass REST callback', {
132
+ middleware: 'masterpass-rest-callback',
133
+ errors,
134
+ requestHeaders,
135
+ ip
136
+ });
137
+
138
+ return NextResponse.redirect(
139
+ `${url.origin}${getUrlPathWithLocale(
140
+ '/orders/checkout/',
141
+ req.cookies.get('pz-locale')?.value
142
+ )}`,
143
+ {
144
+ status: 303,
145
+ headers: {
146
+ 'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
147
+ }
148
+ }
149
+ );
150
+ }
151
+
152
+ logger.info('Masterpass REST callback response', {
153
+ middleware: 'masterpass-rest-callback',
154
+ contextList,
155
+ redirectUrl,
156
+ ip
157
+ });
158
+
159
+ if (!redirectUrl) {
160
+ logger.warn(
161
+ 'No redirection url found in response. Redirecting to checkout page.',
162
+ {
163
+ middleware: 'masterpass-rest-callback',
164
+ requestHeaders,
165
+ response: JSON.stringify(response),
166
+ ip
167
+ }
168
+ );
169
+
170
+ const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
171
+ '/orders/checkout/',
172
+ req.cookies.get('pz-locale')?.value
173
+ )}`;
174
+
175
+ return NextResponse.redirect(redirectUrlWithLocale, 303);
176
+ }
177
+
178
+ const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
179
+ redirectUrl,
180
+ req.cookies.get('pz-locale')?.value
181
+ )}`;
182
+
183
+ logger.info('Redirecting after masterpass REST callback', {
184
+ middleware: 'masterpass-rest-callback',
185
+ redirectUrlWithLocale,
186
+ ip
187
+ });
188
+
189
+ const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
190
+
191
+ nextResponse.headers.set(
192
+ 'Set-Cookie',
193
+ request.headers.get('set-cookie') ?? ''
194
+ );
195
+
196
+ return nextResponse;
197
+ } catch (error) {
198
+ logger.error('Error while processing masterpass REST callback', {
199
+ middleware: 'masterpass-rest-callback',
200
+ error,
201
+ requestHeaders: {
202
+ Cookie: req.headers.get('cookie') ?? '',
203
+ 'x-currency': req.cookies.get('pz-currency')?.value ?? ''
204
+ },
205
+ ip
206
+ });
207
+
208
+ return NextResponse.redirect(
209
+ `${url.origin}${getUrlPathWithLocale(
210
+ '/orders/checkout/',
211
+ req.cookies.get('pz-locale')?.value
212
+ )}`,
213
+ 303
214
+ );
215
+ }
216
+ };
217
+
218
+ export default withMasterpassRestCallback;
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.111.0-rc.16",
4
+ "version": "1.111.0",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -35,7 +35,7 @@
35
35
  "set-cookie-parser": "2.6.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@akinon/eslint-plugin-projectzero": "1.111.0-rc.16",
38
+ "@akinon/eslint-plugin-projectzero": "1.111.0",
39
39
  "@babel/core": "7.26.10",
40
40
  "@babel/preset-env": "7.26.9",
41
41
  "@babel/preset-typescript": "7.27.0",
package/plugins.d.ts CHANGED
@@ -37,16 +37,6 @@ declare module '@akinon/pz-cybersource-uc/src/redux/middleware' {
37
37
  export default middleware as any;
38
38
  }
39
39
 
40
- declare module '@akinon/pz-apple-pay' {}
41
-
42
- declare module '@akinon/pz-similar-products' {
43
- export const SimilarProductsModal: any;
44
- export const SimilarProductsFilterSidebar: any;
45
- export const SimilarProductsResultsGrid: any;
46
- export const SimilarProductsPlugin: any;
47
- export const SimilarProductsButtonPlugin: any;
48
- }
49
-
50
40
  declare module '@akinon/pz-cybersource-uc/src/redux/reducer' {
51
41
  export default reducer as any;
52
42
  }
@@ -56,3 +46,8 @@ declare module '@akinon/pz-cybersource-uc' {
56
46
  }
57
47
 
58
48
  declare module '@akinon/pz-flow-payment' {}
49
+
50
+ declare module '@akinon/pz-masterpass-rest' {
51
+ export const MasterpassRestOption: React.FC;
52
+ export const masterpassRestReducer: any;
53
+ }
package/plugins.js CHANGED
@@ -16,9 +16,9 @@ module.exports = [
16
16
  'pz-tabby-extension',
17
17
  'pz-apple-pay',
18
18
  'pz-tamara-extension',
19
- 'pz-similar-products',
20
19
  'pz-cybersource-uc',
21
20
  'pz-hepsipay',
22
21
  'pz-flow-payment',
23
- 'pz-virtual-try-on'
22
+ 'pz-virtual-try-on',
23
+ 'pz-masterpass-rest'
24
24
  ];
@@ -208,7 +208,10 @@ export const contextListMiddleware: Middleware = ({
208
208
  dispatch(setCardType(context.page_context.card_type));
209
209
  }
210
210
 
211
- if (context.page_context.installments) {
211
+ if (
212
+ context.page_context.installments &&
213
+ preOrder?.payment_option?.payment_type !== 'masterpass_rest'
214
+ ) {
212
215
  dispatch(setInstallmentOptions(context.page_context.installments));
213
216
  }
214
217
  }
@@ -20,6 +20,7 @@ export const installmentOptionMiddleware: Middleware = ({
20
20
  if (
21
21
  !preOrder?.installment &&
22
22
  preOrder?.payment_option?.payment_type !== 'saved_card' &&
23
+ preOrder?.payment_option?.payment_type !== 'masterpass_rest' &&
23
24
  installmentOptions.length > 0
24
25
  ) {
25
26
  const firstInstallmentOptionPk = installmentOptions[0]?.pk;
@@ -9,6 +9,7 @@ import { masterpassReducer } from '@akinon/pz-masterpass';
9
9
  import { otpReducer } from '@akinon/pz-otp';
10
10
  import { savedCardReducer } from '@akinon/pz-saved-card';
11
11
  import cyberSourceUcReducer from '@akinon/pz-cybersource-uc/src/redux/reducer';
12
+ import { masterpassRestReducer } from '@akinon/pz-masterpass-rest';
12
13
 
13
14
  const fallbackReducer = (state = {}) => state;
14
15
 
@@ -21,7 +22,8 @@ const reducers = {
21
22
  masterpass: masterpassReducer || fallbackReducer,
22
23
  otp: otpReducer || fallbackReducer,
23
24
  savedCard: savedCardReducer || fallbackReducer,
24
- cybersource_uc: cyberSourceUcReducer || fallbackReducer
25
+ cybersource_uc: cyberSourceUcReducer || fallbackReducer,
26
+ masterpassRest: masterpassRestReducer || fallbackReducer
25
27
  };
26
28
 
27
29
  export default reducers;
package/types/index.ts CHANGED
@@ -83,12 +83,6 @@ export interface Settings {
83
83
  };
84
84
  usePrettyUrlRoute?: boolean;
85
85
  commerceUrl: string;
86
- /**
87
- * This option allows you to track Sentry events on the client side, in addition to server and edge environments.
88
- *
89
- * It overrides process.env.NEXT_PUBLIC_SENTRY_DSN and process.env.SENTRY_DSN.
90
- */
91
- sentryDsn?: string;
92
86
  redis: {
93
87
  defaultExpirationTime: number;
94
88
  };
@@ -1,14 +1,8 @@
1
1
  const iframeURLChange = (iframe, callback) => {
2
2
  iframe.addEventListener('load', () => {
3
3
  setTimeout(() => {
4
- try {
5
- if (iframe?.contentWindow?.location) {
6
- const iframeLocation = iframe.contentWindow.location;
7
-
8
- callback(iframeLocation);
9
- }
10
- } catch (error) {
11
- // Expected: browser blocks cross-origin iframe access for security
4
+ if (iframe?.contentWindow?.location) {
5
+ callback(iframe.contentWindow.location);
12
6
  }
13
7
  }, 0);
14
8
  });
@@ -1,14 +1,8 @@
1
1
  const iframeURLChange = (iframe, callback) => {
2
2
  iframe.addEventListener('load', () => {
3
3
  setTimeout(() => {
4
- try {
5
- if (iframe?.contentWindow?.location) {
6
- const iframeLocation = iframe.contentWindow.location;
7
-
8
- callback(iframeLocation);
9
- }
10
- } catch (error) {
11
- // Expected: browser blocks cross-origin iframe access for security
4
+ if (iframe?.contentWindow?.location) {
5
+ callback(iframe.contentWindow.location);
12
6
  }
13
7
  }, 0);
14
8
  });
@@ -1,75 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
-
3
- async function proxyImage(imageUrl: string) {
4
- if (!imageUrl) {
5
- return NextResponse.json(
6
- { success: false, error: 'Missing url parameter' },
7
- { status: 400 }
8
- );
9
- }
10
-
11
- const imageResponse = await fetch(imageUrl);
12
-
13
- if (!imageResponse.ok) {
14
- return NextResponse.json(
15
- { success: false, error: 'Failed to fetch image from URL' },
16
- { status: 400 }
17
- );
18
- }
19
-
20
- const imageBuffer = await imageResponse.arrayBuffer();
21
- const base64Image = Buffer.from(imageBuffer).toString('base64');
22
- const contentType = imageResponse.headers.get('content-type') || 'image/jpeg';
23
-
24
- return { base64Image, contentType, imageBuffer };
25
- }
26
-
27
- export async function GET(request: NextRequest) {
28
- try {
29
- const { searchParams } = new URL(request.url);
30
- const imageUrl = searchParams.get('url');
31
-
32
- const result = await proxyImage(imageUrl!);
33
- if (result instanceof NextResponse) {
34
- return result;
35
- }
36
-
37
- return new NextResponse(result.imageBuffer, {
38
- status: 200,
39
- headers: {
40
- 'Content-Type': result.contentType,
41
- 'Access-Control-Allow-Origin': '*',
42
- 'Cache-Control': 'public, max-age=3600'
43
- }
44
- });
45
- } catch (error) {
46
- console.error('Image proxy error:', error);
47
- return NextResponse.json(
48
- { success: false, error: 'Internal server error' },
49
- { status: 500 }
50
- );
51
- }
52
- }
53
-
54
- export async function POST(request: NextRequest) {
55
- try {
56
- const body = await request.json();
57
- const imageUrl = body.imageUrl;
58
-
59
- const result = await proxyImage(imageUrl);
60
- if (result instanceof NextResponse) {
61
- return result;
62
- }
63
-
64
- return NextResponse.json({
65
- success: true,
66
- base64Image: `data:${result.contentType};base64,${result.base64Image}`
67
- });
68
- } catch (error) {
69
- console.error('Image proxy POST error:', error);
70
- return NextResponse.json(
71
- { success: false, error: 'Internal server error' },
72
- { status: 500 }
73
- );
74
- }
75
- }
@@ -1,63 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import Settings from 'settings';
3
-
4
- export async function GET(request: NextRequest) {
5
- try {
6
- const { searchParams } = new URL(request.url);
7
- const dynamicFilter = request.headers.get('x-search-dynamic-filter');
8
- const dynamicExclude = request.headers.get('x-search-dynamic-exclude');
9
-
10
- if (!dynamicFilter && !dynamicExclude) {
11
- return NextResponse.json(
12
- {
13
- error:
14
- 'Missing x-search-dynamic-filter or x-search-dynamic-exclude header'
15
- },
16
- { status: 400 }
17
- );
18
- }
19
-
20
- if (Settings.commerceUrl === 'default') {
21
- return NextResponse.json(
22
- { error: 'Commerce URL is not configured' },
23
- { status: 500 }
24
- );
25
- }
26
-
27
- const queryString = searchParams.toString();
28
- const apiUrl = `${Settings.commerceUrl}/list${
29
- queryString ? `?${queryString}` : ''
30
- }`;
31
-
32
- const headers: Record<string, string> = {
33
- Accept: 'application/json',
34
- 'Content-Type': 'application/json'
35
- };
36
-
37
- if (dynamicFilter) {
38
- headers['x-search-dynamic-filter'] = dynamicFilter;
39
- } else if (dynamicExclude) {
40
- headers['x-search-dynamic-exclude'] = dynamicExclude;
41
- }
42
-
43
- const response = await fetch(apiUrl, {
44
- method: 'GET',
45
- headers
46
- });
47
-
48
- if (!response.ok) {
49
- return NextResponse.json(
50
- { error: `API request failed with status: ${response.status}` },
51
- { status: response.status }
52
- );
53
- }
54
-
55
- const data = await response.json();
56
- return NextResponse.json(data);
57
- } catch (error) {
58
- return NextResponse.json(
59
- { error: (error as Error).message },
60
- { status: 500 }
61
- );
62
- }
63
- }
@@ -1,111 +0,0 @@
1
- import { NextResponse } from 'next/server';
2
- import Settings from 'settings';
3
-
4
- const IMAGE_SEARCH_API_URL = Settings.commerceUrl + '/image-search/';
5
-
6
- const errorResponse = (message: string, status: number) => {
7
- return NextResponse.json({ error: message }, { status });
8
- };
9
-
10
- export async function GET(request: Request) {
11
- const { searchParams } = new URL(request.url);
12
- const limit = searchParams.get('limit') || '20';
13
- const url = searchParams.get('url');
14
- const excludedProductIds = searchParams.get('excluded_product_ids');
15
-
16
- if (!url) {
17
- return errorResponse('URL parameter is required', 400);
18
- }
19
-
20
- if (Settings.commerceUrl === 'default') {
21
- return errorResponse('Commerce URL is not configured', 500);
22
- }
23
-
24
- const apiParams = new URLSearchParams();
25
- apiParams.append('limit', limit);
26
- apiParams.append('url', url);
27
-
28
- if (excludedProductIds) {
29
- apiParams.append('excluded_product_ids', excludedProductIds);
30
- }
31
-
32
- const apiUrl = `${IMAGE_SEARCH_API_URL}?${apiParams.toString()}`;
33
-
34
- try {
35
- const response = await fetch(apiUrl, {
36
- method: 'GET',
37
- headers: {
38
- Accept: 'application/json'
39
- }
40
- });
41
-
42
- if (!response.ok) {
43
- const errorText = await response.text();
44
- return errorResponse(
45
- errorText || `API request failed with status: ${response.status}`,
46
- response.status
47
- );
48
- }
49
-
50
- const responseText = await response.text();
51
-
52
- return NextResponse.json(JSON.parse(responseText));
53
- } catch (error) {
54
- return errorResponse((error as Error).message, 500);
55
- }
56
- }
57
-
58
- export async function POST(request: Request) {
59
- const { searchParams } = new URL(request.url);
60
- const limit = searchParams.get('limit') || '20';
61
-
62
- if (Settings.commerceUrl === 'default') {
63
- return errorResponse('Commerce URL is not configured', 500);
64
- }
65
-
66
- let requestBody;
67
- try {
68
- requestBody = await request.json();
69
- } catch (error) {
70
- return errorResponse('Invalid JSON in request body', 400);
71
- }
72
-
73
- if (!requestBody.image) {
74
- return errorResponse('Image data is required in request body', 400);
75
- }
76
-
77
- const apiParams = new URLSearchParams();
78
- apiParams.append('limit', limit);
79
-
80
- const apiUrl = `${IMAGE_SEARCH_API_URL}?${apiParams.toString()}`;
81
-
82
- const bodyData: any = { image: requestBody.image };
83
-
84
- if (requestBody.excluded_product_ids) {
85
- bodyData.excluded_product_ids = requestBody.excluded_product_ids;
86
- }
87
-
88
- try {
89
- const response = await fetch(apiUrl, {
90
- method: 'POST',
91
- headers: {
92
- 'Content-Type': 'application/json',
93
- Accept: 'application/json'
94
- },
95
- body: JSON.stringify(bodyData)
96
- });
97
-
98
- if (!response.ok) {
99
- const errorText = await response.text();
100
- return errorResponse(
101
- errorText || `API request failed with status: ${response.status}`,
102
- response.status
103
- );
104
- }
105
-
106
- const responseData = await response.json();
107
- return NextResponse.json(responseData);
108
- } catch (error) {
109
- return errorResponse((error as Error).message, 500);
110
- }
111
- }