@akinon/next 1.115.0-snapshot-ZERO-3855-20251209090338 → 1.115.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,10 +1,14 @@
1
1
  # @akinon/next
2
2
 
3
- ## 1.115.0-snapshot-ZERO-3855-20251209090338
3
+ ## 1.115.0
4
4
 
5
5
  ### Minor Changes
6
6
 
7
- - 591e345e: ZERO-3855: Enhance credit card payment handling in checkout middlewares
7
+ - b59bed4d: ZERO-3878: Reintegrated similar products and removed localStorage checks and implemented props based showing of buttons
8
+ - b5f9d75c: ZERO-3873: Refactor payment redirection middlewares to properly forward all set-cookie headers from upstream responses
9
+ - cbcfdf4d: ZERO-3859: Haso payment gateway implmeneted
10
+ - 71882722: ZERO-3897: Add compress option to default Next.js configuration
11
+ - 7056203a: ZERO-3875: removed shared_tags through stripTags function for unused keys
8
12
 
9
13
  ## 1.114.0
10
14
 
@@ -0,0 +1,75 @@
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
+ }
@@ -0,0 +1,53 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getProductData } from '@akinon/next/data/server';
3
+
4
+ export async function GET(request: NextRequest) {
5
+ try {
6
+ const { searchParams } = new URL(request.url);
7
+ const pksParam = searchParams.get('pks');
8
+
9
+ if (!pksParam) {
10
+ return NextResponse.json(
11
+ { error: 'pks parameter required' },
12
+ { status: 400 }
13
+ );
14
+ }
15
+
16
+ const pks = pksParam.split(',').map(Number).filter(Boolean);
17
+
18
+ if (pks.length === 0) {
19
+ return NextResponse.json({ error: 'Invalid pks' }, { status: 400 });
20
+ }
21
+
22
+ const results = await Promise.all(
23
+ pks.map(async (pk) => {
24
+ try {
25
+ const { breadcrumbData } = await getProductData({ pk });
26
+
27
+ const categoryIds =
28
+ breadcrumbData
29
+ ?.map((item: any) => item.extra_context?.attributes?.category_id)
30
+ .filter(Boolean) || [];
31
+
32
+ return { pk, categoryIds };
33
+ } catch (error) {
34
+ console.error(`Error fetching product ${pk}:`, error);
35
+ return { pk, categoryIds: [] };
36
+ }
37
+ })
38
+ );
39
+
40
+ const mapping: Record<string, number[]> = {};
41
+ results.forEach(({ pk, categoryIds }) => {
42
+ mapping[String(pk)] = categoryIds;
43
+ });
44
+
45
+ return NextResponse.json(mapping);
46
+ } catch (error) {
47
+ console.error('Error in product-categories API:', error);
48
+ return NextResponse.json(
49
+ { error: 'Internal server error' },
50
+ { status: 500 }
51
+ );
52
+ }
53
+ }
@@ -0,0 +1,63 @@
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
+ }
@@ -0,0 +1,111 @@
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
+ }
@@ -24,7 +24,9 @@ enum Plugin {
24
24
  FlowPayment = 'pz-flow-payment',
25
25
  VirtualTryOn = 'pz-virtual-try-on',
26
26
  Hepsipay = 'pz-hepsipay',
27
- MasterpassRest = 'pz-masterpass-rest'
27
+ MasterpassRest = 'pz-masterpass-rest',
28
+ SimilarProducts = 'pz-similar-products',
29
+ Haso = 'pz-haso'
28
30
  }
29
31
 
