@akinon/next 2.0.0-beta.2 → 2.0.0-beta.20

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.
Files changed (189) hide show
  1. package/.eslintrc.js +12 -0
  2. package/CHANGELOG.md +377 -7
  3. package/__tests__/next-config.test.ts +83 -0
  4. package/__tests__/tsconfig.json +23 -0
  5. package/api/auth.ts +133 -44
  6. package/api/barcode-search.ts +59 -0
  7. package/api/cache.ts +41 -5
  8. package/api/client.ts +21 -4
  9. package/api/form.ts +85 -0
  10. package/api/image-proxy.ts +75 -0
  11. package/api/product-categories.ts +53 -0
  12. package/api/similar-product-list.ts +63 -0
  13. package/api/similar-products.ts +111 -0
  14. package/api/virtual-try-on.ts +382 -0
  15. package/assets/styles/index.scss +84 -0
  16. package/babel.config.js +6 -0
  17. package/bin/pz-generate-routes.js +115 -0
  18. package/bin/pz-prebuild.js +1 -0
  19. package/bin/pz-predev.js +1 -0
  20. package/bin/pz-run-tests.js +99 -0
  21. package/bin/run-prebuild-tests.js +46 -0
  22. package/components/accordion.tsx +20 -5
  23. package/components/button.tsx +51 -36
  24. package/components/client-root.tsx +138 -2
  25. package/components/file-input.tsx +65 -3
  26. package/components/index.ts +1 -0
  27. package/components/input.tsx +1 -1
  28. package/components/link.tsx +46 -16
  29. package/components/logger-popup.tsx +213 -0
  30. package/components/modal.tsx +32 -16
  31. package/components/plugin-module.tsx +62 -3
  32. package/components/price.tsx +2 -2
  33. package/components/select.tsx +1 -1
  34. package/components/selected-payment-option-view.tsx +21 -0
  35. package/components/theme-editor/blocks/accordion-block.tsx +136 -0
  36. package/components/theme-editor/blocks/block-renderer-registry.tsx +77 -0
  37. package/components/theme-editor/blocks/button-block.tsx +593 -0
  38. package/components/theme-editor/blocks/counter-block.tsx +348 -0
  39. package/components/theme-editor/blocks/divider-block.tsx +20 -0
  40. package/components/theme-editor/blocks/embed-block.tsx +208 -0
  41. package/components/theme-editor/blocks/group-block.tsx +116 -0
  42. package/components/theme-editor/blocks/hotspot-block.tsx +147 -0
  43. package/components/theme-editor/blocks/icon-block.tsx +230 -0
  44. package/components/theme-editor/blocks/image-block.tsx +137 -0
  45. package/components/theme-editor/blocks/image-gallery-block.tsx +269 -0
  46. package/components/theme-editor/blocks/input-block.tsx +123 -0
  47. package/components/theme-editor/blocks/link-block.tsx +216 -0
  48. package/components/theme-editor/blocks/lottie-block.tsx +325 -0
  49. package/components/theme-editor/blocks/map-block.tsx +89 -0
  50. package/components/theme-editor/blocks/slider-block.tsx +595 -0
  51. package/components/theme-editor/blocks/tab-block.tsx +10 -0
  52. package/components/theme-editor/blocks/text-block.tsx +52 -0
  53. package/components/theme-editor/blocks/video-block.tsx +122 -0
  54. package/components/theme-editor/components/action-toolbar.tsx +305 -0
  55. package/components/theme-editor/components/designer-overlay.tsx +74 -0
  56. package/components/theme-editor/components/with-designer-features.tsx +142 -0
  57. package/components/theme-editor/dynamic-font-loader.tsx +79 -0
  58. package/components/theme-editor/hooks/use-designer-features.tsx +100 -0
  59. package/components/theme-editor/hooks/use-external-designer.tsx +95 -0
  60. package/components/theme-editor/hooks/use-native-widget-data.ts +188 -0
  61. package/components/theme-editor/hooks/use-visibility-context.ts +27 -0
  62. package/components/theme-editor/placeholder-registry.ts +31 -0
  63. package/components/theme-editor/sections/before-after-section.tsx +245 -0
  64. package/components/theme-editor/sections/contact-form-section.tsx +563 -0
  65. package/components/theme-editor/sections/countdown-campaign-banner-section.tsx +433 -0
  66. package/components/theme-editor/sections/coupon-banner-section.tsx +710 -0
  67. package/components/theme-editor/sections/divider-section.tsx +62 -0
  68. package/components/theme-editor/sections/featured-product-spotlight-section.tsx +507 -0
  69. package/components/theme-editor/sections/find-in-store-section.tsx +1995 -0
  70. package/components/theme-editor/sections/hover-showcase-section.tsx +326 -0
  71. package/components/theme-editor/sections/image-hotspot-section.tsx +142 -0
  72. package/components/theme-editor/sections/installment-options-section.tsx +1065 -0
  73. package/components/theme-editor/sections/notification-banner-section.tsx +173 -0
  74. package/components/theme-editor/sections/order-tracking-lookup-section.tsx +1379 -0
  75. package/components/theme-editor/sections/posts-slider-section.tsx +472 -0
  76. package/components/theme-editor/sections/pre-order-launch-banner-section.tsx +663 -0
  77. package/components/theme-editor/sections/section-renderer-registry.tsx +89 -0
  78. package/components/theme-editor/sections/section-wrapper.tsx +135 -0
  79. package/components/theme-editor/sections/shipping-threshold-progress-section.tsx +586 -0
  80. package/components/theme-editor/sections/stats-counter-section.tsx +486 -0
  81. package/components/theme-editor/sections/tabs-section.tsx +578 -0
  82. package/components/theme-editor/theme-block.tsx +102 -0
  83. package/components/theme-editor/theme-placeholder-client.tsx +218 -0
  84. package/components/theme-editor/theme-placeholder-wrapper.tsx +732 -0
  85. package/components/theme-editor/theme-placeholder.tsx +288 -0
  86. package/components/theme-editor/theme-section.tsx +1224 -0
  87. package/components/theme-editor/theme-settings-context.tsx +13 -0
  88. package/components/theme-editor/utils/index.ts +792 -0
  89. package/components/theme-editor/utils/iterator-utils.ts +234 -0
  90. package/components/theme-editor/utils/publish-window.ts +86 -0
  91. package/components/theme-editor/utils/visibility-rules.ts +188 -0
  92. package/data/client/account.ts +17 -2
  93. package/data/client/api.ts +2 -0
  94. package/data/client/basket.ts +66 -5
  95. package/data/client/checkout.ts +391 -99
  96. package/data/client/misc.ts +38 -2
  97. package/data/client/product.ts +19 -2
  98. package/data/client/user.ts +16 -8
  99. package/data/server/category.ts +11 -9
  100. package/data/server/flatpage.ts +11 -4
  101. package/data/server/form.ts +15 -4
  102. package/data/server/landingpage.ts +11 -4
  103. package/data/server/list.ts +5 -4
  104. package/data/server/menu.ts +11 -3
  105. package/data/server/product.ts +111 -55
  106. package/data/server/seo.ts +14 -4
  107. package/data/server/special-page.ts +5 -4
  108. package/data/server/widget.ts +90 -5
  109. package/data/urls.ts +16 -5
  110. package/hocs/client/with-segment-defaults.tsx +2 -2
  111. package/hocs/server/with-segment-defaults.tsx +65 -20
  112. package/hooks/index.ts +4 -0
  113. package/hooks/use-localization.ts +24 -10
  114. package/hooks/use-logger-context.tsx +114 -0
  115. package/hooks/use-logger.ts +92 -0
  116. package/hooks/use-loyalty-availability.ts +21 -0
  117. package/hooks/use-payment-options.ts +2 -1
  118. package/hooks/use-pz-params.ts +37 -0
  119. package/hooks/use-router.ts +51 -14
  120. package/hooks/use-sentry-uncaught-errors.ts +24 -0
  121. package/instrumentation/index.ts +10 -1
  122. package/instrumentation/node.ts +2 -20
  123. package/jest.config.js +25 -0
  124. package/lib/cache-handler.mjs +534 -16
  125. package/lib/cache.ts +272 -37
  126. package/localization/index.ts +2 -1
  127. package/localization/provider.tsx +2 -5
  128. package/middlewares/bfcache-headers.ts +18 -0
  129. package/middlewares/checkout-provider.ts +1 -1
  130. package/middlewares/complete-gpay.ts +32 -26
  131. package/middlewares/complete-masterpass.ts +33 -26
  132. package/middlewares/complete-wallet.ts +182 -0
  133. package/middlewares/default.ts +360 -215
  134. package/middlewares/index.ts +10 -2
  135. package/middlewares/locale.ts +34 -11
  136. package/middlewares/masterpass-rest-callback.ts +230 -0
  137. package/middlewares/oauth-login.ts +200 -57
  138. package/middlewares/pretty-url.ts +21 -8
  139. package/middlewares/redirection-payment.ts +32 -26
  140. package/middlewares/saved-card-redirection.ts +33 -26
  141. package/middlewares/three-d-redirection.ts +32 -26
  142. package/middlewares/url-redirection.ts +11 -1
  143. package/middlewares/wallet-complete-redirection.ts +206 -0
  144. package/package.json +25 -10
  145. package/plugins.d.ts +19 -4
  146. package/plugins.js +10 -1
  147. package/redux/actions.ts +47 -0
  148. package/redux/middlewares/checkout.ts +63 -138
  149. package/redux/middlewares/index.ts +14 -10
  150. package/redux/middlewares/pre-order/address.ts +7 -2
  151. package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +7 -1
  152. package/redux/middlewares/pre-order/data-source-shipping-option.ts +7 -1
  153. package/redux/middlewares/pre-order/delivery-option.ts +7 -1
  154. package/redux/middlewares/pre-order/index.ts +16 -10
  155. package/redux/middlewares/pre-order/installment-option.ts +8 -1
  156. package/redux/middlewares/pre-order/payment-option-reset.ts +37 -0
  157. package/redux/middlewares/pre-order/payment-option.ts +7 -1
  158. package/redux/middlewares/pre-order/pre-order-validation.ts +8 -3
  159. package/redux/middlewares/pre-order/redirection.ts +8 -2
  160. package/redux/middlewares/pre-order/set-pre-order.ts +6 -2
  161. package/redux/middlewares/pre-order/shipping-option.ts +7 -1
  162. package/redux/middlewares/pre-order/shipping-step.ts +5 -1
  163. package/redux/reducers/checkout.ts +23 -3
  164. package/redux/reducers/index.ts +11 -3
  165. package/redux/reducers/root.ts +7 -2
  166. package/redux/reducers/widget.ts +80 -0
  167. package/sentry/index.ts +69 -13
  168. package/tailwind/content.js +16 -0
  169. package/types/commerce/account.ts +5 -1
  170. package/types/commerce/checkout.ts +35 -1
  171. package/types/commerce/widget.ts +33 -0
  172. package/types/index.ts +101 -6
  173. package/types/next-auth.d.ts +2 -2
  174. package/types/widget.ts +80 -0
  175. package/utils/app-fetch.ts +7 -2
  176. package/utils/generate-commerce-search-params.ts +3 -2
  177. package/utils/get-checkout-path.ts +3 -0
  178. package/utils/get-root-hostname.ts +28 -0
  179. package/utils/index.ts +64 -10
  180. package/utils/localization.ts +4 -0
  181. package/utils/mobile-3d-iframe.ts +8 -2
  182. package/utils/override-middleware.ts +7 -12
  183. package/utils/pz-segments.ts +92 -0
  184. package/utils/redirect-ignore.ts +35 -0
  185. package/utils/redirect.ts +9 -3
  186. package/utils/redirection-iframe.ts +8 -2
  187. package/utils/widget-styles.ts +107 -0
  188. package/views/error-page.tsx +93 -0
  189. package/with-pz-config.js +13 -6
