Live status of transaction
This commit is contained in:
15
src/app.ts
15
src/app.ts
@@ -5,6 +5,8 @@ import * as express from 'express';
|
||||
import * as rpc from 'jayson';
|
||||
import * as mongoose from 'mongoose';
|
||||
import * as winston from 'winston';
|
||||
import * as socketio from 'socket.io';
|
||||
import { Server } from 'http';
|
||||
|
||||
import { config } from '../config';
|
||||
import { hashPassword, randomPepper, randomString } from './helper/crypto';
|
||||
@@ -12,6 +14,7 @@ import { InvoiceScheduler } from './helper/invoiceScheduler';
|
||||
import { User } from './models/user/user.model';
|
||||
import { invoiceRouter } from './routes/invoice';
|
||||
import { userRouter } from './routes/user';
|
||||
import { SocketManager } from './helper/socketio';
|
||||
|
||||
// Load .env
|
||||
dconfig({ debug: true, encoding: 'UTF-8' });
|
||||
@@ -23,6 +26,7 @@ export const INVOICE_SECRET = process.env.INVOICE_SECRET || "";
|
||||
|
||||
export let rpcClient: rpc.HttpClient | undefined = undefined;
|
||||
export let invoiceScheduler: InvoiceScheduler | undefined = undefined;
|
||||
export let socketManager: SocketManager | undefined = undefined;
|
||||
|
||||
export let logger: winston.Logger;
|
||||
|
||||
@@ -105,15 +109,22 @@ async function run() {
|
||||
invoiceScheduler = new InvoiceScheduler();
|
||||
|
||||
const app = express();
|
||||
const http = new Server(app);
|
||||
|
||||
// Socket.io
|
||||
const io = new socketio(http);
|
||||
socketManager = new SocketManager(io);
|
||||
|
||||
app.use(express.json());
|
||||
app.use(cors());
|
||||
app.use(bodyParser.json({ limit: '2kb' }));
|
||||
|
||||
app.get('/', (req, res) => res.status(200).send('OK'));
|
||||
app.use('/invoice', invoiceRouter);
|
||||
app.use('/user', userRouter);
|
||||
|
||||
app.listen(config.http.port, config.http.host, () => {
|
||||
app.get('/', (req, res) => res.status(200).send('OK'));
|
||||
|
||||
http.listen(config.http.port, config.http.host, () => {
|
||||
logger.info(`HTTP server started on port ${config.http.host}:${config.http.port}`);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { Request, Response } from "express";
|
||||
import { invoiceScheduler, INVOICE_SECRET } from "../app";
|
||||
import { CryptoUnits, FiatUnits } from "../helper/types";
|
||||
import { ICart, IInvoice } from "../models/invoice/invoice.interface";
|
||||
import { Invoice } from "../models/invoice/invoice.model";
|
||||
import { rpcClient } from '../app';
|
||||
import { Request, Response } from 'express';
|
||||
import got from 'got';
|
||||
|
||||
import { invoiceScheduler, INVOICE_SECRET, rpcClient } from '../app';
|
||||
import { randomString } from '../helper/crypto';
|
||||
import { CryptoUnits, FiatUnits, findCryptoBySymbol, PaymentStatus } from '../helper/types';
|
||||
import { ICart, IInvoice, IPaymentMethod } from '../models/invoice/invoice.interface';
|
||||
import { Invoice } from '../models/invoice/invoice.model';
|
||||
import { calculateCart } from '../models/invoice/invoice.schema';
|
||||
|
||||
// POST /invoice/?sercet=XYZ
|
||||
export async function createInvoice(req: Request, res: Response) {
|
||||
@@ -20,14 +23,14 @@ export async function createInvoice(req: Request, res: Response) {
|
||||
}
|
||||
}
|
||||
|
||||
const paymentMethods: CryptoUnits[] = req.body.methods;
|
||||
const paymentMethodsRaw: string[] = req.body.methods;
|
||||
const successUrl: string = req.body.successUrl;
|
||||
const cancelUrl: string = req.body.cancelUrl;
|
||||
const cart: ICart[] = req.body.cart;
|
||||
const currency: FiatUnits = req.body.currency;
|
||||
const totalPrice: number = req.body.totalPrice;
|
||||
let currency: FiatUnits = req.body.currency;
|
||||
let totalPrice: number = req.body.totalPrice;
|
||||
|
||||
if (paymentMethods === undefined) {
|
||||
if (paymentMethodsRaw === undefined) {
|
||||
res.status(400).send({ message: '"paymentMethods" are not provided!' });
|
||||
return;
|
||||
}
|
||||
@@ -45,19 +48,52 @@ export async function createInvoice(req: Request, res: Response) {
|
||||
if (currency === undefined) {
|
||||
res.status(400).send({ message: '"currency" is not provided!' });
|
||||
return;
|
||||
} else {
|
||||
if (Object.keys(FiatUnits).indexOf(currency.toUpperCase()) === -1) {
|
||||
res.status(400).send({ message: '"currency" can only be "eur" and "usd"' });
|
||||
return;
|
||||
} else {
|
||||
currency = FiatUnits[currency.toUpperCase()];
|
||||
}
|
||||
}
|
||||
|
||||
/*if (cart === undefined && totalPrice === undefined) {
|
||||
if (cart === undefined && totalPrice === undefined) {
|
||||
res.status(400).send({ message: 'Either "cart" or "totalPrice" has to be defined.' });
|
||||
return;
|
||||
}*/
|
||||
}
|
||||
|
||||
rpcClient.request('getnewaddress', ['', 'bech32'], async (err, response) => {
|
||||
if (err) throw err;
|
||||
//console.log(response.result);
|
||||
|
||||
// Get price
|
||||
// Convert coin symbol to full text in order to query Coin Gecko. eg.: ['btc', 'xmr'] => ['bitcoin', 'monero']
|
||||
let cgFormat = [];
|
||||
|
||||
paymentMethodsRaw.forEach(coin => {
|
||||
const crypto = findCryptoBySymbol(coin);
|
||||
|
||||
if (crypto !== undefined) {
|
||||
cgFormat.push(crypto.toLowerCase());
|
||||
}
|
||||
});
|
||||
|
||||
const request = await got.get(`https://api.coingecko.com/api/v3/simple/price?ids=${cgFormat.join(',')}&vs_currencies=${currency.toLowerCase()}`, {
|
||||
responseType: 'json'
|
||||
});
|
||||
|
||||
// Calulate total price, if cart is provided
|
||||
if (cart !== undefined && totalPrice === undefined) {
|
||||
totalPrice = calculateCart(cart);
|
||||
}
|
||||
|
||||
let paymentMethods: IPaymentMethod[] = [];
|
||||
Object.keys(request.body).forEach(coin => {
|
||||
paymentMethods.push({ method: CryptoUnits[coin.toUpperCase()], amount: totalPrice / Number(request.body[coin][currency.toLowerCase()]) });
|
||||
});
|
||||
|
||||
Invoice.create({
|
||||
paymentMethods,
|
||||
selector: randomString(128),
|
||||
paymentMethods: paymentMethods,
|
||||
successUrl,
|
||||
cancelUrl,
|
||||
cart,
|
||||
@@ -72,26 +108,37 @@ export async function createInvoice(req: Request, res: Response) {
|
||||
}
|
||||
|
||||
invoiceScheduler.addInvoice(invoice);
|
||||
res.status(200).send({ id: invoice.id });
|
||||
res.status(200).send({ id: invoice.selector });
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// GET /invoice/
|
||||
// GET /invoice/:id
|
||||
// GET /invoice/:selector
|
||||
export async function getInvoice(req: Request, res: Response) {
|
||||
const invoiceId = req.params.id;
|
||||
const selector = req.params.selector;
|
||||
|
||||
// If an id is provided
|
||||
if (invoiceId !== undefined) {
|
||||
const invoice: any = await Invoice.findById(invoiceId);
|
||||
if (selector !== undefined) {
|
||||
const invoice: IInvoice = await Invoice.findOne({ selector: selector });
|
||||
if (invoice === null) {
|
||||
res.status(404).send();
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).send(invoice);
|
||||
|
||||
if(invoice.status === PaymentStatus.UNCONFIRMED || invoice.status === PaymentStatus.DONE) {
|
||||
rpcClient.request('gettransaction', [invoice.transcationHashes[0]], (err, message) => {
|
||||
let invoiceClone: any = invoice;
|
||||
console.log(message.result.confirmations);
|
||||
|
||||
invoiceClone['confirmation'] = message.result.confirmations;
|
||||
res.status(200).send(invoiceClone);
|
||||
});
|
||||
} else {
|
||||
res.status(200).send(invoice);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -117,4 +164,22 @@ export async function getInvoice(req: Request, res: Response) {
|
||||
.sort({ createdAt: sort });
|
||||
|
||||
res.status(200).send(invoices);
|
||||
}
|
||||
|
||||
// DELETE /invoice/:selector
|
||||
export async function cancelPaymnet(req: Request, res: Response) {
|
||||
const selector = req.params.selector;
|
||||
|
||||
// If an id is provided
|
||||
if (selector !== undefined) {
|
||||
const invoice = await Invoice.findOne({ selector: selector });
|
||||
if (invoice === null) {
|
||||
res.status(404).send();
|
||||
return;
|
||||
}
|
||||
|
||||
invoice.status = PaymentStatus.CANCELLED;
|
||||
await invoice.save();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,21 @@
|
||||
import { IInvoice } from "../models/invoice/invoice.interface";
|
||||
import { Subscriber } from 'zeromq';
|
||||
import { logger, rpcClient } from "../app";
|
||||
import { logger, rpcClient, socketManager } from "../app";
|
||||
import { invoiceRouter } from "../routes/invoice";
|
||||
import { Invoice } from "../models/invoice/invoice.model";
|
||||
import { PaymentStatus } from "./types";
|
||||
import { CryptoUnits, PaymentStatus } from "./types";
|
||||
import { config } from "../../config";
|
||||
|
||||
export class InvoiceScheduler {
|
||||
private pendingInvoices: IInvoice[];
|
||||
private unconfirmedTranscations: IInvoice[];
|
||||
private knownConfirmations: Map<string, number>; // Invoice id / confirmation cound
|
||||
private sock: Subscriber;
|
||||
|
||||
constructor() {
|
||||
this.unconfirmedTranscations = [];
|
||||
this.pendingInvoices = [];
|
||||
this.knownConfirmations = new Map<string, number>();
|
||||
|
||||
// Get all pending transcations
|
||||
Invoice.find({ status: PaymentStatus.PENDING }).then(invoices => {
|
||||
@@ -44,7 +47,7 @@ export class InvoiceScheduler {
|
||||
logger.info('Now listing for incoming transaction to any invoices ...');
|
||||
for await (const [topic, msg] of this.sock) {
|
||||
const rawtx = msg.toString('hex');
|
||||
logger.debug(`New tx: ${rawtx}`);
|
||||
//logger.debug(`New tx: ${rawtx}`);
|
||||
rpcClient.request('decoderawtransaction', [rawtx], (err, decoded) => {
|
||||
if (err) {
|
||||
logger.error(`Error while decoding raw tx: ${err.message}`);
|
||||
@@ -54,19 +57,29 @@ export class InvoiceScheduler {
|
||||
decoded.result.vout.forEach(output => {
|
||||
// Loop over each output and check if the address of one matches the one of an invoice.
|
||||
this.pendingInvoices.forEach(invoice => {
|
||||
// We found our transaction
|
||||
if (output.scriptPubKey.addresses === undefined) return; // Sometimes (weird) transaction don't have any addresses
|
||||
|
||||
// We found our transaction (https://developer.bitcoin.org/reference/rpc/decoderawtransaction.html)
|
||||
if (output.scriptPubKey.addresses.indexOf(invoice.receiveAddress) !== -1) {
|
||||
invoice.paid += output.value;
|
||||
logger.info(`Transcation for invoice ${invoice.id} received! (${decoded.result.hash})`);
|
||||
|
||||
// Change state in database
|
||||
invoice.status = PaymentStatus.UNCONFIRMED;
|
||||
invoice.transcationHash = decoded.result.txid;
|
||||
invoice.save();
|
||||
|
||||
// Push to array & remove from pending
|
||||
this.unconfirmedTranscations.push(invoice);
|
||||
this.pendingInvoices.splice(this.pendingInvoices.indexOf(invoice), 1);
|
||||
const price = invoice.paymentMethods.find((item) => { return item.method === CryptoUnits.BITCOIN }).amount;
|
||||
if (invoice.paid < price - config.transcations.acceptMargin) {
|
||||
const left = price - output.value;
|
||||
invoice.status = PaymentStatus.PARTIALLY;
|
||||
invoice.save();
|
||||
logger.info(`Transcation for invoice ${invoice.id} received but there are still ${left} BTC missing (${decoded.result.hash})`);
|
||||
} else {
|
||||
invoice.status = PaymentStatus.UNCONFIRMED;
|
||||
invoice.transcationHashes.push(decoded.result.txid);
|
||||
invoice.save();
|
||||
|
||||
// Push to array & remove from pending
|
||||
this.unconfirmedTranscations.push(invoice);
|
||||
this.pendingInvoices.splice(this.pendingInvoices.indexOf(invoice), 1);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
@@ -81,22 +94,40 @@ export class InvoiceScheduler {
|
||||
private watchConfirmations() {
|
||||
setInterval(() => {
|
||||
this.unconfirmedTranscations.forEach(invoice => {
|
||||
rpcClient.request('gettransaction', [invoice.transcationHash], (err, message) => {
|
||||
if (err) {
|
||||
logger.error(`Error while fetching confirmation state of ${invoice.transcationHash}: ${err.message}`);
|
||||
return;
|
||||
}
|
||||
if (invoice.transcationHashes.length === 0) return;
|
||||
let trustworthy = true; // Will be true if all transactions are above threshold.
|
||||
|
||||
if (Number(message.result.confirmations) > 2) {
|
||||
logger.info(`Transaction (${invoice.transcationHash}) has reached more then 2 confirmations and can now be trusted!`);
|
||||
invoice.status = PaymentStatus.DONE;
|
||||
invoice.save(); // This will trigger a post save hook that will notify the user.
|
||||
for (let i = 0; i < invoice.transcationHashes.length; i++) {
|
||||
const transcation = invoice.transcationHashes[i];
|
||||
|
||||
rpcClient.request('gettransaction', [transcation], (err, message) => {
|
||||
if (err) {
|
||||
logger.error(`Error while fetching confirmation state of ${transcation}: ${err.message}`);
|
||||
trustworthy = false;
|
||||
|
||||
this.unconfirmedTranscations.splice(this.unconfirmedTranscations.indexOf(invoice), 1);
|
||||
} else {
|
||||
logger.debug(`Transcation (${invoice.transcationHash}) has not reached his threshold yet.`);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.knownConfirmations.get(invoice.id) != message.result.confirmations) {
|
||||
this.knownConfirmations.set(invoice.id, message.result.confirmations);
|
||||
socketManager.getSocketByInvoice(invoice).emit('confirmationUpdate', { count: Number(message.result.confirmations) });
|
||||
}
|
||||
|
||||
if (Number(message.result.confirmations) > 0) {
|
||||
logger.info(`Transaction (${transcation}) has reached more then 2 confirmations and can now be trusted!`);
|
||||
|
||||
this.unconfirmedTranscations.splice(this.unconfirmedTranscations.indexOf(invoice), 1);
|
||||
} else {
|
||||
trustworthy = false;
|
||||
logger.debug(`Transcation (${transcation}) has not reached his threshold yet.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (trustworthy) {
|
||||
invoice.status = PaymentStatus.DONE;
|
||||
invoice.save(); // This will trigger a post save hook that will notify the user.
|
||||
}
|
||||
});
|
||||
}, 2_000);
|
||||
}
|
||||
|
||||
60
src/helper/socketio.ts
Normal file
60
src/helper/socketio.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Server, Socket } from "socket.io";
|
||||
import { logger } from "../app";
|
||||
import { IInvoice } from "../models/invoice/invoice.interface";
|
||||
import { Invoice } from "../models/invoice/invoice.model";
|
||||
import { PaymentStatus } from "./types";
|
||||
|
||||
export class SocketManager {
|
||||
io: Server;
|
||||
|
||||
private socketInvoice: Map<string, string>; // Socket ID / _id
|
||||
private idSocket: Map<string, Socket>; // Socket ID / Socket
|
||||
private invoiceSocket: Map<string, Socket>; // _id / Socket
|
||||
|
||||
constructor(io: Server) {
|
||||
this.io = io;
|
||||
this.socketInvoice = new Map<string, string>();
|
||||
this.idSocket = new Map<string, Socket>();
|
||||
this.invoiceSocket = new Map<string, Socket>();
|
||||
this.listen();
|
||||
}
|
||||
|
||||
listen() {
|
||||
console.log("Listen");
|
||||
|
||||
this.io.on('connection', (socket: Socket) => {
|
||||
this.idSocket.set(socket.id, socket);
|
||||
|
||||
// The frontend sends his selector, then pick _id and put it in `socketInvoice` map.
|
||||
// Return `true` if successful and `false` if not.
|
||||
socket.on('subscribe', async data => {
|
||||
if (data.selector !== undefined) {
|
||||
const invoice = await Invoice.findOne({ selector: data.selector });
|
||||
if (invoice === null) {
|
||||
socket.emit('subscribe', false);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`Socket ${socket.id} has subscribed to invoice ${invoice.id} (${PaymentStatus[invoice.status]})`);
|
||||
|
||||
this.socketInvoice.set(socket.id, invoice.id);
|
||||
this.invoiceSocket.set(invoice.id, socket);
|
||||
socket.emit('subscribe', true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getSocketById(id: string) {
|
||||
return this.idSocket.get(id);
|
||||
}
|
||||
|
||||
async getInvoiceBySocket(socketId: string) {
|
||||
const invoiceId = this.socketInvoice.get(socketId);
|
||||
return await Invoice.findById(invoiceId);
|
||||
}
|
||||
|
||||
getSocketByInvoice(invoice: IInvoice) {
|
||||
return this.invoiceSocket.get(invoice.id);
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,16 @@ export enum CryptoUnits {
|
||||
MONERO = 'XMR'
|
||||
}
|
||||
|
||||
export function findCryptoBySymbol(symbol: string): string | null {
|
||||
for (let coin in CryptoUnits) {
|
||||
if (CryptoUnits[coin] === symbol.toUpperCase()) return coin;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export enum FiatUnits {
|
||||
USD = 'USD',
|
||||
EUR = 'EURO'
|
||||
EUR = 'EUR'
|
||||
}
|
||||
|
||||
export enum PaymentStatus {
|
||||
@@ -18,13 +25,23 @@ export enum PaymentStatus {
|
||||
*/
|
||||
PENDING = 0,
|
||||
|
||||
/**
|
||||
* The payment has been paid, but not completly.
|
||||
*/
|
||||
PARTIALLY = 1,
|
||||
|
||||
/**
|
||||
* The payment has been made but it's not yet confirmed.
|
||||
*/
|
||||
UNCONFIRMED = 1,
|
||||
UNCONFIRMED = 2,
|
||||
|
||||
/**
|
||||
* The payment is completed and the crypto is now available.
|
||||
*/
|
||||
DONE = 2
|
||||
DONE = 3,
|
||||
|
||||
/**
|
||||
* The payment has been cancelled by the user.
|
||||
*/
|
||||
CANCELLED = 4
|
||||
}
|
||||
@@ -8,19 +8,30 @@ export interface ICart {
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
export interface IPaymentMethod {
|
||||
method: CryptoUnits;
|
||||
amount: number
|
||||
}
|
||||
|
||||
export interface IInvoice extends Document {
|
||||
selector: string;
|
||||
|
||||
// Available payment methods
|
||||
// [btc, xmr, eth, doge]
|
||||
paymentMethods: CryptoUnits[];
|
||||
// [{ method: 'btc', amount: 0.0000105 }]
|
||||
paymentMethods: IPaymentMethod[];
|
||||
|
||||
// 1Kss3e9iPB9vTgWJJZ1SZNkkFKcFJXPz9t
|
||||
receiveAddress: string;
|
||||
|
||||
paidWith?: CryptoUnits;
|
||||
|
||||
// Already paid amount, in case that not the entire amount was paid with once.
|
||||
// 0.000013
|
||||
paid?: number;
|
||||
|
||||
// Is set when invoice got paid
|
||||
// 3b38c3a215d4e7981e1516b2dcbf76fca58911274d5d55b3d615274d6e10f2c1
|
||||
transcationHash?: string;
|
||||
transcationHashes?: string[];
|
||||
|
||||
cart?: ICart[];
|
||||
totalPrice?: number;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NativeError, Schema, SchemaTypes } from 'mongoose';
|
||||
import { Schema } from 'mongoose';
|
||||
import { socketManager } from '../../app';
|
||||
import { CryptoUnits, FiatUnits, PaymentStatus } from '../../helper/types';
|
||||
import { IInvoice } from './invoice.interface';
|
||||
import { ICart, IInvoice } from './invoice.interface';
|
||||
|
||||
const urlRegex = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/
|
||||
|
||||
@@ -9,16 +10,23 @@ const schemaCart = new Schema({
|
||||
name: { type: String, trim: true, required: true },
|
||||
image: { type: String, match: urlRegex, required: true },
|
||||
quantity: { type: Number, default: 1 }
|
||||
})
|
||||
}, { _id: false });
|
||||
|
||||
const schemaPaymentMethods = new Schema({
|
||||
method: { type: String, enum: Object.values(CryptoUnits), required: true },
|
||||
amount: { type: Number, required: false }
|
||||
}, { _id: false });
|
||||
|
||||
const schemaInvoice = new Schema({
|
||||
paymentMethods: [{ type: String, enum: Object.values(CryptoUnits), default: [CryptoUnits.BITCOIN], required: true }],
|
||||
selector: { type: String, length: 128, required: true },
|
||||
paymentMethods: [{ type: schemaPaymentMethods, required: true }],
|
||||
receiveAddress: { type: String, required: true },
|
||||
paidWith: { type: String, enum: CryptoUnits },
|
||||
transcationHash: { type: String, required: false },
|
||||
paid: { type: Number, default: 0 },
|
||||
transcationHashes: [{ type: String, required: false }],
|
||||
cart: [{ type: schemaCart, required: false }],
|
||||
totalPrice: { type: Number, required: false },
|
||||
currency: { type: String, enum: Object.values(FiatUnits), required: false },
|
||||
currency: { type: String, enum: Object.values(FiatUnits), required: true },
|
||||
dueBy: { type: Number, required: true },
|
||||
status: { type: Number, enum: Object.values(PaymentStatus), default: PaymentStatus.PENDING },
|
||||
email: { type: String, required: false },
|
||||
@@ -31,8 +39,15 @@ const schemaInvoice = new Schema({
|
||||
versionKey: false
|
||||
});
|
||||
|
||||
schemaInvoice.pre('validate', function(next) {
|
||||
let self = this as IInvoice;
|
||||
self.currency = FiatUnits[self.currency];
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
// Validate values
|
||||
schemaInvoice.post('validate', function (res, next) {
|
||||
schemaInvoice.post('validate', function (doc, next) {
|
||||
let self = this as IInvoice;
|
||||
|
||||
// If cart is undefined and price too, error.
|
||||
@@ -40,19 +55,27 @@ schemaInvoice.post('validate', function (res, next) {
|
||||
next(new Error('Either cart or price has to be defined!'));
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
// If cart is provided, calculate price.
|
||||
if (self.cart !== undefined && self.totalPrice === undefined) {
|
||||
let totalPrice = 0;
|
||||
|
||||
for (let i = 0; i < self.cart.length; i++) {
|
||||
const item = self.cart[i];
|
||||
totalPrice += item.price * item.quantity;
|
||||
}
|
||||
schemaInvoice.post('save', function(doc, next) {
|
||||
let self = this as IInvoice;
|
||||
|
||||
self.set({ totalPrice });
|
||||
}
|
||||
if (socketManager.getSocketByInvoice(self) === undefined) return;
|
||||
socketManager.getSocketByInvoice(self).emit('status', self.status);
|
||||
next();
|
||||
})
|
||||
|
||||
export function calculateCart(cart: ICart[]): number {
|
||||
let totalPrice = 0;
|
||||
|
||||
for (let i = 0; i < cart.length; i++) {
|
||||
const item = cart[i];
|
||||
totalPrice += item.price * item.quantity;
|
||||
}
|
||||
|
||||
return totalPrice;
|
||||
}
|
||||
|
||||
export { schemaInvoice }
|
||||
@@ -4,7 +4,7 @@ import { MW_User } from "../controllers/user";
|
||||
|
||||
const invoiceRouter = Router()
|
||||
|
||||
invoiceRouter.get('/:id', getInvoice);
|
||||
invoiceRouter.get('/:selector', getInvoice);
|
||||
invoiceRouter.get('/', MW_User, getInvoice);
|
||||
invoiceRouter.post('/', MW_User, createInvoice);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user