@akinon/next 1.66.0 → 1.68.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,5 +1,26 @@
1
1
  # @akinon/next
2
2
 
3
+ ## 1.68.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 907813c: ZERO-2934: add payment-gateway/<gateway> redirect in middleware
8
+ - c873740: ZERO-2903: add types
9
+ - d899cc7: ZERO-2925: Login by checking the session id
10
+ - 57d1657: ZERO-2925: Update condation
11
+ - 3be7462: ZERO-2934: fix reset basket on redirection payment complete
12
+ - 034b813: ZERO-2903: create saved card plugin
13
+ - 48d508f: ZERO-2989: refine cookie header in payment middlewares
14
+
15
+ ## 1.67.0
16
+
17
+ ### Minor Changes
18
+
19
+ - bc2b411: ZERO-2825: Add attribute-based shipping options to checkout page
20
+ - 3d2212a: ZERO-2745: Add multi basket support
21
+ - 8f47cca: ZERO-2829: fix and add nested JSON support for useFormData in api/client route
22
+ - 9e25a64: ZERO-2835: Update category page layout with breadcrumb
23
+
3
24
  ## 1.66.0
4
25
 
5
26
  ### Minor Changes
package/api/client.ts CHANGED
@@ -4,6 +4,7 @@ import settings from 'settings';
4
4
  import logger from '../utils/log';
5
5
  import formatCookieString from '../utils/format-cookie-string';
6
6
  import cookieParser from 'set-cookie-parser';
7
+ import { cookies } from 'next/headers';
7
8
 
