@akinon/next 1.59.0-rc.0 → 1.59.0-rc.2

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,18 @@
1
1
  # @akinon/next
2
2
 
3
+ ## 1.59.0-rc.2
4
+
5
+ ### Minor Changes
6
+
7
+ - c873740: ZERO-2903: add types
8
+ - 034b813: ZERO-2903: create saved card plugin
9
+
10
+ ## 1.59.0-rc.1
11
+
12
+ ### Minor Changes
13
+
14
+ - 907813c: ZERO-2934: add payment-gateway/<gateway> redirect in middleware
15
+
3
16
  ## 1.59.0-rc.0
4
17
 
5
18
  ### Minor Changes
@@ -19,7 +19,8 @@ enum Plugin {
19
19
  Masterpass = 'pz-masterpass',
20
20
  B2B = 'pz-b2b',
21
21
  Akifast = 'pz-akifast',
22
- MultiBasket = 'pz-multi-basket'
22
+ MultiBasket = 'pz-multi-basket',
23
+ SavedCard = 'pz-saved-card'
23
24
  }
24
25
 
25
26
  export enum Component {
@@ -43,7 +44,8 @@ export enum Component {
43
44
  BasketB2B = 'BasketB2b',
44
45
  AkifastQuickLoginButton = 'QuickLoginButton',
45
46
  AkifastCheckoutButton = 'CheckoutButton',
46
- MultiBasket = 'MultiBasket'
47
+ MultiBasket = 'MultiBasket',
48
+ SavedCard = 'SavedCardOption'
47
49
  }
48
50
 
49
51
  const PluginComponents = new Map([
@@ -75,7 +77,8 @@ const PluginComponents = new Map([
75
77
  Plugin.Akifast,
76
78
  [Component.AkifastQuickLoginButton, Component.AkifastCheckoutButton]
77
79
  ],
78
- [Plugin.MultiBasket, [Component.MultiBasket]]
80
+ [Plugin.MultiBasket, [Component.MultiBasket]],
81
+ [Plugin.SavedCard, [Component.SavedCard]]
79
82
  ]);
80
83
 
81
84
  const getPlugin = (component: Component) => {
@@ -138,6 +141,8 @@ export default function PluginModule({
138
141
  promise = import(`${'@akinon/pz-akifast'}`);
139
142
  } else if (plugin === Plugin.MultiBasket) {
140
143
  promise = import(`${'@akinon/pz-multi-basket'}`);
144
+ } else if (plugin === Plugin.SavedCard) {
145
+ promise = import(`${'@akinon/pz-saved-card'}`);
141
146
  }
142
147
  } catch (error) {
143
148
  logger.error(error);
@@ -17,7 +17,8 @@ const paymentTypeToView = {
17
17
  loyalty_money: 'loyalty',
18
18
  masterpass: 'credit-card',
19
19
  pay_on_delivery: 'pay-on-delivery',
20
- redirection: 'redirection'
20
+ redirection: 'redirection',
21
+ saved_card: 'saved-card'
21
22
  // Add other mappings as needed
22
23
  };
23
24
 
@@ -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 }) {
@@ -19,7 +19,8 @@ export const usePaymentOptions = () => {
19
19
  bkm_express: 'pz-bkm',
20
20
  credit_payment: 'pz-credit-payment',
21
21
  masterpass: 'pz-masterpass',
22
- gpay: 'pz-gpay'
22
+ gpay: 'pz-gpay',
23
+ saved_card: 'pz-saved-card'
23
24
  };
24
25
 
25
26
  const isInitialTypeIncluded = (type: string) => initialTypes.has(type);
@@ -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 '.';
@@ -140,6 +141,14 @@ const withPzDefault =
140
141
  );
141
142
  }
142
143
 
144
+ if (req.nextUrl.pathname.includes('/orders/saved-card-redirect')) {
145
+ return NextResponse.rewrite(
146
+ new URL(
147
+ `${encodeURI(Settings.commerceUrl)}/orders/saved-card-redirect/`
148
+ )
149
+ );
150
+ }
151
+
143
152
  // If commerce redirects to /orders/checkout/ without locale
144
153
  if (
145
154
  req.nextUrl.pathname.match(new RegExp('^/orders/checkout/$')) &&
@@ -157,6 +166,25 @@ const withPzDefault =
157
166
  return NextResponse.redirect(redirectUrlWithLocale, 303);
158
167
  }
159
168
 
169
+ // Dynamically handle any payment gateway without specifying names
170
+ const paymentGatewayRegex = new RegExp('^/payment-gateway/([^/]+)/$');
171
+ const gatewayMatch = req.nextUrl.pathname.match(paymentGatewayRegex);
172
+
173
+ if (
174
+ gatewayMatch && // Check if the URL matches the /payment-gateway/<gateway> pattern
175
+ getUrlPathWithLocale(
176
+ `/payment-gateway/${gatewayMatch[1]}/`,
177
+ req.cookies.get('pz-locale')?.value
178
+ ) !== req.nextUrl.pathname
179
+ ) {
180
+ const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
181
+ `/payment-gateway/${gatewayMatch[1]}/`,
182
+ req.cookies.get('pz-locale')?.value
183
+ )}?${req.nextUrl.searchParams.toString()}`;
184
+
185
+ return NextResponse.redirect(redirectUrlWithLocale);
186
+ }
187
+
160
188
  if (req.nextUrl.pathname.startsWith('/orders/checkout-provider/')) {
161
189
  try {
162
190
  const data = await req.json();
@@ -204,128 +232,141 @@ const withPzDefault =
204
232
  withUrlRedirection(
205
233
  withCompleteGpay(
206
234
  withCompleteMasterpass(
207
- async (req: PzNextRequest, event: NextFetchEvent) => {
208
- let middlewareResult: NextResponse | void =
209
- NextResponse.next();
210
-
211
- try {
212
- const { locale, prettyUrl, currency } =
213
- req.middlewareParams.rewrites;
214
- const { defaultLocaleValue } =
215
- Settings.localization;
216
- const url = req.nextUrl.clone();
217
- const pathnameWithoutLocale = url.pathname.replace(
218
- urlLocaleMatcherRegex,
219
- ''
220
- );
221
-
222
- middlewareResult = (await middleware(
223
- req,
224
- event
225
- )) as NextResponse | void;
226
-
227
- let customRewriteUrlDiff = '';
228
-
229
- if (
230
- middlewareResult instanceof NextResponse &&
231
- middlewareResult.headers.get(
232
- 'pz-override-response'
233
- ) &&
234
- middlewareResult.headers.get(
235
- 'x-middleware-rewrite'
236
- )
237
- ) {
238
- const rewriteUrl = new URL(
239
- middlewareResult.headers.get(
240
- 'x-middleware-rewrite'
241
- )
242
- );
243
- const originalUrl = new URL(req.url);
244
- customRewriteUrlDiff =
245
- rewriteUrl.pathname.replace(
246
- originalUrl.pathname,
247
- ''
248
- );
249
- }
235
+ withSavedCardRedirection(
236
+ async (req: PzNextRequest, event: NextFetchEvent) => {
237
+ let middlewareResult: NextResponse | void =
238
+ NextResponse.next();
250
239
 
251
- url.basePath = `/${commerceUrl}`;
252
- url.pathname = `/${
253
- locale.length ? `${locale}/` : ''
254
- }${currency}/${customRewriteUrlDiff}${
255
- prettyUrl ?? pathnameWithoutLocale
256
- }`.replace(/\/+/g, '/');
257
-
258
- if (
259
- !req.middlewareParams.found &&
260
- Settings.customNotFoundEnabled
261
- ) {
262
- let pathname = url.pathname
263
- .replace(/\/+$/, '')
264
- .split('/');
265
- url.pathname = url.pathname.replace(
266
- pathname.pop(),
267
- 'pz-not-found'
240
+ try {
241
+ const { locale, prettyUrl, currency } =
242
+ req.middlewareParams.rewrites;
243
+ const { defaultLocaleValue } =
244
+ Settings.localization;
245
+ const url = req.nextUrl.clone();
246
+ const pathnameWithoutLocale = url.pathname.replace(
247
+ urlLocaleMatcherRegex,
248
+ ''
268
249
  );
269
- }
270
250
 
271
- if (
272
- Settings.usePrettyUrlRoute &&
273
- url.searchParams.toString().length > 0 &&
274
- !Object.entries(ROUTES).find(([, value]) =>
275
- new RegExp(`^${value}/?$`).test(
276
- pathnameWithoutLocale
277
- )
278
- )
279
- ) {
280
- url.pathname =
281
- url.pathname +
282
- `searchparams|${url.searchParams.toString()}`;
283
- }
251
+ middlewareResult = (await middleware(
252
+ req,
253
+ event
254
+ )) as NextResponse | void;
284
255
 
285
- Settings.rewrites.forEach((rewrite) => {
286
- url.pathname = url.pathname.replace(
287
- rewrite.source,
288
- rewrite.destination
289
- );
290
- });
256
+ let customRewriteUrlDiff = '';
291
257
 
292
- // if middleware.ts has a return value for current url
293
- if (middlewareResult instanceof NextResponse) {
294
- // pz-override-response header is used to prevent 404 page for custom responses.
295
258
  if (
259
+ middlewareResult instanceof NextResponse &&
296
260
  middlewareResult.headers.get(
297
261
  'pz-override-response'
298
- ) !== 'true'
299
- ) {
300
- middlewareResult.headers.set(
301
- 'x-middleware-rewrite',
302
- url.href
303
- );
304
- } else if (
305
- middlewareResult.headers.get(
306
- 'x-middleware-rewrite'
307
262
  ) &&
308
263
  middlewareResult.headers.get(
309
- 'pz-override-response'
310
- ) === 'true'
264
+ 'x-middleware-rewrite'
265
+ )
311
266
  ) {
312
- middlewareResult = NextResponse.rewrite(url);
267
+ const rewriteUrl = new URL(
268
+ middlewareResult.headers.get(
269
+ 'x-middleware-rewrite'
270
+ )
271
+ );
272
+ const originalUrl = new URL(req.url);
273
+ customRewriteUrlDiff =
274
+ rewriteUrl.pathname.replace(
275
+ originalUrl.pathname,
276
+ ''
277
+ );
313
278
  }
314
- } else {
315
- // if middleware.ts doesn't have a return value.
316
- // e.g. NextResponse.next() doesn't exist in middleware.ts
317
279
 
318
- middlewareResult = NextResponse.rewrite(url);
319
- }
280
+ url.basePath = `/${commerceUrl}`;
281
+ url.pathname = `/${
282
+ locale.length ? `${locale}/` : ''
283
+ }${currency}/${customRewriteUrlDiff}${
284
+ prettyUrl ?? pathnameWithoutLocale
285
+ }`.replace(/\/+/g, '/');
286
+
287
+ if (
288
+ !req.middlewareParams.found &&
289
+ Settings.customNotFoundEnabled
290
+ ) {
291
+ let pathname = url.pathname
292
+ .replace(/\/+$/, '')
293
+ .split('/');
294
+ url.pathname = url.pathname.replace(
295
+ pathname.pop(),
296
+ 'pz-not-found'
297
+ );
298
+ }
320
299
 
321
- if (
322
- !url.pathname.startsWith(`/${currency}/orders`)
323
- ) {
300
+ if (
301
+ Settings.usePrettyUrlRoute &&
302
+ url.searchParams.toString().length > 0 &&
303
+ !Object.entries(ROUTES).find(([, value]) =>
304
+ new RegExp(`^${value}/?$`).test(
305
+ pathnameWithoutLocale
306
+ )
307
+ )
308
+ ) {
309
+ url.pathname =
310
+ url.pathname +
311
+ `searchparams|${url.searchParams.toString()}`;
312
+ }
313
+
314
+ Settings.rewrites.forEach((rewrite) => {
315
+ url.pathname = url.pathname.replace(
316
+ rewrite.source,
317
+ rewrite.destination
318
+ );
319
+ });
320
+
321
+ // if middleware.ts has a return value for current url
322
+ if (middlewareResult instanceof NextResponse) {
323
+ // pz-override-response header is used to prevent 404 page for custom responses.
324
+ if (
325
+ middlewareResult.headers.get(
326
+ 'pz-override-response'
327
+ ) !== 'true'
328
+ ) {
329
+ middlewareResult.headers.set(
330
+ 'x-middleware-rewrite',
331
+ url.href
332
+ );
333
+ } else if (
334
+ middlewareResult.headers.get(
335
+ 'x-middleware-rewrite'
336
+ ) &&
337
+ middlewareResult.headers.get(
338
+ 'pz-override-response'
339
+ ) === 'true'
340
+ ) {
341
+ middlewareResult = NextResponse.rewrite(url);
342
+ }
343
+ } else {
344
+ // if middleware.ts doesn't have a return value.
345
+ // e.g. NextResponse.next() doesn't exist in middleware.ts
346
+
347
+ middlewareResult = NextResponse.rewrite(url);
348
+ }
349
+
350
+ if (
351
+ !url.pathname.startsWith(`/${currency}/orders`)
352
+ ) {
353
+ middlewareResult.cookies.set(
354
+ 'pz-locale',
355
+ locale?.length > 0
356
+ ? locale
357
+ : defaultLocaleValue,
358
+ {
359
+ sameSite: 'none',
360
+ secure: true,
361
+ expires: new Date(
362
+ Date.now() + 1000 * 60 * 60 * 24 * 7
363
+ ) // 7 days
364
+ }
365
+ );
366
+ }
324
367
  middlewareResult.cookies.set(
325
- 'pz-locale',
326
- locale?.length > 0
327
- ? locale
328
- : defaultLocaleValue,
368
+ 'pz-currency',
369
+ currency,
329
370
  {
330
371
  sameSite: 'none',
331
372
  secure: true,
@@ -334,76 +375,65 @@ const withPzDefault =
334
375
  ) // 7 days
335
376
  }
336
377
  );
337
- }
338
- middlewareResult.cookies.set(
339
- 'pz-currency',
340
- currency,
341
- {
342
- sameSite: 'none',
343
- secure: true,
344
- expires: new Date(
345
- Date.now() + 1000 * 60 * 60 * 24 * 7
346
- ) // 7 days
347
- }
348
- );
349
-
350
- if (
351
- req.cookies.get('pz-locale') &&
352
- req.cookies.get('pz-locale').value !== locale
353
- ) {
354
- logger.debug('Locale changed', {
355
- locale,
356
- oldLocale: req.cookies.get('pz-locale')?.value,
357
- ip
358
- });
359
- }
360
-
361
- middlewareResult.headers.set(
362
- 'pz-url',
363
- req.nextUrl.toString()
364
- );
365
378
 
366
- if (req.cookies.get('pz-set-currency')) {
367
- middlewareResult.cookies.delete(
368
- 'pz-set-currency'
369
- );
370
- }
379
+ if (
380
+ req.cookies.get('pz-locale') &&
381
+ req.cookies.get('pz-locale').value !== locale
382
+ ) {
383
+ logger.debug('Locale changed', {
384
+ locale,
385
+ oldLocale: req.cookies.get('pz-locale')?.value,
386
+ ip
387
+ });
388
+ }
371
389
 
372
- if (process.env.ACC_APP_VERSION) {
373
390
  middlewareResult.headers.set(
374
- 'acc-app-version',
375
- process.env.ACC_APP_VERSION
391
+ 'pz-url',
392
+ req.nextUrl.toString()
376
393
  );
377
- }
378
394
 
379
- // Set CSRF token if not set
380
- try {
381
- const url = `${Settings.commerceUrl}${user.csrfToken}`;
395
+ if (req.cookies.get('pz-set-currency')) {
396
+ middlewareResult.cookies.delete(
397
+ 'pz-set-currency'
398
+ );
399
+ }
382
400
 
383
- if (!req.cookies.get('csrftoken')) {
384
- const { csrf_token } = await (
385
- await fetch(url)
386
- ).json();
387
- middlewareResult.cookies.set(
388
- 'csrftoken',
389
- csrf_token
401
+ if (process.env.ACC_APP_VERSION) {
402
+ middlewareResult.headers.set(
403
+ 'acc-app-version',
404
+ process.env.ACC_APP_VERSION
390
405
  );
391
406
  }
407
+
408
+ // Set CSRF token if not set
409
+ try {
410
+ const url = `${Settings.commerceUrl}${user.csrfToken}`;
411
+
412
+ if (!req.cookies.get('csrftoken')) {
413
+ const { csrf_token } = await (
414
+ await fetch(url)
415
+ ).json();
416
+ middlewareResult.cookies.set(
417
+ 'csrftoken',
418
+ csrf_token
419
+ );
420
+ }
421
+ } catch (error) {
422
+ logger.error('CSRF Error', {
423
+ error,
424
+ ip
425
+ });
426
+ }
392
427
  } catch (error) {
393
- logger.error('CSRF Error', {
428
+ logger.error('withPzDefault Error', {
394
429
  error,
395
430
  ip
396
431
  });
397
432
  }
398
- } catch (error) {
399
- logger.error('withPzDefault Error', {
400
- error,
401
- ip
402
- });
403
- }
404
433
 
405
- return middlewareResult;
406
- }
434
+ return middlewareResult;
435
+ }
436
+ )
407
437
  )
408
438
  )
409
439
  )
@@ -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 {
@@ -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: `osessionid=${req.cookies.get('osessionid')?.value ?? ''}`,
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;
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.59.0-rc.0",
4
+ "version": "1.59.0-rc.2",
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.59.0-rc.0",
33
+ "@akinon/eslint-plugin-projectzero": "1.59.0-rc.2",
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
@@ -23,3 +23,8 @@ declare module '@akinon/pz-otp/src/redux/reducer' {
23
23
  export const showPopup: any;
24
24
  export const hidePopup: any;
25
25
  }
26
+
27
+ declare module '@akinon/pz-saved-card' {
28
+ export const savedCardReducer: any;
29
+ export const SavedCardOption: any;
30
+ }
package/plugins.js CHANGED
@@ -12,5 +12,6 @@ module.exports = [
12
12
  'pz-b2b',
13
13
  'pz-akifast',
14
14
  'pz-multi-basket',
15
- 'pz-tabby-extension'
15
+ 'pz-tabby-extension',
16
+ 'pz-saved-card'
16
17
  ];
@@ -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,15 +19,12 @@ import {
16
19
  setPreOrder,
17
20
  setRetailStores,
18
21
  setShippingOptions,
19
- setDataSourceShippingOptions,
20
- setShippingStepCompleted,
21
- setCreditPaymentOptions,
22
- setAttributeBasedShippingOptions
22
+ setShippingStepCompleted
23
23
  } from '../../redux/reducers/checkout';
24
24
  import { RootState, TypedDispatch } from 'redux/store';
25
25
  import { checkoutApi } from '../../data/client/checkout';
26
26
  import { CheckoutContext, PreOrder } from '../../types';
27
- import { getCookie, setCookie } from '../../utils';
27
+ import { getCookie } from '../../utils';
28
28
  import settings from 'settings';
29
29
  import { LocaleUrlStrategy } from '../../localization';
30
30
  import { showMobile3dIframe } from '../../utils/mobile-3d-iframe';
@@ -169,7 +169,11 @@ export const preOrderMiddleware: Middleware = ({
169
169
  dispatch(apiEndpoints.setPaymentOption.initiate(paymentOptions[0].pk));
170
170
  }
171
171
 
172
- if (!preOrder.installment && installmentOptions.length > 0) {
172
+ if (
173
+ !preOrder.installment &&
174
+ preOrder.payment_option.payment_type !== 'saved_card' &&
175
+ installmentOptions.length > 0
176
+ ) {
173
177
  dispatch(
174
178
  apiEndpoints.setInstallmentOption.initiate(installmentOptions[0].pk)
175
179
  );
@@ -7,6 +7,7 @@ import { api } from '../../data/client/api';
7
7
  // Plugin reducers
8
8
  import { masterpassReducer } from '@akinon/pz-masterpass';
9
9
  import { otpReducer } from '@akinon/pz-otp';
10
+ import { savedCardReducer } from '@akinon/pz-saved-card';
10
11
 
11
12
  const reducers = {
12
13
  [api.reducerPath]: api.reducer,
@@ -15,7 +16,8 @@ const reducers = {
15
16
  config: configReducer,
16
17
  header: headerReducer,
17
18
  masterpass: masterpassReducer,
18
- otp: otpReducer
19
+ otp: otpReducer,
20
+ savedCard: savedCardReducer
19
21
  };
20
22
 
21
23
  export default reducers;