import { NextFetchEvent, NextMiddleware, NextResponse } from 'next/server'; import Settings from 'settings'; import { Buffer } from 'buffer'; import logger from '../utils/log'; import { getUrlPathWithLocale } from '../utils/localization'; import { PzNextRequest } from '.'; import { getCheckoutPath } from '../utils'; const streamToString = async (stream: ReadableStream | null) => { if (stream) { const chunks = []; let result = ''; try { for await (const chunk of stream as any) { chunks.push(Buffer.from(chunk)); } result = Buffer.concat(chunks).toString('utf-8'); } catch (error) { logger.error('Error while reading body stream', { middleware: 'complete-gpay', error }); } return result; } return null; }; const withCompleteGpay = (middleware: NextMiddleware) => async (req: PzNextRequest, event: NextFetchEvent) => { const url = req.nextUrl.clone(); const ip = req.headers.get('x-forwarded-for') ?? ''; const sessionId = req.cookies.get('osessionid'); const currentLocale = req.middlewareParams?.rewrites?.locale; if (url.search.indexOf('GPayCompletePage') === -1) { return middleware(req, event); } const isPostCheckout = req.cookies.get('pz-post-checkout-flow')?.value === 'true'; const requestUrl = `${Settings.commerceUrl}${getCheckoutPath(isPostCheckout)}${url.search}`; const requestHeaders = { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/x-www-form-urlencoded', Cookie: req.headers.get('cookie') ?? '', 'x-currency': req.cookies.get('pz-currency')?.value ?? '', 'x-forwarded-for': ip, 'Accept-Language': currentLocale ?? req.cookies.get('pz-locale')?.value ?? '' }; try { const body = await streamToString(req.body); if (!sessionId) { logger.warn( 'Make sure that the SESSION_COOKIE_SAMESITE environment variable is set to None in Commerce.', { middleware: 'complete-masterpass', ip } ); } const fetchResponse = await fetch(requestUrl, { method: 'POST', headers: requestHeaders, body }); logger.info('Complete GPay payment request', { requestUrl, status: fetchResponse.status, requestHeaders, ip }); const responseData = await fetchResponse.json(); const { context_list: contextList, errors } = responseData; const redirectionContext = contextList?.find( (context) => context.page_context?.redirect_url ); const redirectUrl = redirectionContext?.page_context?.redirect_url; if (errors && Object.keys(errors).length) { logger.error('Error while completing GPay payment', { middleware: 'complete-gpay', errors, requestHeaders, ip }); const errorResponse = NextResponse.redirect( `${url.origin}${getUrlPathWithLocale( '/orders/checkout/', req.cookies.get('pz-locale')?.value )}`, 303 ); // Forward set-cookie headers from the upstream response const setCookies = fetchResponse.headers.getSetCookie?.() ?? []; setCookies.forEach((cookie) => { errorResponse.headers.append('Set-Cookie', cookie); }); // Add error cookie errorResponse.cookies.set('pz-pos-error', JSON.stringify(errors), { path: '/' }); return errorResponse; } logger.info('Order success page context list', { middleware: 'complete-gpay', contextList, ip }); if (!redirectUrl) { logger.warn( 'No redirection url for order success page found in page_context. Redirecting to checkout page.', { middleware: 'complete-gpay', requestHeaders, response: JSON.stringify(responseData), ip } ); const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale( '/orders/checkout/', req.cookies.get('pz-locale')?.value )}`; return NextResponse.redirect(redirectUrlWithLocale, 303); } const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale( redirectUrl, req.cookies.get('pz-locale')?.value )}`; logger.info('Redirecting to order success page', { middleware: 'complete-gpay', redirectUrlWithLocale, ip }); // Using POST method while redirecting causes an error, // So we use 303 status code to change the method to GET const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303); // Forward all set-cookie headers from the upstream response const setCookies = fetchResponse.headers.getSetCookie?.() ?? []; setCookies.forEach((cookie) => { nextResponse.headers.append('Set-Cookie', cookie); }); return nextResponse; } catch (error) { logger.error('Error while completing GPay payment', { middleware: 'complete-gpay', error, requestHeaders, ip }); return NextResponse.redirect( `${url.origin}${getUrlPathWithLocale( '/orders/checkout/', req.cookies.get('pz-locale')?.value )}`, 303 ); } }; export default withCompleteGpay;