mirror of
https://github.com/KevinMidboe/planetposen-backend.git
synced 2025-10-29 08:20:12 +00:00
Webserver w/ api-routes, controllers, middleware & cart websocket server
This commit is contained in:
44
src/webserver/controllers/loginController.ts
Normal file
44
src/webserver/controllers/loginController.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import cookie from "cookie";
|
||||
import type { Request, Response, NextFunction } from "express";
|
||||
|
||||
const cookieOptions = {
|
||||
path: "/",
|
||||
maxAge: 60 * 60 * 24 * 7,
|
||||
};
|
||||
|
||||
function addAdminCookie(res: Response) {
|
||||
const adminCookie = cookie.serialize("admin", true, cookieOptions);
|
||||
res.setHeader("Set-Cookie", adminCookie);
|
||||
}
|
||||
|
||||
function deleteAdminCookie(res: Response) {
|
||||
const adminCookie = cookie.serialize("admin", false, {
|
||||
path: "/",
|
||||
maxAge: 1,
|
||||
});
|
||||
res.setHeader("Set-Cookie", adminCookie);
|
||||
}
|
||||
|
||||
function login(req: Request, res: Response) {
|
||||
const { username, password } = req.body;
|
||||
|
||||
if (username !== "admin" || password !== "admin") {
|
||||
return res.status(403).send({
|
||||
success: false,
|
||||
message: "Feil brukernavn eller passord",
|
||||
});
|
||||
}
|
||||
|
||||
addAdminCookie(res);
|
||||
res.send({
|
||||
success: true,
|
||||
message: "Velkommen!",
|
||||
});
|
||||
}
|
||||
|
||||
function logout(req: Request, res: Response) {
|
||||
deleteAdminCookie(res);
|
||||
res.status(200).send("ok");
|
||||
}
|
||||
|
||||
export default { login, logout };
|
||||
381
src/webserver/controllers/orderController.ts
Normal file
381
src/webserver/controllers/orderController.ts
Normal file
@@ -0,0 +1,381 @@
|
||||
import logger from "../../logger";
|
||||
import OrderRepository from "../../order";
|
||||
import CustomerRepository from "../../customer";
|
||||
import ProductRepository from "../../product";
|
||||
import WarehouseRepository from "../../warehouse";
|
||||
import Cart from "../../cart/Cart";
|
||||
|
||||
import ICustomer from "../../interfaces/ICustomer";
|
||||
|
||||
import { validEmail } from "../../utils/formValidation";
|
||||
import ICart from "../../interfaces/ICart";
|
||||
|
||||
const orderRepository = new OrderRepository();
|
||||
const customerRepository = new CustomerRepository();
|
||||
const productRepository = new ProductRepository();
|
||||
const warehouseRepository = new WarehouseRepository();
|
||||
|
||||
async function validateCart(cart: ICart[]) {
|
||||
const validationErrors = [];
|
||||
|
||||
for (let i = 0; i < cart.length; i++) {
|
||||
const { sku_id, quantity, name, lineitem_id } = cart[i];
|
||||
const product = await productRepository.get(sku_id);
|
||||
if (!product) {
|
||||
validationErrors.push({
|
||||
type: "order-summary",
|
||||
field: `lineitem-${lineitem_id}`,
|
||||
message: `Fant ikke produktet ${name}.`,
|
||||
});
|
||||
}
|
||||
|
||||
// check if in stock
|
||||
const leftInStockResponse = await warehouseRepository.checkSkuStock(sku_id);
|
||||
if (!leftInStockResponse || quantity > leftInStockResponse?.stock) {
|
||||
validationErrors.push({
|
||||
type: "order-summary",
|
||||
field: `lineitem-${lineitem_id}`,
|
||||
message: `Det er bare ${leftInStockResponse?.stock} igjen av denne varen på lager.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return validationErrors;
|
||||
}
|
||||
|
||||
function validateCustomer(customer: ICustomer) {
|
||||
const {
|
||||
email,
|
||||
first_name,
|
||||
last_name,
|
||||
street_address,
|
||||
zip_code,
|
||||
city,
|
||||
} = customer;
|
||||
const validationErrors = [];
|
||||
|
||||
if (!email?.length) {
|
||||
validationErrors.push({
|
||||
type: "customer",
|
||||
field: "email",
|
||||
message: "Epost adresse er påkrevd",
|
||||
});
|
||||
}
|
||||
|
||||
if (!validEmail(email)) {
|
||||
validationErrors.push({
|
||||
type: "customer",
|
||||
field: "email",
|
||||
message: "Epost addressen er ikke gyldig",
|
||||
});
|
||||
}
|
||||
|
||||
if (!first_name.length) {
|
||||
validationErrors.push({
|
||||
type: "customer",
|
||||
field: "first_name",
|
||||
message: "Fornavn er påkrevd",
|
||||
});
|
||||
}
|
||||
|
||||
if (!last_name.length) {
|
||||
validationErrors.push({
|
||||
type: "customer",
|
||||
field: "last_name",
|
||||
message: "Etternavn er påkrevd",
|
||||
});
|
||||
}
|
||||
|
||||
if (!street_address.length) {
|
||||
validationErrors.push({
|
||||
type: "customer",
|
||||
field: "street_address",
|
||||
message: "Gateadresse er påkrevd",
|
||||
});
|
||||
}
|
||||
|
||||
const _zipcode = String(zip_code || "");
|
||||
if (!_zipcode.length) {
|
||||
validationErrors.push({
|
||||
type: "customer",
|
||||
field: "zip_code",
|
||||
message: "Postnummer er påkrevd",
|
||||
});
|
||||
} else if (_zipcode.length !== 3 && _zipcode.length !== 4) {
|
||||
validationErrors.push({
|
||||
type: "customer",
|
||||
field: "zip_code",
|
||||
message: "Postnummer må være 4 siffer",
|
||||
});
|
||||
}
|
||||
|
||||
if (!city.length) {
|
||||
validationErrors.push({
|
||||
type: "customer",
|
||||
field: "city",
|
||||
message: "By er påkrevd",
|
||||
});
|
||||
}
|
||||
|
||||
return validationErrors;
|
||||
}
|
||||
|
||||
async function getAll(req, res) {
|
||||
logger.info("Getting all orders");
|
||||
try {
|
||||
const orders = await orderRepository.getAll();
|
||||
res.send({
|
||||
success: true,
|
||||
orders,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error while getting all orders", { error });
|
||||
res.statusCode = error?.statusCode || 500;
|
||||
|
||||
res.send({
|
||||
success: false,
|
||||
message: "Unexpected error while getting all orders",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// interface IOrderFormError {
|
||||
// type: string (enum)
|
||||
// field: string (enum) // the field is linked to html form name
|
||||
// message: string
|
||||
// }
|
||||
|
||||
async function createOrder(req, res) {
|
||||
const cart: ICart[] = req.body?.cart;
|
||||
const customer: ICustomer = req.body?.customer;
|
||||
|
||||
logger.info("Submitting new order", { customer, cart });
|
||||
|
||||
// check if product exists
|
||||
let validationErrors = [];
|
||||
try {
|
||||
validationErrors = validationErrors.concat(validateCustomer(customer));
|
||||
validationErrors = validationErrors.concat(await validateCart(cart));
|
||||
} catch (error) {
|
||||
logger.error("Error while validation order", { error });
|
||||
res.statusCode = error?.statusCode || 500;
|
||||
|
||||
return res.send({
|
||||
success: false,
|
||||
form_input: null,
|
||||
message: error?.message || "Unable to validate order",
|
||||
});
|
||||
}
|
||||
|
||||
if (validationErrors.length) {
|
||||
logger.error("Validation error when submitting order", {
|
||||
validationErrors,
|
||||
});
|
||||
res.statusCode = 400;
|
||||
|
||||
return res.send({
|
||||
success: false,
|
||||
validationErrors,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const { customer_no } = await customerRepository.newCustomer(customer);
|
||||
const { order_id } = await orderRepository.newOrder(customer_no);
|
||||
await Promise.all(
|
||||
cart.map((lineItem) =>
|
||||
orderRepository.addOrderLineItem(
|
||||
order_id,
|
||||
lineItem.product_no,
|
||||
lineItem.sku_id,
|
||||
lineItem.price,
|
||||
lineItem.quantity
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
logger.info("Sucessfully created order", { order_id, customer_no });
|
||||
return res.send({
|
||||
success: true,
|
||||
message: "Sucessfull created order!",
|
||||
order_id,
|
||||
customer_no,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error while creating customer or order", { error });
|
||||
res.statusCode = error?.statusCode || 500;
|
||||
|
||||
return res.send({
|
||||
success: false,
|
||||
message: error?.message || "Unexpected error while creating order",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function get(req, res) {
|
||||
const { order_id } = req.params;
|
||||
logger.info("Getting order by id", { order_id });
|
||||
|
||||
// get a order
|
||||
let order = null;
|
||||
try {
|
||||
order = await orderRepository.getOrderDetailed(order_id);
|
||||
logger.info("Found order", { order });
|
||||
|
||||
res.send({
|
||||
success: true,
|
||||
order,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error while looking for order", { order_id, error });
|
||||
res.statusCode = error.statusCode || 500;
|
||||
|
||||
res.send({
|
||||
success: false,
|
||||
message: error?.message || "Unexpected error while getting order",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function getOrderStatus(req, res) {
|
||||
const { order_id } = req.params;
|
||||
logger.info("Getting order status by id", { order_id });
|
||||
|
||||
return await orderRepository.getOrderStatus(order_id).then((orderStatus) => {
|
||||
logger.info("Found order status", { order_id, orderStatus });
|
||||
|
||||
if (orderStatus) {
|
||||
res.send({
|
||||
status: orderStatus?.status,
|
||||
order_id,
|
||||
success: true,
|
||||
});
|
||||
} else {
|
||||
logger.error("Error while getting order status", { order_id });
|
||||
|
||||
res.status(500).send({
|
||||
initiated: null,
|
||||
confirmed: null,
|
||||
message: "Unexpeted error! Unable to get order status",
|
||||
success: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// async function cancelOrder(req, res) {
|
||||
// const { id } = req.params;
|
||||
// let orderId = id;
|
||||
// const vippsId = id;
|
||||
// await vippsRepository.getOrder(id).then((order) => {
|
||||
// if (order && order.parent_order_id) {
|
||||
// orderId = order.parent_order_id;
|
||||
// }
|
||||
// });
|
||||
// return vippsRepository
|
||||
// .cancelOrRefundPartialOrder(vippsId, req.id)
|
||||
// .then((order) => PlsController.turnOff(order))
|
||||
// .then((_) =>
|
||||
// orderRepository.cancelOrder(orderId).then((canceled) =>
|
||||
// res.send({
|
||||
// success: true,
|
||||
// canceled: canceled,
|
||||
// })
|
||||
// )
|
||||
// )
|
||||
// .catch((error) => {
|
||||
// throw error;
|
||||
// });
|
||||
// }
|
||||
|
||||
// function verifyNoCollidingOrders(id) {
|
||||
// return orderRepository
|
||||
// .getOrder(id)
|
||||
// .then((order) => orderRepository.getConflictingProductOrders(order))
|
||||
// .then(({ order, conflicting }) => checkForConflicting(order, conflicting));
|
||||
// }
|
||||
|
||||
// function checkForConflicting(order, conflicting) {
|
||||
// const thisOrderCreated = new Date(order.created);
|
||||
// for (var i = 0; i < conflicting.length; i++) {
|
||||
// const thisConflictinCreated = new Date(conflicting[i].created);
|
||||
// // if we are anywhere with a conflict, we need to cancel/refund this
|
||||
// if (thisOrderCreated > thisConflictinCreated) {
|
||||
// throw new WagoError(
|
||||
// WAGO_ERROR_STATUS_CODES.CONFLICTING_ORDER_RESERVATION
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// return order;
|
||||
// }
|
||||
|
||||
// async function extendOrder(req, res) {
|
||||
// let { amount } = req.body;
|
||||
// let extendeeOrderId = req.params.id;
|
||||
// let extendedTimes = 1;
|
||||
// const previousExtendedAndPreviousOrder = await vippsRepository
|
||||
// .getOrder(extendeeOrderId)
|
||||
// .then((order) => {
|
||||
// if (order.parent_order_id) {
|
||||
// extendeeOrderId = order.parent_order_id;
|
||||
// }
|
||||
// return orderRepository.getExtendedOrders(extendeeOrderId);
|
||||
// });
|
||||
|
||||
// let orderId = `${extendeeOrderId}-ext-`;
|
||||
// if (!previousExtendedAndPreviousOrder || amount < 0) {
|
||||
// res.status(404).send({ success: false });
|
||||
// return;
|
||||
// }
|
||||
|
||||
// for (let i = 0; i < previousExtendedAndPreviousOrder.length; i++) {
|
||||
// const currentElement = previousExtendedAndPreviousOrder[i];
|
||||
// if (currentElement.order_id !== extendeeOrderId) {
|
||||
// extendedTimes += 1;
|
||||
// }
|
||||
// }
|
||||
|
||||
// orderId += extendedTimes;
|
||||
// const orderWithProductData = await orderRepository.getOrderWithProduct(
|
||||
// extendeeOrderId
|
||||
// );
|
||||
|
||||
// try {
|
||||
// amount = parseFloat(amount);
|
||||
// } catch (e) {}
|
||||
// const moneyToPay = orderWithProductData.price * 100 * amount;
|
||||
|
||||
// return vippsRepository
|
||||
// .newExtendedPayment(
|
||||
// orderId,
|
||||
// extendeeOrderId,
|
||||
// moneyToPay,
|
||||
// amount,
|
||||
// orderWithProductData
|
||||
// )
|
||||
// .then((resp) => {
|
||||
// res.send({
|
||||
// success: true,
|
||||
// ...resp,
|
||||
// });
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.statusCode = error.statusCode || 500;
|
||||
|
||||
// res.send({
|
||||
// success: false,
|
||||
// ...error,
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
export default {
|
||||
createOrder,
|
||||
get,
|
||||
getAll,
|
||||
getOrderStatus,
|
||||
// cancelOrder,
|
||||
extendOrder: () => {},
|
||||
checkForConflicting: () => {},
|
||||
verifyNoCollidingOrders: () => {},
|
||||
};
|
||||
237
src/webserver/controllers/productController.ts
Normal file
237
src/webserver/controllers/productController.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
import logger from "../../logger";
|
||||
import ProductRepository from "../../product";
|
||||
const productRepository = new ProductRepository();
|
||||
import type { Request, Response } from "express";
|
||||
|
||||
async function add(req: Request, res: Response) {
|
||||
logger.info("Adding new product");
|
||||
try {
|
||||
const productId = await productRepository.add();
|
||||
const product = await productRepository.get(productId);
|
||||
logger.info("New product", { product });
|
||||
|
||||
return res.send({
|
||||
success: true,
|
||||
product,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error while adding product", { error });
|
||||
res.statusCode = error.statusCode || 500;
|
||||
|
||||
return res.send({
|
||||
success: false,
|
||||
message: error?.message || "Unexpected error while adding product",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function update(req: Request, res: Response) {
|
||||
const { product_id } = req.params;
|
||||
logger.info("Updating product", { product_id });
|
||||
|
||||
return productRepository
|
||||
.get(product_id)
|
||||
.then((product) => {
|
||||
logger.info("Updated product", { product, product_id });
|
||||
|
||||
res.send({
|
||||
success: true,
|
||||
product: product,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error("Error while updating product", { error, product_id });
|
||||
res.statusCode = error.statusCode || 500;
|
||||
return res.send({
|
||||
success: false,
|
||||
message: error?.message || "Unexpected error while updating product",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getAll(req: Request, res: Response) {
|
||||
logger.info("Getting all products");
|
||||
|
||||
return productRepository
|
||||
.getAllProducts()
|
||||
.then((products) => {
|
||||
logger.info("Found products", { products });
|
||||
|
||||
res.send({
|
||||
success: true,
|
||||
products: products,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error("Error while getting all products", { error });
|
||||
res.statusCode = error.statusCode || 500;
|
||||
|
||||
res.send({
|
||||
success: false,
|
||||
message:
|
||||
error?.message || "Unexpected error while getting all products",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getById(req: Request, res: Response) {
|
||||
const { product_id } = req.params;
|
||||
logger.info("Getting product", { product_id });
|
||||
|
||||
return productRepository
|
||||
.get(product_id)
|
||||
.then((product) => {
|
||||
logger.info("Found product", { product, product_id });
|
||||
|
||||
res.send({
|
||||
success: true,
|
||||
product: product,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error("Error while getting product by id", { product_id });
|
||||
res.statusCode = error.statusCode || 500;
|
||||
|
||||
res.send({
|
||||
success: false,
|
||||
message:
|
||||
error?.message || "Unexpected error while getting product by id",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function addSku(req: Request, res: Response) {
|
||||
const { product_id } = req.params;
|
||||
logger.info("Adding new sku", { product_id });
|
||||
|
||||
try {
|
||||
await productRepository.addSku(product_id);
|
||||
const skus = await productRepository.getSkus(product_id);
|
||||
|
||||
if (!skus.find((sku) => sku.default_price === true)) {
|
||||
const setDefaultResponse = await productRepository.setSkuDefaultPrice(
|
||||
product_id,
|
||||
skus[skus.length - 1].sku_id
|
||||
);
|
||||
|
||||
skus[skus.length - 1].default_price = true;
|
||||
}
|
||||
logger.info("New skus after add", { skus, product_id });
|
||||
|
||||
res.send({
|
||||
success: true,
|
||||
skus,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error adding sku", { error, product_id });
|
||||
res.statusCode = error?.statusCode || 500;
|
||||
res.send({
|
||||
success: false,
|
||||
message: error?.message || "Unexpected error while adding new sku",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function getSkus(req: Request, res: Response) {
|
||||
const { product_id } = req.params;
|
||||
const skus = await productRepository.getSkus(product_id);
|
||||
|
||||
return res.send({
|
||||
success: true,
|
||||
skus,
|
||||
});
|
||||
}
|
||||
|
||||
async function updateSku(req: Request, res: Response) {
|
||||
const { product_id, sku_id } = req.params;
|
||||
const { stock, size, price } = req.body;
|
||||
logger.info("Updating sku", { product_id, sku_id, stock, price, size });
|
||||
|
||||
try {
|
||||
await productRepository.updateSku(product_id, sku_id, stock, size, price);
|
||||
const skus = await productRepository.getSkus(product_id);
|
||||
logger.info("New skus after update", { skus, product_id, sku_id });
|
||||
|
||||
res.send({
|
||||
success: true,
|
||||
skus,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error updating sku", { product_id, sku_id, error });
|
||||
res.statusCode = error?.statusCode || 500;
|
||||
|
||||
res.send({
|
||||
success: false,
|
||||
message: error?.message || "Unexpected error while updating sku",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteSku(req: Request, res: Response) {
|
||||
const { product_id, sku_id } = req.params;
|
||||
|
||||
try {
|
||||
await productRepository.deleteSku(product_id, sku_id);
|
||||
const skus = await productRepository.getSkus(product_id);
|
||||
logger.info("New skus after delete", { skus, product_id, sku_id });
|
||||
|
||||
res.send({
|
||||
success: true,
|
||||
skus,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error deleting sku", { product_id, sku_id, error });
|
||||
res.statusCode = error?.statusCode || 500;
|
||||
|
||||
res.send({
|
||||
success: false,
|
||||
message: error?.message || "Unexpected error while deleting sku",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function setSkuDefaultPrice(req: Request, res: Response) {
|
||||
const { product_id, sku_id } = req.params;
|
||||
logger.info("Updating sku default price", { product_id, sku_id });
|
||||
|
||||
try {
|
||||
await productRepository.setSkuDefaultPrice(product_id, sku_id);
|
||||
const skus = await productRepository.getSkus(product_id);
|
||||
logger.info("New skus after update default price", {
|
||||
skus,
|
||||
product_id,
|
||||
sku_id,
|
||||
});
|
||||
|
||||
res.send({
|
||||
success: true,
|
||||
skus,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error while updating sku default price", {
|
||||
product_id,
|
||||
sku_id,
|
||||
error,
|
||||
});
|
||||
res.statusCode = error?.statusCode || 500;
|
||||
|
||||
res.send({
|
||||
success: false,
|
||||
message:
|
||||
error?.message ||
|
||||
"Unexpected error while updating default price for sku",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
add,
|
||||
update,
|
||||
getAll,
|
||||
getById,
|
||||
addSku,
|
||||
getSkus,
|
||||
updateSku,
|
||||
deleteSku,
|
||||
setSkuDefaultPrice,
|
||||
};
|
||||
94
src/webserver/controllers/stripePaymentController.ts
Normal file
94
src/webserver/controllers/stripePaymentController.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import Configuration from "../../config/configuration";
|
||||
import StripeApi from "../../stripe/stripeApi";
|
||||
import StripeRepository from "../../stripe/stripeRepository";
|
||||
import OrderRepository from "../../order";
|
||||
import CustomerRepository from "../../customer";
|
||||
|
||||
import Stripe from "stripe";
|
||||
|
||||
import type { Request, Response, NextFunction } from "express";
|
||||
import type { IOrder, ILineItem } from "../../interfaces/IOrder";
|
||||
import type ICustomer from "../../interfaces/ICustomer";
|
||||
|
||||
const configuration = Configuration.getInstance();
|
||||
const stripePublicKey = configuration.get("stripe", "publicKey");
|
||||
const stripeSecretKey = configuration.get("stripe", "secretKey");
|
||||
const stripeApi = new StripeApi(stripePublicKey, stripeSecretKey);
|
||||
const stripeRepository = new StripeRepository();
|
||||
const orderRepository = new OrderRepository();
|
||||
const customerRepository = new CustomerRepository();
|
||||
|
||||
async function create(req, res) {
|
||||
const clientId = req?.planetId;
|
||||
const { order_id, customer_no } = req.body;
|
||||
|
||||
if (!order_id || !customer_no) {
|
||||
return res.status(400).send({
|
||||
success: false,
|
||||
message: "Missing order_id and/or customer_id",
|
||||
});
|
||||
}
|
||||
|
||||
const order: IOrder = await orderRepository.getOrder(order_id);
|
||||
const customer: ICustomer = await customerRepository.getCustomer(customer_no);
|
||||
|
||||
const sum = order.lineItems?.reduce(
|
||||
(total, lineItem: ILineItem) => total + lineItem.quantity * lineItem.price,
|
||||
0
|
||||
);
|
||||
|
||||
stripeRepository
|
||||
.createPayment(clientId, sum, order_id, customer)
|
||||
.then((clientSecret) =>
|
||||
res.send({
|
||||
success: true,
|
||||
clientSecret,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async function updatePayment(req: Request, res: Response) {
|
||||
console.log("STRIPE WEBHOOK body:", req.body);
|
||||
const { type, data } = req.body;
|
||||
const { object } = data;
|
||||
const { orderId } = object?.metadata;
|
||||
|
||||
if (!data) {
|
||||
console.log("no data found in webhook, nothing to do");
|
||||
return res.status(200).send("ok");
|
||||
}
|
||||
|
||||
if (!orderId) {
|
||||
console.log("no order_id found in webhook, nothing to do");
|
||||
return res.status(200).send("ok");
|
||||
}
|
||||
|
||||
if (type === "payment_intent.created") {
|
||||
console.log("handle payment intent created... doing nothing");
|
||||
} else if (type === "payment_intent.succeeded") {
|
||||
console.log("handle payment succeeded", object);
|
||||
await stripeRepository.updatePaymentIntent(object);
|
||||
orderRepository.confirmOrder(orderId);
|
||||
} else if (type === "payment_intent.payment_failed") {
|
||||
console.log("handle payment failed", object);
|
||||
await stripeRepository.updatePaymentIntent(object);
|
||||
orderRepository.cancelOrder(orderId);
|
||||
} else if (type === "charge.succeeded") {
|
||||
console.log("handle charge succeeded", object);
|
||||
await stripeRepository.updatePaymentCharge(object);
|
||||
} else if (type === "charge.refunded") {
|
||||
console.log("handle charge refunded", object);
|
||||
await stripeRepository.updatePaymentCharge(object);
|
||||
await orderRepository.refundOrder(orderId);
|
||||
} else {
|
||||
console.log(`webhook for ${type}, not setup yet`);
|
||||
}
|
||||
|
||||
// should always return 200 but should try catch and log error
|
||||
return res.status(200).send("ok");
|
||||
}
|
||||
|
||||
export default {
|
||||
create,
|
||||
updatePayment,
|
||||
};
|
||||
190
src/webserver/controllers/vippsPaymentController.js
Normal file
190
src/webserver/controllers/vippsPaymentController.js
Normal file
@@ -0,0 +1,190 @@
|
||||
const { VippsApiResourceNotFound } = require(`${__base}/vipps/vippsApiErrors`);
|
||||
const { WagoError, WAGO_ERROR_STATUS_CODES } = require(`${__base}/errors`);
|
||||
const { VIPPS_STATUS_CODES } = require(`${__enums}/vipps`);
|
||||
const OrderRepository = require(`${__base}/order`);
|
||||
const OrderController = require(`${__controllers}/orderController`);
|
||||
|
||||
const orderRepository = new OrderRepository();
|
||||
const Vipps = require(`${__base}/vipps`);
|
||||
const VippsRepository = require(`${__base}/vipps/vippsRepository`);
|
||||
|
||||
const vipps = new Vipps();
|
||||
const vippsRepository = new VippsRepository();
|
||||
|
||||
function getPaymentDetails(req, res) {
|
||||
console.log("hit get details payment");
|
||||
|
||||
const { id } = req.params;
|
||||
return vipps
|
||||
.getPayment(id)
|
||||
.then((payment) =>
|
||||
res.send({
|
||||
success: true,
|
||||
payment,
|
||||
})
|
||||
)
|
||||
.catch((err) => {
|
||||
const { statusCode, message } = err;
|
||||
|
||||
return res.status(statusCode || 500).send({
|
||||
success: false,
|
||||
payment: null,
|
||||
message: message,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function cancelPayment(req, res) {
|
||||
console.log("hit cancelOrder endpoint");
|
||||
const { id } = req.params;
|
||||
|
||||
return vippsRepository.cancelPayment(id).then((canceled) =>
|
||||
res.send({
|
||||
success: true,
|
||||
canceled: canceled,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function rejectPayment(id, amount) {
|
||||
console.log("rejectPayment");
|
||||
return vippsRepository
|
||||
.cancelPayment(id)
|
||||
.then((_) =>
|
||||
vippsRepository.updatePaymentStatus(
|
||||
id,
|
||||
VIPPS_STATUS_CODES.REJECTED,
|
||||
amount
|
||||
)
|
||||
)
|
||||
.then((_) => orderRepository.rejectByVippsTimeout(id));
|
||||
}
|
||||
|
||||
function rejectByVippsTimeout(id, amount) {
|
||||
console.log("reject-by-timeout");
|
||||
return vippsRepository
|
||||
.cancelPayment(id)
|
||||
.then((_) =>
|
||||
vippsRepository.updatePaymentStatus(
|
||||
id,
|
||||
VIPPS_STATUS_CODES.TIMED_OUT_REJECT,
|
||||
amount
|
||||
)
|
||||
)
|
||||
.then((_) => orderRepository.rejectByVippsTimeout(id));
|
||||
}
|
||||
|
||||
function updatePayment(req, res) {
|
||||
console.log("updatePayment:", req.body);
|
||||
const { id } = req.params;
|
||||
const {
|
||||
transactionInfo: { status },
|
||||
errorInfo,
|
||||
} = req.body;
|
||||
|
||||
let statusCode;
|
||||
if (status && VIPPS_STATUS_CODES[status]) {
|
||||
statusCode = VIPPS_STATUS_CODES[status];
|
||||
}
|
||||
|
||||
// TODO: Don't cancel whole order when one new order
|
||||
// fails/gets rejected/cancels
|
||||
if (status === VIPPS_STATUS_CODES.REJECTED) {
|
||||
switch (errorInfo.errorCode) {
|
||||
case 45:
|
||||
default:
|
||||
return vippsRepository
|
||||
.cancelOrRefundPartialOrder(id)
|
||||
.then((_) => orderRepository.cancelOrder(id));
|
||||
}
|
||||
} else if (status === VIPPS_STATUS_CODES.CANCELLED) {
|
||||
return vippsRepository
|
||||
.cancelOrRefundPartialOrder(id)
|
||||
.then((_) => orderRepository.cancelOrder(id));
|
||||
}
|
||||
|
||||
return vippsRepository
|
||||
.getOrder(id)
|
||||
.then((order) => extendOrUpdateNewOrder(order, statusCode, req))
|
||||
.then((_) => res.status(200).send())
|
||||
.catch((error) => {
|
||||
console.log("we errored here", error);
|
||||
return vippsRepository
|
||||
.cancelOrRefundPartialOrder(id)
|
||||
.then((_) => orderRepository.cancelOrder(id));
|
||||
});
|
||||
}
|
||||
|
||||
function extendOrUpdateNewOrder(order, statusCode, req) {
|
||||
const {
|
||||
transactionInfo: { transactionId, amount },
|
||||
} = req.body;
|
||||
const vippsId = order.order_id;
|
||||
let orderId = order.order_id;
|
||||
if (order.parent_order_id) {
|
||||
orderId = order.parent_order_id;
|
||||
}
|
||||
return vippsRepository
|
||||
.updatePaymentStatus(vippsId, statusCode, amount, transactionId)
|
||||
.then((_) => {
|
||||
if (order.parent_order_id) {
|
||||
return getNewTimeToTurnOn(order);
|
||||
// return getNewTimeToTurnOn(order).then(hours =>
|
||||
// PlsController.extendTime(hours, order)
|
||||
// );
|
||||
}
|
||||
|
||||
return OrderController.verifyNoCollidingOrders(orderId);
|
||||
// return OrderController.verifyNoCollidingOrders(orderId).then(
|
||||
// async order => await PlsController.sendDuration(order)
|
||||
// );
|
||||
})
|
||||
.then(async (plsResponse) => {
|
||||
if (plsResponse.startTime) {
|
||||
await orderRepository.updateStartTime(orderId, plsResponse.startTime);
|
||||
}
|
||||
return orderRepository
|
||||
.updateEndTime(orderId, plsResponse.endTime)
|
||||
.then((_) =>
|
||||
vippsRepository.updateEndTime(vippsId, plsResponse.endTime)
|
||||
);
|
||||
})
|
||||
.then((_) => vipps.captureAmount(vippsId, amount, req.id))
|
||||
.then((capture) =>
|
||||
vippsRepository.updatePaymentCapture(
|
||||
vippsId,
|
||||
capture.transactionSummary.capturedAmount
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async function getNewTimeToTurnOn(order) {
|
||||
const previousExtendedAndPreviousOrder = await orderRepository.getExtendedOrders(
|
||||
order.parent_order_id
|
||||
);
|
||||
let hoursToExpandAndSendToPls = 0;
|
||||
let currentEndTime;
|
||||
const now = new Date();
|
||||
|
||||
for (let i = 0; i < previousExtendedAndPreviousOrder.length; i++) {
|
||||
const currentElement = previousExtendedAndPreviousOrder[i];
|
||||
|
||||
if (currentElement.order_id === order.parent_order_id) {
|
||||
currentEndTime = new Date(currentElement.end_time);
|
||||
if (currentEndTime < now) {
|
||||
currentEndTime = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
const timeLeft = (currentEndTime.getTime() - now.getTime()) / 36e5;
|
||||
hoursToExpandAndSendToPls = order.hours + timeLeft;
|
||||
return hoursToExpandAndSendToPls;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getPaymentDetails,
|
||||
cancelPayment,
|
||||
extendOrUpdateNewOrder,
|
||||
updatePayment,
|
||||
getNewTimeToTurnOn,
|
||||
};
|
||||
12
src/webserver/controllers/vippsTokenController.ts
Normal file
12
src/webserver/controllers/vippsTokenController.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
const Vipps = require(`${__base}/vipps`);
|
||||
const vipps = new Vipps();
|
||||
import type { Request, Response } from "express";
|
||||
|
||||
export default function VippsTokenController(req: Request, res: Response) {
|
||||
return vipps.token.then((token) =>
|
||||
res.send({
|
||||
success: true,
|
||||
token: token,
|
||||
})
|
||||
);
|
||||
}
|
||||
65
src/webserver/controllers/warehouseController.ts
Normal file
65
src/webserver/controllers/warehouseController.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import logger from "../../logger";
|
||||
import WarehouseRepository from "../../warehouse";
|
||||
const warehouseRepository = new WarehouseRepository();
|
||||
import type { Request, Response } from "express";
|
||||
|
||||
function getAll(req: Request, res: Response) {
|
||||
logger.info("Fething all warehouse products");
|
||||
|
||||
return warehouseRepository
|
||||
.all()
|
||||
.then((warehouseProucts) => {
|
||||
logger.info("Found warehouse products", { products: warehouseProucts });
|
||||
|
||||
res.send({
|
||||
success: true,
|
||||
warehouse: warehouseProucts,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error("Error fetching warehouse products", { error });
|
||||
res.statusCode = error.statusCode || 500;
|
||||
|
||||
res.send({
|
||||
success: false,
|
||||
message:
|
||||
error?.message ||
|
||||
"Unexpected error while fetching all warehouse products",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getProduct(req: Request, res: Response) {
|
||||
const { productId } = req.params;
|
||||
logger.info("Fetching warehouse product", { product_id: productId });
|
||||
|
||||
return warehouseRepository
|
||||
.getProduct(productId)
|
||||
.then((product) => {
|
||||
logger.info("Found warehouse product", {
|
||||
product,
|
||||
product_id: productId,
|
||||
});
|
||||
|
||||
res.send({
|
||||
success: true,
|
||||
product: product,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error("Error fetching warehouse product:", {
|
||||
error,
|
||||
product_id: productId,
|
||||
});
|
||||
res.statusCode = error.statusCode || 500;
|
||||
|
||||
res.send({
|
||||
success: false,
|
||||
message:
|
||||
error?.message ||
|
||||
`Unexpected error while fetching product with id: ${productId}`,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default { getAll, getProduct };
|
||||
37
src/webserver/middleware/getOrSetCookieForClient.ts
Normal file
37
src/webserver/middleware/getOrSetCookieForClient.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import cookie from "cookie";
|
||||
import generateUUID from "../../utils/generateUUID";
|
||||
import httpContext from "express-http-context";
|
||||
import type { Request, Response, NextFunction } from "express";
|
||||
|
||||
const cookieClientKey = "planetId";
|
||||
const cookieOptions = {
|
||||
path: "/",
|
||||
maxAge: 60 * 60 * 24 * 7, // 7 days
|
||||
};
|
||||
|
||||
function setClientIdCookieHeader(res: Response, value: string) {
|
||||
const setCookie = cookie.serialize("planetId", value, cookieOptions);
|
||||
return res.setHeader("Set-Cookie", setCookie);
|
||||
}
|
||||
|
||||
const getOrSetCookieForClient = (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
const cookies = cookie.parse(req.headers.cookie || "");
|
||||
const planetId = cookies[cookieClientKey];
|
||||
|
||||
if (planetId) {
|
||||
req.planetId = planetId;
|
||||
httpContext.set("planetId", planetId);
|
||||
return next();
|
||||
}
|
||||
|
||||
const clientId = generateUUID();
|
||||
setClientIdCookieHeader(res, clientId);
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
export default getOrSetCookieForClient;
|
||||
8
src/webserver/middleware/setupCORS.ts
Normal file
8
src/webserver/middleware/setupCORS.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { Request, Response, NextFunction } from "express";
|
||||
|
||||
const openCORS = (req: Request, res: Response, next: NextFunction) => {
|
||||
res.set("Access-Control-Allow-Origin", "*");
|
||||
return next();
|
||||
};
|
||||
|
||||
export default openCORS;
|
||||
43
src/webserver/middleware/setupHeaders.ts
Normal file
43
src/webserver/middleware/setupHeaders.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { Request, Response, NextFunction } from "express";
|
||||
|
||||
const camelToKebabCase = (str: string) =>
|
||||
str.replace(/[A-Z]/g, (letter: string) => `-${letter.toLowerCase()}`);
|
||||
|
||||
const mapFeaturePolicyToString = (features) => {
|
||||
return Object.entries(features)
|
||||
.map(([key, value]) => {
|
||||
key = camelToKebabCase(key);
|
||||
value = value == "*" ? value : `'${value}'`;
|
||||
return `${key} ${value}`;
|
||||
})
|
||||
.join("; ");
|
||||
};
|
||||
|
||||
const setupHeaders = (req: Request, res: Response, next: NextFunction) => {
|
||||
res.set("Access-Control-Allow-Headers", "Content-Type");
|
||||
res.set("Access-Control-Allow-Methods", "POST, PATCH, DELETE");
|
||||
|
||||
// Security
|
||||
res.set("X-Content-Type-Options", "nosniff");
|
||||
res.set("X-XSS-Protection", "1; mode=block");
|
||||
res.set("X-Frame-Options", "SAMEORIGIN");
|
||||
res.set("X-DNS-Prefetch-Control", "off");
|
||||
res.set("X-Download-Options", "noopen");
|
||||
res.set("Strict-Transport-Security", "max-age=15552000; includeSubDomains");
|
||||
|
||||
// Feature policy
|
||||
const features = {
|
||||
fullscreen: "*",
|
||||
payment: "none",
|
||||
microphone: "none",
|
||||
camera: "self",
|
||||
speaker: "*",
|
||||
syncXhr: "self",
|
||||
};
|
||||
const featureString = mapFeaturePolicyToString(features);
|
||||
res.set("Feature-Policy", featureString);
|
||||
|
||||
return next();
|
||||
};
|
||||
|
||||
export default setupHeaders;
|
||||
79
src/webserver/server.ts
Normal file
79
src/webserver/server.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
// import * as global from "../types/global";
|
||||
import path from "path";
|
||||
import express from "express";
|
||||
import { createServer } from "http";
|
||||
|
||||
// services
|
||||
import logger from "../logger";
|
||||
import { setupCartWebsocketServer } from "./websocketCartServer";
|
||||
|
||||
// controllers
|
||||
// const TokenController = require(`${__controllers}/tokenController`);
|
||||
import OrderController from "./controllers/orderController";
|
||||
import ProductController from "./controllers/productController";
|
||||
import WarehouseController from "./controllers/warehouseController";
|
||||
import StripePaymentController from "./controllers/stripePaymentController";
|
||||
import LoginController from "./controllers/loginController";
|
||||
|
||||
// middleware
|
||||
import httpContext from "express-http-context";
|
||||
import setupCORS from "./middleware/setupCORS";
|
||||
import setupHeaders from "./middleware/setupHeaders";
|
||||
import getOrSetCookieForClient from "./middleware/getOrSetCookieForClient";
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 30010;
|
||||
|
||||
app.use(httpContext.middleware);
|
||||
app.use(setupCORS);
|
||||
app.use(setupHeaders);
|
||||
app.use(getOrSetCookieForClient);
|
||||
|
||||
// parse application/json
|
||||
|
||||
const router = express.Router();
|
||||
router.use(express.json());
|
||||
|
||||
router.post("/login", LoginController.login);
|
||||
router.post("/logout", LoginController.logout);
|
||||
|
||||
router.get("/products", ProductController.getAll);
|
||||
router.post("/product", ProductController.add);
|
||||
router.get("/product/:product_id", ProductController.getById);
|
||||
router.post("/product/:product_id/sku", ProductController.addSku);
|
||||
router.patch("/product/:product_id/sku/:sku_id", ProductController.updateSku);
|
||||
router.delete("/product/:product_id/sku/:sku_id", ProductController.deleteSku);
|
||||
router.post(
|
||||
"/product/:product_id/sku/:sku_id/default_price",
|
||||
ProductController.setSkuDefaultPrice
|
||||
);
|
||||
|
||||
router.get("/orders", OrderController.getAll);
|
||||
router.post("/order", OrderController.createOrder);
|
||||
router.get("/order/:order_id", OrderController.get);
|
||||
router.get("/order/:order_id/status", OrderController.getOrderStatus);
|
||||
|
||||
router.get("/warehouse", WarehouseController.getAll);
|
||||
router.get("/warehouse/:productId", WarehouseController.getProduct);
|
||||
// router.get("/api/order/:id", OrderController.getOrderById);
|
||||
// router.post("/api/order/:id/cancel", OrderController.cancelOrder);
|
||||
// router.post("/api/order/:id/extend", OrderController.extendOrder);
|
||||
|
||||
router.post("/payment/stripe", StripePaymentController.create);
|
||||
router.post("/webhook/stripe", StripePaymentController.updatePayment);
|
||||
|
||||
// router.get("/api/payment/vipps/token", VippsTokenController);
|
||||
// router.get("/api/payment/:id/details", VippsPaymentController.getPaymentDetails);
|
||||
// router.post(
|
||||
// "/api/payment/callback/v2/payments/:id",
|
||||
// VippsPaymentController.updatePayment
|
||||
// );
|
||||
|
||||
router.get("/", (req, res) => res.send("hello"));
|
||||
|
||||
app.use("/api", router);
|
||||
|
||||
const server = createServer(app);
|
||||
server.listen(port, () => logger.info(`Server started, listening at :${port}`));
|
||||
|
||||
setupCartWebsocketServer(server);
|
||||
56
src/webserver/websocketCartServer.ts
Normal file
56
src/webserver/websocketCartServer.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import cookie from "cookie";
|
||||
import { ClientRequest } from "http";
|
||||
import { WebSocketServer, Websocket, Request } from "ws";
|
||||
import WSCart from "../cart/WSCart";
|
||||
import CartSession from "../cart/CartSession";
|
||||
import generateUUID from "../utils/generateUUID";
|
||||
|
||||
function getCookieValue(cookieString: string, key: string): string | null {
|
||||
const cookies = cookie.parse(cookieString || "");
|
||||
return cookies[key] || null;
|
||||
}
|
||||
|
||||
function getHeaderValue(url: string, key: string) {
|
||||
const urlSegments = url.split("?");
|
||||
if (urlSegments?.length < 2) return;
|
||||
|
||||
const query = new URLSearchParams(urlSegments[1]);
|
||||
return query.get(key);
|
||||
}
|
||||
|
||||
function setupCartWebsocketServer(server) {
|
||||
const WS_OPTIONS = { server };
|
||||
const wss = new WebSocketServer(WS_OPTIONS);
|
||||
|
||||
const cartSession = new CartSession();
|
||||
// setInterval(() => cartSession.listCarts(), 3000);
|
||||
setInterval(() => cartSession.removeIfNotAlive(), 1000);
|
||||
|
||||
wss.on("connection", (ws, req) => {
|
||||
const sessionId = generateUUID();
|
||||
let clientId =
|
||||
getCookieValue(req.headers.cookie, "planetId") ||
|
||||
getHeaderValue(req.url, "planetId");
|
||||
|
||||
if (clientId === null) return;
|
||||
|
||||
const wsCart = new WSCart(ws, clientId);
|
||||
wsCart.cartSession = cartSession;
|
||||
cartSession.add(sessionId, wsCart);
|
||||
|
||||
ws.on("message", (data, isBinary) => wsCart.handleMessage(data, isBinary));
|
||||
|
||||
ws.on("close", () => {
|
||||
cartSession.remove(sessionId);
|
||||
console.log("the client has closed the connection");
|
||||
});
|
||||
|
||||
ws.onerror = function (error) {
|
||||
console.log("unexpected ws error occured:", error);
|
||||
};
|
||||
});
|
||||
|
||||
console.log("Booted websocket cart");
|
||||
}
|
||||
|
||||
export { setupCartWebsocketServer };
|
||||
Reference in New Issue
Block a user