30
32
  export enum Component {
@@ -55,7 +57,15 @@ export enum Component {
55
57
  IyzicoSavedCard = 'IyzicoSavedCardOption',
56
58
  Hepsipay = 'Hepsipay',
57
59
  FlowPayment = 'FlowPayment',
58
- MasterpassRest = 'MasterpassRestOption'
60
+ MasterpassRest = 'MasterpassRestOption',
61
+ SimilarProductsModal = 'SimilarProductsModal',
62
+ SimilarProductsFilterSidebar = 'SimilarProductsFilterSidebar',
63
+ SimilarProductsResultsGrid = 'SimilarProductsResultsGrid',
64
+ SimilarProductsPlugin = 'SimilarProductsPlugin',
65
+ ProductImageSearchFeature = 'ProductImageSearchFeature',
66
+ ImageSearchButton = 'ImageSearchButton',
67
+ HeaderImageSearchFeature = 'HeaderImageSearchFeature',
68
+ HasoPaymentGateway = 'HasoPaymentGateway'
59
69
  }
60
70
 
61
71
  const PluginComponents = new Map([
@@ -88,6 +98,18 @@ const PluginComponents = new Map([
88
98
  [Component.AkifastQuickLoginButton, Component.AkifastCheckoutButton]
89
99
  ],
90
100
  [Plugin.MultiBasket, [Component.MultiBasket]],
101
+ [
102
+ Plugin.SimilarProducts,
103
+ [
104
+ Component.SimilarProductsModal,
105
+ Component.SimilarProductsFilterSidebar,
106
+ Component.SimilarProductsResultsGrid,
107
+ Component.SimilarProductsPlugin,
108
+ Component.ProductImageSearchFeature,
109
+ Component.ImageSearchButton,
110
+ Component.HeaderImageSearchFeature
111
+ ]
112
+ ],
91
113
  [Plugin.SavedCard, [Component.SavedCard, Component.IyzicoSavedCard]],
92
114
  [Plugin.SavedCard, [Component.SavedCard]],
93
115
  [Plugin.FlowPayment, [Component.FlowPayment]],
@@ -97,7 +119,8 @@ const PluginComponents = new Map([
97
119
  ],
98
120
  [Plugin.Hepsipay, [Component.Hepsipay]],
99
121
  [Plugin.Hepsipay, [Component.Hepsipay]],
100
- [Plugin.MasterpassRest, [Component.MasterpassRest]]
122
+ [Plugin.MasterpassRest, [Component.MasterpassRest]],
123
+ [Plugin.Haso, [Component.HasoPaymentGateway]]
101
124
  ]);
102
125
 
103
126
  const getPlugin = (component: Component) => {
@@ -170,6 +193,10 @@ export default function PluginModule({
170
193
  promise = import(`${'@akinon/pz-virtual-try-on'}`);
171
194
  } else if (plugin === Plugin.MasterpassRest) {
172
195
  promise = import(`${'@akinon/pz-masterpass-rest'}`);
196
+ } else if (plugin === Plugin.SimilarProducts) {
197
+ promise = import(`${'@akinon/pz-similar-products'}`);
198
+ } else if (plugin === Plugin.Haso) {
199
+ promise = import(`${'@akinon/pz-haso'}`);
173
200
  }
174
201
  } catch (error) {
175
202
  logger.error(error);
@@ -463,6 +463,14 @@ CacheHandler.onCreation(async () => {
463
463
  set: async (key, value, context) => {
464
464
  const vKey = versionKey(key);
465
465
 
466
+ const stripTags = (val) => {
467
+ if (val && typeof val === 'object') {
468
+ const { tags, ...rest } = val;
469
+ return { ...rest, tags: [] };
470
+ }
471
+ return val;
472
+ };
473
+
466
474
  let compressedValue;
467
475
  let shouldUseCompressed = false;
468
476
 
@@ -482,12 +490,12 @@ CacheHandler.onCreation(async () => {
482
490
 
483
491
  if (shouldUseCompressed) {
484
492
  try {
485
- await redisHandler.set(vKey, compressedValue, context);
493
+ await redisHandler.set(vKey, stripTags(compressedValue), context);
486
494
 
487
495
  redisSetResult = { status: 'fulfilled' };
488
496
  } catch (compressionError) {
489
497
  try {
490
- await redisHandler.set(vKey, value, context);
498
+ await redisHandler.set(vKey, stripTags(value), context);
491
499
 
492
500
  redisSetResult = { status: 'fulfilled' };
493
501
  } catch (fallbackError) {
@@ -496,7 +504,7 @@ CacheHandler.onCreation(async () => {
496
504
  }
497
505
  } else {
498
506
  try {
499
- await redisHandler.set(vKey, value, context);
507
+ await redisHandler.set(vKey, stripTags(value), context);
500
508
  redisSetResult = { status: 'fulfilled' };
501
509
  } catch (error) {
502
510
  redisSetResult = { status: 'rejected', reason: error };
@@ -510,8 +518,8 @@ CacheHandler.onCreation(async () => {
510
518
  localSetResult = { status: 'fulfilled' };
511
519
  } catch (error) {
512
520
  localSetResult = { status: 'rejected', reason: error };
513
- return localSetResult;
514
521
  }
522
+ return localSetResult;
515
523
  },
516
524
  delete: async (key, context) => {
517
525
  const vKey = versionKey(key);
@@ -64,7 +64,7 @@ const withCompleteGpay =
64
64
  );
65
65
  }
66
66
 
67
- const request = await fetch(requestUrl, {
67
+ const fetchResponse = await fetch(requestUrl, {
68
68
  method: 'POST',
69
69
  headers: requestHeaders,
70
70
  body
@@ -72,14 +72,14 @@ const withCompleteGpay =
72
72
 
73
73
  logger.info('Complete GPay payment request', {
74
74
  requestUrl,
75
- status: request.status,
75
+ status: fetchResponse.status,
76
76
  requestHeaders,
77
77
  ip
78
78
  });
79
79
 
80
- const response = await request.json();
80
+ const responseData = await fetchResponse.json();
81
81
 
82
- const { context_list: contextList, errors } = response;
82
+ const { context_list: contextList, errors } = responseData;
83
83
  const redirectionContext = contextList?.find(
84
84
  (context) => context.page_context?.redirect_url
85
85
  );
@@ -93,18 +93,27 @@ const withCompleteGpay =
93
93
  ip
94
94
  });
95
95
 
96
- return NextResponse.redirect(
96
+ const errorResponse = NextResponse.redirect(
97
97
  `${url.origin}${getUrlPathWithLocale(
98
98
  '/orders/checkout/',
99
99
  req.cookies.get('pz-locale')?.value
100
100
  )}`,
101
- {
102
- status: 303,
103
- headers: {
104
- 'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
105
- }
106
- }
101
+ 303
107
102
  );
103
+
104
+ // Forward set-cookie headers from the upstream response
105
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
106
+ setCookies.forEach((cookie) => {
107
+ errorResponse.headers.append('Set-Cookie', cookie);
108
+ });
109
+
110
+ // Add error cookie
111
+ errorResponse.headers.append(
112
+ 'Set-Cookie',
113
+ `pz-pos-error=${JSON.stringify(errors)}; path=/;`
114
+ );
115
+
116
+ return errorResponse;
108
117
  }
109
118
 
110
119
  logger.info('Order success page context list', {
@@ -119,7 +128,7 @@ const withCompleteGpay =
119
128
  {
120
129
  middleware: 'complete-gpay',
121
130
  requestHeaders,
122
- response: JSON.stringify(response),
131
+ response: JSON.stringify(responseData),
123
132
  ip
124
133
  }
125
134
  );
@@ -147,10 +156,11 @@ const withCompleteGpay =
147
156
  // So we use 303 status code to change the method to GET
148
157
  const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
149
158
 
150
- nextResponse.headers.set(
151
- 'Set-Cookie',
152
- request.headers.get('set-cookie') ?? ''
153
- );
159
+ // Forward all set-cookie headers from the upstream response
160
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
161
+ setCookies.forEach((cookie) => {
162
+ nextResponse.headers.append('Set-Cookie', cookie);
163
+ });
154
164
 
155
165
  return nextResponse;
156
166
  } catch (error) {
@@ -65,7 +65,7 @@ const withCompleteMasterpass =
65
65
  );
66
66
  }
67
67
 
68
- const request = await fetch(requestUrl, {
68
+ const fetchResponse = await fetch(requestUrl, {
69
69
  method: 'POST',
70
70
  headers: requestHeaders,
71
71
  body
@@ -73,14 +73,14 @@ const withCompleteMasterpass =
73
73
 
74
74
  logger.info('Complete Masterpass payment request', {
75
75
  requestUrl,
76
- status: request.status,
76
+ status: fetchResponse.status,
77
77
  requestHeaders,
78
78
  ip
79
79
  });
80
80
 
81
- const response = await request.json();
81
+ const responseData = await fetchResponse.json();
82
82
 
83
- const { context_list: contextList, errors } = response;
83
+ const { context_list: contextList, errors } = responseData;
84
84
  const redirectionContext = contextList?.find(
85
85
  (context) => context.page_context?.redirect_url
86
86
  );
@@ -94,18 +94,27 @@ const withCompleteMasterpass =
94
94
  ip
95
95
  });
96
96
 
97
- return NextResponse.redirect(
97
+ const errorResponse = NextResponse.redirect(
98
98
  `${url.origin}${getUrlPathWithLocale(
99
99
  '/orders/checkout/',
100
100
  req.cookies.get('pz-locale')?.value
101
101
  )}`,
102
- {
103
- status: 303,
104
- headers: {
105
- 'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
106
- }
107
- }
102
+ 303
108
103
  );
104
+
105
+ // Forward set-cookie headers from the upstream response
106
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
107
+ setCookies.forEach((cookie) => {
108
+ errorResponse.headers.append('Set-Cookie', cookie);
109
+ });
110
+
111
+ // Add error cookie
112
+ errorResponse.headers.append(
113
+ 'Set-Cookie',
114
+ `pz-pos-error=${JSON.stringify(errors)}; path=/;`
115
+ );
116
+
117
+ return errorResponse;
109
118
  }
110
119
 
111
120
  logger.info('Order success page context list', {
@@ -120,7 +129,7 @@ const withCompleteMasterpass =
120
129
  {
121
130
  middleware: 'complete-masterpass',
122
131
  requestHeaders,
123
- response: JSON.stringify(response),
132
+ response: JSON.stringify(responseData),
124
133
  ip
125
134
  }
126
135
  );
@@ -148,10 +157,11 @@ const withCompleteMasterpass =
148
157
  // So we use 303 status code to change the method to GET
149
158
  const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
150
159
 
151
- nextResponse.headers.set(
152
- 'Set-Cookie',
153
- request.headers.get('set-cookie') ?? ''
154
- );
160
+ // Forward all set-cookie headers from the upstream response
161
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
162
+ setCookies.forEach((cookie) => {
163
+ nextResponse.headers.append('Set-Cookie', cookie);
164
+ });
155
165
 
156
166
  return nextResponse;
157
167
  } catch (error) {
@@ -61,7 +61,7 @@ const withCompleteWallet =
61
61
  );
62
62
  }
63
63
 
64
- const request = await fetch(requestUrl, {
64
+ const fetchResponse = await fetch(requestUrl, {
65
65
  method: 'POST',
66
66
  headers: requestHeaders,
67
67
  body
@@ -69,14 +69,14 @@ const withCompleteWallet =
69
69
 
70
70
  logger.info('Complete Wallet payment request', {
71
71
  requestUrl,
72
- status: request.status,
72
+ status: fetchResponse.status,
73
73
  requestHeaders,
74
74
  ip
75
75
  });
76
76
 
77
- const response = await request.json();
77
+ const responseData = await fetchResponse.json();
78
78
 
79
- const { context_list: contextList, errors } = response;
79
+ const { context_list: contextList, errors } = responseData;
80
80
  const redirectionContext = contextList?.find(
81
81
  (context) => context.page_context?.redirect_url
82
82
  );
@@ -90,18 +90,27 @@ const withCompleteWallet =
90
90
  ip
91
91
  });
92
92
 
93
- return NextResponse.redirect(
93
+ const errorResponse = NextResponse.redirect(
94
94
  `${url.origin}${getUrlPathWithLocale(
95
95
  '/orders/checkout/',
96
96
  req.cookies.get('pz-locale')?.value
97
97
  )}`,
98
- {
99
- status: 303,
100
- headers: {
101
- 'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
102
- }
103
- }
98
+ 303
104
99
  );
100
+
101
+ // Forward set-cookie headers from the upstream response
102
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
103
+ setCookies.forEach((cookie) => {
104
+ errorResponse.headers.append('Set-Cookie', cookie);
105
+ });
106
+
107
+ // Add error cookie
108
+ errorResponse.headers.append(
109
+ 'Set-Cookie',
110
+ `pz-pos-error=${JSON.stringify(errors)}; path=/;`
111
+ );
112
+
113
+ return errorResponse;
105
114
  }
106
115
 
107
116
  logger.info('Order success page context list', {
@@ -116,7 +125,7 @@ const withCompleteWallet =
116
125
  {
117
126
  middleware: 'complete-wallet',
118
127
  requestHeaders,
119
- response: JSON.stringify(response),
128
+ response: JSON.stringify(responseData),
120
129
  ip
121
130
  }
122
131
  );
@@ -144,10 +153,11 @@ const withCompleteWallet =
144
153
  // So we use 303 status code to change the method to GET
145
154
  const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
146
155
 
147
- nextResponse.headers.set(
148
- 'Set-Cookie',
149
- request.headers.get('set-cookie') ?? ''
150
- );
156
+ // Forward all set-cookie headers from the upstream response
157
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
158
+ setCookies.forEach((cookie) => {
159
+ nextResponse.headers.append('Set-Cookie', cookie);
160
+ });
151
161
 
152
162
  return nextResponse;
153
163
  } catch (error) {
@@ -65,7 +65,7 @@ const withRedirectionPayment =
65
65
  );
66
66
  }
67
67
 
68
- const request = await fetch(requestUrl, {
68
+ const fetchResponse = await fetch(requestUrl, {
69
69
  method: 'POST',
70
70
  headers: requestHeaders,
71
71
  body
@@ -73,14 +73,14 @@ const withRedirectionPayment =
73
73
 
74
74
  logger.info('Complete redirection payment request', {
75
75
  requestUrl,
76
- status: request.status,
76
+ status: fetchResponse.status,
77
77
  requestHeaders,
78
78
  ip
79
79
  });
80
80
 
81
- const response = await request.json();
81
+ const responseData = await fetchResponse.json();
82
82
 
83
- const { context_list: contextList, errors } = response;
83
+ const { context_list: contextList, errors } = responseData;
84
84
  const redirectionContext = contextList?.find(
85
85
  (context) => context.page_context?.redirect_url
86
86
  );
@@ -94,18 +94,27 @@ const withRedirectionPayment =
94
94
  ip
95
95
  });
96
96
 
97
- return NextResponse.redirect(
97
+ const errorResponse = NextResponse.redirect(
98
98
  `${url.origin}${getUrlPathWithLocale(
99
99
  '/orders/checkout/',
100
100
  req.cookies.get('pz-locale')?.value
101
101
  )}`,
102
- {
103
- status: 303,
104
- headers: {
105
- 'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
106
- }
107
- }
102
+ 303
108
103
  );
104
+
105
+ // Forward set-cookie headers from the upstream response
106
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
107
+ setCookies.forEach((cookie) => {
108
+ errorResponse.headers.append('Set-Cookie', cookie);
109
+ });
110
+
111
+ // Add error cookie
112
+ errorResponse.headers.append(
113
+ 'Set-Cookie',
114
+ `pz-pos-error=${JSON.stringify(errors)}; path=/;`
115
+ );
116
+
117
+ return errorResponse;
109
118
  }
110
119
 
111
120
  logger.info('Order success page context list', {
@@ -120,7 +129,7 @@ const withRedirectionPayment =
120
129
  {
121
130
  middleware: 'redirection-payment',
122
131
  requestHeaders,
123
- response: JSON.stringify(response),
132
+ response: JSON.stringify(responseData),
124
133
  ip
125
134
  }
126
135
  );
@@ -148,10 +157,11 @@ const withRedirectionPayment =
148
157
  // So we use 303 status code to change the method to GET
149
158
  const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
150
159
 
151
- nextResponse.headers.set(
152
- 'Set-Cookie',
153
- request.headers.get('set-cookie') ?? ''
154
- );
160
+ // Forward all set-cookie headers from the upstream response
161
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
162
+ setCookies.forEach((cookie) => {
163
+ nextResponse.headers.append('Set-Cookie', cookie);
164
+ });
155
165
 
156
166
  return nextResponse;
157
167
  } catch (error) {
@@ -65,7 +65,7 @@ const withSavedCardRedirection =
65
65
  );
66
66
  }
67
67
 
68
- const request = await fetch(requestUrl, {
68
+ const fetchResponse = await fetch(requestUrl, {
69
69
  method: 'POST',
70
70
  headers: requestHeaders,
71
71
  body
@@ -73,14 +73,14 @@ const withSavedCardRedirection =
73
73
 
74
74
  logger.info('Complete 3D payment request', {
75
75
  requestUrl,
76
- status: request.status,
76
+ status: fetchResponse.status,
77
77
  requestHeaders,
78
78
  ip
79
79
  });
80
80
 
81
- const response = await request.json();
81
+ const responseData = await fetchResponse.json();
82
82
 
83
- const { context_list: contextList, errors } = response;
83
+ const { context_list: contextList, errors } = responseData;
84
84
  const redirectionContext = contextList?.find(
85
85
  (context) => context.page_context?.redirect_url
86
86
  );
@@ -94,18 +94,27 @@ const withSavedCardRedirection =
94
94
  ip
95
95
  });
96
96
 
97
- return NextResponse.redirect(
97
+ const errorResponse = NextResponse.redirect(
98
98
  `${url.origin}${getUrlPathWithLocale(
99
99
  '/orders/checkout/',
100
100
  req.cookies.get('pz-locale')?.value
101
101
  )}`,
102
- {
103
- status: 303,
104
- headers: {
105
- 'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
106
- }
107
- }
102
+ 303
108
103
  );
104
+
105
+ // Forward set-cookie headers from the upstream response
106
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
107
+ setCookies.forEach((cookie) => {
108
+ errorResponse.headers.append('Set-Cookie', cookie);
109
+ });
110
+
111
+ // Add error cookie
112
+ errorResponse.headers.append(
113
+ 'Set-Cookie',
114
+ `pz-pos-error=${JSON.stringify(errors)}; path=/;`
115
+ );
116
+
117
+ return errorResponse;
109
118
  }
110
119
 
111
120
  logger.info('Order success page context list', {
@@ -120,7 +129,7 @@ const withSavedCardRedirection =
120
129
  {
121
130
  middleware: 'saved-card-redirection',
122
131
  requestHeaders,
123
- response: JSON.stringify(response),
132
+ response: JSON.stringify(responseData),
124
133
  ip
125
134
  }
126
135
  );
@@ -148,10 +157,11 @@ const withSavedCardRedirection =
148
157
  // So we use 303 status code to change the method to GET
149
158
  const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
150
159
 
151
- nextResponse.headers.set(
152
- 'Set-Cookie',
153
- request.headers.get('set-cookie') ?? ''
154
- );
160
+ // Forward all set-cookie headers from the upstream response
161
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
162
+ setCookies.forEach((cookie) => {
163
+ nextResponse.headers.append('Set-Cookie', cookie);
164
+ });
155
165
 
156
166
  return nextResponse;
157
167
  } catch (error) {
@@ -64,7 +64,7 @@ const withThreeDRedirection =
64
64
  );
65
65
  }
66
66
 
67
- const request = await fetch(requestUrl, {
67
+ const fetchResponse = await fetch(requestUrl, {
68
68
  method: 'POST',
69
69
  headers: requestHeaders,
70
70
  body
@@ -72,14 +72,14 @@ const withThreeDRedirection =
72
72
 
73
73
  logger.info('Complete 3D payment request', {
74
74
  requestUrl,
75
- status: request.status,
75
+ status: fetchResponse.status,
76
76
  requestHeaders,
77
77
  ip
78
78
  });
79
79
 
80
- const response = await request.json();
80
+ const responseData = await fetchResponse.json();
81
81
 
82
- const { context_list: contextList, errors } = response;
82
+ const { context_list: contextList, errors } = responseData;
83
83
  const redirectionContext = contextList?.find(
84
84
  (context) => context.page_context?.redirect_url
85
85
  );
@@ -93,18 +93,27 @@ const withThreeDRedirection =
93
93
  ip
94
94
  });
95
95
 
96
- return NextResponse.redirect(
96
+ const errorResponse = NextResponse.redirect(
97
97
  `${url.origin}${getUrlPathWithLocale(
98
98
  '/orders/checkout/',
99
99
  req.cookies.get('pz-locale')?.value
100
100
  )}`,
101
- {
102
- status: 303,
103
- headers: {
104
- 'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
105
- }
106
- }
101
+ 303
107
102
  );
103
+
104
+ // Forward set-cookie headers from the upstream response
105
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
106
+ setCookies.forEach((cookie) => {
107
+ errorResponse.headers.append('Set-Cookie', cookie);
108
+ });
109
+
110
+ // Add error cookie
111
+ errorResponse.headers.append(
112
+ 'Set-Cookie',
113
+ `pz-pos-error=${JSON.stringify(errors)}; path=/;`
114
+ );
115
+
116
+ return errorResponse;
108
117
  }
109
118
 
110
119
  logger.info('Order success page context list', {
@@ -119,7 +128,7 @@ const withThreeDRedirection =
119
128
  {
120
129
  middleware: 'three-d-redirection',
121
130
  requestHeaders,
122
- response: JSON.stringify(response),
131
+ response: JSON.stringify(responseData),
123
132
  ip
124
133
  }
125
134
  );
@@ -147,10 +156,11 @@ const withThreeDRedirection =
147
156
  // So we use 303 status code to change the method to GET
148
157
  const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
149
158
 
150
- nextResponse.headers.set(
151
- 'Set-Cookie',
152
- request.headers.get('set-cookie') ?? ''
153
- );
159
+ // Forward all set-cookie headers from the upstream response
160
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
161
+ setCookies.forEach((cookie) => {
162
+ nextResponse.headers.append('Set-Cookie', cookie);
163
+ });
154
164
 
155
165
  return nextResponse;
156
166
  } catch (error) {
@@ -85,7 +85,7 @@ const withWalletCompleteRedirection =
85
85
  );
86
86
  }
87
87
 
88
- const request = await fetch(requestUrl, {
88
+ const fetchResponse = await fetch(requestUrl, {
89
89
  method: 'POST',
90
90
  headers: requestHeaders,
91
91
  body
@@ -93,14 +93,14 @@ const withWalletCompleteRedirection =
93
93
 
94
94
  logger.info('Complete wallet payment request', {
95
95
  requestUrl,
96
- status: request.status,
96
+ status: fetchResponse.status,
97
97
  requestHeaders,
98
98
  ip
99
99
  });
100
100
 
101
- const response = await request.json();
101
+ const responseData = await fetchResponse.json();
102
102
 
103
- const { context_list: contextList, errors } = response;
103
+ const { context_list: contextList, errors } = responseData;
104
104
  const redirectionContext = contextList?.find(
105
105
  (context) => context.page_context?.redirect_url
106
106
  );
@@ -114,18 +114,27 @@ const withWalletCompleteRedirection =
114
114
  ip
115
115
  });
116
116
 
117
- return NextResponse.redirect(
117
+ const errorResponse = NextResponse.redirect(
118
118
  `${url.origin}${getUrlPathWithLocale(
119
119
  '/orders/checkout/',
120
120
  req.cookies.get('pz-locale')?.value
121
121
  )}`,
122
- {
123
- status: 303,
124
- headers: {
125
- 'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
126
- }
127
- }
122
+ 303
128
123
  );
124
+
125
+ // Forward set-cookie headers from the upstream response
126
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
127
+ setCookies.forEach((cookie) => {
128
+ errorResponse.headers.append('Set-Cookie', cookie);
129
+ });
130
+
131
+ // Add error cookie
132
+ errorResponse.headers.append(
133
+ 'Set-Cookie',
134
+ `pz-pos-error=${JSON.stringify(errors)}; path=/;`
135
+ );
136
+
137
+ return errorResponse;
129
138
  }
130
139
 
131
140
  logger.info('Order success page context list', {
@@ -140,7 +149,7 @@ const withWalletCompleteRedirection =
140
149
  {
141
150
  middleware: 'wallet-complete-redirection',
142
151
  requestHeaders,
143
- response: JSON.stringify(response),
152
+ response: JSON.stringify(responseData),
144
153
  ip
145
154
  }
146
155
  );
@@ -168,10 +177,11 @@ const withWalletCompleteRedirection =
168
177
  // So we use 303 status code to change the method to GET
169
178
  const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
170
179
 
171
- nextResponse.headers.set(
172
- 'Set-Cookie',
173
- request.headers.get('set-cookie') ?? ''
174
- );
180
+ // Forward all set-cookie headers from the upstream response
181
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
182
+ setCookies.forEach((cookie) => {
183
+ nextResponse.headers.append('Set-Cookie', cookie);
184
+ });
175
185
 
176
186
  return nextResponse;
177
187
  } catch (error) {
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.115.0-snapshot-ZERO-3855-20251209090338",
4
+ "version": "1.115.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.115.0-snapshot-ZERO-3855-20251209090338",
38
+ "@akinon/eslint-plugin-projectzero": "1.115.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.js CHANGED
@@ -20,5 +20,7 @@ module.exports = [
20
20
  'pz-hepsipay',
21
21
  'pz-flow-payment',
22
22
  'pz-virtual-try-on',
23
- 'pz-masterpass-rest'
23
+ 'pz-masterpass-rest',
24
+ 'pz-similar-products',
25
+ 'pz-haso'
24
26
  ];
@@ -204,25 +204,15 @@ export const contextListMiddleware: Middleware = ({
204
204
  (ctx) => ctx.page_name === 'DeliveryOptionSelectionPage'
205
205
  )
206
206
  ) {
207
- const isCreditCardPayment =
208
- preOrder?.payment_option?.payment_type === 'credit_card' ||
209
- preOrder?.payment_option?.payment_type === 'masterpass';
210
-
211
207
  if (context.page_context.card_type) {
212
208
  dispatch(setCardType(context.page_context.card_type));
213
- } else if (isCreditCardPayment) {
214
- dispatch(setCardType(null));
215
209
  }
216
210
 
217
211
  if (
218
212
  context.page_context.installments &&
219
213
  preOrder?.payment_option?.payment_type !== 'masterpass_rest'
220
214
  ) {
221
- if (!isCreditCardPayment || context.page_context.card_type) {
222
- dispatch(
223
- setInstallmentOptions(context.page_context.installments)
224
- );
225
- }
215
+ dispatch(setInstallmentOptions(context.page_context.installments));
226
216
  }
227
217
  }
228
218
 
@@ -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/with-pz-config.js CHANGED
@@ -9,6 +9,7 @@ const defaultConfig = {
9
9
  skipTrailingSlashRedirect: true,
10
10
  poweredByHeader: false,
11
11
  cacheMaxMemorySize: 0,
12
+ compress: false,
12
13
  env: {
13
14
  NEXT_PUBLIC_SENTRY_DSN: process.env.SENTRY_DSN
14
15
  },