External providers can now be loaded
Implemented invoice protocol (see docs)
This commit is contained in:
@@ -25,7 +25,8 @@ export const config: IConfig = {
|
|||||||
methods: [
|
methods: [
|
||||||
CryptoUnits.BITCOIN,
|
CryptoUnits.BITCOIN,
|
||||||
CryptoUnits.DOGECOIN,
|
CryptoUnits.DOGECOIN,
|
||||||
CryptoUnits.ETHEREUM
|
CryptoUnits.ETHEREUM,
|
||||||
|
CryptoUnits.MONERO
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,9 @@ export const JWT_SECRET = process.env.JWT_SECRET || "";
|
|||||||
export const INVOICE_SECRET = process.env.INVOICE_SECRET || "";
|
export const INVOICE_SECRET = process.env.INVOICE_SECRET || "";
|
||||||
|
|
||||||
export let rpcClient: rpc.HttpClient | undefined = undefined;
|
export let rpcClient: rpc.HttpClient | undefined = undefined;
|
||||||
export let invoiceScheduler: InvoiceManager | undefined = undefined;
|
export let invoiceManager: InvoiceManager | undefined = undefined;
|
||||||
export let socketManager: SocketManager | undefined = undefined;
|
export let socketManager: SocketManager | undefined = undefined;
|
||||||
|
export let providerManager: ProviderManager = undefined;
|
||||||
|
|
||||||
export let logger: winston.Logger;
|
export let logger: winston.Logger;
|
||||||
|
|
||||||
@@ -108,10 +109,10 @@ async function run() {
|
|||||||
logger.debug("At least one admin user already exists, skip.");
|
logger.debug("At least one admin user already exists, skip.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const providerManager = new ProviderManager(resolve('./src/helper/providers'));
|
providerManager = new ProviderManager(resolve('./src/helper/providers'));
|
||||||
providerManager.scan();
|
providerManager.scan();
|
||||||
|
|
||||||
invoiceScheduler = new InvoiceManager();
|
invoiceManager = new InvoiceManager();
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const http = new Server(app);
|
const http = new Server(app);
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { Request, Response } from 'express';
|
|||||||
import got from 'got';
|
import got from 'got';
|
||||||
import { config } from '../../config';
|
import { config } from '../../config';
|
||||||
|
|
||||||
import { invoiceScheduler, INVOICE_SECRET, rpcClient } from '../app';
|
import { invoiceManager, INVOICE_SECRET, logger, providerManager, rpcClient } from '../app';
|
||||||
import { randomString } from '../helper/crypto';
|
import { randomString } from '../helper/crypto';
|
||||||
import { CryptoUnits, FiatUnits, findCryptoBySymbol, PaymentStatus } from '../helper/types';
|
import { CryptoUnits, decimalPlaces, FiatUnits, findCryptoBySymbol, PaymentStatus, roundNumber } from '../helper/types';
|
||||||
import { ICart, IInvoice, IPaymentMethod } from '../models/invoice/invoice.interface';
|
import { ICart, IInvoice, IPaymentMethod } from '../models/invoice/invoice.interface';
|
||||||
import { Invoice } from '../models/invoice/invoice.model';
|
import { Invoice } from '../models/invoice/invoice.model';
|
||||||
import { calculateCart } from '../models/invoice/invoice.schema';
|
import { calculateCart } from '../models/invoice/invoice.schema';
|
||||||
@@ -24,18 +24,12 @@ export async function createInvoice(req: Request, res: Response) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const paymentMethodsRaw: string[] = req.body.methods;
|
|
||||||
const successUrl: string = req.body.successUrl;
|
const successUrl: string = req.body.successUrl;
|
||||||
const cancelUrl: string = req.body.cancelUrl;
|
const cancelUrl: string = req.body.cancelUrl;
|
||||||
const cart: ICart[] = req.body.cart;
|
const cart: ICart[] = req.body.cart;
|
||||||
let currency: FiatUnits = req.body.currency;
|
let currency: FiatUnits = req.body.currency;
|
||||||
let totalPrice: number = req.body.totalPrice;
|
let totalPrice: number = req.body.totalPrice;
|
||||||
|
|
||||||
if (paymentMethodsRaw === undefined) {
|
|
||||||
res.status(400).send({ message: '"paymentMethods" are not provided!' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (successUrl === undefined) {
|
if (successUrl === undefined) {
|
||||||
res.status(400).send({ message: '"successUrl" is not provided!' });
|
res.status(400).send({ message: '"successUrl" is not provided!' });
|
||||||
return;
|
return;
|
||||||
@@ -67,7 +61,7 @@ export async function createInvoice(req: Request, res: Response) {
|
|||||||
// Convert coin symbol to full text in order to query Coin Gecko. eg.: ['btc', 'xmr'] => ['bitcoin', 'monero']
|
// Convert coin symbol to full text in order to query Coin Gecko. eg.: ['btc', 'xmr'] => ['bitcoin', 'monero']
|
||||||
let cgFormat = [];
|
let cgFormat = [];
|
||||||
|
|
||||||
paymentMethodsRaw.forEach(coin => {
|
config.payment.methods.forEach(coin => {
|
||||||
const crypto = findCryptoBySymbol(coin);
|
const crypto = findCryptoBySymbol(coin);
|
||||||
|
|
||||||
if (crypto !== undefined) {
|
if (crypto !== undefined) {
|
||||||
@@ -78,6 +72,7 @@ export async function createInvoice(req: Request, res: Response) {
|
|||||||
const request = await got.get(`https://api.coingecko.com/api/v3/simple/price?ids=${cgFormat.join(',')}&vs_currencies=${currency.toLowerCase()}`, {
|
const request = await got.get(`https://api.coingecko.com/api/v3/simple/price?ids=${cgFormat.join(',')}&vs_currencies=${currency.toLowerCase()}`, {
|
||||||
responseType: 'json'
|
responseType: 'json'
|
||||||
});
|
});
|
||||||
|
console.log(request.body);
|
||||||
|
|
||||||
// Calulate total price, if cart is provided
|
// Calulate total price, if cart is provided
|
||||||
if (cart !== undefined && totalPrice === undefined) {
|
if (cart !== undefined && totalPrice === undefined) {
|
||||||
@@ -85,8 +80,14 @@ export async function createInvoice(req: Request, res: Response) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let paymentMethods: IPaymentMethod[] = [];
|
let paymentMethods: IPaymentMethod[] = [];
|
||||||
config.payment.methods.forEach(coin => {
|
|
||||||
paymentMethods.push({ method: CryptoUnits[coin.toUpperCase()], amount: totalPrice / Number(request.body[coin][currency.toLowerCase()]) });
|
cgFormat.forEach(coinFullName => {
|
||||||
|
console.log(coinFullName);
|
||||||
|
const coin = CryptoUnits[coinFullName.toUpperCase()];
|
||||||
|
|
||||||
|
paymentMethods.push({ method: coin, amount:
|
||||||
|
roundNumber(totalPrice / Number(request.body[coinFullName][currency.toLowerCase()]), decimalPlaces.get(coin))
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const dueBy = new Date(Date.now() + 1000 * 60 * 60);
|
const dueBy = new Date(Date.now() + 1000 * 60 * 60);
|
||||||
@@ -131,13 +132,19 @@ export async function getInvoice(req: Request, res: Response) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(invoice.status === PaymentStatus.UNCONFIRMED || invoice.status === PaymentStatus.DONE) {
|
if(invoice.status === PaymentStatus.UNCONFIRMED || invoice.status === PaymentStatus.DONE) {
|
||||||
rpcClient.request('gettransaction', [invoice.transcationHashes[0]], (err, message) => {
|
const transaction = await providerManager.getProvider(invoice.paymentMethod).getTransaction(invoice.transcationHash);
|
||||||
|
try {
|
||||||
let invoiceClone: any = invoice;
|
let invoiceClone: any = invoice;
|
||||||
console.log(message.result.confirmations);
|
console.log(transaction.confirmations);
|
||||||
|
|
||||||
invoiceClone['confirmation'] = message.result.confirmations;
|
invoiceClone['confirmation'] = transaction.confirmations;
|
||||||
res.status(200).send(invoiceClone);
|
res.status(200).send(invoiceClone);
|
||||||
});
|
} catch (err) {
|
||||||
|
if (err) {
|
||||||
|
logger.error(`There was an error while getting transaction: ${err.message}`);
|
||||||
|
res.status(500).send();
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
res.status(200).send(invoice);
|
res.status(200).send(invoice);
|
||||||
}
|
}
|
||||||
@@ -187,6 +194,40 @@ export async function cancelInvoice(req: Request, res: Response) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST /invoice/:selector/setmethod
|
||||||
|
export async function setPaymentMethod(req: Request, res: Response) {
|
||||||
|
const method: string = req.body.method;
|
||||||
|
const selector: string = req.params.selector;
|
||||||
|
|
||||||
|
if (method === undefined || selector === undefined) {
|
||||||
|
res.status(400).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.values(CryptoUnits).indexOf(method.toUpperCase() as any) === -1) {
|
||||||
|
res.status(400).send({ message: 'Unknown payment method' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const invoice = await Invoice.findOne({ selector: selector });
|
||||||
|
if (invoice === null) {
|
||||||
|
res.status(404).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
invoice.status = PaymentStatus.PENDING;
|
||||||
|
invoice.paymentMethod = CryptoUnits[findCryptoBySymbol(method)];
|
||||||
|
invoice.receiveAddress = await providerManager.getProvider(invoice.paymentMethod).getNewAddress();
|
||||||
|
|
||||||
|
await invoice.save();
|
||||||
|
|
||||||
|
res.status(200).send({
|
||||||
|
receiveAddress: invoice.receiveAddress
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /invoice/paymentmethods
|
||||||
export async function getPaymentMethods(req: Request, res: Response) {
|
export async function getPaymentMethods(req: Request, res: Response) {
|
||||||
res.status(200).send({ methods: config.payment.methods });
|
res.status(200).send({ methods: config.payment.methods });
|
||||||
}
|
}
|
||||||
@@ -8,12 +8,6 @@ import { CryptoUnits } from './types';
|
|||||||
*/
|
*/
|
||||||
export abstract class BackendProvider {
|
export abstract class BackendProvider {
|
||||||
|
|
||||||
invoiceManager: InvoiceManager = null;
|
|
||||||
|
|
||||||
constructor (invoiceManager: InvoiceManager) {
|
|
||||||
this.invoiceManager = invoiceManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Provider information */
|
/* Provider information */
|
||||||
abstract readonly NAME: string;
|
abstract readonly NAME: string;
|
||||||
abstract readonly DESCRIPTION: string;
|
abstract readonly DESCRIPTION: string;
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ export class InvoiceManager {
|
|||||||
|
|
||||||
// Get all pending transcations
|
// Get all pending transcations
|
||||||
Invoice.find({ status: PaymentStatus.PENDING }).then(invoices => {
|
Invoice.find({ status: PaymentStatus.PENDING }).then(invoices => {
|
||||||
|
console.log('These are pending', invoices);
|
||||||
|
|
||||||
this.pendingInvoices = invoices;
|
this.pendingInvoices = invoices;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -28,8 +30,6 @@ export class InvoiceManager {
|
|||||||
Invoice.find({ status: PaymentStatus.UNCONFIRMED }).then(invoices => {
|
Invoice.find({ status: PaymentStatus.UNCONFIRMED }).then(invoices => {
|
||||||
this.unconfirmedTranscations = invoices;
|
this.unconfirmedTranscations = invoices;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.watchConfirmations();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,11 +75,4 @@ export class InvoiceManager {
|
|||||||
socketManager.emitInvoiceEvent(invoice, 'confirmationUpdate', { count });
|
socketManager.emitInvoiceEvent(invoice, 'confirmationUpdate', { count });
|
||||||
return this.knownConfirmations.set(invoice.id, count);
|
return this.knownConfirmations.set(invoice.id, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This functions loops over each unconfirmed transaction to check if it reached "trusted" threshold.
|
|
||||||
*/
|
|
||||||
private watchConfirmations() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,49 @@
|
|||||||
import { readdirSync } from 'fs';
|
import { readdirSync } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { invoiceManager, logger } from '../app';
|
||||||
|
import { BackendProvider } from './backendProvider';
|
||||||
|
import { CryptoUnits } from './types';
|
||||||
|
|
||||||
export class ProviderManager {
|
export class ProviderManager {
|
||||||
|
|
||||||
providerFilePath: string;
|
providerFilePath: string;
|
||||||
|
cryptoProvider: Map<CryptoUnits, BackendProvider>;
|
||||||
|
|
||||||
constructor(filePath: string) {
|
constructor(filePath: string) {
|
||||||
this.providerFilePath = filePath;
|
this.providerFilePath = filePath;
|
||||||
|
this.cryptoProvider = new Map<CryptoUnits, BackendProvider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getProvider(crypto: CryptoUnits): BackendProvider | undefined {
|
||||||
|
return this.cryptoProvider.get(crypto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan & load all found providers
|
||||||
|
*/
|
||||||
scan() {
|
scan() {
|
||||||
const getDirectories = () =>
|
const getDirectories = () =>
|
||||||
readdirSync(this.providerFilePath, { withFileTypes: true })
|
readdirSync(this.providerFilePath, { withFileTypes: true })
|
||||||
.filter(dirent => dirent.isDirectory())
|
.filter(dirent => dirent.name.endsWith('.ts'))
|
||||||
.map(dirent => dirent.name)
|
.map(dirent => dirent.name)
|
||||||
|
|
||||||
console.log(getDirectories());
|
getDirectories().forEach(file => {
|
||||||
|
const absolutePath = join(this.providerFilePath, file);
|
||||||
|
const providerModule = require(absolutePath);
|
||||||
|
const provider = new providerModule.Provider() as BackendProvider;
|
||||||
|
|
||||||
|
if (this.cryptoProvider.has(provider.CRYPTO)) {
|
||||||
|
logger.warn(`Provider ${provider.NAME} will be ignored since there is already another provider active for ${provider.CRYPTO}!`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cryptoProvider.set(provider.CRYPTO, provider);
|
||||||
|
|
||||||
|
// Execute onEnable() function of this provider
|
||||||
|
provider.onEnable();
|
||||||
|
|
||||||
|
logger.info(`Loaded provider ${provider.NAME} by ${provider.AUTHOR} (${provider.VERSION}) for ${provider.CRYPTO}`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
import { Socket, Subscriber } from "zeromq";
|
import { Socket, Subscriber } from "zeromq";
|
||||||
import { config } from "../../../config";
|
import { config } from "../../../config";
|
||||||
import { logger, rpcClient } from "../../app";
|
import { invoiceManager, logger, rpcClient } from "../../app";
|
||||||
import { BackendProvider, ITransaction, IRawTransaction } from "../backendProvider";
|
import { BackendProvider, ITransaction, IRawTransaction } from "../backendProvider";
|
||||||
import { InvoiceManager } from "../invoiceManager";
|
import { InvoiceManager } from "../invoiceManager";
|
||||||
import { CryptoUnits, PaymentStatus } from "../types";
|
import { CryptoUnits, PaymentStatus } from "../types";
|
||||||
|
|
||||||
export class BitcoinCore implements BackendProvider {
|
export class Provider implements BackendProvider {
|
||||||
invoiceManager: InvoiceManager;
|
|
||||||
|
|
||||||
private sock: Subscriber;
|
private sock: Subscriber;
|
||||||
|
|
||||||
@@ -17,7 +16,14 @@ export class BitcoinCore implements BackendProvider {
|
|||||||
CRYPTO = CryptoUnits.BITCOIN;
|
CRYPTO = CryptoUnits.BITCOIN;
|
||||||
|
|
||||||
onEnable() {
|
onEnable() {
|
||||||
logger.info('The Bitcoin Core backend is now available!');
|
this.sock = new Subscriber();
|
||||||
|
this.sock.connect('tcp://127.0.0.1:29000');
|
||||||
|
this.sock.subscribe('rawtx');
|
||||||
|
|
||||||
|
this.listener();
|
||||||
|
this.watchConfirmations();
|
||||||
|
|
||||||
|
//logger.info('The Bitcoin Core backend is now available!');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNewAddress(): Promise<string> {
|
async getNewAddress(): Promise<string> {
|
||||||
@@ -78,10 +84,6 @@ export class BitcoinCore implements BackendProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async listener() {
|
async listener() {
|
||||||
this.sock = new Subscriber();
|
|
||||||
this.sock.connect('tcp://127.0.0.1:29000');
|
|
||||||
this.sock.subscribe('rawtx');
|
|
||||||
|
|
||||||
logger.info('Now listing for incoming transaction to any invoices ...');
|
logger.info('Now listing for incoming transaction to any invoices ...');
|
||||||
for await (const [topic, msg] of this.sock) {
|
for await (const [topic, msg] of this.sock) {
|
||||||
const rawtx = msg.toString('hex');
|
const rawtx = msg.toString('hex');
|
||||||
@@ -89,7 +91,7 @@ export class BitcoinCore implements BackendProvider {
|
|||||||
|
|
||||||
tx.vout.forEach(output => {
|
tx.vout.forEach(output => {
|
||||||
// Loop over each output and check if the address of one matches the one of an invoice.
|
// Loop over each output and check if the address of one matches the one of an invoice.
|
||||||
this.invoiceManager.getPendingInvoices().forEach(async invoice => {
|
invoiceManager.getPendingInvoices().forEach(async invoice => {
|
||||||
if (output.scriptPubKey.addresses === undefined) return; // Sometimes (weird) transaction don't have any addresses
|
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)
|
// We found our transaction (https://developer.bitcoin.org/reference/rpc/decoderawtransaction.html)
|
||||||
@@ -107,10 +109,10 @@ export class BitcoinCore implements BackendProvider {
|
|||||||
logger.info(`Sent ${output.value} BTC back to ${senderAddress}`);
|
logger.info(`Sent ${output.value} BTC back to ${senderAddress}`);
|
||||||
} else {
|
} else {
|
||||||
invoice.status = PaymentStatus.UNCONFIRMED;
|
invoice.status = PaymentStatus.UNCONFIRMED;
|
||||||
invoice.transcationHashes = tx.txid;
|
invoice.transcationHash = tx.txid;
|
||||||
invoice.save();
|
invoice.save();
|
||||||
|
|
||||||
this.invoiceManager.upgradeInvoice(invoice);
|
invoiceManager.upgradeInvoice(invoice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -121,22 +123,22 @@ export class BitcoinCore implements BackendProvider {
|
|||||||
|
|
||||||
async watchConfirmations() {
|
async watchConfirmations() {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
this.invoiceManager.getUnconfirmedTransactions().forEach(async invoice => {
|
invoiceManager.getUnconfirmedTransactions().forEach(async invoice => {
|
||||||
if (invoice.transcationHashes.length === 0) return;
|
if (invoice.transcationHash.length === 0) return;
|
||||||
let trustworthy = true; // Will be true if all transactions are above threshold.
|
let trustworthy = true; // Will be true if all transactions are above threshold.
|
||||||
|
|
||||||
for (let i = 0; i < invoice.transcationHashes.length; i++) {
|
for (let i = 0; i < invoice.transcationHash.length; i++) {
|
||||||
const transcation = invoice.transcationHashes[i];
|
const transcation = invoice.transcationHash;
|
||||||
|
|
||||||
const tx = await this.getTransaction(transcation);
|
const tx = await this.getTransaction(transcation);
|
||||||
|
|
||||||
if (this.invoiceManager.hasConfirmationChanged(invoice, tx.confirmations)) {
|
if (invoiceManager.hasConfirmationChanged(invoice, tx.confirmations)) {
|
||||||
this.invoiceManager.setConfirmationCount(invoice, tx.confirmations);
|
invoiceManager.setConfirmationCount(invoice, tx.confirmations);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Number(tx.confirmations) > 0) {
|
if (Number(tx.confirmations) > 0) {
|
||||||
logger.info(`Transaction (${transcation}) has reached more then 2 confirmations and can now be trusted!`);
|
logger.info(`Transaction (${transcation}) has reached more then 2 confirmations and can now be trusted!`);
|
||||||
this.invoiceManager.removeInvoice(invoice);
|
invoiceManager.removeInvoice(invoice);
|
||||||
} else {
|
} else {
|
||||||
trustworthy = false;
|
trustworthy = false;
|
||||||
logger.debug(`Transcation (${transcation}) has not reached his threshold yet.`);
|
logger.debug(`Transcation (${transcation}) has not reached his threshold yet.`);
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ export class SocketManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
listen() {
|
listen() {
|
||||||
console.log("Listen");
|
|
||||||
|
|
||||||
this.io.on('connection', (socket: Socket) => {
|
this.io.on('connection', (socket: Socket) => {
|
||||||
// The frontend sends his selector, then pick _id and put it in `socketInvoice` map.
|
// The frontend sends his selector, then pick _id and put it in `socketInvoice` map.
|
||||||
// Return `true` if successful and `false` if not.
|
// Return `true` if successful and `false` if not.
|
||||||
@@ -29,6 +27,22 @@ export class SocketManager {
|
|||||||
logger.info(`Socket ${socket.id} has subscribed to invoice ${invoice.id} (${PaymentStatus[invoice.status]})`);
|
logger.info(`Socket ${socket.id} has subscribed to invoice ${invoice.id} (${PaymentStatus[invoice.status]})`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on('subscribe', async (data: any) => {
|
||||||
|
if (data === undefined || data === null) {
|
||||||
|
socket.emit('subscribe', false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const invoice = await Invoice.findOne({ selector: data.selector });
|
||||||
|
if (invoice === null) {
|
||||||
|
socket.emit('subscribe', false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.join(invoice.selector);
|
||||||
|
socket.emit('subscribe', true);
|
||||||
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { logger } from "../app";
|
||||||
|
|
||||||
export enum CryptoUnits {
|
export enum CryptoUnits {
|
||||||
BITCOIN = 'BTC',
|
BITCOIN = 'BTC',
|
||||||
BITCOINCASH = 'BCH',
|
BITCOINCASH = 'BCH',
|
||||||
@@ -7,9 +9,23 @@ export enum CryptoUnits {
|
|||||||
MONERO = 'XMR'
|
MONERO = 'XMR'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the decimal places by id
|
||||||
|
*/
|
||||||
|
export const decimalPlaces = new Map<CryptoUnits, number>([
|
||||||
|
[CryptoUnits.BITCOIN, 8],
|
||||||
|
[CryptoUnits.BITCOINCASH, 8],
|
||||||
|
[CryptoUnits.ETHEREUM, 18],
|
||||||
|
[CryptoUnits.LITECOIN, 8],
|
||||||
|
[CryptoUnits.DOGECOIN, 8],
|
||||||
|
[CryptoUnits.MONERO, 12]
|
||||||
|
])
|
||||||
|
|
||||||
export function findCryptoBySymbol(symbol: string): string | null {
|
export function findCryptoBySymbol(symbol: string): string | null {
|
||||||
for (let coin in CryptoUnits) {
|
for (let coin in CryptoUnits) {
|
||||||
if (CryptoUnits[coin] === symbol.toUpperCase()) return coin;
|
if (CryptoUnits[coin] === symbol.toUpperCase()) {
|
||||||
|
return coin;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -20,6 +36,11 @@ export enum FiatUnits {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum PaymentStatus {
|
export enum PaymentStatus {
|
||||||
|
/**
|
||||||
|
* The payment has been cancelled by the user.
|
||||||
|
*/
|
||||||
|
CANCELLED = -2,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The invoice has been requested but the payment method has to be choosen.
|
* The invoice has been requested but the payment method has to be choosen.
|
||||||
*/
|
*/
|
||||||
@@ -30,23 +51,21 @@ export enum PaymentStatus {
|
|||||||
*/
|
*/
|
||||||
PENDING = 0,
|
PENDING = 0,
|
||||||
|
|
||||||
/**
|
|
||||||
* The payment has been paid, but not completly.
|
|
||||||
*/
|
|
||||||
PARTIALLY = 1,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The payment has been made but it's not yet confirmed.
|
* The payment has been made but it's not yet confirmed.
|
||||||
*/
|
*/
|
||||||
UNCONFIRMED = 2,
|
UNCONFIRMED = 1,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The payment is completed and the crypto is now available.
|
* The payment is completed and the crypto is now available.
|
||||||
*/
|
*/
|
||||||
DONE = 3,
|
DONE = 2,
|
||||||
|
|
||||||
/**
|
|
||||||
* The payment has been cancelled by the user.
|
|
||||||
*/
|
|
||||||
CANCELLED = 4
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// I'll will just leave that here
|
||||||
|
export function roundNumber(number: number, precision: number) {
|
||||||
|
var factor = Math.pow(10, precision);
|
||||||
|
var tmpNumber = number * factor;
|
||||||
|
var rounded = Math.round(tmpNumber);
|
||||||
|
return rounded / factor;
|
||||||
|
};
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export interface IInvoice extends Document {
|
|||||||
|
|
||||||
// Is set when invoice got paid
|
// Is set when invoice got paid
|
||||||
// 3b38c3a215d4e7981e1516b2dcbf76fca58911274d5d55b3d615274d6e10f2c1
|
// 3b38c3a215d4e7981e1516b2dcbf76fca58911274d5d55b3d615274d6e10f2c1
|
||||||
transcationHashes?: string;
|
transcationHash?: string;
|
||||||
|
|
||||||
cart?: ICart[];
|
cart?: ICart[];
|
||||||
totalPrice?: number;
|
totalPrice?: number;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const schemaInvoice = new Schema({
|
|||||||
paymentMethods: [{ type: schemaPaymentMethods, required: true }],
|
paymentMethods: [{ type: schemaPaymentMethods, required: true }],
|
||||||
paymentMethod: { type: String, enum: Object.values(CryptoUnits), required: false },
|
paymentMethod: { type: String, enum: Object.values(CryptoUnits), required: false },
|
||||||
receiveAddress: { type: String, required: false },
|
receiveAddress: { type: String, required: false },
|
||||||
transcationHashes: { type: String, required: false },
|
transcationHash: { type: String, required: false },
|
||||||
cart: [{ type: schemaCart, required: false }],
|
cart: [{ type: schemaCart, required: false }],
|
||||||
totalPrice: { type: Number, required: false },
|
totalPrice: { type: Number, required: false },
|
||||||
currency: { type: String, enum: Object.values(FiatUnits), required: true },
|
currency: { type: String, enum: Object.values(FiatUnits), required: true },
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import { createInvoice, getInvoice, getPaymentMethods } from "../controllers/invoice";
|
import { createInvoice, getInvoice, getPaymentMethods, setPaymentMethod } from "../controllers/invoice";
|
||||||
import { MW_User } from "../controllers/user";
|
import { MW_User } from "../controllers/user";
|
||||||
|
|
||||||
const invoiceRouter = Router()
|
const invoiceRouter = Router()
|
||||||
|
|
||||||
invoiceRouter.get('/paymentmethods', getPaymentMethods);
|
invoiceRouter.get('/paymentmethods', getPaymentMethods);
|
||||||
invoiceRouter.get('/:selector', getInvoice);
|
invoiceRouter.get('/:selector', getInvoice);
|
||||||
|
invoiceRouter.post('/:selector/setmethod', setPaymentMethod);
|
||||||
invoiceRouter.get('/', MW_User, getInvoice);
|
invoiceRouter.get('/', MW_User, getInvoice);
|
||||||
invoiceRouter.post('/', MW_User, createInvoice);
|
invoiceRouter.post('/', MW_User, createInvoice);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user