@akinon/next 1.126.0-rc.3 → 1.126.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,40 +1,14 @@
1
1
  # @akinon/next
2
2
 
3
- ## 1.126.0-rc.3
3
+ ## 1.126.0
4
4
 
5
- ### Minor Changes
6
-
7
- - 324f97d5: ZERO-4219: replace masterpass-rest-complete with masterpass-rest-callback
8
- - 4ac7b2a1: ZERO-4219: fix masterpass-rest callback route format and double-encoded error cookie
9
-
10
- ## 1.126.0-rc.2
11
-
12
- ### Minor Changes
13
-
14
- - 4998a963: ZERO-4168: Add server-side payload optimization
5
+ ## 1.125.2
15
6
 
16
- ## 1.126.0-rc.1
7
+ ## 1.125.1
17
8
 
18
- ### Minor Changes
19
-
20
- - 0cf9ea23: BRDG-16491: Prevent redirect when iframe payment is active
21
-
22
- ## 1.126.0-rc.0
23
-
24
- ### Minor Changes
9
+ ### Patch Changes
25
10
 
26
- - ZERO-4160: Enhance oauth-login middleware with improved request handling and logging
27
- - b55acb76: ZERO-2577: Fix pagination bug and update usePagination hook and ensure pagination controls rendering correctly
28
- - 760258c1: ZERO-4160: Enhance oauth-login middleware to handle fetch errors and improve response handling
29
- - 143be2b9: ZERO-3457: Crop styles are customizable and logic improved for rendering similar products modal
30
- - 9f8cd3bc: ZERO-3449: AI Search Active Filters & Crop Style changes have been implemented
31
- - bfafa3f4: ZERO-4160: Refactor oauth-login middleware to use fetchCommerce for API calls and improve cookie handling
32
- - d99a6a7d: ZERO-3457_1: Fixed the settings prop and made sure everything is customizable.
33
- - 591e345e: ZERO-3855: Enhance credit card payment handling in checkout middlewares
34
- - 4de5303c: ZERO-2504: add cookie filter to api client request
35
- - 95b139dc: ZERO-3795: Remove duplicate entry for SavedCard in PluginComponents map
36
- - 3909d322: Edit the duplicate Plugin.SimilarProducts in the plugin-module.
37
- - e18836b2: ZERO-4160: Restore scope in Sentry addon configuration in akinon.json
11
+ - 06da9a95: ZERO-4241: Upgrade Next.js from 14.2.25 to 14.2.35 to fix security vulnerabilities (CVE-2025-55184, CVE-2025-67779)
38
12
 
39
13
  ## 1.125.0
40
14
 
@@ -114,6 +114,7 @@ const PluginComponents = new Map([
114
114
  ]
115
115
  ],
116
116
  [Plugin.SavedCard, [Component.SavedCard, Component.IyzicoSavedCard]],
117
+ [Plugin.SavedCard, [Component.SavedCard]],
117
118
  [Plugin.FlowPayment, [Component.FlowPayment]],
