Files
nullcart/backend/src/modules/storefrontCheckout/controllers/StorefrontCheckoutController.ts
T

181 lines
7.3 KiB
TypeScript

import { Controller, Get, HttpStatus, Post, Req, Res, UseFilters, Body, BadRequestException } from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
import { ConfigService } from '@nestjs/config';
import type { Request, Response } from 'express';
import type { Config } from '../../../types/Config';
import { throttleProfiles } from '../../../config/throttleProfiles';
import { deriveCheckoutSessionState } from '../../../utils/checkout/deriveCheckoutSessionState';
import { OrderService } from '../../order/services/OrderService';
import { StorefrontExceptionFilter } from '../../storefrontCore/filters/StorefrontExceptionFilter';
import { StorefrontCartCookieService } from '../../storefrontCore/services/StorefrontCartCookieService';
import { StorefrontCheckoutSessionCookieService } from '../../storefrontCore/services/StorefrontCheckoutSessionCookieService';
import { StorefrontDiscountCookieService } from '../../storefrontCore/services/StorefrontDiscountCookieService';
import { StorefrontFeedbackCookieService } from '../../storefrontCore/services/StorefrontFeedbackCookieService';
import { StorefrontCaptchaCookieService } from '../../storefrontCore/services/StorefrontCaptchaCookieService';
import { StorefrontCaptchaService } from '../../storefrontCore/services/StorefrontCaptchaService';
import { StorefrontOrderAuthCookieService } from '../../storefrontCore/services/StorefrontOrderAuthCookieService';
import { StorefrontShopViewService } from '../../storefrontCore/services/StorefrontShopViewService';
import { StorefrontCartService } from '../../storefrontCart/services/StorefrontCartService';
import { CheckoutSessionService } from '../services/CheckoutSessionService';
import { PayCheckoutDto } from '../dto/PayCheckoutDto';
import { StorefrontCheckoutViewService } from '../services/StorefrontCheckoutViewService';
@Controller()
@UseFilters(StorefrontExceptionFilter)
export class StorefrontCheckoutController {
constructor(
private readonly cartService: StorefrontCartService,
private readonly checkoutSessionService: CheckoutSessionService,
private readonly checkoutViewService: StorefrontCheckoutViewService,
private readonly shopViewService: StorefrontShopViewService,
private readonly cartCookieService: StorefrontCartCookieService,
private readonly discountCookieService: StorefrontDiscountCookieService,
private readonly checkoutSessionCookieService: StorefrontCheckoutSessionCookieService,
private readonly feedbackCookieService: StorefrontFeedbackCookieService,
private readonly orderAuthCookieService: StorefrontOrderAuthCookieService,
private readonly orderService: OrderService,
private readonly configService: ConfigService,
private readonly captchaService: StorefrontCaptchaService,
private readonly captchaCookieService: StorefrontCaptchaCookieService
) {}
@Post('shop/checkout/pay')
@Throttle(throttleProfiles.checkoutPay)
async pay(@Req() req: Request, @Res() res: Response, @Body() { captcha, paymentMethod }: PayCheckoutDto): Promise<void> {
const sessionId = this.checkoutSessionCookieService.getSessionId(req, res);
if (sessionId) {
res.redirect(HttpStatus.FOUND, '/shop/checkout');
return;
}
const encryptedAnswer = this.captchaCookieService.getAnswer(req, res, { consume: true });
const isCaptchaValid = this.captchaService.verify(captcha, encryptedAnswer);
if (!isCaptchaValid) {
throw new BadRequestException('Incorrect captcha. Try again.');
}
const cart = this.cartCookieService.getCart(req, res);
const discountCodes = this.discountCookieService.getDiscountCodes(req, res);
const summary = await this.cartService.getCartSummary(cart, discountCodes);
const session = await this.checkoutSessionService.createFromCartSummary(summary, paymentMethod);
this.checkoutSessionCookieService.setSessionId(req, res, session.id);
res.redirect(HttpStatus.FOUND, '/shop/checkout');
}
@Get('shop/checkout')
async checkoutPage(@Req() req: Request, @Res() res: Response) {
const sessionId = this.checkoutSessionCookieService.getSessionId(req, res);
if (!sessionId) {
res.redirect(HttpStatus.FOUND, '/shop/cart');
return;
}
const session = await this.checkoutSessionService.findById(sessionId);
if (!session) {
this.checkoutSessionCookieService.clearSession(req, res);
this.feedbackCookieService.setFeedback(req, res, {
type: 'error',
text: 'Checkout session no longer available.'
});
res.redirect(HttpStatus.FOUND, '/shop/cart');
return;
}
const orderId = await this.orderService.findIdByCheckoutSessionId(sessionId);
if (orderId) {
this.checkoutSessionCookieService.clearSession(req, res);
this.cartCookieService.clearCart(req, res);
this.discountCookieService.clearDiscount(req, res);
this.orderAuthCookieService.grantAccess(req, res, orderId);
this.feedbackCookieService.setFeedback(req, res, {
type: 'success',
text: 'Order placed successfully.'
});
res.redirect(HttpStatus.FOUND, `/shop/order/${orderId}`);
return;
}
const sessionState = deriveCheckoutSessionState(session);
if (sessionState.isPastDue) {
this.checkoutSessionCookieService.clearSession(req, res);
this.feedbackCookieService.setFeedback(req, res, {
type: 'error',
text: 'Checkout session expired.'
});
res.redirect(HttpStatus.FOUND, '/shop/cart');
return;
}
if (sessionState.isCancelled) {
this.checkoutSessionCookieService.clearSession(req, res);
this.feedbackCookieService.setFeedback(req, res, {
type: 'success',
text: 'Checkout cancelled.'
});
res.redirect(HttpStatus.FOUND, '/shop/cart');
return;
}
const [shopLocals, checkoutView] = await Promise.all([
this.shopViewService.buildShopRenderLocals(req, res, {
title: 'Checkout',
metaDescription: 'Complete your purchase at {shopName}.'
}),
this.checkoutViewService.toCheckoutView(session)
]);
const { checkoutStatusRefreshSec } = this.configService.get('order') as Config['order'];
return res.render('checkout', {
checkout: checkoutView,
refreshSec: checkoutStatusRefreshSec,
...shopLocals
});
}
@Post('shop/checkout/cancel')
async cancelCheckout(@Req() req: Request, @Res() res: Response): Promise<void> {
const sessionId = this.checkoutSessionCookieService.getSessionId(req, res);
if (sessionId) {
await this.checkoutSessionService.cancelSession(sessionId);
}
this.checkoutSessionCookieService.clearSession(req, res);
this.feedbackCookieService.setFeedback(req, res, {
type: 'success',
text: 'Checkout cancelled.'
});
res.redirect(HttpStatus.FOUND, '/shop/cart');
}
}