8
9
  interface RouteParams {
9
10
  params: {
@@ -80,11 +81,27 @@ async function proxyRequest(...args) {
80
81
  }
81
82
  } as RequestInit;
82
83
 
84
+ const nextCookies = cookies();
85
+ const segment = nextCookies.get('pz-segment')?.value;
86
+ const currency = nextCookies.get('pz-external-currency')?.value;
87
+
88
+ if (segment) {
89
+ fetchOptions.headers['X-Segment-Id'] = segment;
90
+ }
91
+
92
+ if (currency) {
93
+ fetchOptions.headers = Object.assign({}, fetchOptions.headers, {
94
+ 'x-currency': currency
95
+ });
96
+ }
97
+
83
98
  if (options.contentType) {
84
99
  fetchOptions.headers['Content-Type'] = options.contentType;
85
100
  }
86
101
 
87
- const isMultipartFormData = req.headers.get('content-type')?.includes('multipart/form-data;');
102
+ const isMultipartFormData = req.headers
103
+ .get('content-type')
104
+ ?.includes('multipart/form-data;');
88
105
 
89
106
  if (req.method !== 'GET') {
90
107
  let body: Record<string, any> | FormData = {};
@@ -108,7 +125,11 @@ async function proxyRequest(...args) {
108
125
 
109
126
  Object.keys(body ?? {}).forEach((key) => {
110
127
  if (body[key]) {
111
- formData.append(key, body[key]);
128
+ if (typeof body[key] === 'object' && body[key] !== null) {
129
+ formData.append(key, JSON.stringify(body[key]));
130
+ } else {
131
+ formData.append(key, body[key]);
132
+ }
112
133
  }
113
134
  });
114
135
 
@@ -45,7 +45,7 @@ export enum Component {
45
45
  AkifastQuickLoginButton = 'QuickLoginButton',
46
46
  AkifastCheckoutButton = 'CheckoutButton',
47
47
  MultiBasket = 'MultiBasket',
48
- SavedCard = 'Option'
48
+ SavedCard = 'SavedCardOption'
49
49
  }
50
50
 
51
51
  const PluginComponents = new Map([
@@ -67,7 +67,7 @@ export const api = createApi({
67
67
  baseQuery: customBaseQuery,
68
68
  tagTypes: [
69
69
  'Basket',
70
- 'AllBaskets',
70
+ 'MultiBasket',
71
71
  'BasketB2b',
72
72
  'DraftsB2b',
73
73
  'Product',
@@ -28,7 +28,7 @@ export const basketApi = api.injectEndpoints({
28
28
  query: ({ namespace }) =>
29
29
  buildClientRequestUrl(basket.getBasketDetail(namespace)),
30
30
  transformResponse: (response: { basket: Basket }) => response.basket,
31
- providesTags: ['AllBaskets']
31
+ providesTags: ['MultiBasket']
32
32
  }),
33
33
  getAllBaskets: build.query<Basket[], void>({
34
34
  query: () =>
@@ -36,7 +36,7 @@ export const basketApi = api.injectEndpoints({
36
36
  contentType: 'application/json'
37
37
  }),
38
38
  transformResponse: (response: { baskets: Basket[] }) => response.baskets,
39
- providesTags: ['AllBaskets']
39
+ providesTags: ['MultiBasket']
40
40
  }),
41
41
  removeBasket: build.mutation<Basket, { pk: number }>({
42
42
  query: ({ pk }) => ({
@@ -46,7 +46,7 @@ export const basketApi = api.injectEndpoints({
46
46
  method: 'DELETE',
47
47
  body: { pk }
48
48
  }),
49
- invalidatesTags: ['AllBaskets', 'Basket']
49
+ invalidatesTags: ['MultiBasket', 'Basket']
50
50
  }),
51
51
  selectMainBasket: build.mutation<Basket, { pk: number }>({
52
52
  query: ({ pk }) => ({
@@ -57,7 +57,7 @@ export const basketApi = api.injectEndpoints({
57
57
  body: { pk }
58
58
  }),
59
59
  transformResponse: (response: { baskets: Basket }) => response.baskets,
60
- invalidatesTags: ['AllBaskets', 'Basket']
60
+ invalidatesTags: ['MultiBasket', 'Basket']
61
61
  }),
62
62
  updateQuantity: build.mutation<
63
63
  UpdateQuantityResponse,
@@ -69,7 +69,8 @@ export const basketApi = api.injectEndpoints({
69
69
  }),
70
70
  method: 'PUT',
71
71
  body
72
- })
72
+ }),
73
+ invalidatesTags: ['MultiBasket', 'Basket']
73
74
  }),
74
75
  clearBasket: build.mutation<Basket, void>({
75
76
  query: (body) => ({
@@ -57,6 +57,7 @@ export interface CompleteCreditCardParams {
57
57
  card_month: string;
58
58
  card_year: string;
59
59
  use_three_d?: boolean;
60
+ save?: boolean;
60
61
  }
61
62
 
62
63
  interface GetContractResponse {
@@ -228,7 +229,8 @@ export const checkoutApi = api.injectEndpoints({
228
229
  card_number,
229
230
  card_month,
230
231
  card_year,
231
- use_three_d = true
232
+ use_three_d = true,
233
+ save = undefined
232
234
  }) => {
233
235
  const paymentOption =
234
236
  store.getState().checkout?.preOrder?.payment_option;
@@ -242,20 +244,26 @@ export const checkoutApi = api.injectEndpoints({
242
244
  };
243
245
  }
244
246
 
247
+ const body: Record<string, string> = {
248
+ agreement: '1',
249
+ use_three_d: use_three_d ? '1' : '0',
250
+ card_cvv,
251
+ card_holder,
252
+ card_month,
253
+ card_number,
254
+ card_year
255
+ };
256
+
257
+ if (save !== undefined) {
258
+ body.save = save ? '1' : '0';
259
+ }
260
+
245
261
  return {
246
262
  url: buildClientRequestUrl(checkout.completeCreditCardPayment, {
247
263
  useFormData: true
248
264
  }),
249
265
  method: 'POST',
250
- body: {
251
- agreement: '1',
252
- use_three_d: use_three_d ? '1' : '0',
253
- card_cvv,
254
- card_holder,
255
- card_month,
256
- card_number,
257
- card_year
258
- }
266
+ body
259
267
  };
260
268
  },
261
269
  async onQueryStarted(args, { dispatch, queryFulfilled }) {
@@ -697,6 +705,25 @@ export const checkoutApi = api.injectEndpoints({
697
705
  };
698
706
  }
699
707
  }),
708
+ setAttributeBasedShippingOptions: build.mutation<
709
+ CheckoutResponse,
710
+ Record<string, number>
711
+ >({
712
+ query: (options) => ({
713
+ url: buildClientRequestUrl(checkout.setAttributeBasedShippingOption, {
714
+ useFormData: true
715
+ }),
716
+ method: 'POST',
717
+ body: {
718
+ attribute_based_shipping_options: JSON.stringify(options)
719
+ }
720
+ }),
721
+ async onQueryStarted(arg, { dispatch, queryFulfilled }) {
722
+ dispatch(setShippingStepBusy(true));
723
+ await queryFulfilled;
724
+ dispatch(setShippingStepBusy(false));
725
+ }
726
+ }),
700
727
  setOrderSelectionPage: build.mutation<
701
728
  CheckoutResponse,
702
729
  { extra_field: ExtraField }
@@ -747,5 +774,6 @@ export const {
747
774
  usePayWithLoyaltyBalanceMutation,
748
775
  useSetOrderNoteMutation,
749
776
  useSetDeliveryBagsMutation,
777
+ useSetAttributeBasedShippingOptionsMutation,
750
778
  useSetOrderSelectionPageMutation
751
779
  } = checkoutApi;
package/data/urls.ts CHANGED
@@ -17,16 +17,22 @@ export const account = {
17
17
  page,
18
18
  limit,
19
19
  createdDate,
20
- endDate
20
+ endDate,
21
+ shipping_option_slug
21
22
  }: {
22
23
  page?: number;
23
24
  limit?: number;
24
25
  createdDate?: string;
25
26
  endDate?: string;
27
+ shipping_option_slug?: string;
26
28
  }) =>
27
- `/users/orders/?page=${page || 1}&limit=${limit || 12} ${
29
+ `/users/orders/?page=${page || 1}&limit=${limit || 12}${
28
30
  createdDate ? `&created_date__gte=${createdDate}` : ''
29
- } ${endDate ? `&created_date__lte=${endDate}` : ''}`,
31
+ }${endDate ? `&created_date__lte=${endDate}` : ''}${
32
+ shipping_option_slug
33
+ ? `&shipping_option_slug=${shipping_option_slug}`
34
+ : ''
35
+ }`,
30
36
  getQuotations: (page?: number, status?: string, limit?: number) =>
31
37
  `/b2b/my-quotations/?page=${page || 1}` +
32
38
  (status ? `&status=${status}` : '') +
@@ -111,6 +117,8 @@ export const checkout = {
111
117
  loyaltyMoneyUsage: '/orders/checkout/?page=LoyaltyMoneyUsagePage',
112
118
  setOrderNote: '/orders/checkout/?page=OrderNotePage',
113
119
  couponSelectionPage: '/orders/checkout/?page=CouponSelectionPage',
120
+ setAttributeBasedShippingOption:
121
+ '/orders/checkout/?page=AttributeBasedShippingOptionSelectionPage',
114
122
  deliveryBagsPage: '/orders/checkout/?page=DeliveryBagsPage',
115
123
  setOrderSelectionPage: '/orders/checkout/?page=OrderSelectionPage'
116
124
  };
@@ -43,7 +43,7 @@ const withCompleteGpay =
43
43
  const requestHeaders = {
44
44
  'X-Requested-With': 'XMLHttpRequest',
45
45
  'Content-Type': 'application/x-www-form-urlencoded',
46
- Cookie: `osessionid=${req.cookies.get('osessionid')?.value ?? ''}`,
46
+ Cookie: req.headers.get('cookie') ?? '',
47
47
  'x-currency': req.cookies.get('pz-currency')?.value ?? '',
48
48
  'x-forwarded-for': ip
49
49
  };
@@ -43,7 +43,7 @@ const withCompleteMasterpass =
43
43
  const requestHeaders = {
44
44
  'X-Requested-With': 'XMLHttpRequest',
45
45
  'Content-Type': 'application/x-www-form-urlencoded',
46
- Cookie: `osessionid=${req.cookies.get('osessionid')?.value ?? ''}`,
46
+ Cookie: req.headers.get('cookie') ?? '',
47
47
  'x-currency': req.cookies.get('pz-currency')?.value ?? '',
48
48
  'x-forwarded-for': ip
49
49
  };
@@ -75,6 +75,7 @@ const withCurrency =
75
75
  url.pathname.match(urlLocaleMatcherRegex) &&
76
76
  !url.search.includes('mobile_app=true') &&
77
77
  !url.search.includes('page=CreditCardThreeDSecurePage') &&
78
+ !url.search.includes('page=RedirectionPageCompletePage') &&
78
79
  settings.resetBasketOnCurrencyChange !== false
79
80
  ) {
80
81
  logger.info('Currency changed. Resetting basket...', {
@@ -9,6 +9,7 @@ import {
9
9
  withOauthLogin,
10
10
  withPrettyUrl,
11
11
  withRedirectionPayment,
12
+ withSavedCardRedirection,
12
13
  withThreeDRedirection,
13
14
  withUrlRedirection
14
15
  } from '.';
@@ -118,6 +119,14 @@ const withPzDefault =
118
119
  );
119
120
  }
120
121
 
122
+ if (req.nextUrl.pathname.includes('/orders/saved-card-redirect')) {
123
+ return NextResponse.rewrite(
124
+ new URL(
125
+ `${encodeURI(Settings.commerceUrl)}/orders/saved-card-redirect/`
126
+ )
127
+ );
128
+ }
129
+
121
130
  // If commerce redirects to /orders/checkout/ without locale
122
131
  if (
123
132
  req.nextUrl.pathname.match(new RegExp('^/orders/checkout/$')) &&
@@ -135,6 +144,25 @@ const withPzDefault =
135
144
  return NextResponse.redirect(redirectUrlWithLocale, 303);
136
145
  }
137
146
 
147
+ // Dynamically handle any payment gateway without specifying names
148
+ const paymentGatewayRegex = new RegExp('^/payment-gateway/([^/]+)/$');
149
+ const gatewayMatch = req.nextUrl.pathname.match(paymentGatewayRegex);
150
+
151
+ if (
152
+ gatewayMatch && // Check if the URL matches the /payment-gateway/<gateway> pattern
153
+ getUrlPathWithLocale(
154
+ `/payment-gateway/${gatewayMatch[1]}/`,
155
+ req.cookies.get('pz-locale')?.value
156
+ ) !== req.nextUrl.pathname
157
+ ) {
158
+ const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
159
+ `/payment-gateway/${gatewayMatch[1]}/`,
160
+ req.cookies.get('pz-locale')?.value
161
+ )}?${req.nextUrl.searchParams.toString()}`;
162
+
163
+ return NextResponse.redirect(redirectUrlWithLocale);
164
+ }
165
+
138
166
  if (req.nextUrl.pathname.startsWith('/orders/checkout-provider/')) {
139
167
  try {
140
168
  const data = await req.json();
@@ -182,129 +210,140 @@ const withPzDefault =
182
210
  withUrlRedirection(
183
211
  withCompleteGpay(
184
212
  withCompleteMasterpass(
185
- async (req: PzNextRequest, event: NextFetchEvent) => {
186
- let middlewareResult: NextResponse | void =
187
- NextResponse.next();
188
-
189
- try {
190
- const { locale, prettyUrl, currency } =
191
- req.middlewareParams.rewrites;
192
- const { defaultLocaleValue } =
193
- Settings.localization;
194
- const url = req.nextUrl.clone();
195
- const pathnameWithoutLocale = url.pathname.replace(
196
- urlLocaleMatcherRegex,
197
- ''
198
- );
199
-
200
- middlewareResult = (await middleware(
201
- req,
202
- event
203
- )) as NextResponse | void;
204
-
205
- let customRewriteUrlDiff = '';
206
-
207
- if (
208
- middlewareResult instanceof NextResponse &&
209
- middlewareResult.headers.get(
210
- 'pz-override-response'
211
- ) &&
212
- middlewareResult.headers.get(
213
- 'x-middleware-rewrite'
214
- )
215
- ) {
216
- const rewriteUrl = new URL(
217
- middlewareResult.headers.get(
218
- 'x-middleware-rewrite'
219
- )
220
- );
221
- const originalUrl = new URL(req.url);
222
- customRewriteUrlDiff =
223
- rewriteUrl.pathname.replace(
224
- originalUrl.pathname,
225
- ''
226
- );
227
- }
213
+ withSavedCardRedirection(
214
+ async (req: PzNextRequest, event: NextFetchEvent) => {
215
+ let middlewareResult: NextResponse | void =
216
+ NextResponse.next();
228
217
 
229
- url.basePath = `/${commerceUrl}`;
230
- url.pathname = `/${
231
- locale.length ? `${locale}/` : ''
232
- }${currency}/${customRewriteUrlDiff}${
233
- prettyUrl ?? pathnameWithoutLocale
234
- }`.replace(/\/+/g, '/');
235
-
236
- if (
237
- Settings.usePrettyUrlRoute &&
238
- url.searchParams.toString().length > 0 &&
239
- !Object.entries(ROUTES).find(([, value]) =>
240
- new RegExp(`^${value}/?$`).test(
241
- pathnameWithoutLocale
242
- )
243
- )
244
- ) {
245
- url.pathname =
246
- url.pathname +
247
- (/\/$/.test(url.pathname) ? '' : '/') +
248
- `searchparams|${url.searchParams.toString()}`;
249
- }
218
+ try {
219
+ const { locale, prettyUrl, currency } =
220
+ req.middlewareParams.rewrites;
221
+ const { defaultLocaleValue } =
222
+ Settings.localization;
223
+ const url = req.nextUrl.clone();
224
+ const pathnameWithoutLocale =
225
+ url.pathname.replace(urlLocaleMatcherRegex, '');
250
226
 
251
- if (
252
- !req.middlewareParams.found &&
253
- Settings.customNotFoundEnabled
254
- ) {
255
- let pathname = url.pathname
256
- .replace(/\/+$/, '')
257
- .split('/');
258
- url.pathname = url.pathname.replace(
259
- pathname.pop(),
260
- 'pz-not-found'
261
- );
262
- }
227
+ middlewareResult = (await middleware(
228
+ req,
229
+ event
230
+ )) as NextResponse | void;
263
231
 
264
- Settings.rewrites.forEach((rewrite) => {
265
- url.pathname = url.pathname.replace(
266
- rewrite.source,
267
- rewrite.destination
268
- );
269
- });
232
+ let customRewriteUrlDiff = '';
270
233
 
271
- // if middleware.ts has a return value for current url
272
- if (middlewareResult instanceof NextResponse) {
273
- // pz-override-response header is used to prevent 404 page for custom responses.
274
234
  if (
235
+ middlewareResult instanceof NextResponse &&
275
236
  middlewareResult.headers.get(
276
237
  'pz-override-response'
277
- ) !== 'true'
278
- ) {
279
- middlewareResult.headers.set(
280
- 'x-middleware-rewrite',
281
- url.href
282
- );
283
- } else if (
284
- middlewareResult.headers.get(
285
- 'x-middleware-rewrite'
286
238
  ) &&
287
239
  middlewareResult.headers.get(
288
- 'pz-override-response'
289
- ) === 'true'
240
+ 'x-middleware-rewrite'
241
+ )
290
242
  ) {
291
- middlewareResult = NextResponse.rewrite(url);
243
+ const rewriteUrl = new URL(
244
+ middlewareResult.headers.get(
245
+ 'x-middleware-rewrite'
246
+ )
247
+ );
248
+ const originalUrl = new URL(req.url);
249
+ customRewriteUrlDiff =
250
+ rewriteUrl.pathname.replace(
251
+ originalUrl.pathname,
252
+ ''
253
+ );
292
254
  }
293
- } else {
294
- // if middleware.ts doesn't have a return value.
295
- // e.g. NextResponse.next() doesn't exist in middleware.ts
296
255
 
297
- middlewareResult = NextResponse.rewrite(url);
298
- }
256
+ url.basePath = `/${commerceUrl}`;
257
+ url.pathname = `/${
258
+ locale.length ? `${locale}/` : ''
259
+ }${currency}/${customRewriteUrlDiff}${
260
+ prettyUrl ?? pathnameWithoutLocale
261
+ }`.replace(/\/+/g, '/');
299
262
 
300
- if (
301
- !url.pathname.startsWith(`/${currency}/orders`)
302
- ) {
263
+ if (
264
+ Settings.usePrettyUrlRoute &&
265
+ url.searchParams.toString().length > 0 &&
266
+ !Object.entries(ROUTES).find(([, value]) =>
267
+ new RegExp(`^${value}/?$`).test(
268
+ pathnameWithoutLocale
269
+ )
270
+ )
271
+ ) {
272
+ url.pathname =
273
+ url.pathname +
274
+ (/\/$/.test(url.pathname) ? '' : '/') +
275
+ `searchparams|${url.searchParams.toString()}`;
276
+ }
277
+
278
+ if (
279
+ !req.middlewareParams.found &&
280
+ Settings.customNotFoundEnabled
281
+ ) {
282
+ let pathname = url.pathname
283
+ .replace(/\/+$/, '')
284
+ .split('/');
285
+ url.pathname = url.pathname.replace(
286
+ pathname.pop(),
287
+ 'pz-not-found'
288
+ );
289
+ }
290
+
291
+ Settings.rewrites.forEach((rewrite) => {
292
+ url.pathname = url.pathname.replace(
293
+ rewrite.source,
294
+ rewrite.destination
295
+ );
296
+ });
297
+
298
+ // if middleware.ts has a return value for current url
299
+ if (middlewareResult instanceof NextResponse) {
300
+ // pz-override-response header is used to prevent 404 page for custom responses.
301
+ if (
302
+ middlewareResult.headers.get(
303
+ 'pz-override-response'
304
+ ) !== 'true'
305
+ ) {
306
+ middlewareResult.headers.set(
307
+ 'x-middleware-rewrite',
308
+ url.href
309
+ );
310
+ } else if (
311
+ middlewareResult.headers.get(
312
+ 'x-middleware-rewrite'
313
+ ) &&
314
+ middlewareResult.headers.get(
315
+ 'pz-override-response'
316
+ ) === 'true'
317
+ ) {
318
+ middlewareResult = NextResponse.rewrite(url);
319
+ }
320
+ } else {
321
+ // if middleware.ts doesn't have a return value.
322
+ // e.g. NextResponse.next() doesn't exist in middleware.ts
323
+
324
+ middlewareResult = NextResponse.rewrite(url);
325
+ }
326
+
327
+ if (
328
+ !url.pathname.startsWith(`/${currency}/orders`)
329
+ ) {
330
+ middlewareResult.cookies.set(
331
+ 'pz-locale',
332
+ locale?.length > 0
333
+ ? locale
334
+ : defaultLocaleValue,
335
+ {
336
+ sameSite: 'none',
337
+ secure: true,
338
+ expires: new Date(
339
+ Date.now() + 1000 * 60 * 60 * 24 * 7
340
+ ) // 7 days
341
+ }
342
+ );
343
+ }
303
344
  middlewareResult.cookies.set(
304
- 'pz-locale',
305
- locale?.length > 0
306
- ? locale
307
- : defaultLocaleValue,
345
+ 'pz-currency',
346
+ currency,
308
347
  {
309
348
  sameSite: 'none',
310
349
  secure: true,
@@ -313,76 +352,66 @@ const withPzDefault =
313
352
  ) // 7 days
314
353
  }
315
354
  );
316
- }
317
- middlewareResult.cookies.set(
318
- 'pz-currency',
319
- currency,
320
- {
321
- sameSite: 'none',
322
- secure: true,
323
- expires: new Date(
324
- Date.now() + 1000 * 60 * 60 * 24 * 7
325
- ) // 7 days
326
- }
327
- );
328
-
329
- if (
330
- req.cookies.get('pz-locale') &&
331
- req.cookies.get('pz-locale').value !== locale
332
- ) {
333
- logger.debug('Locale changed', {
334
- locale,
335
- oldLocale: req.cookies.get('pz-locale')?.value,
336
- ip
337
- });
338
- }
339
-
340
- middlewareResult.headers.set(
341
- 'pz-url',
342
- req.nextUrl.toString()
343
- );
344
355
 
345
- if (req.cookies.get('pz-set-currency')) {
346
- middlewareResult.cookies.delete(
347
- 'pz-set-currency'
348
- );
349
- }
356
+ if (
357
+ req.cookies.get('pz-locale') &&
358
+ req.cookies.get('pz-locale').value !== locale
359
+ ) {
360
+ logger.debug('Locale changed', {
361
+ locale,
362
+ oldLocale:
363
+ req.cookies.get('pz-locale')?.value,
364
+ ip
365
+ });
366
+ }
350
367
 
351
- if (process.env.ACC_APP_VERSION) {
352
368
  middlewareResult.headers.set(
353
- 'acc-app-version',
354
- process.env.ACC_APP_VERSION
369
+ 'pz-url',
370
+ req.nextUrl.toString()
355
371
  );
356
- }
357
372
 
358
- // Set CSRF token if not set
359
- try {
360
- const url = `${Settings.commerceUrl}${user.csrfToken}`;
373
+ if (req.cookies.get('pz-set-currency')) {
374
+ middlewareResult.cookies.delete(
375
+ 'pz-set-currency'
376
+ );
377
+ }
361
378
 
362
- if (!req.cookies.get('csrftoken')) {
363
- const { csrf_token } = await (
364
- await fetch(url)
365
- ).json();
366
- middlewareResult.cookies.set(
367
- 'csrftoken',
368
- csrf_token
379
+ if (process.env.ACC_APP_VERSION) {
380
+ middlewareResult.headers.set(
381
+ 'acc-app-version',
382
+ process.env.ACC_APP_VERSION
369
383
  );
370
384
  }
385
+
386
+ // Set CSRF token if not set
387
+ try {
388
+ const url = `${Settings.commerceUrl}${user.csrfToken}`;
389
+
390
+ if (!req.cookies.get('csrftoken')) {
391
+ const { csrf_token } = await (
392
+ await fetch(url)
393
+ ).json();
394
+ middlewareResult.cookies.set(
395
+ 'csrftoken',
396
+ csrf_token
397
+ );
398
+ }
399
+ } catch (error) {
400
+ logger.error('CSRF Error', {
401
+ error,
402
+ ip
403
+ });
404
+ }
371
405
  } catch (error) {
372
- logger.error('CSRF Error', {
406
+ logger.error('withPzDefault Error', {
373
407
  error,
374
408
  ip
375
409
  });
376
410
  }
377
- } catch (error) {
378
- logger.error('withPzDefault Error', {
379
- error,
380
- ip
381
- });
382
- }
383
411
 
384
- return middlewareResult;
385
- }
412
+ return middlewareResult;
413
+ }
414
+ )
386
415
  )
387
416
  )
388
417
  )
@@ -8,6 +8,7 @@ import withUrlRedirection from './url-redirection';
8
8
  import withCompleteGpay from './complete-gpay';
9
9
  import withCompleteMasterpass from './complete-masterpass';
10
10
  import withCheckoutProvider from './checkout-provider';
11
+ import withSavedCardRedirection from './saved-card-redirection';
11
12
  import { NextRequest } from 'next/server';
12
13
 
13
14
  export {
@@ -20,7 +21,8 @@ export {
20
21
  withUrlRedirection,
21
22
  withCompleteGpay,
22
23
  withCompleteMasterpass,
23
- withCheckoutProvider
24
+ withCheckoutProvider,
25
+ withSavedCardRedirection
24
26
  };
25
27
 
26
28
  export interface PzNextRequest extends NextRequest {
@@ -58,7 +58,12 @@ const withOauthLogin =
58
58
  return middleware(req, event);
59
59
  }
60
60
 
61
- if (req.cookies.get('messages')?.value.includes('Successfully signed in')) {
61
+ const currentSessionId = req.cookies.get('osessionid');
62
+
63
+ if (
64
+ req.cookies.get('messages')?.value.includes('Successfully signed in') ||
65
+ (currentSessionId && req.cookies.get('messages'))
66
+ ) {
62
67
  let redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
63
68
  '/auth/oauth-login',
64
69
  req.cookies.get('pz-locale')?.value
@@ -0,0 +1,179 @@
1
+ import { NextFetchEvent, NextMiddleware, NextResponse } from 'next/server';
2
+ import Settings from 'settings';
3
+ import { Buffer } from 'buffer';
4
+ import logger from '../utils/log';
5
+ import { getUrlPathWithLocale } from '../utils/localization';
6
+ import { PzNextRequest } from '.';
7
+
8
+ const streamToString = async (stream: ReadableStream<Uint8Array> | null) => {
9
+ if (stream) {
10
+ const chunks = [];
11
+ let result = '';
12
+
13
+ try {
14
+ for await (const chunk of stream as any) {
15
+ chunks.push(Buffer.from(chunk));
16
+ }
17
+
18
+ result = Buffer.concat(chunks).toString('utf-8');
19
+ } catch (error) {
20
+ logger.error('Error while reading body stream', {
21
+ middleware: 'saved-card-redirection',
22
+ error
23
+ });
24
+ }
25
+
26
+ return result;
27
+ }
28
+ return null;
29
+ };
30
+
31
+ const withSavedCardRedirection =
32
+ (middleware: NextMiddleware) =>
33
+ async (req: PzNextRequest, event: NextFetchEvent) => {
34
+ const url = req.nextUrl.clone();
35
+ const ip = req.headers.get('x-forwarded-for') ?? '';
36
+ const sessionId = req.cookies.get('osessionid');
37
+
38
+ if (url.search.indexOf('SavedCardThreeDSecurePage') === -1) {
39
+ return middleware(req, event);
40
+ }
41
+
42
+ const requestUrl = `${Settings.commerceUrl}/orders/checkout/${url.search}`;
43
+ const requestHeaders = {
44
+ 'X-Requested-With': 'XMLHttpRequest',
45
+ 'Content-Type': 'application/x-www-form-urlencoded',
46
+ Cookie: req.headers.get('cookie') ?? '',
47
+ 'x-currency': req.cookies.get('pz-currency')?.value ?? '',
48
+ 'x-forwarded-for': ip
49
+ };
50
+
51
+ try {
52
+ const body = await streamToString(req.body);
53
+
54
+ if (!sessionId) {
55
+ logger.warn(
56
+ 'Make sure that the SESSION_COOKIE_SAMESITE environment variable is set to None in Commerce.',
57
+ {
58
+ middleware: 'saved-card-redirection',
59
+ ip
60
+ }
61
+ );
62
+
63
+ return NextResponse.redirect(
64
+ `${url.origin}${getUrlPathWithLocale(
65
+ '/orders/checkout/',
66
+ req.cookies.get('pz-locale')?.value
67
+ )}`,
68
+ 303
69
+ );
70
+ }
71
+
72
+ const request = await fetch(requestUrl, {
73
+ method: 'POST',
74
+ headers: requestHeaders,
75
+ body
76
+ });
77
+
78
+ logger.info('Complete 3D payment request', {
79
+ requestUrl,
80
+ status: request.status,
81
+ requestHeaders,
82
+ ip
83
+ });
84
+
85
+ const response = await request.json();
86
+
87
+ const { context_list: contextList, errors } = response;
88
+ const redirectionContext = contextList?.find(
89
+ (context) => context.page_context?.redirect_url
90
+ );
91
+ const redirectUrl = redirectionContext?.page_context?.redirect_url;
92
+
93
+ if (errors && Object.keys(errors).length) {
94
+ logger.error('Error while completing 3D payment', {
95
+ middleware: 'saved-card-redirection',
96
+ errors,
97
+ requestHeaders,
98
+ ip
99
+ });
100
+
101
+ return NextResponse.redirect(
102
+ `${url.origin}${getUrlPathWithLocale(
103
+ '/orders/checkout/',
104
+ req.cookies.get('pz-locale')?.value
105
+ )}`,
106
+ {
107
+ status: 303,
108
+ headers: {
109
+ 'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
110
+ }
111
+ }
112
+ );
113
+ }
114
+
115
+ logger.info('Order success page context list', {
116
+ middleware: 'saved-card-redirection',
117
+ contextList,
118
+ ip
119
+ });
120
+
121
+ if (!redirectUrl) {
122
+ logger.warn(
123
+ 'No redirection url for order success page found in page_context. Redirecting to checkout page.',
124
+ {
125
+ middleware: 'saved-card-redirection',
126
+ requestHeaders,
127
+ response: JSON.stringify(response),
128
+ ip
129
+ }
130
+ );
131
+
132
+ const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
133
+ '/orders/checkout/',
134
+ req.cookies.get('pz-locale')?.value
135
+ )}`;
136
+
137
+ return NextResponse.redirect(redirectUrlWithLocale, 303);
138
+ }
139
+
140
+ const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
141
+ redirectUrl,
142
+ req.cookies.get('pz-locale')?.value
143
+ )}`;
144
+
145
+ logger.info('Redirecting to order success page', {
146
+ middleware: 'saved-card-redirection',
147
+ redirectUrlWithLocale,
148
+ ip
149
+ });
150
+
151
+ // Using POST method while redirecting causes an error,
152
+ // So we use 303 status code to change the method to GET
153
+ const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
154
+
155
+ nextResponse.headers.set(
156
+ 'Set-Cookie',
157
+ request.headers.get('set-cookie') ?? ''
158
+ );
159
+
160
+ return nextResponse;
161
+ } catch (error) {
162
+ logger.error('Error while completing 3D payment', {
163
+ middleware: 'saved-card-redirection',
164
+ error,
165
+ requestHeaders,
166
+ ip
167
+ });
168
+
169
+ return NextResponse.redirect(
170
+ `${url.origin}${getUrlPathWithLocale(
171
+ '/orders/checkout/',
172
+ req.cookies.get('pz-locale')?.value
173
+ )}`,
174
+ 303
175
+ );
176
+ }
177
+ };
178
+
179
+ export default withSavedCardRedirection;
@@ -43,7 +43,7 @@ const withThreeDRedirection =
43
43
  const requestHeaders = {
44
44
  'X-Requested-With': 'XMLHttpRequest',
45
45
  'Content-Type': 'application/x-www-form-urlencoded',
46
- Cookie: `osessionid=${req.cookies.get('osessionid')?.value ?? ''}`,
46
+ Cookie: req.headers.get('cookie') ?? '',
47
47
  'x-currency': req.cookies.get('pz-currency')?.value ?? '',
48
48
  'x-forwarded-for': ip
49
49
  };
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.66.0",
4
+ "version": "1.68.0",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -30,7 +30,7 @@
30
30
  "set-cookie-parser": "2.6.0"
31
31
  },
32
32
  "devDependencies": {
33
- "@akinon/eslint-plugin-projectzero": "1.66.0",
33
+ "@akinon/eslint-plugin-projectzero": "1.68.0",
34
34
  "@types/react-redux": "7.1.30",
35
35
  "@types/set-cookie-parser": "2.4.7",
36
36
  "@typescript-eslint/eslint-plugin": "6.7.4",
package/plugins.d.ts CHANGED
@@ -24,7 +24,14 @@ declare module '@akinon/pz-otp/src/redux/reducer' {
24
24
  export const hidePopup: any;
25
25
  }
26
26
 
27
- declare module 'pz-saved-card' {
28
- export const savedCardReducer: unknown;
29
- export const savedCardMiddleware: unknown;
27
+ declare module '@akinon/pz-saved-card' {
28
+ export const savedCardReducer: any;
29
+ export const SavedCardOption: any;
30
+ export const savedCardReducer: any;
31
+ export const savedCardMiddleware: any;
32
+ }
33
+
34
+ declare module '@akinon/pz-saved-card' {
35
+ export const iyzicoSavedCardReducer: any;
36
+ export const iyzicoSavedCardMiddleware: any;
30
37
  }
@@ -3,9 +3,12 @@
3
3
  import { Middleware } from '@reduxjs/toolkit';
4
4
  import {
5
5
  setAddressList,
6
+ setAttributeBasedShippingOptions,
6
7
  setBankAccounts,
7
8
  setCanGuestPurchase,
8
9
  setCardType,
10
+ setCreditPaymentOptions,
11
+ setDataSourceShippingOptions,
9
12
  setDeliveryOptions,
10
13
  setErrors,
11
14
  setHasGiftBox,
@@ -16,14 +19,12 @@ import {
16
19
  setPreOrder,
17
20
  setRetailStores,
18
21
  setShippingOptions,
19
- setDataSourceShippingOptions,
20
- setShippingStepCompleted,
21
- setCreditPaymentOptions
22
+ setShippingStepCompleted
22
23
  } from '../../redux/reducers/checkout';
23
24
  import { RootState, TypedDispatch } from 'redux/store';
24
25
  import { checkoutApi } from '../../data/client/checkout';
25
26
  import { CheckoutContext, PreOrder } from '../../types';
26
- import { getCookie, setCookie } from '../../utils';
27
+ import { getCookie } from '../../utils';
27
28
  import settings from 'settings';
28
29
  import { LocaleUrlStrategy } from '../../localization';
29
30
  import { showMobile3dIframe } from '../../utils/mobile-3d-iframe';
@@ -77,7 +78,8 @@ export const preOrderMiddleware: Middleware = ({
77
78
  shippingOptions,
78
79
  dataSourceShippingOptions,
79
80
  paymentOptions,
80
- installmentOptions
81
+ installmentOptions,
82
+ attributeBasedShippingOptions
81
83
  } = getState().checkout;
82
84
  const { endpoints: apiEndpoints } = checkoutApi;
83
85
 
@@ -145,11 +147,33 @@ export const preOrderMiddleware: Middleware = ({
145
147
  );
146
148
  }
147
149
 
150
+ if (
151
+ Object.keys(attributeBasedShippingOptions).length > 0 &&
152
+ !preOrder.attribute_based_shipping_options
153
+ ) {
154
+ const initialSelectedOptions: Record<string, number> = Object.fromEntries(
155
+ Object.entries(attributeBasedShippingOptions).map(([key, options]) => [
156
+ key,
157
+ options.attribute_based_shipping_options[0].pk
158
+ ])
159
+ );
160
+
161
+ dispatch(
162
+ apiEndpoints.setAttributeBasedShippingOptions.initiate(
163
+ initialSelectedOptions
164
+ )
165
+ );
166
+ }
167
+
148
168
  if (!preOrder.payment_option && paymentOptions.length > 0) {
149
169
  dispatch(apiEndpoints.setPaymentOption.initiate(paymentOptions[0].pk));
150
170
  }
151
171
 
152
- if (!preOrder.installment && installmentOptions.length > 0) {
172
+ if (
173
+ !preOrder.installment &&
174
+ preOrder.payment_option?.payment_type !== 'saved_card' &&
175
+ installmentOptions.length > 0
176
+ ) {
153
177
  dispatch(
154
178
  apiEndpoints.setInstallmentOption.initiate(installmentOptions[0].pk)
155
179
  );
@@ -250,6 +274,14 @@ export const contextListMiddleware: Middleware = ({
250
274
  );
251
275
  }
252
276
 
277
+ if (context.page_context.attribute_based_shipping_options) {
278
+ dispatch(
279
+ setAttributeBasedShippingOptions(
280
+ context.page_context.attribute_based_shipping_options
281
+ )
282
+ );
283
+ }
284
+
253
285
  if (context.page_context.payment_options) {
254
286
  dispatch(setPaymentOptions(context.page_context.payment_options));
255
287
  }
@@ -15,7 +15,8 @@ import {
15
15
  PreOrder,
16
16
  RetailStore,
17
17
  ShippingOption,
18
- DataSource
18
+ DataSource,
19
+ AttributeBasedShippingOption
19
20
  } from '../../types';
20
21
 
21
22
  export interface CheckoutState {
@@ -48,6 +49,8 @@ export interface CheckoutState {
48
49
  selectedBankAccountPk: number;
49
50
  loyaltyBalance?: string;
50
51
  retailStores: RetailStore[];
52
+ attributeBasedShippingOptions: AttributeBasedShippingOption[];
53
+ selectedShippingOptions: Record<string, number>;
51
54
  }
52
55
 
53
56
  const initialState: CheckoutState = {
@@ -78,7 +81,9 @@ const initialState: CheckoutState = {
78
81
  installmentOptions: [],
79
82
  bankAccounts: [],
80
83
  selectedBankAccountPk: null,
81
- retailStores: []
84
+ retailStores: [],
85
+ attributeBasedShippingOptions: [],
86
+ selectedShippingOptions: {}
82
87
  };
83
88
 
84
89
  const checkoutSlice = createSlice({
@@ -156,6 +161,12 @@ const checkoutSlice = createSlice({
156
161
  },
157
162
  setRetailStores(state, { payload }) {
158
163
  state.retailStores = payload;
164
+ },
165
+ setAttributeBasedShippingOptions(state, { payload }) {
166
+ state.attributeBasedShippingOptions = payload;
167
+ },
168
+ setSelectedShippingOptions: (state, { payload }) => {
169
+ state.selectedShippingOptions = payload;
159
170
  }
160
171
  }
161
172
  });
@@ -184,7 +195,9 @@ export const {
184
195
  setBankAccounts,
185
196
  setSelectedBankAccountPk,
186
197
  setLoyaltyBalance,
187
- setRetailStores
198
+ setRetailStores,
199
+ setAttributeBasedShippingOptions,
200
+ setSelectedShippingOptions
188
201
  } = checkoutSlice.actions;
189
202
 
190
203
  export default checkoutSlice.reducer;
@@ -8,6 +8,7 @@ import { api } from '../../data/client/api';
8
8
  import { masterpassReducer } from '@akinon/pz-masterpass';
9
9
  import { otpReducer } from '@akinon/pz-otp';
10
10
  import { savedCardReducer } from '@akinon/pz-saved-card';
11
+ import { iyzicoSavedCardReducer } from '@akinon/pz-iyzico-saved-card';
11
12
 
12
13
  const reducers = {
13
14
  [api.reducerPath]: api.reducer,
@@ -17,7 +18,8 @@ const reducers = {
17
18
  header: headerReducer,
18
19
  masterpass: masterpassReducer,
19
20
  otp: otpReducer,
20
- saved_card: savedCardReducer
21
+ savedCard: savedCardReducer,
22
+ iyzicoSavedCard: iyzicoSavedCardReducer
21
23
  };
22
24
 
23
25
  export default reducers;
@@ -86,6 +86,7 @@ export interface PreOrder {
86
86
  total_amount?: string;
87
87
  total_amount_with_interest?: string;
88
88
  unpaid_amount?: string;
89
+ notes?: string;
89
90
  user_phone_number?: string;
90
91
  loyalty_money?: string;
91
92
  currency_type_label?: string;
@@ -97,6 +98,7 @@ export interface PreOrder {
97
98
  gift_box?: GiftBox;
98
99
  credit_payment_option?: CheckoutCreditPaymentOption;
99
100
  data_source_shipping_options: any;
101
+ attribute_based_shipping_options: any;
100
102
  bags?: Product[];
101
103
  bags_fee?: string;
102
104
  extra_field?: ExtraField;
@@ -141,6 +143,7 @@ export interface CheckoutContext {
141
143
  redirect_url?: string;
142
144
  context_data?: any;
143
145
  balance?: string;
146
+ attribute_based_shipping_options?: AttributeBasedShippingOption[];
144
147
  [key: string]: any;
145
148
  };
146
149
  }
@@ -166,3 +169,13 @@ export interface BankAccount {
166
169
  pk: number;
167
170
  sort_order: number;
168
171
  }
172
+
173
+ export interface AttributeBasedShippingOption {
174
+ pk: number;
175
+ shipping_amount: string | null;
176
+ shipping_option_name: string | null;
177
+ shipping_option_logo: string | null;
178
+ attribute_value: string | null;
179
+ attribute_key: string;
180
+ [key: string]: any;
181
+ }