@akinon/next 1.111.0-rc.16 → 1.111.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 +3 -8
- package/components/plugin-module.tsx +12 -28
- package/components/selected-payment-option-view.tsx +1 -0
- package/data/urls.ts +1 -5
- package/hooks/use-payment-options.ts +2 -1
- package/middlewares/default.ts +217 -208
- package/middlewares/index.ts +3 -1
- package/middlewares/masterpass-rest-callback.ts +218 -0
- package/package.json +2 -2
- package/plugins.d.ts +5 -10
- package/plugins.js +2 -2
- package/redux/middlewares/checkout.ts +4 -1
- package/redux/middlewares/pre-order/installment-option.ts +1 -0
- package/redux/reducers/index.ts +3 -1
- package/types/index.ts +0 -6
- package/utils/mobile-3d-iframe.ts +2 -8
- package/utils/redirection-iframe.ts +2 -8
- package/api/image-proxy.ts +0 -75
- package/api/similar-product-list.ts +0 -63
- package/api/similar-products.ts +0 -111
package/CHANGELOG.md
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
# @akinon/next
|
|
2
2
|
|
|
3
|
-
## 1.111.0
|
|
3
|
+
## 1.111.0
|
|
4
4
|
|
|
5
5
|
### Minor Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
- 143be2b: ZERO-3457: Crop styles are customizable and logic improved for rendering similar products modal
|
|
10
|
-
- 9f8cd3bc: ZERO-3449: AI Search Active Filters & Crop Style changes have been implemented
|
|
11
|
-
- d99a6a7: ZERO-3457_1: Fixed the settings prop and made sure everything is customizable.
|
|
12
|
-
- 4de5303c: ZERO-2504: add cookie filter to api client request
|
|
13
|
-
- 95b139d: ZERO-3795: Remove duplicate entry for SavedCard in PluginComponents map
|
|
7
|
+
- c026300: ZERO-3833: fix masterpass rest conflicts
|
|
8
|
+
- b47b9d8: ZERO-3414: create masterpass-rest package
|
|
14
9
|
|
|
15
10
|
## 1.110.0
|
|
16
11
|
|
|
@@ -24,7 +24,7 @@ enum Plugin {
|
|
|
24
24
|
FlowPayment = 'pz-flow-payment',
|
|
25
25
|
VirtualTryOn = 'pz-virtual-try-on',
|
|
26
26
|
Hepsipay = 'pz-hepsipay',
|
|
27
|
-
|
|
27
|
+
MasterpassRest = 'pz-masterpass-rest'
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export enum Component {
|
|
@@ -51,16 +51,10 @@ export enum Component {
|
|
|
51
51
|
MultiBasket = 'MultiBasket',
|
|
52
52
|
SavedCard = 'SavedCardOption',
|
|
53
53
|
VirtualTryOnPlugin = 'VirtualTryOnPlugin',
|
|
54
|
-
SimilarProductsModal = 'SimilarProductsModal',
|
|
55
|
-
SimilarProductsFilterSidebar = 'SimilarProductsFilterSidebar',
|
|
56
|
-
SimilarProductsResultsGrid = 'SimilarProductsResultsGrid',
|
|
57
|
-
SimilarProductsPlugin = 'SimilarProductsPlugin',
|
|
58
|
-
ProductImageSearchFeature = 'ProductImageSearchFeature',
|
|
59
|
-
ImageSearchButton = 'ImageSearchButton',
|
|
60
|
-
HeaderImageSearchFeature = 'HeaderImageSearchFeature',
|
|
61
54
|
IyzicoSavedCard = 'IyzicoSavedCardOption',
|
|
62
55
|
Hepsipay = 'Hepsipay',
|
|
63
|
-
FlowPayment = 'FlowPayment'
|
|
56
|
+
FlowPayment = 'FlowPayment',
|
|
57
|
+
MasterpassRest = 'MasterpassRestOption'
|
|
64
58
|
}
|
|
65
59
|
|
|
66
60
|
const PluginComponents = new Map([
|
|
@@ -93,22 +87,12 @@ const PluginComponents = new Map([
|
|
|
93
87
|
[Component.AkifastQuickLoginButton, Component.AkifastCheckoutButton]
|
|
94
88
|
],
|
|
95
89
|
[Plugin.MultiBasket, [Component.MultiBasket]],
|
|
96
|
-
[
|
|
97
|
-
Plugin.SimilarProducts,
|
|
98
|
-
[
|
|
99
|
-
Component.SimilarProductsModal,
|
|
100
|
-
Component.SimilarProductsFilterSidebar,
|
|
101
|
-
Component.SimilarProductsResultsGrid,
|
|
102
|
-
Component.SimilarProductsPlugin,
|
|
103
|
-
Component.ProductImageSearchFeature,
|
|
104
|
-
Component.ImageSearchButton,
|
|
105
|
-
Component.HeaderImageSearchFeature
|
|
106
|
-
]
|
|
107
|
-
],
|
|
108
90
|
[Plugin.SavedCard, [Component.SavedCard, Component.IyzicoSavedCard]],
|
|
91
|
+
[Plugin.SavedCard, [Component.SavedCard]],
|
|
109
92
|
[Plugin.FlowPayment, [Component.FlowPayment]],
|
|
110
93
|
[Plugin.VirtualTryOn, [Component.VirtualTryOnPlugin]],
|
|
111
|
-
[Plugin.Hepsipay, [Component.Hepsipay]]
|
|
94
|
+
[Plugin.Hepsipay, [Component.Hepsipay]],
|
|
95
|
+
[Plugin.MasterpassRest, [Component.MasterpassRest]]
|
|
112
96
|
]);
|
|
113
97
|
|
|
114
98
|
const getPlugin = (component: Component) => {
|
|
@@ -120,10 +104,10 @@ const getPlugin = (component: Component) => {
|
|
|
120
104
|
};
|
|
121
105
|
|
|
122
106
|
export default function PluginModule({
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}: {
|
|
107
|
+
component,
|
|
108
|
+
props,
|
|
109
|
+
children
|
|
110
|
+
}: {
|
|
127
111
|
component: Component;
|
|
128
112
|
props?: any;
|
|
129
113
|
children?: React.ReactNode;
|
|
@@ -173,14 +157,14 @@ export default function PluginModule({
|
|
|
173
157
|
promise = import(`${'@akinon/pz-multi-basket'}`);
|
|
174
158
|
} else if (plugin === Plugin.SavedCard) {
|
|
175
159
|
promise = import(`${'@akinon/pz-saved-card'}`);
|
|
176
|
-
} else if (plugin === Plugin.SimilarProducts) {
|
|
177
|
-
promise = import(`${'@akinon/pz-similar-products'}`);
|
|
178
160
|
} else if (plugin === Plugin.Hepsipay) {
|
|
179
161
|
promise = import(`${'@akinon/pz-hepsipay'}`);
|
|
180
162
|
} else if (plugin === Plugin.FlowPayment) {
|
|
181
163
|
promise = import(`${'@akinon/pz-flow-payment'}`);
|
|
182
164
|
} else if (plugin === Plugin.VirtualTryOn) {
|
|
183
165
|
promise = import(`${'@akinon/pz-virtual-try-on'}`);
|
|
166
|
+
} else if (plugin === Plugin.MasterpassRest) {
|
|
167
|
+
promise = import(`${'@akinon/pz-masterpass-rest'}`);
|
|
184
168
|
}
|
|
185
169
|
} catch (error) {
|
|
186
170
|
logger.error(error);
|
package/data/urls.ts
CHANGED
|
@@ -183,11 +183,7 @@ export const product = {
|
|
|
183
183
|
breadcrumbUrl: (menuitemmodel: string) =>
|
|
184
184
|
`/menus/generate_breadcrumb/?item=${menuitemmodel}&generator_name=menu_item`,
|
|
185
185
|
bundleProduct: (productPk: string, queryString: string) =>
|
|
186
|
-
`/bundle-product/${productPk}/?${queryString}
|
|
187
|
-
similarProducts: (params?: string) =>
|
|
188
|
-
`/similar-products${params ? `?${params}` : ''}`,
|
|
189
|
-
similarProductsList: (params?: string) =>
|
|
190
|
-
`/similar-product-list${params ? `?${params}` : ''}`
|
|
186
|
+
`/bundle-product/${productPk}/?${queryString}`
|
|
191
187
|
};
|
|
192
188
|
|
|
193
189
|
export const wishlist = {
|
|
@@ -21,7 +21,8 @@ export const usePaymentOptions = () => {
|
|
|
21
21
|
credit_payment: 'pz-credit-payment',
|
|
22
22
|
masterpass: 'pz-masterpass',
|
|
23
23
|
saved_card: 'pz-saved-card',
|
|
24
|
-
gpay: 'pz-gpay'
|
|
24
|
+
gpay: 'pz-gpay',
|
|
25
|
+
masterpass_rest: 'pz-masterpass-rest'
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
const isInitialTypeIncluded = (type: string) => initialTypes.has(type);
|
package/middlewares/default.ts
CHANGED
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
withThreeDRedirection,
|
|
14
14
|
withUrlRedirection,
|
|
15
15
|
withCompleteWallet,
|
|
16
|
-
withWalletCompleteRedirection
|
|
16
|
+
withWalletCompleteRedirection,
|
|
17
|
+
withMasterpassRestCallback
|
|
17
18
|
} from '.';
|
|
18
19
|
import { urlLocaleMatcherRegex } from '../utils';
|
|
19
20
|
import withCurrency from './currency';
|
|
@@ -230,226 +231,235 @@ const withPzDefault =
|
|
|
230
231
|
withCompleteMasterpass(
|
|
231
232
|
withSavedCardRedirection(
|
|
232
233
|
withCompleteWallet(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
234
|
+
withWalletCompleteRedirection(
|
|
235
|
+
withMasterpassRestCallback(
|
|
236
|
+
async (
|
|
237
|
+
req: PzNextRequest,
|
|
238
|
+
event: NextFetchEvent
|
|
239
|
+
) => {
|
|
240
|
+
let middlewareResult: NextResponse | void =
|
|
241
|
+
NextResponse.next();
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
const { locale, prettyUrl, currency } =
|
|
245
|
+
req.middlewareParams.rewrites;
|
|
246
|
+
const { defaultLocaleValue } =
|
|
247
|
+
Settings.localization;
|
|
248
|
+
const url = req.nextUrl.clone();
|
|
249
|
+
const pathnameWithoutLocale =
|
|
250
|
+
url.pathname.replace(
|
|
251
|
+
urlLocaleMatcherRegex,
|
|
252
|
+
''
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
middlewareResult = (await middleware(
|
|
256
|
+
req,
|
|
257
|
+
event
|
|
258
|
+
)) as NextResponse | void;
|
|
259
|
+
|
|
260
|
+
let customRewriteUrlDiff = '';
|
|
261
|
+
|
|
262
|
+
if (
|
|
263
|
+
middlewareResult instanceof
|
|
264
|
+
NextResponse &&
|
|
265
|
+
middlewareResult.headers.get(
|
|
266
|
+
'pz-override-response'
|
|
267
|
+
) &&
|
|
268
|
+
middlewareResult.headers.get(
|
|
269
|
+
'x-middleware-rewrite'
|
|
270
|
+
)
|
|
271
|
+
) {
|
|
272
|
+
const rewriteUrl = new URL(
|
|
273
|
+
middlewareResult.headers.get(
|
|
274
|
+
'x-middleware-rewrite'
|
|
275
|
+
)
|
|
276
|
+
);
|
|
277
|
+
const originalUrl = new URL(req.url);
|
|
278
|
+
customRewriteUrlDiff =
|
|
279
|
+
rewriteUrl.pathname.replace(
|
|
280
|
+
originalUrl.pathname,
|
|
281
|
+
''
|
|
282
|
+
);
|
|
283
|
+
}
|
|
281
284
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
285
|
+
url.basePath = `/${commerceUrl}`;
|
|
286
|
+
url.pathname = `/${
|
|
287
|
+
locale.length ? `${locale}/` : ''
|
|
288
|
+
}${currency}/${customRewriteUrlDiff}${
|
|
289
|
+
prettyUrl ?? pathnameWithoutLocale
|
|
290
|
+
}`.replace(/\/+/g, '/');
|
|
291
|
+
|
|
292
|
+
if (
|
|
293
|
+
Settings.usePrettyUrlRoute &&
|
|
294
|
+
url.searchParams.toString().length > 0 &&
|
|
295
|
+
!Object.entries(ROUTES).find(
|
|
296
|
+
([, value]) =>
|
|
297
|
+
new RegExp(`^${value}/?$`).test(
|
|
298
|
+
pathnameWithoutLocale
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
) {
|
|
302
|
+
url.pathname =
|
|
303
|
+
url.pathname +
|
|
304
|
+
(/\/$/.test(url.pathname) ? '' : '/') +
|
|
305
|
+
`searchparams|${encodeURIComponent(
|
|
306
|
+
url.searchParams.toString()
|
|
307
|
+
)}`;
|
|
308
|
+
}
|
|
305
309
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
310
|
+
Settings.rewrites.forEach((rewrite) => {
|
|
311
|
+
url.pathname = url.pathname.replace(
|
|
312
|
+
rewrite.source,
|
|
313
|
+
rewrite.destination
|
|
314
|
+
);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// if middleware.ts has a return value for current url
|
|
318
|
+
if (
|
|
319
|
+
middlewareResult instanceof NextResponse
|
|
320
|
+
) {
|
|
321
|
+
// pz-override-response header is used to prevent 404 page for custom responses.
|
|
322
|
+
if (
|
|
323
|
+
middlewareResult.headers.get(
|
|
324
|
+
'pz-override-response'
|
|
325
|
+
) !== 'true'
|
|
326
|
+
) {
|
|
327
|
+
middlewareResult.headers.set(
|
|
328
|
+
'x-middleware-rewrite',
|
|
329
|
+
url.href
|
|
330
|
+
);
|
|
331
|
+
} else if (
|
|
332
|
+
middlewareResult.headers.get(
|
|
333
|
+
'x-middleware-rewrite'
|
|
334
|
+
) &&
|
|
335
|
+
middlewareResult.headers.get(
|
|
336
|
+
'pz-override-response'
|
|
337
|
+
) === 'true'
|
|
338
|
+
) {
|
|
339
|
+
middlewareResult =
|
|
340
|
+
NextResponse.rewrite(url);
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
// if middleware.ts doesn't have a return value.
|
|
344
|
+
// e.g. NextResponse.next() doesn't exist in middleware.ts
|
|
339
345
|
|
|
340
|
-
|
|
341
|
-
|
|
346
|
+
middlewareResult =
|
|
347
|
+
NextResponse.rewrite(url);
|
|
348
|
+
}
|
|
342
349
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
350
|
+
const { localeUrlStrategy } =
|
|
351
|
+
Settings.localization;
|
|
352
|
+
|
|
353
|
+
const fallbackHost =
|
|
354
|
+
req.headers.get('x-forwarded-host') ||
|
|
355
|
+
req.headers.get('host');
|
|
356
|
+
const hostname =
|
|
357
|
+
process.env.NEXT_PUBLIC_URL ||
|
|
358
|
+
`https://${fallbackHost}`;
|
|
359
|
+
const rootHostname =
|
|
360
|
+
localeUrlStrategy ===
|
|
361
|
+
LocaleUrlStrategy.Subdomain
|
|
362
|
+
? getRootHostname(hostname)
|
|
363
|
+
: null;
|
|
364
|
+
|
|
365
|
+
if (
|
|
366
|
+
!url.pathname.startsWith(
|
|
367
|
+
`/${currency}/orders`
|
|
368
|
+
)
|
|
369
|
+
) {
|
|
370
|
+
middlewareResult.cookies.set(
|
|
371
|
+
'pz-locale',
|
|
372
|
+
locale?.length > 0
|
|
373
|
+
? locale
|
|
374
|
+
: defaultLocaleValue,
|
|
375
|
+
{
|
|
376
|
+
domain: rootHostname,
|
|
377
|
+
sameSite: 'none',
|
|
378
|
+
secure: true,
|
|
379
|
+
expires: new Date(
|
|
380
|
+
Date.now() + 1000 * 60 * 60 * 24 * 7
|
|
381
|
+
) // 7 days
|
|
382
|
+
}
|
|
383
|
+
);
|
|
375
384
|
}
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
385
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
if (
|
|
393
|
-
req.cookies.get('pz-locale') &&
|
|
394
|
-
req.cookies.get('pz-locale').value !== locale
|
|
395
|
-
) {
|
|
396
|
-
logger.debug('Locale changed', {
|
|
397
|
-
locale,
|
|
398
|
-
oldLocale:
|
|
399
|
-
req.cookies.get('pz-locale')?.value,
|
|
400
|
-
ip
|
|
401
|
-
});
|
|
402
|
-
}
|
|
386
|
+
middlewareResult.cookies.set(
|
|
387
|
+
'pz-currency',
|
|
388
|
+
currency,
|
|
389
|
+
{
|
|
390
|
+
domain: rootHostname,
|
|
391
|
+
sameSite: 'none',
|
|
392
|
+
secure: true,
|
|
393
|
+
expires: new Date(
|
|
394
|
+
Date.now() + 1000 * 60 * 60 * 24 * 7
|
|
395
|
+
) // 7 days
|
|
396
|
+
}
|
|
397
|
+
);
|
|
403
398
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
399
|
+
if (
|
|
400
|
+
req.cookies.get('pz-locale') &&
|
|
401
|
+
req.cookies.get('pz-locale').value !==
|
|
402
|
+
locale
|
|
403
|
+
) {
|
|
404
|
+
logger.debug('Locale changed', {
|
|
405
|
+
locale,
|
|
406
|
+
oldLocale:
|
|
407
|
+
req.cookies.get('pz-locale')?.value,
|
|
408
|
+
ip
|
|
409
|
+
});
|
|
410
|
+
}
|
|
408
411
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
412
|
+
middlewareResult.headers.set(
|
|
413
|
+
'pz-url',
|
|
414
|
+
req.nextUrl.toString()
|
|
415
|
+
);
|
|
414
416
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
417
|
+
if (req.cookies.get('pz-set-currency')) {
|
|
418
|
+
middlewareResult.cookies.delete(
|
|
419
|
+
'pz-set-currency'
|
|
420
|
+
);
|
|
421
|
+
}
|
|
421
422
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
423
|
+
if (process.env.ACC_APP_VERSION) {
|
|
424
|
+
middlewareResult.headers.set(
|
|
425
|
+
'acc-app-version',
|
|
426
|
+
process.env.ACC_APP_VERSION
|
|
427
|
+
);
|
|
428
|
+
}
|
|
425
429
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
430
|
+
// Set CSRF token if not set
|
|
431
|
+
try {
|
|
432
|
+
const url = `${Settings.commerceUrl}${user.csrfToken}`;
|
|
433
|
+
|
|
434
|
+
if (!req.cookies.get('csrftoken')) {
|
|
435
|
+
const { csrf_token } = await (
|
|
436
|
+
await fetch(url)
|
|
437
|
+
).json();
|
|
438
|
+
middlewareResult.cookies.set(
|
|
439
|
+
'csrftoken',
|
|
440
|
+
csrf_token,
|
|
441
|
+
{
|
|
442
|
+
domain: rootHostname
|
|
443
|
+
}
|
|
444
|
+
);
|
|
435
445
|
}
|
|
436
|
-
)
|
|
446
|
+
} catch (error) {
|
|
447
|
+
logger.error('CSRF Error', {
|
|
448
|
+
error,
|
|
449
|
+
ip
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
} catch (error) {
|
|
453
|
+
logger.error('withPzDefault Error', {
|
|
454
|
+
error,
|
|
455
|
+
ip
|
|
456
|
+
});
|
|
437
457
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
error,
|
|
441
|
-
ip
|
|
442
|
-
});
|
|
458
|
+
|
|
459
|
+
return middlewareResult;
|
|
443
460
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
error,
|
|
447
|
-
ip
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
return middlewareResult;
|
|
452
|
-
}
|
|
461
|
+
)
|
|
462
|
+
)
|
|
453
463
|
)
|
|
454
464
|
)
|
|
455
465
|
)
|
|
@@ -461,7 +471,6 @@ const withPzDefault =
|
|
|
461
471
|
)
|
|
462
472
|
)
|
|
463
473
|
)
|
|
464
|
-
)
|
|
465
474
|
)(req, event);
|
|
466
475
|
};
|
|
467
476
|
|
package/middlewares/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import withCheckoutProvider from './checkout-provider';
|
|
|
11
11
|
import withSavedCardRedirection from './saved-card-redirection';
|
|
12
12
|
import withCompleteWallet from './complete-wallet';
|
|
13
13
|
import withWalletCompleteRedirection from './wallet-complete-redirection';
|
|
14
|
+
import withMasterpassRestCallback from './masterpass-rest-callback';
|
|
14
15
|
import { NextRequest } from 'next/server';
|
|
15
16
|
|
|
16
17
|
export {
|
|
@@ -26,7 +27,8 @@ export {
|
|
|
26
27
|
withCheckoutProvider,
|
|
27
28
|
withSavedCardRedirection,
|
|
28
29
|
withCompleteWallet,
|
|
29
|
-
withWalletCompleteRedirection
|
|
30
|
+
withWalletCompleteRedirection,
|
|
31
|
+
withMasterpassRestCallback
|
|
30
32
|
};
|
|
31
33
|
|
|
32
34
|
export interface PzNextRequest extends NextRequest {
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { NextFetchEvent, NextMiddleware, NextResponse } from 'next/server';
|
|
2
|
+
import Settings from 'settings';
|
|
3
|
+
import logger from '../utils/log';
|
|
4
|
+
import { getUrlPathWithLocale } from '../utils/localization';
|
|
5
|
+
import { PzNextRequest } from '.';
|
|
6
|
+
|
|
7
|
+
const withMasterpassRestCallback =
|
|
8
|
+
(middleware: NextMiddleware) =>
|
|
9
|
+
async (req: PzNextRequest, event: NextFetchEvent) => {
|
|
10
|
+
const url = req.nextUrl.clone();
|
|
11
|
+
const ip = req.headers.get('x-forwarded-for') ?? '';
|
|
12
|
+
const sessionId = req.cookies.get('osessionid');
|
|
13
|
+
|
|
14
|
+
if (!url.pathname.includes('/orders/masterpass-rest-callback')) {
|
|
15
|
+
return middleware(req, event);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (req.method !== 'POST') {
|
|
19
|
+
logger.warn('Invalid request method for masterpass REST callback', {
|
|
20
|
+
middleware: 'masterpass-rest-callback',
|
|
21
|
+
method: req.method,
|
|
22
|
+
ip
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return NextResponse.redirect(
|
|
26
|
+
`${url.origin}${getUrlPathWithLocale(
|
|
27
|
+
'/orders/checkout/',
|
|
28
|
+
req.cookies.get('pz-locale')?.value
|
|
29
|
+
)}`,
|
|
30
|
+
303
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const responseCode = url.searchParams.get('responseCode');
|
|
35
|
+
const token = url.searchParams.get('token');
|
|
36
|
+
|
|
37
|
+
if (!responseCode || !token) {
|
|
38
|
+
logger.warn('Missing required parameters for masterpass REST callback', {
|
|
39
|
+
middleware: 'masterpass-rest-callback',
|
|
40
|
+
responseCode,
|
|
41
|
+
token,
|
|
42
|
+
ip
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return NextResponse.redirect(
|
|
46
|
+
`${url.origin}${getUrlPathWithLocale(
|
|
47
|
+
'/orders/checkout/',
|
|
48
|
+
req.cookies.get('pz-locale')?.value
|
|
49
|
+
)}`,
|
|
50
|
+
303
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const formData = await req.formData();
|
|
56
|
+
const body: Record<string, string> = {};
|
|
57
|
+
|
|
58
|
+
Array.from(formData.entries()).forEach(([key, value]) => {
|
|
59
|
+
body[key] = value.toString();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!sessionId) {
|
|
63
|
+
logger.warn(
|
|
64
|
+
'Make sure that the SESSION_COOKIE_SAMESITE environment variable is set to None in Commerce.',
|
|
65
|
+
{
|
|
66
|
+
middleware: 'masterpass-rest-callback',
|
|
67
|
+
ip
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return NextResponse.redirect(
|
|
72
|
+
`${url.origin}${getUrlPathWithLocale(
|
|
73
|
+
'/orders/checkout/',
|
|
74
|
+
req.cookies.get('pz-locale')?.value
|
|
75
|
+
)}`,
|
|
76
|
+
303
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const requestUrl = new URL('/orders/checkout/', Settings.commerceUrl);
|
|
81
|
+
requestUrl.searchParams.set('page', 'MasterpassRestCompletePage');
|
|
82
|
+
requestUrl.searchParams.set('responseCode', responseCode);
|
|
83
|
+
requestUrl.searchParams.set('token', token);
|
|
84
|
+
requestUrl.searchParams.set(
|
|
85
|
+
'three_d_secure',
|
|
86
|
+
body.transactionType?.includes('3D') ? 'true' : 'false'
|
|
87
|
+
);
|
|
88
|
+
requestUrl.searchParams.set(
|
|
89
|
+
'transactionType',
|
|
90
|
+
body.transactionType || ''
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const requestHeaders = {
|
|
94
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
95
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
96
|
+
Cookie: req.headers.get('cookie') ?? '',
|
|
97
|
+
'x-currency': req.cookies.get('pz-currency')?.value ?? '',
|
|
98
|
+
'x-forwarded-for': ip,
|
|
99
|
+
'User-Agent': req.headers.get('user-agent') ?? ''
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const request = await fetch(requestUrl.toString(), {
|
|
103
|
+
method: 'POST',
|
|
104
|
+
headers: requestHeaders,
|
|
105
|
+
body: new URLSearchParams(body)
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
logger.info('Masterpass REST callback request', {
|
|
109
|
+
requestUrl: requestUrl.toString(),
|
|
110
|
+
status: request.status,
|
|
111
|
+
requestHeaders,
|
|
112
|
+
ip
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const response = await request.json();
|
|
116
|
+
|
|
117
|
+
const { context_list: contextList, errors } = response;
|
|
118
|
+
|
|
119
|
+
let redirectUrl = response.redirect_url;
|
|
120
|
+
|
|
121
|
+
if (!redirectUrl && contextList && contextList.length > 0) {
|
|
122
|
+
for (const context of contextList) {
|
|
123
|
+
if (context.page_context && context.page_context.redirect_url) {
|
|
124
|
+
redirectUrl = context.page_context.redirect_url;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (errors && Object.keys(errors).length) {
|
|
131
|
+
logger.error('Error while processing masterpass REST callback', {
|
|
132
|
+
middleware: 'masterpass-rest-callback',
|
|
133
|
+
errors,
|
|
134
|
+
requestHeaders,
|
|
135
|
+
ip
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return NextResponse.redirect(
|
|
139
|
+
`${url.origin}${getUrlPathWithLocale(
|
|
140
|
+
'/orders/checkout/',
|
|
141
|
+
req.cookies.get('pz-locale')?.value
|
|
142
|
+
)}`,
|
|
143
|
+
{
|
|
144
|
+
status: 303,
|
|
145
|
+
headers: {
|
|
146
|
+
'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
logger.info('Masterpass REST callback response', {
|
|
153
|
+
middleware: 'masterpass-rest-callback',
|
|
154
|
+
contextList,
|
|
155
|
+
redirectUrl,
|
|
156
|
+
ip
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (!redirectUrl) {
|
|
160
|
+
logger.warn(
|
|
161
|
+
'No redirection url found in response. Redirecting to checkout page.',
|
|
162
|
+
{
|
|
163
|
+
middleware: 'masterpass-rest-callback',
|
|
164
|
+
requestHeaders,
|
|
165
|
+
response: JSON.stringify(response),
|
|
166
|
+
ip
|
|
167
|
+
}
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
|
|
171
|
+
'/orders/checkout/',
|
|
172
|
+
req.cookies.get('pz-locale')?.value
|
|
173
|
+
)}`;
|
|
174
|
+
|
|
175
|
+
return NextResponse.redirect(redirectUrlWithLocale, 303);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
|
|
179
|
+
redirectUrl,
|
|
180
|
+
req.cookies.get('pz-locale')?.value
|
|
181
|
+
)}`;
|
|
182
|
+
|
|
183
|
+
logger.info('Redirecting after masterpass REST callback', {
|
|
184
|
+
middleware: 'masterpass-rest-callback',
|
|
185
|
+
redirectUrlWithLocale,
|
|
186
|
+
ip
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
|
|
190
|
+
|
|
191
|
+
nextResponse.headers.set(
|
|
192
|
+
'Set-Cookie',
|
|
193
|
+
request.headers.get('set-cookie') ?? ''
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
return nextResponse;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
logger.error('Error while processing masterpass REST callback', {
|
|
199
|
+
middleware: 'masterpass-rest-callback',
|
|
200
|
+
error,
|
|
201
|
+
requestHeaders: {
|
|
202
|
+
Cookie: req.headers.get('cookie') ?? '',
|
|
203
|
+
'x-currency': req.cookies.get('pz-currency')?.value ?? ''
|
|
204
|
+
},
|
|
205
|
+
ip
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
return NextResponse.redirect(
|
|
209
|
+
`${url.origin}${getUrlPathWithLocale(
|
|
210
|
+
'/orders/checkout/',
|
|
211
|
+
req.cookies.get('pz-locale')?.value
|
|
212
|
+
)}`,
|
|
213
|
+
303
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export default withMasterpassRestCallback;
|
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.111.0
|
|
4
|
+
"version": "1.111.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"set-cookie-parser": "2.6.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@akinon/eslint-plugin-projectzero": "1.111.0
|
|
38
|
+
"@akinon/eslint-plugin-projectzero": "1.111.0",
|
|
39
39
|
"@babel/core": "7.26.10",
|
|
40
40
|
"@babel/preset-env": "7.26.9",
|
|
41
41
|
"@babel/preset-typescript": "7.27.0",
|
package/plugins.d.ts
CHANGED
|
@@ -37,16 +37,6 @@ declare module '@akinon/pz-cybersource-uc/src/redux/middleware' {
|
|
|
37
37
|
export default middleware as any;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
declare module '@akinon/pz-apple-pay' {}
|
|
41
|
-
|
|
42
|
-
declare module '@akinon/pz-similar-products' {
|
|
43
|
-
export const SimilarProductsModal: any;
|
|
44
|
-
export const SimilarProductsFilterSidebar: any;
|
|
45
|
-
export const SimilarProductsResultsGrid: any;
|
|
46
|
-
export const SimilarProductsPlugin: any;
|
|
47
|
-
export const SimilarProductsButtonPlugin: any;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
40
|
declare module '@akinon/pz-cybersource-uc/src/redux/reducer' {
|
|
51
41
|
export default reducer as any;
|
|
52
42
|
}
|
|
@@ -56,3 +46,8 @@ declare module '@akinon/pz-cybersource-uc' {
|
|
|
56
46
|
}
|
|
57
47
|
|
|
58
48
|
declare module '@akinon/pz-flow-payment' {}
|
|
49
|
+
|
|
50
|
+
declare module '@akinon/pz-masterpass-rest' {
|
|
51
|
+
export const MasterpassRestOption: React.FC;
|
|
52
|
+
export const masterpassRestReducer: any;
|
|
53
|
+
}
|
package/plugins.js
CHANGED
|
@@ -16,9 +16,9 @@ module.exports = [
|
|
|
16
16
|
'pz-tabby-extension',
|
|
17
17
|
'pz-apple-pay',
|
|
18
18
|
'pz-tamara-extension',
|
|
19
|
-
'pz-similar-products',
|
|
20
19
|
'pz-cybersource-uc',
|
|
21
20
|
'pz-hepsipay',
|
|
22
21
|
'pz-flow-payment',
|
|
23
|
-
'pz-virtual-try-on'
|
|
22
|
+
'pz-virtual-try-on',
|
|
23
|
+
'pz-masterpass-rest'
|
|
24
24
|
];
|
|
@@ -208,7 +208,10 @@ export const contextListMiddleware: Middleware = ({
|
|
|
208
208
|
dispatch(setCardType(context.page_context.card_type));
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
if (
|
|
211
|
+
if (
|
|
212
|
+
context.page_context.installments &&
|
|
213
|
+
preOrder?.payment_option?.payment_type !== 'masterpass_rest'
|
|
214
|
+
) {
|
|
212
215
|
dispatch(setInstallmentOptions(context.page_context.installments));
|
|
213
216
|
}
|
|
214
217
|
}
|
|
@@ -20,6 +20,7 @@ export const installmentOptionMiddleware: Middleware = ({
|
|
|
20
20
|
if (
|
|
21
21
|
!preOrder?.installment &&
|
|
22
22
|
preOrder?.payment_option?.payment_type !== 'saved_card' &&
|
|
23
|
+
preOrder?.payment_option?.payment_type !== 'masterpass_rest' &&
|
|
23
24
|
installmentOptions.length > 0
|
|
24
25
|
) {
|
|
25
26
|
const firstInstallmentOptionPk = installmentOptions[0]?.pk;
|
package/redux/reducers/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { masterpassReducer } from '@akinon/pz-masterpass';
|
|
|
9
9
|
import { otpReducer } from '@akinon/pz-otp';
|
|
10
10
|
import { savedCardReducer } from '@akinon/pz-saved-card';
|
|
11
11
|
import cyberSourceUcReducer from '@akinon/pz-cybersource-uc/src/redux/reducer';
|
|
12
|
+
import { masterpassRestReducer } from '@akinon/pz-masterpass-rest';
|
|
12
13
|
|
|
13
14
|
const fallbackReducer = (state = {}) => state;
|
|
14
15
|
|
|
@@ -21,7 +22,8 @@ const reducers = {
|
|
|
21
22
|
masterpass: masterpassReducer || fallbackReducer,
|
|
22
23
|
otp: otpReducer || fallbackReducer,
|
|
23
24
|
savedCard: savedCardReducer || fallbackReducer,
|
|
24
|
-
cybersource_uc: cyberSourceUcReducer || fallbackReducer
|
|
25
|
+
cybersource_uc: cyberSourceUcReducer || fallbackReducer,
|
|
26
|
+
masterpassRest: masterpassRestReducer || fallbackReducer
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
export default reducers;
|
package/types/index.ts
CHANGED
|
@@ -83,12 +83,6 @@ export interface Settings {
|
|
|
83
83
|
};
|
|
84
84
|
usePrettyUrlRoute?: boolean;
|
|
85
85
|
commerceUrl: string;
|
|
86
|
-
/**
|
|
87
|
-
* This option allows you to track Sentry events on the client side, in addition to server and edge environments.
|
|
88
|
-
*
|
|
89
|
-
* It overrides process.env.NEXT_PUBLIC_SENTRY_DSN and process.env.SENTRY_DSN.
|
|
90
|
-
*/
|
|
91
|
-
sentryDsn?: string;
|
|
92
86
|
redis: {
|
|
93
87
|
defaultExpirationTime: number;
|
|
94
88
|
};
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
const iframeURLChange = (iframe, callback) => {
|
|
2
2
|
iframe.addEventListener('load', () => {
|
|
3
3
|
setTimeout(() => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const iframeLocation = iframe.contentWindow.location;
|
|
7
|
-
|
|
8
|
-
callback(iframeLocation);
|
|
9
|
-
}
|
|
10
|
-
} catch (error) {
|
|
11
|
-
// Expected: browser blocks cross-origin iframe access for security
|
|
4
|
+
if (iframe?.contentWindow?.location) {
|
|
5
|
+
callback(iframe.contentWindow.location);
|
|
12
6
|
}
|
|
13
7
|
}, 0);
|
|
14
8
|
});
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
const iframeURLChange = (iframe, callback) => {
|
|
2
2
|
iframe.addEventListener('load', () => {
|
|
3
3
|
setTimeout(() => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const iframeLocation = iframe.contentWindow.location;
|
|
7
|
-
|
|
8
|
-
callback(iframeLocation);
|
|
9
|
-
}
|
|
10
|
-
} catch (error) {
|
|
11
|
-
// Expected: browser blocks cross-origin iframe access for security
|
|
4
|
+
if (iframe?.contentWindow?.location) {
|
|
5
|
+
callback(iframe.contentWindow.location);
|
|
12
6
|
}
|
|
13
7
|
}, 0);
|
|
14
8
|
});
|
package/api/image-proxy.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
|
|
3
|
-
async function proxyImage(imageUrl: string) {
|
|
4
|
-
if (!imageUrl) {
|
|
5
|
-
return NextResponse.json(
|
|
6
|
-
{ success: false, error: 'Missing url parameter' },
|
|
7
|
-
{ status: 400 }
|
|
8
|
-
);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const imageResponse = await fetch(imageUrl);
|
|
12
|
-
|
|
13
|
-
if (!imageResponse.ok) {
|
|
14
|
-
return NextResponse.json(
|
|
15
|
-
{ success: false, error: 'Failed to fetch image from URL' },
|
|
16
|
-
{ status: 400 }
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const imageBuffer = await imageResponse.arrayBuffer();
|
|
21
|
-
const base64Image = Buffer.from(imageBuffer).toString('base64');
|
|
22
|
-
const contentType = imageResponse.headers.get('content-type') || 'image/jpeg';
|
|
23
|
-
|
|
24
|
-
return { base64Image, contentType, imageBuffer };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export async function GET(request: NextRequest) {
|
|
28
|
-
try {
|
|
29
|
-
const { searchParams } = new URL(request.url);
|
|
30
|
-
const imageUrl = searchParams.get('url');
|
|
31
|
-
|
|
32
|
-
const result = await proxyImage(imageUrl!);
|
|
33
|
-
if (result instanceof NextResponse) {
|
|
34
|
-
return result;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return new NextResponse(result.imageBuffer, {
|
|
38
|
-
status: 200,
|
|
39
|
-
headers: {
|
|
40
|
-
'Content-Type': result.contentType,
|
|
41
|
-
'Access-Control-Allow-Origin': '*',
|
|
42
|
-
'Cache-Control': 'public, max-age=3600'
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
} catch (error) {
|
|
46
|
-
console.error('Image proxy error:', error);
|
|
47
|
-
return NextResponse.json(
|
|
48
|
-
{ success: false, error: 'Internal server error' },
|
|
49
|
-
{ status: 500 }
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export async function POST(request: NextRequest) {
|
|
55
|
-
try {
|
|
56
|
-
const body = await request.json();
|
|
57
|
-
const imageUrl = body.imageUrl;
|
|
58
|
-
|
|
59
|
-
const result = await proxyImage(imageUrl);
|
|
60
|
-
if (result instanceof NextResponse) {
|
|
61
|
-
return result;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return NextResponse.json({
|
|
65
|
-
success: true,
|
|
66
|
-
base64Image: `data:${result.contentType};base64,${result.base64Image}`
|
|
67
|
-
});
|
|
68
|
-
} catch (error) {
|
|
69
|
-
console.error('Image proxy POST error:', error);
|
|
70
|
-
return NextResponse.json(
|
|
71
|
-
{ success: false, error: 'Internal server error' },
|
|
72
|
-
{ status: 500 }
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import Settings from 'settings';
|
|
3
|
-
|
|
4
|
-
export async function GET(request: NextRequest) {
|
|
5
|
-
try {
|
|
6
|
-
const { searchParams } = new URL(request.url);
|
|
7
|
-
const dynamicFilter = request.headers.get('x-search-dynamic-filter');
|
|
8
|
-
const dynamicExclude = request.headers.get('x-search-dynamic-exclude');
|
|
9
|
-
|
|
10
|
-
if (!dynamicFilter && !dynamicExclude) {
|
|
11
|
-
return NextResponse.json(
|
|
12
|
-
{
|
|
13
|
-
error:
|
|
14
|
-
'Missing x-search-dynamic-filter or x-search-dynamic-exclude header'
|
|
15
|
-
},
|
|
16
|
-
{ status: 400 }
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (Settings.commerceUrl === 'default') {
|
|
21
|
-
return NextResponse.json(
|
|
22
|
-
{ error: 'Commerce URL is not configured' },
|
|
23
|
-
{ status: 500 }
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const queryString = searchParams.toString();
|
|
28
|
-
const apiUrl = `${Settings.commerceUrl}/list${
|
|
29
|
-
queryString ? `?${queryString}` : ''
|
|
30
|
-
}`;
|
|
31
|
-
|
|
32
|
-
const headers: Record<string, string> = {
|
|
33
|
-
Accept: 'application/json',
|
|
34
|
-
'Content-Type': 'application/json'
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
if (dynamicFilter) {
|
|
38
|
-
headers['x-search-dynamic-filter'] = dynamicFilter;
|
|
39
|
-
} else if (dynamicExclude) {
|
|
40
|
-
headers['x-search-dynamic-exclude'] = dynamicExclude;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const response = await fetch(apiUrl, {
|
|
44
|
-
method: 'GET',
|
|
45
|
-
headers
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
if (!response.ok) {
|
|
49
|
-
return NextResponse.json(
|
|
50
|
-
{ error: `API request failed with status: ${response.status}` },
|
|
51
|
-
{ status: response.status }
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const data = await response.json();
|
|
56
|
-
return NextResponse.json(data);
|
|
57
|
-
} catch (error) {
|
|
58
|
-
return NextResponse.json(
|
|
59
|
-
{ error: (error as Error).message },
|
|
60
|
-
{ status: 500 }
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
}
|
package/api/similar-products.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server';
|
|
2
|
-
import Settings from 'settings';
|
|
3
|
-
|
|
4
|
-
const IMAGE_SEARCH_API_URL = Settings.commerceUrl + '/image-search/';
|
|
5
|
-
|
|
6
|
-
const errorResponse = (message: string, status: number) => {
|
|
7
|
-
return NextResponse.json({ error: message }, { status });
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export async function GET(request: Request) {
|
|
11
|
-
const { searchParams } = new URL(request.url);
|
|
12
|
-
const limit = searchParams.get('limit') || '20';
|
|
13
|
-
const url = searchParams.get('url');
|
|
14
|
-
const excludedProductIds = searchParams.get('excluded_product_ids');
|
|
15
|
-
|
|
16
|
-
if (!url) {
|
|
17
|
-
return errorResponse('URL parameter is required', 400);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (Settings.commerceUrl === 'default') {
|
|
21
|
-
return errorResponse('Commerce URL is not configured', 500);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const apiParams = new URLSearchParams();
|
|
25
|
-
apiParams.append('limit', limit);
|
|
26
|
-
apiParams.append('url', url);
|
|
27
|
-
|
|
28
|
-
if (excludedProductIds) {
|
|
29
|
-
apiParams.append('excluded_product_ids', excludedProductIds);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const apiUrl = `${IMAGE_SEARCH_API_URL}?${apiParams.toString()}`;
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
const response = await fetch(apiUrl, {
|
|
36
|
-
method: 'GET',
|
|
37
|
-
headers: {
|
|
38
|
-
Accept: 'application/json'
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
if (!response.ok) {
|
|
43
|
-
const errorText = await response.text();
|
|
44
|
-
return errorResponse(
|
|
45
|
-
errorText || `API request failed with status: ${response.status}`,
|
|
46
|
-
response.status
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const responseText = await response.text();
|
|
51
|
-
|
|
52
|
-
return NextResponse.json(JSON.parse(responseText));
|
|
53
|
-
} catch (error) {
|
|
54
|
-
return errorResponse((error as Error).message, 500);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export async function POST(request: Request) {
|
|
59
|
-
const { searchParams } = new URL(request.url);
|
|
60
|
-
const limit = searchParams.get('limit') || '20';
|
|
61
|
-
|
|
62
|
-
if (Settings.commerceUrl === 'default') {
|
|
63
|
-
return errorResponse('Commerce URL is not configured', 500);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
let requestBody;
|
|
67
|
-
try {
|
|
68
|
-
requestBody = await request.json();
|
|
69
|
-
} catch (error) {
|
|
70
|
-
return errorResponse('Invalid JSON in request body', 400);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (!requestBody.image) {
|
|
74
|
-
return errorResponse('Image data is required in request body', 400);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const apiParams = new URLSearchParams();
|
|
78
|
-
apiParams.append('limit', limit);
|
|
79
|
-
|
|
80
|
-
const apiUrl = `${IMAGE_SEARCH_API_URL}?${apiParams.toString()}`;
|
|
81
|
-
|
|
82
|
-
const bodyData: any = { image: requestBody.image };
|
|
83
|
-
|
|
84
|
-
if (requestBody.excluded_product_ids) {
|
|
85
|
-
bodyData.excluded_product_ids = requestBody.excluded_product_ids;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
const response = await fetch(apiUrl, {
|
|
90
|
-
method: 'POST',
|
|
91
|
-
headers: {
|
|
92
|
-
'Content-Type': 'application/json',
|
|
93
|
-
Accept: 'application/json'
|
|
94
|
-
},
|
|
95
|
-
body: JSON.stringify(bodyData)
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
if (!response.ok) {
|
|
99
|
-
const errorText = await response.text();
|
|
100
|
-
return errorResponse(
|
|
101
|
-
errorText || `API request failed with status: ${response.status}`,
|
|
102
|
-
response.status
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const responseData = await response.json();
|
|
107
|
-
return NextResponse.json(responseData);
|
|
108
|
-
} catch (error) {
|
|
109
|
-
return errorResponse((error as Error).message, 500);
|
|
110
|
-
}
|
|
111
|
-
}
|