Cart, order, warehouse, product, customer & stripe payment files

This commit is contained in:
2022-12-11 18:19:19 +01:00
parent b636f86151
commit 2dbbd7914d
10 changed files with 1313 additions and 0 deletions

149
src/cart/Cart.ts Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;

View 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
View 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;