@@ -4,6 +4,7 @@ import Settings from 'settings';
4
4
  import logger from '../utils/log';
5
5
  import { PzNextRequest } from '.';
6
6
  import { getUrlPathWithLocale } from '../utils/localization';
7
+ import { getCheckoutPath } from '../utils';
7
8
 
8
9
  const streamToString = async (stream: ReadableStream<Uint8Array> | null) => {
9
10
  if (stream) {
@@ -35,18 +36,22 @@ const withRedirectionPayment =
35
36
  const searchParams = new URLSearchParams(url.search);
36
37
  const ip = req.headers.get('x-forwarded-for') ?? '';
37
38
  const sessionId = req.cookies.get('osessionid');
39
+ const currentLocale = req.middlewareParams?.rewrites?.locale;
38
40
 
39
41
  if (searchParams.get('page') !== 'RedirectionPageCompletePage') {
40
42
  return middleware(req, event);
41
43
  }
42
44
 
43
- const requestUrl = `${Settings.commerceUrl}/orders/checkout/${url.search}`;
45
+ const isPostCheckout = req.cookies.get('pz-post-checkout-flow')?.value === 'true';
46
+ const requestUrl = `${Settings.commerceUrl}${getCheckoutPath(isPostCheckout)}${url.search}`;
44
47
  const requestHeaders = {
45
48
  'X-Requested-With': 'XMLHttpRequest',
46
49
  'Content-Type': 'application/x-www-form-urlencoded',
47
50
  Cookie: req.headers.get('cookie') ?? '',
48
51
  'x-currency': req.cookies.get('pz-currency')?.value ?? '',
49
- 'x-forwarded-for': ip
52
+ 'x-forwarded-for': ip,
53
+ 'Accept-Language':
54
+ currentLocale ?? req.cookies.get('pz-locale')?.value ?? ''
50
55
  };
51
56
 
52
57
  try {
@@ -60,17 +65,9 @@ const withRedirectionPayment =
60
65
  ip
61
66
  }
62
67
  );
63
-
64
- return NextResponse.redirect(
65
- `${url.origin}${getUrlPathWithLocale(
66
- '/orders/checkout/',
67
- req.cookies.get('pz-locale')?.value
68
- )}`,
69
- 303
70
- );
71
68
  }
