mirror of
https://github.com/KevinMidboe/planetposen-backend.git
synced 2025-10-29 08:20:12 +00:00
Cart, order, warehouse, product, customer & stripe payment files
This commit is contained in:
149
src/cart/Cart.ts
Normal file
149
src/cart/Cart.ts
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
import establishedDatabase from "../database";
|
||||||
|
|
||||||
|
import ProductRepository from "../product";
|
||||||
|
|
||||||
|
import type { IProduct } from "../interfaces/IProduct";
|
||||||
|
import type IDeliveryAddress from "../interfaces/IDeliveryAddress";
|
||||||
|
|
||||||
|
const productRepository = new ProductRepository();
|
||||||
|
|
||||||
|
class Cart {
|
||||||
|
clientId: string;
|
||||||
|
database: any;
|
||||||
|
|
||||||
|
constructor(clientId: string) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.database = establishedDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(): Promise<Array<object>> {
|
||||||
|
const query =
|
||||||
|
"SELECT * FROM cart_detailed WHERE client_id = $1 ORDER BY lineitem_id";
|
||||||
|
return this.database.all(query, [this.clientId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLineItem(product_sku_no: number) {
|
||||||
|
const query = `
|
||||||
|
SELECT product_no, quantity
|
||||||
|
FROM cart_lineitem
|
||||||
|
WHERE product_sku_no = $2
|
||||||
|
AND cart_id = (
|
||||||
|
SELECT cart_id
|
||||||
|
FROM cart
|
||||||
|
WHERE client_id = $1
|
||||||
|
)
|
||||||
|
`;
|
||||||
|
|
||||||
|
return this.database.get(query, [this.clientId, product_sku_no]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async exists() {
|
||||||
|
const query = `
|
||||||
|
SELECT cart_id
|
||||||
|
FROM cart
|
||||||
|
WHERE client_id = $1
|
||||||
|
`;
|
||||||
|
|
||||||
|
const exists = await this.database.get(query, [this.clientId]);
|
||||||
|
return exists !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
create() {
|
||||||
|
const query = `
|
||||||
|
INSERT INTO cart (client_id) values ($1) ON CONFLICT DO NOTHING
|
||||||
|
`;
|
||||||
|
|
||||||
|
return this.database.update(query, [this.clientId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async add(product_no: number, product_sku_no: number, quantity: number) {
|
||||||
|
if ((await this.exists()) === false) {
|
||||||
|
await this.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingLineItem = await this.getLineItem(product_sku_no);
|
||||||
|
|
||||||
|
let query = `
|
||||||
|
INSERT INTO cart_lineitem (cart_id, product_no, product_sku_no, quantity) values (
|
||||||
|
(
|
||||||
|
SELECT cart_id
|
||||||
|
FROM cart
|
||||||
|
WHERE client_id = $1
|
||||||
|
),
|
||||||
|
$2,
|
||||||
|
$3,
|
||||||
|
$4
|
||||||
|
)
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (existingLineItem) {
|
||||||
|
quantity = quantity + existingLineItem.quantity;
|
||||||
|
query = `
|
||||||
|
UPDATE cart_lineitem
|
||||||
|
SET quantity = $4
|
||||||
|
WHERE product_no = $2
|
||||||
|
AND product_sku_no = $3
|
||||||
|
AND cart_id = (
|
||||||
|
SELECT cart_id
|
||||||
|
FROM cart
|
||||||
|
WHERE client_id = $1
|
||||||
|
)
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.database.update(query, [
|
||||||
|
this.clientId,
|
||||||
|
product_no,
|
||||||
|
product_sku_no,
|
||||||
|
quantity,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(lineitem_id: number) {
|
||||||
|
// TODO should match w/ cart.client_id
|
||||||
|
const query = `
|
||||||
|
DELETE FROM cart_lineitem
|
||||||
|
WHERE lineitem_id = $1
|
||||||
|
`;
|
||||||
|
|
||||||
|
return this.database.update(query, [lineitem_id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
decrement(lineitem_id: number) {
|
||||||
|
// TODO should match w/ cart.client_id
|
||||||
|
const query = `
|
||||||
|
UPDATE cart_lineitem
|
||||||
|
SET quantity = quantity - 1
|
||||||
|
WHERE lineitem_id = $1
|
||||||
|
`;
|
||||||
|
|
||||||
|
return this.database.update(query, [lineitem_id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async increment(lineitem_id: number) {
|
||||||
|
// if (!productRepository.hasQuantityOfSkuInStock(lineitem_id)) {
|
||||||
|
// throw new SkuQuantityNotInStockError("");
|
||||||
|
// }
|
||||||
|
// TODO should match w/ cart.client_id
|
||||||
|
const query = `
|
||||||
|
UPDATE cart_lineitem
|
||||||
|
SET quantity = quantity + 1
|
||||||
|
WHERE lineitem_id = $1
|
||||||
|
`;
|
||||||
|
|
||||||
|
return this.database.update(query, [lineitem_id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// addItem(item: IProduct) {
|
||||||
|
// this.products.push(item);
|
||||||
|
// }
|
||||||
|
|
||||||
|
removeItem(item: IProduct) {}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
const query = `DELETE FROM chart WHERE client_id = $1`;
|
||||||
|
this.database.update(query, [this.clientId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Cart;
|
||||||
14
src/cart/CartErrors.ts
Normal file
14
src/cart/CartErrors.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
class SkuQuantityNotInStockError extends Error {
|
||||||
|
name: string;
|
||||||
|
statusCode: number;
|
||||||
|
|
||||||
|
constructor(message) {
|
||||||
|
super(message);
|
||||||
|
this.name = "SkuQuantityNotInStockError";
|
||||||
|
this.statusCode = 200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
SkuQuantityNotInStockError,
|
||||||
|
};
|
||||||
81
src/cart/CartSession.ts
Normal file
81
src/cart/CartSession.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import type WSCart from "./WSCart";
|
||||||
|
|
||||||
|
let coldLog = 0;
|
||||||
|
|
||||||
|
interface IGlobalCart {
|
||||||
|
sessionId: string;
|
||||||
|
wsCart: WSCart;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CartSession {
|
||||||
|
carts: Array<IGlobalCart>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.carts = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
getWSCartByClientId(clientId: string): WSCart {
|
||||||
|
const match = this.carts.find((cart) => cart.wsCart?.clientId === clientId);
|
||||||
|
if (!match) return null;
|
||||||
|
return match?.wsCart;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(sessionId: string, cart: WSCart) {
|
||||||
|
console.log(
|
||||||
|
`adding session ${sessionId} with cart id: ${cart?.cart?.clientId}`
|
||||||
|
);
|
||||||
|
this.carts.push({ wsCart: cart, sessionId });
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(sessionId) {
|
||||||
|
console.log(`removing session ${sessionId}`);
|
||||||
|
this.carts = this.carts.filter((cart) => cart.sessionId !== sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeIfNotAlive() {
|
||||||
|
this.carts.forEach((cart) => {
|
||||||
|
if (cart.wsCart.isAlive) return;
|
||||||
|
this.remove(cart);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async emitChangeToClients(wsCart: WSCart) {
|
||||||
|
const { clientId } = wsCart;
|
||||||
|
|
||||||
|
const matchingCarts = this.carts.filter(
|
||||||
|
(cart) => cart.wsCart.clientId === clientId
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`emit change to all carts with id ${clientId}:`,
|
||||||
|
matchingCarts.length
|
||||||
|
);
|
||||||
|
|
||||||
|
const cart = await matchingCarts[0]?.wsCart.cart.get();
|
||||||
|
matchingCarts.forEach((_cart) => _cart.wsCart.emitCart(cart));
|
||||||
|
}
|
||||||
|
|
||||||
|
listCarts() {
|
||||||
|
if (this.carts.length === 0) {
|
||||||
|
if (coldLog < 4) {
|
||||||
|
console.log("No clients");
|
||||||
|
coldLog = coldLog + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Active clients: (${this.carts.length})`);
|
||||||
|
this.carts.forEach((cart: IGlobalCart) => {
|
||||||
|
console.table({
|
||||||
|
isAlive: cart.wsCart?.isAlive,
|
||||||
|
clientId: cart.wsCart?.clientId,
|
||||||
|
sessionId: cart?.sessionId,
|
||||||
|
hasCart: cart.wsCart?.cart !== null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
coldLog = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CartSession;
|
||||||
138
src/cart/WSCart.ts
Normal file
138
src/cart/WSCart.ts
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import Cart from "./Cart";
|
||||||
|
import CartSession from "./CartSession";
|
||||||
|
import type ICart from "../interfaces/ICart";
|
||||||
|
import { IProduct } from "../interfaces/IProduct";
|
||||||
|
|
||||||
|
const cartSession = new CartSession();
|
||||||
|
|
||||||
|
class InvalidLineItemIdError extends Error {
|
||||||
|
statusCode: number;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const message = "Fant ikke produktet som ble lagt til";
|
||||||
|
super(message);
|
||||||
|
|
||||||
|
this.statusCode = 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDataAsCartPayload(message: string): ICartPayload {
|
||||||
|
let json: ICartPayload = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
json = JSON.parse(message);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ICartPayload {
|
||||||
|
command: string;
|
||||||
|
message: string;
|
||||||
|
product_no?: number;
|
||||||
|
product_sku_no?: number;
|
||||||
|
quantity?: number;
|
||||||
|
lineitem_id?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WSCart {
|
||||||
|
ws: WebSocket;
|
||||||
|
clientId: string | null;
|
||||||
|
cart: Cart;
|
||||||
|
cartSession: CartSession;
|
||||||
|
|
||||||
|
constructor(ws, clientId) {
|
||||||
|
this.ws = ws;
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.cart = new Cart(clientId);
|
||||||
|
this.cartSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isAlive() {
|
||||||
|
return this.ws.readyState === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* emitters */
|
||||||
|
message(message: string, success = true) {
|
||||||
|
this.ws.send(JSON.stringify({ message, success }));
|
||||||
|
}
|
||||||
|
|
||||||
|
async emitCart(cart: any[] | null = null) {
|
||||||
|
if (cart === null || cart?.length === 0) {
|
||||||
|
cart = await this.cart.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ws.send(JSON.stringify({ cart, success: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* handle known commands */
|
||||||
|
async addCartProduct(payload): Promise<boolean> {
|
||||||
|
const { product_no, product_sku_no, quantity } = payload;
|
||||||
|
if (!product_no || !quantity) {
|
||||||
|
// throw here?
|
||||||
|
this.message("Missing product_no or quantity", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.cart.add(product_no, product_sku_no, quantity);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeCartProduct(lineitem_id: number): Promise<boolean> {
|
||||||
|
if (isNaN(lineitem_id)) throw new InvalidLineItemIdError();
|
||||||
|
await this.cart.remove(lineitem_id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async decrementProductInCart(lineitem_id: number): Promise<boolean> {
|
||||||
|
if (isNaN(lineitem_id)) throw new InvalidLineItemIdError();
|
||||||
|
await this.cart.decrement(lineitem_id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async incrementProductInCart(lineitem_id: number): Promise<boolean> {
|
||||||
|
// TODO validate the quantity trying to be added here ??
|
||||||
|
|
||||||
|
if (isNaN(lineitem_id)) throw new InvalidLineItemIdError();
|
||||||
|
await this.cart.increment(lineitem_id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* main ws data/message handler */
|
||||||
|
async handleMessage(data: Buffer | string, isBinary: boolean) {
|
||||||
|
const dataMessage = isBinary ? String(data) : data.toString();
|
||||||
|
if (dataMessage === "heartbeat") return;
|
||||||
|
|
||||||
|
const payload = parseDataAsCartPayload(dataMessage);
|
||||||
|
const { command } = payload;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let emitCart = false;
|
||||||
|
|
||||||
|
if (command === "cart") this.emitCart();
|
||||||
|
else if (command === "add") {
|
||||||
|
emitCart = await this.addCartProduct(payload);
|
||||||
|
} else if (command === "rm") {
|
||||||
|
emitCart = await this.removeCartProduct(payload?.lineitem_id);
|
||||||
|
} else if (command === "decrement") {
|
||||||
|
emitCart = await this.decrementProductInCart(payload?.lineitem_id);
|
||||||
|
} else if (command === "increment") {
|
||||||
|
emitCart = await this.incrementProductInCart(payload?.lineitem_id);
|
||||||
|
} else {
|
||||||
|
console.log(`client has sent us other/without command: ${dataMessage}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emitCart) {
|
||||||
|
this.cartSession.emitChangeToClients(this);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// ????
|
||||||
|
if (error.message) this.message(error?.message, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleError() {}
|
||||||
|
|
||||||
|
destroy() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WSCart;
|
||||||
67
src/customer.ts
Normal file
67
src/customer.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import establishedDatabase from "./database";
|
||||||
|
import ICustomer from "./interfaces/ICustomer";
|
||||||
|
import logger from "./logger";
|
||||||
|
|
||||||
|
class CustomerRepository {
|
||||||
|
database: typeof establishedDatabase;
|
||||||
|
|
||||||
|
constructor(database = establishedDatabase) {
|
||||||
|
this.database = database || establishedDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
newCustomer(customer: ICustomer) {
|
||||||
|
const query = `
|
||||||
|
INSERT INTO customer (email, first_name, last_name, street_address, zip_code, city)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6)
|
||||||
|
RETURNING customer_no
|
||||||
|
`;
|
||||||
|
|
||||||
|
const {
|
||||||
|
email,
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
street_address,
|
||||||
|
zip_code,
|
||||||
|
city,
|
||||||
|
} = customer;
|
||||||
|
logger.info("Creating customer", { customer });
|
||||||
|
|
||||||
|
return this.database.get(query, [
|
||||||
|
email,
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
street_address,
|
||||||
|
zip_code,
|
||||||
|
city,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCustomer(customer_no) {
|
||||||
|
const query = `
|
||||||
|
SELECT email, first_name, last_name, street_address, zip_code, city
|
||||||
|
FROM customer
|
||||||
|
WHERE customer_no = $1`;
|
||||||
|
|
||||||
|
return this.database.get(query, [customer_no]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomerRepository;
|
||||||
|
|
||||||
|
// ```
|
||||||
|
// SELECT products.*
|
||||||
|
// FROM products
|
||||||
|
// INNER JOIN orders_lineitem
|
||||||
|
// ON products.product_no = orders_lineitem.product_no
|
||||||
|
// WHERE order_id = 'fb9a5910-0dcf-4c65-9c25-3fb3eb883ce5';
|
||||||
|
|
||||||
|
// SELECT orders.*
|
||||||
|
// FROM orders
|
||||||
|
// WHERE order_id = 'fb9a5910-0dcf-4c65-9c25-3fb3eb883ce5';
|
||||||
|
|
||||||
|
// SELECT customer.*
|
||||||
|
// FROM customer
|
||||||
|
// INNER JOIN orders
|
||||||
|
// ON customer.customer_no = orders.customer_no
|
||||||
|
// WHERE order_id = 'fb9a5910-0dcf-4c65-9c25-3fb3eb883ce5';
|
||||||
|
// ```;
|
||||||
395
src/order.ts
Normal file
395
src/order.ts
Normal file
@@ -0,0 +1,395 @@
|
|||||||
|
import logger from "./logger";
|
||||||
|
import establishedDatabase from "./database";
|
||||||
|
import { ORDER_STATUS_CODES } from "./enums/order";
|
||||||
|
import type { IOrder } from "./interfaces/IOrder";
|
||||||
|
import type { IProduct } from "./interfaces/IProduct";
|
||||||
|
|
||||||
|
class MissingOrderError extends Error {
|
||||||
|
statusCode: number;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const message = "Requested order not found.";
|
||||||
|
super(message);
|
||||||
|
|
||||||
|
this.name = "MissingOrderError";
|
||||||
|
this.statusCode = 404;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PaymentType {
|
||||||
|
database: typeof establishedDatabase;
|
||||||
|
|
||||||
|
constructor(database) {
|
||||||
|
this.database = database || establishedDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
getId(name) {
|
||||||
|
const query = `SELECT payment_id FROM payment_types WHERE name = $1`;
|
||||||
|
return this.database.get(query, [name]).then((resp) => resp["payment_id"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderRepository {
|
||||||
|
database: typeof establishedDatabase;
|
||||||
|
|
||||||
|
constructor(database = establishedDatabase) {
|
||||||
|
this.database = database || establishedDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
async newOrder(customer_no: string) {
|
||||||
|
const query = `
|
||||||
|
INSERT INTO orders (customer_no) VALUES ($1)
|
||||||
|
RETURNING order_id
|
||||||
|
`;
|
||||||
|
|
||||||
|
logger.info("Creating order with customer", { customer_no });
|
||||||
|
return await this.database.get(query, [customer_no]);
|
||||||
|
}
|
||||||
|
|
||||||
|
addOrderLineItem(
|
||||||
|
order_id,
|
||||||
|
product_no: number,
|
||||||
|
sku_id: number,
|
||||||
|
price: number,
|
||||||
|
quantity: number
|
||||||
|
) {
|
||||||
|
const query = `
|
||||||
|
INSERT INTO orders_lineitem (order_id, product_no, product_sku_no, price, quantity)
|
||||||
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
|
`;
|
||||||
|
|
||||||
|
logger.info("Adding lineitem to order", {
|
||||||
|
order_id,
|
||||||
|
product_no,
|
||||||
|
sku_id,
|
||||||
|
price,
|
||||||
|
quantity,
|
||||||
|
});
|
||||||
|
return this.database.get(query, [
|
||||||
|
order_id,
|
||||||
|
product_no,
|
||||||
|
sku_id,
|
||||||
|
price,
|
||||||
|
quantity,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getExistingOrdersOnProduct(product) {
|
||||||
|
const query = `
|
||||||
|
SELECT order_id, product_no, end_time, start_time
|
||||||
|
FROM orders
|
||||||
|
WHERE product_no = $1
|
||||||
|
AND NOT status = ''
|
||||||
|
AND end_time > now()
|
||||||
|
`;
|
||||||
|
|
||||||
|
return this.database.all(query, [product.product_no]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getConflictingProductOrders(order) {
|
||||||
|
const query = `
|
||||||
|
SELECT order_id, product_no, end_time, start_time, status, created, updated
|
||||||
|
FROM orders
|
||||||
|
WHERE product_no = $1
|
||||||
|
AND NOT status = ''
|
||||||
|
AND NOT order_id = $2
|
||||||
|
AND NOT status = $3
|
||||||
|
AND NOT status = $4
|
||||||
|
AND NOT status = $5
|
||||||
|
`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
order,
|
||||||
|
conflicting: this.database.all(query, [
|
||||||
|
order.product_no,
|
||||||
|
order.order_id,
|
||||||
|
ORDER_STATUS_CODES.INITIATED,
|
||||||
|
ORDER_STATUS_CODES.COMPLETED,
|
||||||
|
ORDER_STATUS_CODES.CANCELLED,
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getAll() {
|
||||||
|
const query = `
|
||||||
|
SELECT orders.created, orders.order_id, customer.first_name, customer.last_name, customer.email, status, orders_lines.order_sum
|
||||||
|
FROM orders
|
||||||
|
INNER JOIN (
|
||||||
|
SELECT order_id, SUM(quantity * orders_lineitem.price) as order_sum
|
||||||
|
FROM orders_lineitem
|
||||||
|
INNER JOIN product_sku
|
||||||
|
ON orders_lineitem.product_sku_no = product_sku.sku_id
|
||||||
|
GROUP BY order_id
|
||||||
|
) AS orders_lines
|
||||||
|
ON orders.order_id = orders_lines.order_id
|
||||||
|
INNER JOIN customer
|
||||||
|
ON customer.customer_no = orders.customer_no;`;
|
||||||
|
|
||||||
|
return this.database.all(query, []);
|
||||||
|
|
||||||
|
// SELECT orders.created, orders.order_id, customer.first_name, customer.last_name, customer.email, status, orders_lines.sku_id, orders_lines.quantity, size, price, orders_lines.quantity * price as sum
|
||||||
|
// FROM orders
|
||||||
|
// INNER JOIN (
|
||||||
|
// SELECT order_id, sku_id, product_sku.product_no, size, orders_lineitem.price, stock, quantity
|
||||||
|
// FROM orders_lineitem
|
||||||
|
// INNER JOIN product_sku
|
||||||
|
// ON orders_lineitem.product_sku_no = product_sku.sku_id
|
||||||
|
// ) AS orders_lines
|
||||||
|
// ON orders.order_id = orders_lines.order_id
|
||||||
|
// INNER JOIN customer
|
||||||
|
// ON customer.customer_no = orders.customer_no;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOrderDetailed(orderId) {
|
||||||
|
const customerQuery = `
|
||||||
|
SELECT customer.*
|
||||||
|
FROM orders
|
||||||
|
INNER JOIN (
|
||||||
|
SELECT customer_no, email, first_name, last_name, street_address, zip_code, city
|
||||||
|
FROM customer
|
||||||
|
) AS customer
|
||||||
|
ON orders.customer_no = customer.customer_no
|
||||||
|
WHERE order_id = $1`;
|
||||||
|
|
||||||
|
// const paymentQuery = ``
|
||||||
|
|
||||||
|
const orderQuery = `
|
||||||
|
SELECT order_id as orderId, created, updated, status
|
||||||
|
FROM orders
|
||||||
|
WHERE order_id = $1`;
|
||||||
|
|
||||||
|
const lineItemsQuery = `
|
||||||
|
SELECT product.name, product.image, orders_lineitem.quantity, product_sku.sku_id, product_sku.price, product_sku.size
|
||||||
|
FROM orders_lineitem
|
||||||
|
INNER JOIN product
|
||||||
|
ON orders_lineitem.product_no = product.product_no
|
||||||
|
INNER JOIN product_sku
|
||||||
|
ON orders_lineitem.product_sku_no = product_sku.sku_id
|
||||||
|
WHERE orders_lineitem.order_id = $1`;
|
||||||
|
|
||||||
|
const shippingQuery = `
|
||||||
|
SELECT shipping_company as company, tracking_code, tracking_link, user_notified
|
||||||
|
FROM shipping
|
||||||
|
WHERE shipping.order_id = $1`;
|
||||||
|
|
||||||
|
const [order, customer, shipping, lineItems] = await Promise.all([
|
||||||
|
this.database.get(orderQuery, [orderId]),
|
||||||
|
this.database.get(customerQuery, [orderId]),
|
||||||
|
this.database.get(shippingQuery, [orderId]),
|
||||||
|
this.database.all(lineItemsQuery, [orderId]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...order,
|
||||||
|
customer,
|
||||||
|
shipping,
|
||||||
|
lineItems,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOrder(orderId): Promise<IOrder> {
|
||||||
|
const orderQuery = `
|
||||||
|
SELECT order_id, customer_no, created, updated, status
|
||||||
|
FROM orders
|
||||||
|
WHERE order_id = $1`;
|
||||||
|
|
||||||
|
const lineItemsQuery = `
|
||||||
|
SELECT product.name, product.image, orders_lineitem.quantity, product_sku.sku_id, product_sku.price, product_sku.size
|
||||||
|
FROM orders_lineitem
|
||||||
|
INNER JOIN product
|
||||||
|
ON orders_lineitem.product_no = product.product_no
|
||||||
|
INNER JOIN product_sku
|
||||||
|
ON orders_lineitem.product_sku_no = product_sku.sku_id
|
||||||
|
WHERE orders_lineitem.order_id = $1`;
|
||||||
|
|
||||||
|
const [order, lineItems] = await Promise.all([
|
||||||
|
this.database.get(orderQuery, [orderId]),
|
||||||
|
this.database.all(lineItemsQuery, [orderId]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...order,
|
||||||
|
lineItems,
|
||||||
|
};
|
||||||
|
|
||||||
|
// return this.database.get(query, [orderId]).then((row) => {
|
||||||
|
// if (row) {
|
||||||
|
// return row;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// throw new MissingOrderError();
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrderWithProduct(orderId) {
|
||||||
|
const query = `
|
||||||
|
SELECT order_id, product.price, product.product_no, product.name
|
||||||
|
FROM orders
|
||||||
|
INNER JOIN products as product
|
||||||
|
ON (orders.product_no = product.product_no)
|
||||||
|
WHERE orders.order_id = $1
|
||||||
|
`;
|
||||||
|
|
||||||
|
return this.database.get(query, [orderId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
rejectOrder(orderId) {
|
||||||
|
const timestamp = new Date();
|
||||||
|
const query = `
|
||||||
|
UPDATE orders
|
||||||
|
SET status = $1, updated = $2
|
||||||
|
WHERE order_id = $3
|
||||||
|
`;
|
||||||
|
|
||||||
|
return this.database.update(query, [
|
||||||
|
ORDER_STATUS_CODES.REJECTED,
|
||||||
|
timestamp,
|
||||||
|
orderId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
refundOrder(orderId: string) {
|
||||||
|
const timestamp = new Date();
|
||||||
|
const query = `
|
||||||
|
UPDATE orders
|
||||||
|
SET status = $1, updated = $2
|
||||||
|
WHERE order_id = $3
|
||||||
|
`;
|
||||||
|
|
||||||
|
return this.database.update(query, [
|
||||||
|
ORDER_STATUS_CODES.REFUNDED,
|
||||||
|
timestamp,
|
||||||
|
orderId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
rejectByVippsTimeout(orderId) {
|
||||||
|
const timestamp = new Date();
|
||||||
|
const query = `
|
||||||
|
UPDATE orders
|
||||||
|
SET status = $1, end_time = $2, updated = $3
|
||||||
|
WHERE order_id = $4
|
||||||
|
`;
|
||||||
|
|
||||||
|
return this.database.update(query, [
|
||||||
|
ORDER_STATUS_CODES.TIMED_OUT_REJECT,
|
||||||
|
timestamp,
|
||||||
|
timestamp,
|
||||||
|
orderId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getExtendedOrders(orderId) {
|
||||||
|
const query = `
|
||||||
|
SELECT status, orders.end_time, start_time, vp.vipps_status, vp.amount, vp.hours, vp.captured, vp.refunded, vp.parent_order_id, vp.order_id
|
||||||
|
FROM orders
|
||||||
|
INNER JOIN vipps_payments as vp
|
||||||
|
ON (orders.order_id = vp.order_id OR orders.order_id = vp.parent_order_id)
|
||||||
|
WHERE (vp.order_id = $1 or vp.parent_order_id = $2)
|
||||||
|
`;
|
||||||
|
|
||||||
|
return this.database.all(query, [orderId, orderId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrderStatus(orderId: string) {
|
||||||
|
const query = `SELECT status
|
||||||
|
FROM orders
|
||||||
|
WHERE orders.order_id = $1`;
|
||||||
|
|
||||||
|
return this.database.get(query, [orderId]).then((row) => {
|
||||||
|
if (row) return row;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelOrder(orderId: string) {
|
||||||
|
const timestamp = new Date();
|
||||||
|
const query = `UPDATE orders
|
||||||
|
SET status = $1, updated = $2
|
||||||
|
WHERE order_id = $3`;
|
||||||
|
|
||||||
|
return this.database.update(query, [
|
||||||
|
ORDER_STATUS_CODES.CANCELLED,
|
||||||
|
timestamp,
|
||||||
|
orderId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async confirmOrder(orderId: string) {
|
||||||
|
const order = await this.getOrder(orderId);
|
||||||
|
|
||||||
|
const orderSkuQuantity = order.lineItems.map((lineItem) => {
|
||||||
|
return {
|
||||||
|
sku_id: lineItem.sku_id,
|
||||||
|
quantity: lineItem.quantity,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
orderSkuQuantity.map((el) =>
|
||||||
|
this.reduceSkuInStock(el.sku_id, el.quantity)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const timestamp = new Date();
|
||||||
|
const query = `
|
||||||
|
UPDATE orders
|
||||||
|
SET status = $1, updated = $2
|
||||||
|
WHERE order_id = $3`;
|
||||||
|
|
||||||
|
return this.database.update(query, [
|
||||||
|
ORDER_STATUS_CODES.CONFIRMED,
|
||||||
|
timestamp,
|
||||||
|
orderId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
reduceSkuInStock(sku_id: number, quantity: number) {
|
||||||
|
console.log("reducing stock for sku_id:", sku_id, quantity);
|
||||||
|
const query = `UPDATE product_sku
|
||||||
|
SET stock = stock - $2
|
||||||
|
WHERE sku_id = $1`;
|
||||||
|
|
||||||
|
return this.database.update(query, [sku_id, quantity]);
|
||||||
|
}
|
||||||
|
|
||||||
|
increaseSkuInStock(sku_id: number, quantity: number) {
|
||||||
|
const query = `UPDATE product_sku
|
||||||
|
SET stock = stock + $2
|
||||||
|
WHERE sku_id = $1`;
|
||||||
|
|
||||||
|
return this.database.update(query, [sku_id, quantity]);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateEndTime(orderId, endTime) {
|
||||||
|
const timestamp = new Date();
|
||||||
|
const query = `UPDATE orders
|
||||||
|
SET status = $1, updated = $2, end_time = $3
|
||||||
|
WHERE order_id = $4`;
|
||||||
|
|
||||||
|
return this.database.update(query, [
|
||||||
|
ORDER_STATUS_CODES.CONFIRMED,
|
||||||
|
timestamp,
|
||||||
|
endTime,
|
||||||
|
orderId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStartTime(orderId, startTime) {
|
||||||
|
const timestamp = new Date();
|
||||||
|
const query = `
|
||||||
|
UPDATE orders
|
||||||
|
SET status = $1, updated = $2, start_time = $3
|
||||||
|
WHERE order_id = $4`;
|
||||||
|
|
||||||
|
return this.database.update(query, [
|
||||||
|
ORDER_STATUS_CODES.CONFIRMED,
|
||||||
|
timestamp,
|
||||||
|
startTime,
|
||||||
|
orderId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrderRepository;
|
||||||
203
src/product.ts
Normal file
203
src/product.ts
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
import establishedDatabase from "./database";
|
||||||
|
|
||||||
|
class ProductRepository {
|
||||||
|
database: typeof establishedDatabase;
|
||||||
|
|
||||||
|
constructor(database = establishedDatabase) {
|
||||||
|
this.database = database || establishedDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
async add(
|
||||||
|
name = "foo",
|
||||||
|
description = "foo",
|
||||||
|
image = "/static/no-product.png",
|
||||||
|
subtext = "foo",
|
||||||
|
primary_color = "foo"
|
||||||
|
) {
|
||||||
|
const query = `
|
||||||
|
INSERT INTO
|
||||||
|
product (name, description, image, subtext, primary_color)
|
||||||
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
|
`;
|
||||||
|
|
||||||
|
await this.database.update(query, [
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
image,
|
||||||
|
subtext,
|
||||||
|
primary_color,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const productIdQuery = `SELECT currval('product_product_no_seq')`;
|
||||||
|
const productCurrVal = await this.database.get(productIdQuery, []);
|
||||||
|
return productCurrVal.currval;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllProducts() {
|
||||||
|
const query = `
|
||||||
|
SELECT product.*, variations
|
||||||
|
FROM product
|
||||||
|
JOIN (
|
||||||
|
SELECT product_no, count(size) AS variations
|
||||||
|
FROM product_sku
|
||||||
|
GROUP BY product_no
|
||||||
|
) AS product_sku
|
||||||
|
ON product.product_no = product_sku.product_no`;
|
||||||
|
|
||||||
|
return this.database.all(query, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(productId) {
|
||||||
|
const productQuery = `SELECT * FROM product WHERE product_no = $1`;
|
||||||
|
const product = await this.database.get(productQuery, [productId]);
|
||||||
|
|
||||||
|
const skuQuery = `
|
||||||
|
SELECT sku_id, size, price, stock, default_price, updated, created
|
||||||
|
FROM product_sku
|
||||||
|
WHERE product_no = $1
|
||||||
|
ORDER BY created`;
|
||||||
|
|
||||||
|
const productSkus = await this.database.all(skuQuery, [productId]);
|
||||||
|
return Promise.resolve({
|
||||||
|
...product,
|
||||||
|
variations: productSkus,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async addSku(
|
||||||
|
productId,
|
||||||
|
price = 10,
|
||||||
|
size = "",
|
||||||
|
stock = 0,
|
||||||
|
defaultPrice = false
|
||||||
|
) {
|
||||||
|
const query = `
|
||||||
|
INSERT INTO
|
||||||
|
product_sku (product_no, price, size, stock, default_price)
|
||||||
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
|
`;
|
||||||
|
|
||||||
|
return this.database.update(query, [
|
||||||
|
productId,
|
||||||
|
price,
|
||||||
|
size,
|
||||||
|
stock,
|
||||||
|
defaultPrice,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSkus(productId) {
|
||||||
|
const q = `SELECT sku_id, product_no, price, size, stock, default_price, created, updated
|
||||||
|
FROM product_sku
|
||||||
|
WHERE product_no = $1
|
||||||
|
ORDER BY created`;
|
||||||
|
|
||||||
|
return this.database.all(q, [productId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSkuStock(skuId) {
|
||||||
|
const query = "SELECT stock FROM product_sku WHERE sku_id = $1";
|
||||||
|
const stockResponse = await this.database.get(query, [skuId]);
|
||||||
|
return stockResponse?.stock || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper
|
||||||
|
async hasQuantityOfSkuInStock(skuId, quantity) {
|
||||||
|
const stock = await this.getSkuStock(skuId);
|
||||||
|
if (!stock) return false;
|
||||||
|
|
||||||
|
// if requested quantity is less or equal to current stock
|
||||||
|
return quantity <= stock;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProduct(product) {
|
||||||
|
const {
|
||||||
|
product_no,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
image,
|
||||||
|
subtext,
|
||||||
|
primary_color,
|
||||||
|
// new Date
|
||||||
|
} = product;
|
||||||
|
const query = `
|
||||||
|
UPDATE product
|
||||||
|
SET name = $1, description = $2, image = $3, subtext = $4, primary_color = $5, updated = to_timestamp($6 / 1000.0)
|
||||||
|
WHERE product_no = $7
|
||||||
|
`;
|
||||||
|
|
||||||
|
return this.database.update(query, [
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
image,
|
||||||
|
subtext,
|
||||||
|
primary_color,
|
||||||
|
new Date().getTime(),
|
||||||
|
product_no,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSku(productId, skuId, stock = 0, size = 0, price = 10) {
|
||||||
|
const query = `
|
||||||
|
UPDATE product_sku
|
||||||
|
SET
|
||||||
|
price = $1,
|
||||||
|
size = $2,
|
||||||
|
stock = $3,
|
||||||
|
updated = to_timestamp($4 / 1000.0)
|
||||||
|
WHERE product_no = $5 and sku_id = $6`;
|
||||||
|
|
||||||
|
console.log("update sql:", query, [
|
||||||
|
price,
|
||||||
|
size,
|
||||||
|
stock,
|
||||||
|
new Date().getTime(),
|
||||||
|
productId,
|
||||||
|
skuId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return this.database.update(query, [
|
||||||
|
price,
|
||||||
|
size,
|
||||||
|
stock,
|
||||||
|
new Date().getTime(),
|
||||||
|
productId,
|
||||||
|
skuId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setSkuDefaultPrice(productId, skuId) {
|
||||||
|
const resetOld = `
|
||||||
|
UPDATE product_sku
|
||||||
|
SET default_price = false, updated = to_timestamp($1 / 1000.0)
|
||||||
|
WHERE product_no = $2 and default_price = true`;
|
||||||
|
const setNew = `
|
||||||
|
UPDATE product_sku
|
||||||
|
SET default_price = true, updated = to_timestamp($1 / 1000.0)
|
||||||
|
WHERE product_no = $2 AND sku_id = $3
|
||||||
|
`;
|
||||||
|
|
||||||
|
await this.database.update(resetOld, [new Date().getTime(), productId]);
|
||||||
|
await this.database.update(setNew, [
|
||||||
|
new Date().getTime(),
|
||||||
|
productId,
|
||||||
|
skuId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultPrice(productId, skuId) {
|
||||||
|
const query = `
|
||||||
|
SELECT *
|
||||||
|
FROM product_sku
|
||||||
|
WHERE default_price = true and product_no = $1 and sku_id = $2`;
|
||||||
|
|
||||||
|
return this.database.query(query, [productId, skuId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSku(productId, skuId) {
|
||||||
|
const query = `DELETE from product_sku WHERE product_no = $1 AND sku_id = $2`;
|
||||||
|
return this.database.update(query, [productId, skuId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProductRepository;
|
||||||
69
src/stripe/stripeApi.ts
Normal file
69
src/stripe/stripeApi.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import Stripe from "stripe";
|
||||||
|
import type ICustomer from "../interfaces/ICustomer";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does calls to stripe API
|
||||||
|
*/
|
||||||
|
|
||||||
|
class StripeApi {
|
||||||
|
publicKey: string;
|
||||||
|
secretKey: string;
|
||||||
|
stripe: Stripe;
|
||||||
|
|
||||||
|
constructor(publicKey: string, secretKey: string) {
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
this.secretKey = secretKey;
|
||||||
|
this.stripe = new Stripe(this.secretKey, {
|
||||||
|
apiVersion: "2022-08-01",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async createPaymentIntent(
|
||||||
|
clientId: string,
|
||||||
|
total: number,
|
||||||
|
orderId: string,
|
||||||
|
customer: ICustomer
|
||||||
|
): Promise<Stripe.Response<Stripe.PaymentIntent>> {
|
||||||
|
const stripeCustomer = await this.createCustomer(clientId, customer);
|
||||||
|
const paymentIntent = await this.stripe.paymentIntents.create({
|
||||||
|
customer: stripeCustomer?.id,
|
||||||
|
amount: total * 100,
|
||||||
|
currency: "NOK",
|
||||||
|
shipping: {
|
||||||
|
name: stripeCustomer.name,
|
||||||
|
address: stripeCustomer.address,
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
clientId,
|
||||||
|
orderId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return paymentIntent;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createCustomer(clientId: string, customer: ICustomer) {
|
||||||
|
return await this.stripe.customers.create({
|
||||||
|
email: customer.email,
|
||||||
|
name: `${customer.first_name} ${customer.last_name}`,
|
||||||
|
address: {
|
||||||
|
city: customer.city,
|
||||||
|
line1: customer.street_address,
|
||||||
|
postal_code: String(customer.zip_code),
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
clientId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createProduct(cart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createShipping() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StripeApi;
|
||||||
87
src/stripe/stripeRepository.ts
Normal file
87
src/stripe/stripeRepository.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import establishedDatabase from "../database";
|
||||||
|
import Configuration from "../config/configuration";
|
||||||
|
import StripeApi from "./stripeApi";
|
||||||
|
import Stripe from "stripe";
|
||||||
|
import type ICustomer from "../interfaces/ICustomer";
|
||||||
|
|
||||||
|
const configuration = Configuration.getInstance();
|
||||||
|
const stripeApi = new StripeApi(
|
||||||
|
configuration.get("stripe", "publicKey"),
|
||||||
|
configuration.get("stripe", "secretKey")
|
||||||
|
);
|
||||||
|
|
||||||
|
class StripeRepository {
|
||||||
|
database: typeof establishedDatabase;
|
||||||
|
|
||||||
|
constructor(database = establishedDatabase) {
|
||||||
|
this.database = database || establishedDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
commitPaymentToDatabase(
|
||||||
|
orderId: string,
|
||||||
|
payload: Stripe.Response<Stripe.PaymentIntent>
|
||||||
|
) {
|
||||||
|
const query = `
|
||||||
|
INSERT INTO stripe_payments
|
||||||
|
(order_id, amount, stripe_initiation_response, stripe_transaction_id, stripe_status)
|
||||||
|
VALUES ($1,$2,$3,$4,$5)`;
|
||||||
|
|
||||||
|
return this.database.query(query, [
|
||||||
|
orderId,
|
||||||
|
payload.amount,
|
||||||
|
payload,
|
||||||
|
payload.id,
|
||||||
|
payload.status,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePaymentIntent(payload: Stripe.Response<Stripe.PaymentIntent>) {
|
||||||
|
const query = `
|
||||||
|
UPDATE stripe_payments
|
||||||
|
SET stripe_status = $2, amount_received = $3, updated = $4
|
||||||
|
WHERE order_id = $1`;
|
||||||
|
|
||||||
|
return this.database.update(query, [
|
||||||
|
payload.metadata.orderId,
|
||||||
|
payload.status,
|
||||||
|
payload.amount_received,
|
||||||
|
new Date(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePaymentCharge(payload: Stripe.Response<Stripe.Charge>) {
|
||||||
|
const query = `
|
||||||
|
UPDATE stripe_payments
|
||||||
|
SET stripe_status = $2, amount_captured = $3, amount_refunded = $4, updated = $5
|
||||||
|
WHERE order_id = $1
|
||||||
|
`;
|
||||||
|
|
||||||
|
return this.database.update(query, [
|
||||||
|
payload.metadata.orderId,
|
||||||
|
payload.status,
|
||||||
|
payload.amount_captured,
|
||||||
|
payload.amount_refunded,
|
||||||
|
new Date(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createPayment(
|
||||||
|
clientId: string,
|
||||||
|
total: number,
|
||||||
|
orderId: string,
|
||||||
|
customer: ICustomer
|
||||||
|
) {
|
||||||
|
const paymentIntent = await stripeApi.createPaymentIntent(
|
||||||
|
clientId,
|
||||||
|
total,
|
||||||
|
orderId,
|
||||||
|
customer
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.commitPaymentToDatabase(orderId, paymentIntent).then(
|
||||||
|
() => paymentIntent.client_secret
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StripeRepository;
|
||||||
110
src/warehouse.ts
Normal file
110
src/warehouse.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import establishedDatabase from "./database";
|
||||||
|
import type { IProductWithSkus } from "./interfaces/IProduct";
|
||||||
|
|
||||||
|
// interface IProductSku {
|
||||||
|
// id: string
|
||||||
|
// size: string
|
||||||
|
// stock: number
|
||||||
|
// price: number
|
||||||
|
// created?: string
|
||||||
|
// updated?: string
|
||||||
|
// }
|
||||||
|
|
||||||
|
// interface IProductWithSku extends IProduct {
|
||||||
|
// sku: IProductSku
|
||||||
|
// }
|
||||||
|
|
||||||
|
class WarehouseRepository {
|
||||||
|
database: any;
|
||||||
|
|
||||||
|
constructor(database = null) {
|
||||||
|
this.database = database || establishedDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllProductIds() {
|
||||||
|
return this.database.get("SELECT product_no FROM product");
|
||||||
|
}
|
||||||
|
|
||||||
|
async getProduct(productId): Promise<IProductWithSkus> {
|
||||||
|
const productQuery = `SELECT * FROM product WHERE product_no = $1`;
|
||||||
|
const product = await this.database.get(productQuery, [productId]);
|
||||||
|
|
||||||
|
const skuQuery = `
|
||||||
|
SELECT sku_id, size, price, stock, default_price, updated, created
|
||||||
|
FROM product_sku
|
||||||
|
WHERE product_no = $1
|
||||||
|
ORDER BY created`;
|
||||||
|
|
||||||
|
const productSkus = await this.database.all(skuQuery, [productId]);
|
||||||
|
return Promise.resolve({
|
||||||
|
...product,
|
||||||
|
variations: productSkus,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
all(): Promise<IProductWithSkus[]> {
|
||||||
|
const query = `
|
||||||
|
SELECT product.*, variation_count, sum_stock
|
||||||
|
FROM product
|
||||||
|
INNER JOIN (
|
||||||
|
SELECT product_no, count(size) AS variation_count, sum(stock) as sum_stock
|
||||||
|
FROM product_sku
|
||||||
|
GROUP BY product_no
|
||||||
|
) AS product_sku
|
||||||
|
ON product.product_no = product_sku.product_no`;
|
||||||
|
|
||||||
|
return this.database.all(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAvailableProducts() {
|
||||||
|
const query = `SELECT * from available_products`;
|
||||||
|
return this.database.all(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSkuStock(skuId) {
|
||||||
|
const query = "SELECT stock FROM product_sku WHERE sku_id = $1";
|
||||||
|
|
||||||
|
return this.database.get(query, [skuId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
createWarehouseProduct(skuId, stock) {
|
||||||
|
const query = `
|
||||||
|
INSERT INTO
|
||||||
|
warehouse (product_sku_id, stock)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
`;
|
||||||
|
|
||||||
|
return this.database.update(query, [skuId, stock]);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWarehouseProductSkuStock(skuId, stock) {
|
||||||
|
const query = `
|
||||||
|
UPDATE warehouse
|
||||||
|
SET stock = $1, updated = to_timestamp($2 / 1000.0)
|
||||||
|
WHERE product_sku_id = $3
|
||||||
|
`;
|
||||||
|
|
||||||
|
return this.database.update(query, [stock, new Date(), skuId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWarehouseProductSkuStatus(skuId, status) {
|
||||||
|
const sqlStatus = status ? "TRUE" : "FALSE";
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
UPDATE warehouse
|
||||||
|
SET enabled = $1, updated = to_timestamp($2 / 1000.0)
|
||||||
|
WHERE product_sku_id = $3`;
|
||||||
|
|
||||||
|
return this.database.query(query, [sqlStatus, new Date(), skuId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
disableWarehouseProductSku(skuId) {
|
||||||
|
return this.updateWarehouseProductSkuStatus(skuId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
enableWarehouseProductSku(skuId) {
|
||||||
|
return this.updateWarehouseProductSkuStatus(skuId, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WarehouseRepository;
|
||||||
Reference in New Issue
Block a user