118
119
  [
119
120
  Plugin.VirtualTryOn,
@@ -7,8 +7,6 @@ import { parse } from 'lossless-json';
7
7
  import logger from '../../utils/log';
8
8
  import { headers as nHeaders } from 'next/headers';
9
9
  import { ServerVariables } from '../../utils/server-variables';
10
- import { optimizeCategoryResponse } from '../../utils/payload-optimizer';
11
- import settings from 'settings';
12
10
 
13
11
  function getCategoryDataHandler(
14
12
  pk: number,
@@ -82,7 +80,7 @@ function getCategoryDataHandler(
82
80
  };
83
81
  }
84
82
 
85
- export const getCategoryData = async ({
83
+ export const getCategoryData = ({
86
84
  pk,
87
85
  searchParams,
88
86
  headers,
@@ -95,7 +93,7 @@ export const getCategoryData = async ({
95
93
  searchParams?: URLSearchParams;
96
94
  headers?: Record<string, string>;
97
95
  }) => {
98
- const result = await Cache.wrap(
96
+ return Cache.wrap(
99
97
  CacheKey.Category(pk, searchParams, headers),
100
98
  locale,
101
99
  getCategoryDataHandler(pk, locale, currency, searchParams, headers),
@@ -104,16 +102,6 @@ export const getCategoryData = async ({
104
102
  compressed: true
105
103
  }
106
104
  );
107
-
108
- if (settings.payloadOptimization?.enabled && result?.data) {
109
- try {
110
- return { ...result, data: optimizeCategoryResponse(result.data, settings.payloadOptimization) };
111
- } catch (e) {
112
- logger.error('Payload optimization failed for category', { pk, error: (e as Error).message });
113
- }
114
- }
115
-
116
- return result;
117
105
  };
118
106
 
119
107
  function getCategoryBySlugDataHandler(
@@ -6,8 +6,6 @@ import appFetch, { FetchResponseType } from '../../utils/app-fetch';
6
6
  import { parse } from 'lossless-json';
7
7
  import logger from '../../utils/log';
8
8
  import { ServerVariables } from '../../utils/server-variables';
9
- import { optimizeCategoryResponse } from '../../utils/payload-optimizer';
10
- import settings from 'settings';
11
9
 
12
10
  const getListDataHandler = (
13
11
  locale,
@@ -68,7 +66,7 @@ export const getListData = async ({
68
66
  searchParams: URLSearchParams;
69
67
  headers?: Record<string, string>;
70
68
  }) => {
71
- const result = await Cache.wrap(
69
+ return Cache.wrap(
72
70
  CacheKey.List(searchParams, headers),
73
71
  locale,
74
72
  getListDataHandler(locale, currency, searchParams, headers),
@@ -77,14 +75,4 @@ export const getListData = async ({
77
75
  compressed: true
78
76
  }
79
77
  );
80
-
81
- if (settings.payloadOptimization?.enabled && result) {
82
- try {
83
- return optimizeCategoryResponse(result, settings.payloadOptimization);
84
- } catch (e) {
85
- logger.error('Payload optimization failed for list', { error: (e as Error).message });
86
- }
87
- }
88
-
89
- return result;
90
78
  };
@@ -4,8 +4,6 @@ import { ProductCategoryResult, ProductResult } from '../../types';
4
4
  import appFetch from '../../utils/app-fetch';
5
5
  import { ServerVariables } from '../../utils/server-variables';
6
6
  import logger from '../../utils/log';
7
- import { optimizeProductResponse } from '../../utils/payload-optimizer';
8
- import settings from 'settings';
9
7
 
10
8
  type GetProduct = {
11
9
  pk: number | string;
@@ -165,13 +163,5 @@ export const getProductData = async ({
165
163
  throw error;
166
164
  }
167
165
 
168
- if (settings.payloadOptimization?.enabled && result?.data) {
169
- try {
170
- return { ...result, data: optimizeProductResponse(result.data, settings.payloadOptimization) };
171
- } catch (e) {
172
- logger.error('Payload optimization failed for product', { pk, error: (e as Error).message });
173
- }
174
- }
175
-
176
166
  return result;
177
167
  };
@@ -4,9 +4,6 @@ import { GetCategoryResponse } from '../../types';
4
4
  import { generateCommerceSearchParams } from '../../utils';
5
5
  import appFetch from '../../utils/app-fetch';
6
6
  import { ServerVariables } from '../../utils/server-variables';
7
- import { optimizeCategoryResponse } from '../../utils/payload-optimizer';
8
- import logger from '../../utils/log';
9
- import settings from 'settings';
10
7
 
11
8
  const getSpecialPageDataHandler = (
12
9
  pk: number,
@@ -48,7 +45,7 @@ export const getSpecialPageData = async ({
48
45
  searchParams: URLSearchParams;
49
46
  headers?: Record<string, string>;
50
47
  }) => {
51
- const result = await Cache.wrap(
48
+ return Cache.wrap(
52
49
  CacheKey.SpecialPage(pk, searchParams, headers),
53
50
  locale,
54
51
  getSpecialPageDataHandler(pk, locale, currency, searchParams, headers),
@@ -57,14 +54,4 @@ export const getSpecialPageData = async ({
57
54
  compressed: true
58
55
  }
59
56
  );
60
-
61
- if (settings.payloadOptimization?.enabled && result) {
62
- try {
63
- return optimizeCategoryResponse(result, settings.payloadOptimization);
64
- } catch (e) {
65
- logger.error('Payload optimization failed for special-page', { pk, error: (e as Error).message });
66
- }
67
- }
68
-
69
- return result;
70
57
  };
@@ -4,9 +4,6 @@ import { CacheOptions, WidgetResultType } from '../../types';
4
4
  import appFetch from '../../utils/app-fetch';
5
5
  import { widgets } from '../urls';
6
6
  import { ServerVariables } from '../../utils/server-variables';
7
- import { optimizeWidgetResponse } from '../../utils/payload-optimizer';
8
- import logger from '../../utils/log';
9
- import settings from 'settings';
10
7
 
11
8
  const getWidgetDataHandler =
12
9
  (
@@ -43,7 +40,7 @@ export const getWidgetData = async <T>({
43
40
  cacheOptions?: CacheOptions;
44
41
  headers?: Record<string, string>;
45
42
  }): Promise<WidgetResultType<T>> => {
46
- const result = await Cache.wrap(
43
+ return Cache.wrap(
47
44
  CacheKey.Widget(slug),
48
45
  locale,
49
46
  getWidgetDataHandler(slug, locale, currency, headers),
@@ -52,14 +49,4 @@ export const getWidgetData = async <T>({
52
49
  ...cacheOptions
53
50
  }
54
51
  );
55
-
56
- if (settings.payloadOptimization?.enabled && result) {
57
- try {
58
- return optimizeWidgetResponse(result, settings.payloadOptimization) as WidgetResultType<T>;
59
- } catch (e) {
60
- logger.error('Payload optimization failed for widget', { slug, error: (e as Error).message });
61
- }
62
- }
63
-
64
- return result as WidgetResultType<T>;
65
52
  };
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 = {
@@ -10,23 +10,49 @@ const withMasterpassRestCallback =
10
10
  async (req: PzNextRequest, event: NextFetchEvent) => {
11
11
  const url = req.nextUrl.clone();
12
12
  const ip = req.headers.get('x-forwarded-for') ?? '';
13
+ const sessionId = req.cookies.get('osessionid');
13
14
 
14
- const isMasterpassCompletePage =
15
- url.pathname.includes('/orders/checkout') &&
16
- url.searchParams.get('page') === 'MasterpassRestCompletePage';
17
-
18
- if (!isMasterpassCompletePage) {
15
+ if (!url.pathname.includes('/orders/masterpass-rest-callback')) {
19
16
  return middleware(req, event);
20
17
  }
21
18
 
22
- try {
23
- const isPostCheckout = req.cookies.get('pz-post-checkout-flow')?.value === 'true';
24
- const requestUrl = new URL(getCheckoutPath(isPostCheckout), Settings.commerceUrl);
19
+ if (req.method !== 'POST') {
20
+ logger.warn('Invalid request method for masterpass REST callback', {
21
+ middleware: 'masterpass-rest-callback',
22
+ method: req.method,
23
+ ip
24
+ });
25
+
26
+ return NextResponse.redirect(
27
+ `${url.origin}${getUrlPathWithLocale(
28
+ '/orders/checkout/',
29
+ req.cookies.get('pz-locale')?.value
30
+ )}`,
31
+ 303
32
+ );
33
+ }
25
34
 
26
- url.searchParams.forEach((value, key) => {
27
- requestUrl.searchParams.set(key, value);
35
+ const responseCode = url.searchParams.get('responseCode');
36
+ const token = url.searchParams.get('token');
37
+
38
+ if (!responseCode || !token) {
39
+ logger.warn('Missing required parameters for masterpass REST callback', {
40
+ middleware: 'masterpass-rest-callback',
41
+ responseCode,
42
+ token,
43
+ ip
28
44
  });
29
45
 
46
+ return NextResponse.redirect(
47
+ `${url.origin}${getUrlPathWithLocale(
48
+ '/orders/checkout/',
49
+ req.cookies.get('pz-locale')?.value
50
+ )}`,
51
+ 303
52
+ );
53
+ }
54
+
55
+ try {
30
56
  const formData = await req.formData();
31
57
  const body: Record<string, string> = {};
32
58
 
@@ -34,6 +60,38 @@ const withMasterpassRestCallback =
34
60
  body[key] = value.toString();
35
61
  });
36
62
 
63
+ if (!sessionId) {
64
+ logger.warn(
65
+ 'Make sure that the SESSION_COOKIE_SAMESITE environment variable is set to None in Commerce.',
66
+ {
67
+ middleware: 'masterpass-rest-callback',
68
+ ip
69
+ }
70
+ );
71
+
72
+ return NextResponse.redirect(
73
+ `${url.origin}${getUrlPathWithLocale(
74
+ '/orders/checkout/',
75
+ req.cookies.get('pz-locale')?.value
76
+ )}`,
77
+ 303
78
+ );
79
+ }
80
+
81
+ const isPostCheckout = req.cookies.get('pz-post-checkout-flow')?.value === 'true';
82
+ const requestUrl = new URL(getCheckoutPath(isPostCheckout), Settings.commerceUrl);
83
+ requestUrl.searchParams.set('page', 'MasterpassRestCompletePage');
84
+ requestUrl.searchParams.set('responseCode', responseCode);
85
+ requestUrl.searchParams.set('token', token);
86
+ requestUrl.searchParams.set(
87
+ 'three_d_secure',
88
+ body.transactionType?.includes('3D') ? 'true' : 'false'
89
+ );
90
+ requestUrl.searchParams.set(
91
+ 'transactionType',
92
+ body.transactionType || ''
93
+ );
94
+
37
95
  const requestHeaders = {
38
96
  'Content-Type': 'application/x-www-form-urlencoded',
39
97
  'X-Requested-With': 'XMLHttpRequest',
@@ -49,13 +107,33 @@ const withMasterpassRestCallback =
49
107
  body: new URLSearchParams(body)
50
108
  });
51
109
 
110
+ logger.info('Masterpass REST callback request', {
111
+ requestUrl: requestUrl.toString(),
112
+ status: request.status,
113
+ requestHeaders,
114
+ ip
115
+ });
116
+
52
117
  const response = await request.json();
53
- const { errors } = response;
118
+
119
+ const { context_list: contextList, errors } = response;
120
+
121
+ let redirectUrl = response.redirect_url;
122
+
123
+ if (!redirectUrl && contextList && contextList.length > 0) {
124
+ for (const context of contextList) {
125
+ if (context.page_context && context.page_context.redirect_url) {
126
+ redirectUrl = context.page_context.redirect_url;
127
+ break;
128
+ }
129
+ }
130
+ }
54
131
 
55
132
  if (errors && Object.keys(errors).length) {
56
- logger.error('Error while processing MasterpassRestCompletePage', {
133
+ logger.error('Error while processing masterpass REST callback', {
57
134
  middleware: 'masterpass-rest-callback',
58
135
  errors,
136
+ requestHeaders,
59
137
  ip
60
138
  });
61
139
 
@@ -64,7 +142,12 @@ const withMasterpassRestCallback =
64
142
  '/orders/checkout/',
65
143
  req.cookies.get('pz-locale')?.value
66
144
  )}`,
67
- 303
145
+ {
146
+ status: 303,
147
+ headers: {
148
+ 'Set-Cookie': `pz-pos-error=${encodeURIComponent(JSON.stringify(errors))}; path=/;`
149
+ }
150
+ }
68
151
  );
69
152
 
70
153
  // Add error cookie
@@ -75,40 +158,70 @@ const withMasterpassRestCallback =
75
158
  return errorResponse;
76
159
  }
77
160
 
78
- const redirectUrl =
79
- response.redirect_url ||
80
- response.context_list?.[0]?.page_context?.redirect_url;
161
+ logger.info('Masterpass REST callback response', {
162
+ middleware: 'masterpass-rest-callback',
163
+ contextList,
164
+ redirectUrl,
165
+ ip
166
+ });
81
167
 
82
- if (redirectUrl) {
83
- const nextResponse = NextResponse.redirect(
84
- `${url.origin}${getUrlPathWithLocale(redirectUrl, req.cookies.get('pz-locale')?.value)}`,
85
- 303
168
+ if (!redirectUrl) {
169
+ logger.warn(
170
+ 'No redirection url found in response. Redirecting to checkout page.',
171
+ {
172
+ middleware: 'masterpass-rest-callback',
173
+ requestHeaders,
174
+ response: JSON.stringify(response),
175
+ ip
176
+ }
86
177
  );
87
178
 
88
- // Forward set-cookie headers from the upstream response
89
- const setCookieHeader = request.headers.get('set-cookie');
90
- if (setCookieHeader) {
91
- setCookieHeader.split(',').forEach((cookie) => {
92
- nextResponse.headers.append('Set-Cookie', cookie.trim());
93
- });
94
- }
179
+ const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
180
+ '/orders/checkout/',
181
+ req.cookies.get('pz-locale')?.value
182
+ )}`;
95
183
 
96
- return nextResponse;
184
+ return NextResponse.redirect(redirectUrlWithLocale, 303);
97
185
  }
98
186
 
99
- return NextResponse.redirect(
100
- `${url.origin}${getUrlPathWithLocale('/orders/checkout/', req.cookies.get('pz-locale')?.value)}`,
101
- 303
102
- );
187
+ const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
188
+ redirectUrl,
189
+ req.cookies.get('pz-locale')?.value
190
+ )}`;
191
+
192
+ logger.info('Redirecting after masterpass REST callback', {
193
+ middleware: 'masterpass-rest-callback',
194
+ redirectUrl: redirectUrlWithLocale,
195
+ ip
196
+ });
197
+
198
+ const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
199
+
200
+ // Forward set-cookie headers from the upstream response
201
+ const setCookieHeader = request.headers.get('set-cookie');
202
+ if (setCookieHeader) {
203
+ setCookieHeader.split(',').forEach((cookie) => {
204
+ nextResponse.headers.append('Set-Cookie', cookie.trim());
205
+ });
206
+ }
207
+
208
+ return nextResponse;
103
209
  } catch (error) {
104
- logger.error('Error while processing MasterpassRestCompletePage', {
210
+ logger.error('Error while processing masterpass REST callback', {
105
211
  middleware: 'masterpass-rest-callback',
106
212
  error,
213
+ requestHeaders: {
214
+ Cookie: req.headers.get('cookie') ?? '',
215
+ 'x-currency': req.cookies.get('pz-currency')?.value ?? ''
216
+ },
107
217
  ip
108
218
  });
109
219
 
110
220
  return NextResponse.redirect(
111
- `${url.origin}${getUrlPathWithLocale('/orders/checkout/', req.cookies.get('pz-locale')?.value)}`,
221
+ `${url.origin}${getUrlPathWithLocale(
222
+ '/orders/checkout/',
223
+ req.cookies.get('pz-locale')?.value
224
+ )}`,
112
225
  303
113
226
  );
114
227
  }
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.126.0-rc.3",
4
+ "version": "1.126.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.126.0-rc.3",
38
+ "@akinon/eslint-plugin-projectzero": "1.126.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
  }
package/plugins.js CHANGED
@@ -16,7 +16,6 @@ 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',
@@ -33,21 +33,6 @@ import { LocaleUrlStrategy } from '../../localization';
33
33
  import { showMobile3dIframe } from '../../utils/mobile-3d-iframe';
34
34
  import { showRedirectionIframe } from '../../utils/redirection-iframe';
35
35
 
36
- const IFRAME_REDIRECTION_KEY = 'pz-iframe-redirection-active';
37
-
38
- const isIframeRedirectionActive = () =>
39
- typeof window !== 'undefined' &&
40
- sessionStorage.getItem(IFRAME_REDIRECTION_KEY) === 'true';
41
-
42
- const setIframeRedirectionActive = (active: boolean) => {
43
- if (typeof window === 'undefined') return;
44
- if (active) {
45
- sessionStorage.setItem(IFRAME_REDIRECTION_KEY, 'true');
46
- } else {
47
- sessionStorage.removeItem(IFRAME_REDIRECTION_KEY);
48
- }
49
- };
50
-
51
36
  interface CheckoutResult {
52
37
  payload: {
53
38
  errors?: Record<string, string[]>;
@@ -84,7 +69,7 @@ export const redirectUrlMiddleware: Middleware = () => {
84
69
  const result: CheckoutResult = next(action);
85
70
  const redirectUrl = result?.payload?.redirect_url;
86
71
 
87
- if (redirectUrl && !isIframeRedirectionActive()) {
72
+ if (redirectUrl) {
88
73
  const currentLocale = getCookie('pz-locale');
89
74
 
90
75
  let url = redirectUrl;
@@ -149,7 +134,6 @@ export const contextListMiddleware: Middleware = ({
149
134
  if (isMobileDevice && isIframePaymentOptionIncluded) {
150
135
  showMobile3dIframe(urlObj.toString());
151
136
  } else if (isIframe) {
152
- setIframeRedirectionActive(true);
153
137
  showRedirectionIframe(urlObj.toString());
154
138
  } else {
155
139
  window.location.href = urlObj.toString();
@@ -225,25 +209,15 @@ export const contextListMiddleware: Middleware = ({
225
209
  (ctx) => ctx.page_name === 'DeliveryOptionSelectionPage'
226
210
  )
227
211
  ) {
228
- const isCreditCardPayment =
229
- preOrder?.payment_option?.payment_type === 'credit_card' ||
230
- preOrder?.payment_option?.payment_type === 'masterpass';
231
-
232
212
  if (context.page_context.card_type) {
233
213
  dispatch(setCardType(context.page_context.card_type));
234
- } else if (isCreditCardPayment) {
235
- dispatch(setCardType(null));
236
214
  }
237
215
 
238
216
  if (
239
217
  context.page_context.installments &&
240
218
  preOrder?.payment_option?.payment_type !== 'masterpass_rest'
241
219
  ) {
242
- if (!isCreditCardPayment || context.page_context.card_type) {
243
- dispatch(
244
- setInstallmentOptions(context.page_context.installments)
245
- );
246
- }
220
+ dispatch(setInstallmentOptions(context.page_context.installments));
247
221
  }
248
222
  }
249
223
 
@@ -14,17 +14,9 @@ export const installmentOptionMiddleware: Middleware = ({
14
14
  return result;
15
15
  }
16
16
 
17
- const { installmentOptions, cardType } = getState().checkout;
17
+ const { installmentOptions } = getState().checkout;
18
18
  const { endpoints: apiEndpoints } = checkoutApi;
19
19
 
20
- const isCreditCardPayment =
21
- preOrder?.payment_option?.payment_type === 'credit_card' ||
22
- preOrder?.payment_option?.payment_type === 'masterpass';
23
-
24
- if (isCreditCardPayment && !cardType) {
25
- return result;
26
- }
27
-
28
20
  if (
29
21
  !preOrder?.installment &&
30
22
  preOrder?.payment_option?.payment_type !== 'saved_card' &&
package/types/index.ts CHANGED
@@ -85,12 +85,6 @@ export interface Settings {
85
85
  };
86
86
  usePrettyUrlRoute?: boolean;
87
87
  commerceUrl: string;
88
- /**
89
- * This option allows you to track Sentry events on the client side, in addition to server and edge environments.
90
- *
91
- * It overrides process.env.NEXT_PUBLIC_SENTRY_DSN and process.env.SENTRY_DSN.
92
- */
93
- sentryDsn?: string;
94
88
  redis: {
95
89
  defaultExpirationTime: number;
96
90
  };
@@ -223,7 +217,6 @@ export interface Settings {
223
217
  separator?: string;
224
218
  segments: PzSegmentDefinition[];
225
219
  };
226
- payloadOptimization?: import('../utils/payload-optimizer').PayloadOptimizationConfig;
227
220
  }
228
221
 
229
222
  export interface CacheOptions {
package/with-pz-config.js CHANGED
@@ -62,8 +62,7 @@ const defaultConfig = {
62
62
  acc[`@akinon/${plugin}`] = false;
63
63
  return acc;
64
64
  }, {}),
65
- translations: false,
66
- '@opentelemetry/exporter-jaeger': false
65
+ translations: false
67
66
  };
68
67
  return config;
69
68
  }
@@ -1,481 +0,0 @@
1
- import { GetCategoryResponse } from '../types/commerce/category'
2
- import { Product, ProductResult } from '../types/commerce/product'
3
-
4
- /**
5
- * Payload Optimization Config
6
- *
7
- * - Optimizer returns minimum fields needed for the base project.
8
- * - Projects extend via "Extra" arrays in settings.js.
9
- * - Each config array works as "base + extra".
10
- *
11
- * Example:
12
- * ```js
13
- * payloadOptimization: {
14
- * enabled: true,
15
- * listProductImageLimit: 1,
16
- * listProductExtraFields: ['stock', 'tax_rate'],
17
- * listProductExtraAttributes: ['my_custom_attr'],
18
- * listProductExtraAttributesKwargs: ['integration_renk'],
19
- * }
20
- * ```
21
- */
22
- export interface PayloadOptimizationConfig {
23
- enabled: boolean
24
-
25
- // ── Image Limits ───────────────────────────────────────────────────────────
26
- /** Max images per product on list pages (default: 3) */
27
- listProductImageLimit?: number
28
- /** Extra hover image count after the limit (default: 2) */
29
- listHoverImageCount?: number
30
- /** Max images per product in widgets (default: listProductImageLimit) */
31
- widgetProductImageLimit?: number
32
- /** Widget hover image count (default: listHoverImageCount) */
33
- widgetHoverImageCount?: number
34
- /** Max images per variant product on PDP (default: 1) */
35
- pdpVariantImageLimit?: number
36
-
37
- // ── List / Category / Special Page ─────────────────────────────────────────
38
- /** Extra top-level product fields on top of base */
39
- listProductExtraFields?: string[]
40
- /** Extra keys from product.attributes on top of base */
41
- listProductExtraAttributes?: string[]
42
- /** Extra keys from product.attributes_kwargs on top of base */
43
- listProductExtraAttributesKwargs?: string[]
44
- /** Extra keys from product.extra_attributes on top of base */
45
- listProductExtraExtraAttributes?: string[]
46
- /** Extra keys from product.data_source on top of base */
47
- listDataSourceExtraFields?: string[]
48
-
49
- // ── Product Detail Page ────────────────────────────────────────────────────
50
- /** Extra keys from product.attributes on top of base */
51
- pdpProductExtraAttributes?: string[]
52
- /** Extra keys from product.attributes_kwargs on top of base */
53
- pdpProductExtraAttributesKwargs?: string[]
54
- /** Extra keys from product.extra_attributes on top of base */
55
- pdpProductExtraExtraAttributes?: string[]
56
- /** Extra keys from variant product.attributes on top of base */
57
- pdpVariantExtraAttributes?: string[]
58
- /** Extra keys from variant product.attributes_kwargs on top of base */
59
- pdpVariantExtraAttributesKwargs?: string[]
60
- /** Extra keys from product.data_source on top of base */
61
- pdpDataSourceExtraFields?: string[]
62
- /** Extra top-level product fields on top of base */
63
- pdpProductExtraFields?: string[]
64
-
65
- // ── Category ───────────────────────────────────────────────────────────────
66
- /** Extra top-level category fields on top of base */
67
- categoryExtraFields?: string[]
68
- /** Extra keys from category.attributes on top of base */
69
- categoryExtraAttributes?: string[]
70
-
71
- // ── Special Page ───────────────────────────────────────────────────────────
72
- /** Extra fields from special_page on top of base */
73
- specialPageExtraFields?: string[]
74
-
75
- // ── Offers ─────────────────────────────────────────────────────────────────
76
- /** Extra keys from offer.listing_kwargs on top of base */
77
- offerExtraListingKwargsFields?: string[]
78
- }
79
-
80
- // ─── Base Field Sets ─────────────────────────────────────────────────────────
81
- // Minimum fields required by the base project (projectzeronext).
82
- // Projects extend these via "Extra" arrays in settings.js.
83
-
84
- const BASE_LIST_PRODUCT_FIELDS = [
85
- 'pk', 'name', 'base_code', 'absolute_url',
86
- 'price', 'retail_price', 'in_stock'
87
- ]
88
-
89
- const BASE_LIST_PRODUCT_ATTRIBUTES: string[] = []
90
- const BASE_LIST_PRODUCT_ATTRIBUTES_KWARGS: string[] = []
91
- const BASE_LIST_PRODUCT_EXTRA_ATTRIBUTES: string[] = []
92
- const BASE_LIST_DATASOURCE_FIELDS = ['pk', 'name']
93
-
94
- const BASE_PDP_PRODUCT_FIELDS = [
95
- 'pk', 'name', 'sku', 'base_code', 'price', 'retail_price',
96
- 'in_stock', 'product_type', 'currency_type', 'absolute_url',
97
- 'productimage_set', 'attribute_set', 'unit_type', 'tax_rate',
98
- 'stock', 'price_type', 'form_schema', 'is_ready_to_basket',
99
- 'data_source', 'basket_offers', 'data_source_offers',
100
- 'is_listable', 'listing_code', 'price_extra_field'
101
- ]
102
-
103
- const BASE_PDP_PRODUCT_ATTRIBUTES = [
104
- 'integration_ProductAtt03Desc', 'integration_kumas_icerik',
105
- 'model_olculeri', 'aciklama'
106
- ]
107
-
108
- const BASE_PDP_PRODUCT_ATTRIBUTES_KWARGS: string[] = []
109
- const BASE_PDP_PRODUCT_EXTRA_ATTRIBUTES: string[] = []
110
-
111
- const BASE_PDP_DATASOURCE_FIELDS = [
112
- 'pk', 'name', 'title', 'phone_number', 'address',
113
- 'mersis_number', 'kep_address'
114
- ]
115
-
116
- const BASE_PDP_VARIANT_ATTRIBUTES: string[] = []
117
- const BASE_PDP_VARIANT_ATTRIBUTES_KWARGS: string[] = []
118
-
119
- const BASE_CATEGORY_FIELDS = [
120
- 'pk', 'name', 'menuitemmodel', 'absolute_url', 'sort_option'
121
- ]
122
-
123
- const BASE_CATEGORY_ATTRIBUTES: string[] = []
124
-
125
- const BASE_SPECIAL_PAGE_FIELDS = ['pk', 'name', 'url', 'banner_url']
126
-
127
- const DEFAULT_LIST_IMAGE_LIMIT = 3
128
- const DEFAULT_PDP_VARIANT_IMAGE_LIMIT = 1
129
-
130
- // ─── Helpers ─────────────────────────────────────────────────────────────────
131
-
132
- function merge(base: string[], extra?: string[]): string[] {
133
- if (!extra?.length) return base
134
- return [...base, ...extra]
135
- }
136
-
137
- function pickKeys(obj: any, keys: string[]): any {
138
- if (!obj) return undefined
139
- const result: any = {}
140
- for (const key of keys) {
141
- if (obj[key] != null) {
142
- result[key] = obj[key]
143
- }
144
- }
145
- return result
146
- }
147
-
148
- function pickAttributesKwargs(kwargs: any, keys: string[]): any {
149
- if (!kwargs) return undefined
150
- const result: any = {}
151
- for (const key of keys) {
152
- if (kwargs[key] != null) {
153
- result[key] = kwargs[key]?.label != null
154
- ? { label: kwargs[key].label }
155
- : kwargs[key]
156
- }
157
- }
158
- return result
159
- }
160
-
161
- function stripImages(images: any[] | undefined, limit: number) {
162
- if (!images?.length) return undefined
163
- return images.slice(0, limit).map((img: any) => ({ image: img.image }))
164
- }
165
-
166
- function stripOffer(offer: any, extraFields?: string[]) {
167
- if (!offer) return offer
168
- const lk: any = offer.listing_kwargs
169
- ? { discounted_total_price: offer.listing_kwargs.discounted_total_price, quantity: offer.listing_kwargs.quantity }
170
- : undefined
171
-
172
- if (lk && extraFields && offer.listing_kwargs) {
173
- for (const f of extraFields) {
174
- if (offer.listing_kwargs[f] !== undefined) lk[f] = offer.listing_kwargs[f]
175
- }
176
- }
177
-
178
- return {
179
- pk: offer.pk,
180
- label: offer.label,
181
- listing_kwargs: lk,
182
- kwargs: offer.kwargs,
183
- data_source: offer.data_source ? { pk: offer.data_source.pk } : undefined
184
- }
185
- }
186
-
187
- function applyOffers(target: any, source: any, config: PayloadOptimizationConfig) {
188
- const extra = config.offerExtraListingKwargsFields
189
- if (source.basket_offers) {
190
- target.basket_offers = source.basket_offers.map((o: any) => stripOffer(o, extra))
191
- }
192
- if (source.data_source_offers) {
193
- target.data_source_offers = source.data_source_offers.map(
194
- (g: any) => Array.isArray(g) ? g.map((o: any) => stripOffer(o, extra)) : stripOffer(g, extra)
195
- )
196
- }
197
- }
198
-
199
- function stripFacet(facet: any) {
200
- if (!facet) return facet
201
- return {
202
- key: facet.key,
203
- name: facet.name,
204
- search_key: facet.search_key,
205
- widget_type: facet.widget_type,
206
- data: facet.data ? {
207
- choices: facet.data.choices?.map((c: any) => ({
208
- label: c.label, value: c.value, quantity: c.quantity,
209
- selected: c.selected, is_selected: c.is_selected,
210
- children: c.children
211
- })),
212
- key: facet.data.key, name: facet.data.name, order: facet.data.order,
213
- search_key: facet.data.search_key, widget_type: facet.data.widget_type
214
- } : undefined
215
- }
216
- }
217
-
218
- // ─── Precomputed Key Sets ────────────────────────────────────────────────────
219
- // Computed once per config to avoid repeated merge() calls per product.
220
-
221
- interface ListKeySet {
222
- fields: string[]
223
- attrKeys: string[]
224
- kwargsKeys: string[]
225
- extraAttrKeys: string[]
226
- dsFields: string[]
227
- imageLimit: number
228
- hoverCount: number
229
- }
230
-
231
- function buildListKeySet(config: PayloadOptimizationConfig): ListKeySet {
232
- return {
233
- fields: merge(BASE_LIST_PRODUCT_FIELDS, config.listProductExtraFields),
234
- attrKeys: merge(BASE_LIST_PRODUCT_ATTRIBUTES, config.listProductExtraAttributes),
235
- kwargsKeys: merge(BASE_LIST_PRODUCT_ATTRIBUTES_KWARGS, config.listProductExtraAttributesKwargs),
236
- extraAttrKeys: merge(BASE_LIST_PRODUCT_EXTRA_ATTRIBUTES, config.listProductExtraExtraAttributes),
237
- dsFields: merge(BASE_LIST_DATASOURCE_FIELDS, config.listDataSourceExtraFields),
238
- imageLimit: config.listProductImageLimit ?? DEFAULT_LIST_IMAGE_LIMIT,
239
- hoverCount: config.listHoverImageCount ?? 2
240
- }
241
- }
242
-
243
- function buildWidgetKeySet(config: PayloadOptimizationConfig): ListKeySet {
244
- const listKeys = buildListKeySet(config)
245
- return {
246
- ...listKeys,
247
- imageLimit: config.widgetProductImageLimit ?? listKeys.imageLimit,
248
- hoverCount: config.widgetHoverImageCount ?? listKeys.hoverCount
249
- }
250
- }
251
-
252
- // ─── Shared Product Stripping ────────────────────────────────────────────────
253
-
254
- function stripProduct(
255
- product: any,
256
- keys: ListKeySet,
257
- config: PayloadOptimizationConfig,
258
- opts?: { includeHoverImages?: boolean, includeWidgetVariants?: boolean }
259
- ): any {
260
- if (!product) return product
261
-
262
- const allImages = product.productimage_set ?? []
263
- const stripped: any = pickKeys(product, keys.fields)
264
-
265
- stripped.productimage_set = stripImages(allImages, keys.imageLimit)
266
-
267
- if (opts?.includeHoverImages && keys.hoverCount > 0 && allImages.length > keys.imageLimit) {
268
- stripped._hoverImageUrls = allImages
269
- .slice(keys.imageLimit, keys.imageLimit + keys.hoverCount)
270
- .map((img: any) => img.image)
271
- }
272
-
273
- stripped.attributes = pickKeys(product.attributes, keys.attrKeys)
274
- stripped.attributes_kwargs = pickAttributesKwargs(product.attributes_kwargs, keys.kwargsKeys)
275
- stripped.extra_attributes = pickKeys(product.extra_attributes, keys.extraAttrKeys)
276
-
277
- if (product.data_source) {
278
- stripped.data_source = pickKeys(product.data_source, keys.dsFields)
279
- }
280
-
281
- applyOffers(stripped, product, config)
282
-
283
- // Widget variants: strip to minimal
284
- if (opts?.includeWidgetVariants && product.extra_data?.variants) {
285
- stripped.extra_data = {
286
- variants: product.extra_data.variants.map((variant: any) => ({
287
- attribute_key: variant.attribute_key,
288
- options: variant.options?.map((option: any) => {
289
- if (!option.product) return option
290
- const op = option.product
291
- return {
292
- ...option,
293
- product: {
294
- pk: op.pk,
295
- absolute_url: op.absolute_url,
296
- price: op.price,
297
- retail_price: op.retail_price,
298
- stock: op.stock,
299
- in_stock: op.in_stock != null ? op.in_stock : (op.stock ?? 0) > 0,
300
- productimage_set: stripImages(op.productimage_set, 1),
301
- attributes: pickKeys(op.attributes, keys.attrKeys),
302
- price_extra_field: op.price_extra_field
303
- }
304
- }
305
- })
306
- }))
307
- }
308
- }
309
-
310
- return stripped
311
- }
312
-
313
- // ─── List / Category / Special Page ──────────────────────────────────────────
314
-
315
- export function optimizeCategoryResponse(data: GetCategoryResponse, config: PayloadOptimizationConfig): GetCategoryResponse {
316
- if (!data) return data
317
-
318
- // Shallow copy to avoid mutating cached data
319
- const result = { ...data }
320
- const keys = buildListKeySet(config)
321
-
322
- if (result.products?.length) {
323
- result.products = result.products.map(
324
- (p) => stripProduct(p, keys, config, { includeHoverImages: true }) as Product
325
- )
326
- }
327
-
328
- if (result.facets?.length) {
329
- result.facets = result.facets.map((f) => stripFacet(f) as typeof f)
330
- }
331
-
332
- if (result.category) {
333
- const cat = result.category as any
334
- const catFields = merge(BASE_CATEGORY_FIELDS, config.categoryExtraFields)
335
- const strippedCat: any = pickKeys(cat, catFields)
336
- strippedCat.attributes = config.categoryExtraAttributes?.length
337
- ? pickKeys(cat.attributes, merge(BASE_CATEGORY_ATTRIBUTES, config.categoryExtraAttributes))
338
- : cat.attributes
339
- result.category = strippedCat
340
- }
341
-
342
- if ((result as any).special_page) {
343
- const spFields = merge(BASE_SPECIAL_PAGE_FIELDS, config.specialPageExtraFields)
344
- ;(result as any).special_page = pickKeys((result as any).special_page, spFields)
345
- }
346
-
347
- return result
348
- }
349
-
350
- // ─── Product Detail Page ─────────────────────────────────────────────────────
351
-
352
- export function optimizeProductResponse(data: ProductResult, config: PayloadOptimizationConfig): ProductResult {
353
- if (!data) return data
354
-
355
- // Shallow copy to avoid mutating cached data
356
- const result = { ...data } as any
357
- const variantImageLimit = config.pdpVariantImageLimit ?? DEFAULT_PDP_VARIANT_IMAGE_LIMIT
358
-
359
- // Precompute key sets once
360
- const pdpFields = merge(BASE_PDP_PRODUCT_FIELDS, config.pdpProductExtraFields)
361
- const attrKeys = merge(BASE_PDP_PRODUCT_ATTRIBUTES, config.pdpProductExtraAttributes)
362
- const kwargsKeys = merge(BASE_PDP_PRODUCT_ATTRIBUTES_KWARGS, config.pdpProductExtraAttributesKwargs)
363
- const extraAttrKeys = merge(BASE_PDP_PRODUCT_EXTRA_ATTRIBUTES, config.pdpProductExtraExtraAttributes)
364
- const dsFields = merge(BASE_PDP_DATASOURCE_FIELDS, config.pdpDataSourceExtraFields)
365
- const varAttrKeys = merge(BASE_PDP_VARIANT_ATTRIBUTES, config.pdpVariantExtraAttributes)
366
- const varKwargsKeys = config.pdpVariantExtraAttributesKwargs?.length
367
- ? merge(BASE_PDP_VARIANT_ATTRIBUTES_KWARGS, config.pdpVariantExtraAttributesKwargs)
368
- : undefined
369
-
370
- if (data.product) {
371
- const p = data.product as any
372
- const stripped: any = pickKeys(p, pdpFields)
373
-
374
- // Strip specialimage_set from images
375
- if (stripped.productimage_set) {
376
- stripped.productimage_set = stripped.productimage_set.map((img: any) => ({
377
- pk: img.pk, image: img.image, order: img.order, status: img.status
378
- }))
379
- }
380
-
381
- stripped.attributes = pickKeys(p.attributes, attrKeys)
382
- stripped.attributes_kwargs = pickKeys(p.attributes_kwargs, kwargsKeys)
383
- stripped.extra_attributes = pickKeys(p.extra_attributes, extraAttrKeys)
384
-
385
- if (p.data_source) {
386
- stripped.data_source = pickKeys(p.data_source, dsFields)
387
- }
388
-
389
- applyOffers(stripped, p, config)
390
- result.product = stripped
391
- }
392
-
393
- if (result.offers) {
394
- result.offers = result.offers.map((offer: any) => ({
395
- pk: offer.pk,
396
- name: offer.name,
397
- in_stock: offer.in_stock,
398
- price: offer.price,
399
- retail_price: offer.retail_price,
400
- absolute_url: offer.absolute_url,
401
- attributes: pickKeys(offer.attributes, attrKeys),
402
- attributes_kwargs: pickKeys(offer.attributes_kwargs, kwargsKeys),
403
- extra_attributes: pickKeys(offer.extra_attributes, extraAttrKeys),
404
- data_source: offer.data_source ? pickKeys(offer.data_source, dsFields) : undefined
405
- }))
406
- }
407
-
408
- if (data.variants) {
409
- result.variants = data.variants.map((variant: any) => ({
410
- ...variant,
411
- options: variant.options?.map((option: any) => {
412
- if (!option.product) return option
413
- const op = option.product
414
- return {
415
- ...option,
416
- product: {
417
- pk: op.pk,
418
- sku: op.sku,
419
- name: op.name,
420
- in_stock: op.in_stock,
421
- stock: op.stock,
422
- price: op.price,
423
- retail_price: op.retail_price,
424
- absolute_url: op.absolute_url,
425
- productimage_set: op.productimage_set?.slice(0, variantImageLimit),
426
- price_extra_field: op.price_extra_field,
427
- attributes: pickKeys(op.attributes, varAttrKeys),
428
- attributes_kwargs: varKwargsKeys ? pickKeys(op.attributes_kwargs, varKwargsKeys) : op.attributes_kwargs
429
- }
430
- }
431
- })
432
- }))
433
- }
434
-
435
- if (data.group_products) {
436
- result.group_products = data.group_products.map((gp: any) => ({
437
- pk: gp.pk, name: gp.name, sku: gp.sku,
438
- in_stock: gp.in_stock, price: gp.price, retail_price: gp.retail_price,
439
- productimage_set: gp.productimage_set
440
- }))
441
- }
442
-
443
- return result as ProductResult
444
- }
445
-
446
- // ─── Widget ──────────────────────────────────────────────────────────────────
447
-
448
- export function optimizeWidgetResponse(data: any, config: PayloadOptimizationConfig): any {
449
- if (!data) return data
450
-
451
- const result = { ...data }
452
- const keys = buildWidgetKeySet(config)
453
-
454
- if (result.products?.length && result.products[0]?.pk) {
455
- result.products = result.products.map(
456
- (p: any) => stripProduct(p, keys, config, { includeHoverImages: true, includeWidgetVariants: true })
457
- )
458
- }
459
-
460
- if (result.attributes) {
461
- result.attributes = { ...result.attributes }
462
- for (const key of Object.keys(result.attributes)) {
463
- const val = result.attributes[key]
464
- if (Array.isArray(val) && val.length > 0 && val[0]?.pk && val[0]?.productimage_set) {
465
- result.attributes[key] = val.map(
466
- (p: any) => stripProduct(p, keys, config, { includeHoverImages: true, includeWidgetVariants: true })
467
- )
468
- }
469
- if (val?.value && Array.isArray(val.value) && val.value.length > 0 && val.value[0]?.pk && val.value[0]?.productimage_set) {
470
- result.attributes[key] = {
471
- ...val,
472
- value: val.value.map(
473
- (p: any) => stripProduct(p, keys, config, { includeHoverImages: true, includeWidgetVariants: true })
474
- )
475
- }
476
- }
477
- }
478
- }
479
-
480
- return result
481
- }