72
69
 
73
- const request = await fetch(requestUrl, {
70
+ const fetchResponse = await fetch(requestUrl, {
74
71
  method: 'POST',
75
72
  headers: requestHeaders,
76
73
  body
@@ -78,14 +75,14 @@ const withRedirectionPayment =
78
75
 
79
76
  logger.info('Complete redirection payment request', {
80
77
  requestUrl,
81
- status: request.status,
78
+ status: fetchResponse.status,
82
79
  requestHeaders,
83
80
  ip
84
81
  });
85
82
 
86
- const response = await request.json();
83
+ const responseData = await fetchResponse.json();
87
84
 
88
- const { context_list: contextList, errors } = response;
85
+ const { context_list: contextList, errors } = responseData;
89
86
  const redirectionContext = contextList?.find(
90
87
  (context) => context.page_context?.redirect_url
91
88
  );
@@ -99,18 +96,26 @@ const withRedirectionPayment =
99
96
  ip
100
97
  });
101
98
 
102
- return NextResponse.redirect(
99
+ const errorResponse = NextResponse.redirect(
103
100
  `${url.origin}${getUrlPathWithLocale(
104
101
  '/orders/checkout/',
105
102
  req.cookies.get('pz-locale')?.value
106
103
  )}`,
107
- {
108
- status: 303,
109
- headers: {
110
- 'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
111
- }
112
- }
104
+ 303
113
105
  );
106
+
107
+ // Forward set-cookie headers from the upstream response
108
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
109
+ setCookies.forEach((cookie) => {
110
+ errorResponse.headers.append('Set-Cookie', cookie);
111
+ });
112
+
113
+ // Add error cookie
114
+ errorResponse.cookies.set('pz-pos-error', JSON.stringify(errors), {
115
+ path: '/'
116
+ });
117
+
118
+ return errorResponse;
114
119
  }
115
120
 
116
121
  logger.info('Order success page context list', {
@@ -125,7 +130,7 @@ const withRedirectionPayment =
125
130
  {
126
131
  middleware: 'redirection-payment',
127
132
  requestHeaders,
128
- response: JSON.stringify(response),
133
+ response: JSON.stringify(responseData),
129
134
  ip
130
135
  }
131
136
  );
@@ -153,10 +158,11 @@ const withRedirectionPayment =
153
158
  // So we use 303 status code to change the method to GET
154
159
  const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
155
160
 
156
- nextResponse.headers.set(
157
- 'Set-Cookie',
158
- request.headers.get('set-cookie') ?? ''
159
- );
161
+ // Forward all set-cookie headers from the upstream response
162
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
163
+ setCookies.forEach((cookie) => {
164
+ nextResponse.headers.append('Set-Cookie', cookie);
165
+ });
160
166
 
161
167
  return nextResponse;
162
168
  } catch (error) {
@@ -4,6 +4,8 @@ import { Buffer } from 'buffer';
4
4
  import logger from '../utils/log';
5
5
  import { getUrlPathWithLocale } from '../utils/localization';
6
6
  import { PzNextRequest } from '.';
7
+ import { ServerVariables } from '../utils/server-variables';
8
+ import { getCheckoutPath } from '../utils';
7
9
 
8
10
  const streamToString = async (stream: ReadableStream<Uint8Array> | null) => {
9
11
  if (stream) {
@@ -34,18 +36,22 @@ const withSavedCardRedirection =
34
36
  const url = req.nextUrl.clone();
35
37
  const ip = req.headers.get('x-forwarded-for') ?? '';
36
38
  const sessionId = req.cookies.get('osessionid');
39
+ const currentLocale = req.middlewareParams?.rewrites?.locale;
37
40
 
38
41
  if (url.search.indexOf('SavedCardThreeDSecurePage') === -1) {
39
42
  return middleware(req, event);
40
43
  }
41
44
 
42
- const requestUrl = `${Settings.commerceUrl}/orders/checkout/${url.search}`;
45
+ const isPostCheckout = req.cookies.get('pz-post-checkout-flow')?.value === 'true';
46
+ const requestUrl = `${Settings.commerceUrl}${getCheckoutPath(isPostCheckout)}${url.search}`;
43
47
  const requestHeaders = {
44
48
  'X-Requested-With': 'XMLHttpRequest',
45
49
  'Content-Type': 'application/x-www-form-urlencoded',
46
50
  Cookie: req.headers.get('cookie') ?? '',
47
51
  'x-currency': req.cookies.get('pz-currency')?.value ?? '',
48
- 'x-forwarded-for': ip
52
+ 'x-forwarded-for': ip,
53
+ 'Accept-Language':
54
+ currentLocale ?? req.cookies.get('pz-locale')?.value ?? ''
49
55
  };
50
56
 
51
57
  try {
@@ -59,17 +65,9 @@ const withSavedCardRedirection =
59
65
  ip
60
66
  }
61
67
  );
62
-
63
- return NextResponse.redirect(
64
- `${url.origin}${getUrlPathWithLocale(
65
- '/orders/checkout/',
66
- req.cookies.get('pz-locale')?.value
67
- )}`,
68
- 303
69
- );
70
68
  }
71
69
 
72
- const request = await fetch(requestUrl, {
70
+ const fetchResponse = await fetch(requestUrl, {
73
71
  method: 'POST',
74
72
  headers: requestHeaders,
75
73
  body
@@ -77,14 +75,14 @@ const withSavedCardRedirection =
77
75
 
78
76
  logger.info('Complete 3D payment request', {
79
77
  requestUrl,
80
- status: request.status,
78
+ status: fetchResponse.status,
81
79
  requestHeaders,
82
80
  ip
83
81
  });
84
82
 
85
- const response = await request.json();
83
+ const responseData = await fetchResponse.json();
86
84
 
87
- const { context_list: contextList, errors } = response;
85
+ const { context_list: contextList, errors } = responseData;
88
86
  const redirectionContext = contextList?.find(
89
87
  (context) => context.page_context?.redirect_url
90
88
  );
@@ -98,18 +96,26 @@ const withSavedCardRedirection =
98
96
  ip
99
97
  });
100
98
 
101
- return NextResponse.redirect(
99
+ const errorResponse = NextResponse.redirect(
102
100
  `${url.origin}${getUrlPathWithLocale(
103
101
  '/orders/checkout/',
104
102
  req.cookies.get('pz-locale')?.value
105
103
  )}`,
106
- {
107
- status: 303,
108
- headers: {
109
- 'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
110
- }
111
- }
104
+ 303
112
105
  );
106
+
107
+ // Forward set-cookie headers from the upstream response
108
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
109
+ setCookies.forEach((cookie) => {
110
+ errorResponse.headers.append('Set-Cookie', cookie);
111
+ });
112
+
113
+ // Add error cookie
114
+ errorResponse.cookies.set('pz-pos-error', JSON.stringify(errors), {
115
+ path: '/'
116
+ });
117
+
118
+ return errorResponse;
113
119
  }
114
120
 
115
121
  logger.info('Order success page context list', {
@@ -124,7 +130,7 @@ const withSavedCardRedirection =
124
130
  {
125
131
  middleware: 'saved-card-redirection',
126
132
  requestHeaders,
127
- response: JSON.stringify(response),
133
+ response: JSON.stringify(responseData),
128
134
  ip
129
135
  }
130
136
  );
@@ -152,10 +158,11 @@ const withSavedCardRedirection =
152
158
  // So we use 303 status code to change the method to GET
153
159
  const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
154
160
 
155
- nextResponse.headers.set(
156
- 'Set-Cookie',
157
- request.headers.get('set-cookie') ?? ''
158
- );
161
+ // Forward all set-cookie headers from the upstream response
162
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
163
+ setCookies.forEach((cookie) => {
164
+ nextResponse.headers.append('Set-Cookie', cookie);
165
+ });
159
166
 
160
167
  return nextResponse;
161
168
  } catch (error) {
@@ -4,6 +4,7 @@ import { Buffer } from 'buffer';
4
4
  import logger from '../utils/log';
5
5
  import { getUrlPathWithLocale } from '../utils/localization';
6
6
  import { PzNextRequest } from '.';
7
+ import { getCheckoutPath } from '../utils';
7
8
 
8
9
  const streamToString = async (stream: ReadableStream<Uint8Array> | null) => {
9
10
  if (stream) {
@@ -34,18 +35,22 @@ const withThreeDRedirection =
34
35
  const url = req.nextUrl.clone();
35
36
  const ip = req.headers.get('x-forwarded-for') ?? '';
36
37
  const sessionId = req.cookies.get('osessionid');
38
+ const currentLocale = req.middlewareParams?.rewrites?.locale;
37
39
 
38
40
  if (url.search.indexOf('CreditCardThreeDSecurePage') === -1) {
39
41
  return middleware(req, event);
40
42
  }
41
43
 
42
- const requestUrl = `${Settings.commerceUrl}/orders/checkout/${url.search}`;
44
+ const isPostCheckout = req.cookies.get('pz-post-checkout-flow')?.value === 'true';
45
+ const requestUrl = `${Settings.commerceUrl}${getCheckoutPath(isPostCheckout)}${url.search}`;
43
46
  const requestHeaders = {
44
47
  'X-Requested-With': 'XMLHttpRequest',
45
48
  'Content-Type': 'application/x-www-form-urlencoded',
46
49
  Cookie: req.headers.get('cookie') ?? '',
47
50
  'x-currency': req.cookies.get('pz-currency')?.value ?? '',
48
- 'x-forwarded-for': ip
51
+ 'x-forwarded-for': ip,
52
+ 'Accept-Language':
53
+ currentLocale ?? req.cookies.get('pz-locale')?.value ?? ''
49
54
  };
50
55
 
51
56
  try {
@@ -59,17 +64,9 @@ const withThreeDRedirection =
59
64
  ip
60
65
  }
61
66
  );
62
-
63
- return NextResponse.redirect(
64
- `${url.origin}${getUrlPathWithLocale(
65
- '/orders/checkout/',
66
- req.cookies.get('pz-locale')?.value
67
- )}`,
68
- 303
69
- );
70
67
  }
71
68
 
72
- const request = await fetch(requestUrl, {
69
+ const fetchResponse = await fetch(requestUrl, {
73
70
  method: 'POST',
74
71
  headers: requestHeaders,
75
72
  body
@@ -77,14 +74,14 @@ const withThreeDRedirection =
77
74
 
78
75
  logger.info('Complete 3D payment request', {
79
76
  requestUrl,
80
- status: request.status,
77
+ status: fetchResponse.status,
81
78
  requestHeaders,
82
79
  ip
83
80
  });
84
81
 
85
- const response = await request.json();
82
+ const responseData = await fetchResponse.json();
86
83
 
87
- const { context_list: contextList, errors } = response;
84
+ const { context_list: contextList, errors } = responseData;
88
85
  const redirectionContext = contextList?.find(
89
86
  (context) => context.page_context?.redirect_url
90
87
  );
@@ -98,18 +95,26 @@ const withThreeDRedirection =
98
95
  ip
99
96
  });
100
97
 
101
- return NextResponse.redirect(
98
+ const errorResponse = NextResponse.redirect(
102
99
  `${url.origin}${getUrlPathWithLocale(
103
100
  '/orders/checkout/',
104
101
  req.cookies.get('pz-locale')?.value
105
102
  )}`,
106
- {
107
- status: 303,
108
- headers: {
109
- 'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
110
- }
111
- }
103
+ 303
112
104
  );
105
+
106
+ // Forward set-cookie headers from the upstream response
107
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
108
+ setCookies.forEach((cookie) => {
109
+ errorResponse.headers.append('Set-Cookie', cookie);
110
+ });
111
+
112
+ // Add error cookie
113
+ errorResponse.cookies.set('pz-pos-error', JSON.stringify(errors), {
114
+ path: '/'
115
+ });
116
+
117
+ return errorResponse;
113
118
  }
114
119
 
115
120
  logger.info('Order success page context list', {
@@ -124,7 +129,7 @@ const withThreeDRedirection =
124
129
  {
125
130
  middleware: 'three-d-redirection',
126
131
  requestHeaders,
127
- response: JSON.stringify(response),
132
+ response: JSON.stringify(responseData),
128
133
  ip
129
134
  }
130
135
  );
@@ -152,10 +157,11 @@ const withThreeDRedirection =
152
157
  // So we use 303 status code to change the method to GET
153
158
  const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
154
159
 
155
- nextResponse.headers.set(
156
- 'Set-Cookie',
157
- request.headers.get('set-cookie') ?? ''
158
- );
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
+ });
159
165
 
160
166
  return nextResponse;
161
167
  } catch (error) {
@@ -4,6 +4,7 @@ import { PzNextRequest } from '.';
4
4
  import logger from '../utils/log';
5
5
  import { urlLocaleMatcherRegex } from '../utils';
6
6
  import { getUrlPathWithLocale } from '../utils/localization';
7
+ import { shouldIgnoreRedirect } from '../utils/redirect-ignore';
7
8
  import { ROUTES } from 'routes';
8
9
 
9
10
  // This middleware is used to handle url redirections set in Omnitron
@@ -50,7 +51,7 @@ const withUrlRedirection =
50
51
  const location = request.headers.get('location');
51
52
  const redirectUrl = new URL(
52
53
  request.headers.get('location'),
53
- location.startsWith('http') ? '' : process.env.NEXT_PUBLIC_URL
54
+ location.startsWith('http') ? '' : url.origin
54
55
  );
55
56
 
56
57
  redirectUrl.pathname = getUrlPathWithLocale(
@@ -60,6 +61,15 @@ const withUrlRedirection =
60
61
 
61
62
  const setCookies = request.headers.getSetCookie();
62
63
 
64
+ if (
65
+ shouldIgnoreRedirect(
66
+ url.pathname,
67
+ req.middlewareParams.rewrites.locale
68
+ )
69
+ ) {
70
+ return middleware(req, event);
71
+ }
72
+
63
73
  const response = NextResponse.redirect(redirectUrl.toString(), {
64
74
  status: request.status
65
75
  });
@@ -0,0 +1,206 @@
1
+ import { NextFetchEvent, NextMiddleware, NextResponse } from 'next/server';
2
+ import Settings from 'settings';
3
+ import { Buffer } from 'buffer';
4
+ import logger from '../utils/log';
5
+ import { getUrlPathWithLocale } from '../utils/localization';
6
+ import { PzNextRequest } from '.';
7
+ import { getCheckoutPath } from '../utils';
8
+
9
+ const streamToString = async (stream: ReadableStream<Uint8Array> | null) => {
10
+ if (stream) {
11
+ const chunks = [];
12
+ let result = '';
13
+
14
+ try {
15
+ for await (const chunk of stream as any) {
16
+ chunks.push(Buffer.from(chunk));
17
+ }
18
+
19
+ result = Buffer.concat(chunks).toString('utf-8');
20
+ } catch (error) {
21
+ logger.error('Error while reading body stream', {
22
+ middleware: 'wallet-complete-redirection',
23
+ error
24
+ });
25
+ }
26
+
27
+ return result;
28
+ }
29
+ return null;
30
+ };
31
+
32
+ const withWalletCompleteRedirection =
33
+ (middleware: NextMiddleware) =>
34
+ async (req: PzNextRequest, event: NextFetchEvent) => {
35
+ const url = req.nextUrl.clone();
36
+ const ip = req.headers.get('x-forwarded-for') ?? '';
37
+ const sessionId = req.cookies.get('osessionid');
38
+
39
+ if (url.search.indexOf('WalletCompletePage') === -1) {
40
+ return middleware(req, event);
41
+ }
42
+
43
+ const isPostCheckout = req.cookies.get('pz-post-checkout-flow')?.value === 'true';
44
+ const requestUrl = `${Settings.commerceUrl}${getCheckoutPath(isPostCheckout)}${url.search}`;
45
+ const requestHeaders = {
46
+ 'X-Requested-With': 'XMLHttpRequest',
47
+ 'Content-Type': 'application/x-www-form-urlencoded',
48
+ Cookie: req.headers.get('cookie') ?? '',
49
+ 'x-currency': req.cookies.get('pz-currency')?.value ?? '',
50
+ 'x-forwarded-for': ip
51
+ };
52
+
53
+ try {
54
+ const existingBody = await streamToString(req.body);
55
+ const queryParams = new URLSearchParams(url.search);
56
+ const bodyParams = new URLSearchParams();
57
+
58
+ if (existingBody) {
59
+ try {
60
+ const existingParams = new URLSearchParams(existingBody);
61
+
62
+ existingParams.forEach((value, key) => {
63
+ bodyParams.append(key, value);
64
+ });
65
+ } catch {
66
+ logger.error('Error parsing existing body as URL-encoded data', {
67
+ middleware: 'wallet-complete-redirection',
68
+ existingBody,
69
+ ip
70
+ });
71
+ }
72
+ }
73
+
74
+ queryParams.forEach((value, key) => {
75
+ bodyParams.append(key, value);
76
+ });
77
+
78
+ const body = bodyParams.toString();
79
+
80
+ if (!sessionId) {
81
+ logger.warn(
82
+ 'Make sure that the SESSION_COOKIE_SAMESITE environment variable is set to None in Commerce.',
83
+ {
84
+ middleware: 'wallet-complete-redirection',
85
+ ip
86
+ }
87
+ );
88
+ }
89
+
90
+ const fetchResponse = await fetch(requestUrl, {
91
+ method: 'POST',
92
+ headers: requestHeaders,
93
+ body
94
+ });
95
+
96
+ logger.info('Complete wallet payment request', {
97
+ requestUrl,
98
+ status: fetchResponse.status,
99
+ requestHeaders,
100
+ ip
101
+ });
102
+
103
+ const responseData = await fetchResponse.json();
104
+
105
+ const { context_list: contextList, errors } = responseData;
106
+ const redirectionContext = contextList?.find(
107
+ (context) => context.page_context?.redirect_url
108
+ );
109
+ const redirectUrl = redirectionContext?.page_context?.redirect_url;
110
+
111
+ if (errors && Object.keys(errors).length) {
112
+ logger.error('Error while completing wallet payment', {
113
+ middleware: 'wallet-complete-redirection',
114
+ errors,
115
+ requestHeaders,
116
+ ip
117
+ });
118
+
119
+ const errorResponse = NextResponse.redirect(
120
+ `${url.origin}${getUrlPathWithLocale(
121
+ '/orders/checkout/',
122
+ req.cookies.get('pz-locale')?.value
123
+ )}`,
124
+ 303
125
+ );
126
+
127
+ // Forward set-cookie headers from the upstream response
128
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
129
+ setCookies.forEach((cookie) => {
130
+ errorResponse.headers.append('Set-Cookie', cookie);
131
+ });
132
+
133
+ // Add error cookie
134
+ errorResponse.cookies.set('pz-pos-error', JSON.stringify(errors), {
135
+ path: '/'
136
+ });
137
+
138
+ return errorResponse;
139
+ }
140
+
141
+ logger.info('Order success page context list', {
142
+ middleware: 'wallet-complete-redirection',
143
+ contextList,
144
+ ip
145
+ });
146
+
147
+ if (!redirectUrl) {
148
+ logger.warn(
149
+ 'No redirection url for order success page found in page_context. Redirecting to checkout page.',
150
+ {
151
+ middleware: 'wallet-complete-redirection',
152
+ requestHeaders,
153
+ response: JSON.stringify(responseData),
154
+ ip
155
+ }
156
+ );
157
+
158
+ const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
159
+ '/orders/checkout/',
160
+ req.cookies.get('pz-locale')?.value
161
+ )}`;
162
+
163
+ return NextResponse.redirect(redirectUrlWithLocale, 303);
164
+ }
165
+
166
+ const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
167
+ redirectUrl,
168
+ req.cookies.get('pz-locale')?.value
169
+ )}`;
170
+
171
+ logger.info('Redirecting to order success page', {
172
+ middleware: 'wallet-complete-redirection',
173
+ redirectUrlWithLocale,
174
+ ip
175
+ });
176
+
177
+ // Using POST method while redirecting causes an error,
178
+ // So we use 303 status code to change the method to GET
179
+ const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
180
+
181
+ // Forward all set-cookie headers from the upstream response
182
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
183
+ setCookies.forEach((cookie) => {
184
+ nextResponse.headers.append('Set-Cookie', cookie);
185
+ });
186
+
187
+ return nextResponse;
188
+ } catch (error) {
189
+ logger.error('Error while completing wallet payment', {
190
+ middleware: 'wallet-complete-redirection',
191
+ error,
192
+ requestHeaders,
193
+ ip
194
+ });
195
+
196
+ return NextResponse.redirect(
197
+ `${url.origin}${getUrlPathWithLocale(
198
+ '/orders/checkout/',
199
+ req.cookies.get('pz-locale')?.value
200
+ )}`,
201
+ 303
202
+ );
203
+ }
204
+ };
205
+
206
+ export default withWalletCompleteRedirection;
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": "2.0.0-beta.2",
4
+ "version": "2.0.0-beta.20",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -13,30 +13,45 @@
13
13
  "pz-predev": "bin/pz-predev.js",
14
14
  "pz-postdev": "bin/pz-postdev.js"
15
15
  },
16
+ "scripts": {
17
+ "test": "jest"
18
+ },
16
19
  "dependencies": {
20
+ "@mongodb-js/zstd": "^2.0.1",
21
+ "@neshca/cache-handler": "1.9.0",
17
22
  "@opentelemetry/exporter-trace-otlp-http": "0.46.0",
18
23
  "@opentelemetry/resources": "1.19.0",
19
24
  "@opentelemetry/sdk-node": "0.46.0",
20
25
  "@opentelemetry/sdk-trace-node": "1.19.0",
21
26
  "@opentelemetry/semantic-conventions": "1.19.0",
22
- "@reduxjs/toolkit": "1.9.7",
23
- "@neshca/cache-handler": "1.5.1",
27
+ "@reduxjs/toolkit": "2.11.2",
28
+ "@sentry/nextjs": "10.39.0",
24
29
  "cross-spawn": "7.0.3",
25
30
  "generic-pool": "3.9.0",
26
- "react-redux": "8.1.3",
31
+ "lottie-web": "^5.13.0",
32
+ "react-multi-carousel": "2.8.4",
33
+ "react-redux": "9.2.0",
27
34
  "react-string-replace": "1.1.1",
28
35
  "redis": "4.6.13",
29
36
  "semver": "7.6.2",
30
37
  "set-cookie-parser": "2.6.0"
31
38
  },
32
39
  "devDependencies": {
33
- "@akinon/eslint-plugin-projectzero": "2.0.0-beta.2",
40
+ "@akinon/eslint-plugin-projectzero": "2.0.0-beta.20",
41
+ "@babel/core": "7.26.10",
42
+ "@babel/preset-env": "7.26.9",
43
+ "@babel/preset-typescript": "7.27.0",
44
+ "@types/jest": "29.5.14",
34
45
  "@types/react-redux": "7.1.30",
35
46
  "@types/set-cookie-parser": "2.4.7",
36
- "@typescript-eslint/eslint-plugin": "8.18.2",
37
- "@typescript-eslint/parser": "8.18.2",
38
- "eslint": "9.17.0",
39
- "eslint-config-next": "15.1.2",
40
- "eslint-config-prettier": "9.1.0"
47
+ "@typescript-eslint/eslint-plugin": "6.7.4",
48
+ "@typescript-eslint/parser": "6.7.4",
49
+ "babel-jest": "29.7.0",
50
+ "eslint": "8.56.0",
51
+ "eslint-config-next": "16.1.6",
52
+ "eslint-config-prettier": "8.5.0",
53
+ "jest": "29.7.0",
54
+ "ts-jest": "29.3.2",
55
+ "typescript": "5.9.3"
41
56
  }
42
57
  }