Proper handling of unequal payments
- Cryptocurrencies are enabled dynamically - Invoice handler handles now more - Better status change handling
This commit is contained in:
@@ -37,6 +37,7 @@ async function run() {
|
||||
const { combine, timestamp, label, printf, prettyPrint } = winston.format;
|
||||
|
||||
const myFormat = printf(({ level, message, label, timestamp }) => {
|
||||
if (label !== undefined) return `${timestamp} ${level} (${label}) ${message}`;
|
||||
return `${timestamp} ${level} ${message}`;
|
||||
});
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ export async function createInvoice(req: Request, res: Response) {
|
||||
});
|
||||
});
|
||||
|
||||
const dueBy = new Date(Date.now() + 1000 * 60 * 60);
|
||||
const dueBy = new Date(Date.now() + 1000 * 60 * 15);
|
||||
|
||||
Invoice.create({
|
||||
selector: randomString(128),
|
||||
@@ -131,23 +131,7 @@ export async function getInvoice(req: Request, res: Response) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(invoice.status === PaymentStatus.UNCONFIRMED || invoice.status === PaymentStatus.DONE) {
|
||||
const transaction = await providerManager.getProvider(invoice.paymentMethod).getTransaction(invoice.transcationHash);
|
||||
try {
|
||||
let invoiceClone: any = invoice;
|
||||
console.log(transaction.confirmations);
|
||||
|
||||
invoiceClone['confirmation'] = transaction.confirmations;
|
||||
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 {
|
||||
res.status(200).send(invoice);
|
||||
}
|
||||
res.status(200).send(invoice);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -176,22 +160,43 @@ export async function getInvoice(req: Request, res: Response) {
|
||||
res.status(200).send(invoices);
|
||||
}
|
||||
|
||||
// GET /invoice/:selector/confirmation
|
||||
export async function getConfirmation(req: Request, res: Response) {
|
||||
const selector = req.params.selector;
|
||||
|
||||
const invoice = await Invoice.findOne({ selector: selector });
|
||||
if (invoice === null) {
|
||||
res.status(404).send();
|
||||
return;
|
||||
}
|
||||
|
||||
if (invoice.status !== PaymentStatus.UNCONFIRMED) {
|
||||
res.status(400).send({ message: 'This has no unconfirmed transaction (yet)!' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const confirmation = (await providerManager.getProvider(invoice.paymentMethod).getTransaction(invoice.transcationHash)).confirmations;
|
||||
res.status(200).send({ confirmation });
|
||||
} catch (err) {
|
||||
res.status(500).send();
|
||||
logger.error(`Error while getting confirmations for: ${invoice.transcationHash}`);
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /invoice/:selector
|
||||
export async function cancelInvoice(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();
|
||||
const invoice = await Invoice.findOne({ selector: selector });
|
||||
if (invoice === null) {
|
||||
res.status(404).send();
|
||||
return;
|
||||
}
|
||||
|
||||
invoice.status = PaymentStatus.CANCELLED;
|
||||
await invoice.save();
|
||||
return;
|
||||
}
|
||||
|
||||
// POST /invoice/:selector/setmethod
|
||||
@@ -221,6 +226,8 @@ export async function setPaymentMethod(req: Request, res: Response) {
|
||||
invoice.receiveAddress = await providerManager.getProvider(invoice.paymentMethod).getNewAddress();
|
||||
|
||||
await invoice.save();
|
||||
|
||||
invoiceManager.addInvoice(invoice)
|
||||
|
||||
res.status(200).send({
|
||||
receiveAddress: invoice.receiveAddress
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { IInvoice } from '../models/invoice/invoice.interface';
|
||||
import { InvoiceManager } from './invoiceManager';
|
||||
import { CryptoUnits } from './types';
|
||||
|
||||
@@ -68,6 +69,26 @@ export abstract class BackendProvider {
|
||||
* Keep track of unconfirmed transactions.
|
||||
*/
|
||||
abstract watchConfirmations(): void;
|
||||
|
||||
/**
|
||||
* Provided is an array with pending invoices that have to be check.
|
||||
*
|
||||
* **Note:** It can happen that you'll get an invoice that is not
|
||||
* intended for your cryptocurrency. Please check if invoice is
|
||||
* made for your cryptocurrency.
|
||||
*
|
||||
* *Mainly used when LibrePay starts.*
|
||||
*/
|
||||
abstract validateInvoices(invoices: IInvoice[]): void;
|
||||
}
|
||||
|
||||
export interface ITransactionDetails {
|
||||
address: string;
|
||||
category: 'send' | 'receive' | 'generate' | 'immature' | 'orphan'
|
||||
vout: number;
|
||||
fee: number;
|
||||
amount: number;
|
||||
abandoned: boolean
|
||||
}
|
||||
|
||||
export interface ITransaction {
|
||||
@@ -75,16 +96,19 @@ export interface ITransaction {
|
||||
fee: number;
|
||||
confirmations: number;
|
||||
time: number; // Unix timestamp
|
||||
details: {
|
||||
address: string;
|
||||
category: 'send' | 'receive' | 'generate' | 'immature' | 'orphan'
|
||||
vout: number;
|
||||
fee: number;
|
||||
abandoned: boolean
|
||||
}[];
|
||||
details: ITransactionDetails[];
|
||||
hex: string;
|
||||
}
|
||||
|
||||
// Special interface for RPC call `listreceivedbyaddress`
|
||||
export interface ITransactionList {
|
||||
address: string;
|
||||
amount: number;
|
||||
confirmation: number;
|
||||
label: string;
|
||||
txids: string[];
|
||||
}
|
||||
|
||||
export interface IRawTransaction {
|
||||
txid: string;
|
||||
hash: string;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { IInvoice } from "../models/invoice/invoice.interface";
|
||||
import { Subscriber } from 'zeromq';
|
||||
import { logger, rpcClient, socketManager } from "../app";
|
||||
import { invoiceRouter } from "../routes/invoice";
|
||||
import { Invoice } from "../models/invoice/invoice.model";
|
||||
import { CryptoUnits, PaymentStatus } from "./types";
|
||||
import { config } from "../../config";
|
||||
import { logger, providerManager, socketManager } from '../app';
|
||||
import { IInvoice } from '../models/invoice/invoice.interface';
|
||||
import { Invoice } from '../models/invoice/invoice.model';
|
||||
import { CryptoUnits, PaymentStatus } from './types';
|
||||
|
||||
/**
|
||||
* This invoice manager keeps track of the status of each transaction.
|
||||
@@ -21,27 +18,58 @@ export class InvoiceManager {
|
||||
|
||||
// Get all pending transcations
|
||||
Invoice.find({ status: PaymentStatus.PENDING }).then(invoices => {
|
||||
console.log('These are pending', invoices);
|
||||
|
||||
this.pendingInvoices = invoices;
|
||||
logger.info(`There are ${invoices.length} invoices pending`);
|
||||
providerManager.getProvider(CryptoUnits.BITCOIN).validateInvoices(invoices);
|
||||
});
|
||||
|
||||
// Get all unconfirmed transactions
|
||||
Invoice.find({ status: PaymentStatus.UNCONFIRMED }).then(invoices => {
|
||||
this.unconfirmedTranscations = invoices;
|
||||
logger.info(`There are ${invoices.length} invoices unconfirmed`);
|
||||
providerManager.getProvider(CryptoUnits.BITCOIN).validateInvoices(invoices);
|
||||
});
|
||||
|
||||
this.expireScheduler();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is basicly close all invoices that have not been paid in time.
|
||||
*/
|
||||
private expireScheduler() {
|
||||
setInterval(async () => {
|
||||
const expiredInvoices = await Invoice.find({
|
||||
dueBy: { $lte: new Date() },
|
||||
$or: [ { status: PaymentStatus.PENDING }, { status: PaymentStatus.REQUESTED } ]
|
||||
});
|
||||
|
||||
expiredInvoices.forEach(async eInvoice => {
|
||||
eInvoice.status = PaymentStatus.TOOLATE;
|
||||
await eInvoice.save();
|
||||
});
|
||||
|
||||
}, 5_000);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will add `invoice` to the pending list.
|
||||
* @param upgrade If `true` then this invoice will be directly added to the unconfirmed invoices.
|
||||
*/
|
||||
addInvoice(invoice: IInvoice) {
|
||||
logger.info(`A new invoice has been created: ${invoice.id}`)
|
||||
this.pendingInvoices.push(invoice);
|
||||
addInvoice(invoice: IInvoice, upgrade?: boolean) {
|
||||
// Avoid duplicates
|
||||
this.removeInvoice(invoice);
|
||||
|
||||
if (upgrade) {
|
||||
logger.info(`A new unconfirmed invoice has been created: ${invoice.id}`)
|
||||
this.pendingInvoices.push(invoice);
|
||||
this.upgradeInvoice(invoice);
|
||||
} else {
|
||||
logger.info(`A new invoice has been created: ${invoice.id}`)
|
||||
this.pendingInvoices.push(invoice);
|
||||
}
|
||||
}
|
||||
|
||||
removeInvoice(invoice: IInvoice) {
|
||||
this.unconfirmedTranscations.splice(this.unconfirmedTranscations.indexOf(invoice), 1);
|
||||
if (this.unconfirmedTranscations.indexOf(invoice) != -1) this.unconfirmedTranscations.splice(this.unconfirmedTranscations.indexOf(invoice), 1);
|
||||
if (this.pendingInvoices.indexOf(invoice) != -1) this.pendingInvoices.splice(this.pendingInvoices.indexOf(invoice), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,9 +78,15 @@ export class InvoiceManager {
|
||||
upgradeInvoice(invoice: IInvoice) {
|
||||
const target = this.pendingInvoices.find(item => { return item.id = invoice.id });
|
||||
if (target !== undefined) {
|
||||
this.pendingInvoices.push(invoice);
|
||||
this.unconfirmedTranscations.push(invoice);
|
||||
this.pendingInvoices.splice(this.pendingInvoices.indexOf(invoice), 1);
|
||||
} else {
|
||||
this.unconfirmedTranscations.push(invoice);
|
||||
}
|
||||
|
||||
this.knownConfirmations.set(invoice.id, 0);
|
||||
invoice.status = PaymentStatus.UNCONFIRMED;
|
||||
invoice.save();
|
||||
}
|
||||
|
||||
getPendingInvoices() {
|
||||
@@ -63,6 +97,20 @@ export class InvoiceManager {
|
||||
return this.unconfirmedTranscations;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will return you the price in the choosen cryptocurrency.
|
||||
*
|
||||
* If no payment methods has been choosen yet, you'll get `0` back.
|
||||
*/
|
||||
getPriceByInvoice(invoice: IInvoice): number {
|
||||
if (invoice.paymentMethod === undefined) return 0;
|
||||
return invoice.paymentMethods.find(method => { return method.method === invoice.paymentMethod }).amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param confirmations Your confirmation count
|
||||
* @returns If yours is different then what the manager knows, this function returns `true`.
|
||||
*/
|
||||
hasConfirmationChanged(invoice: IInvoice, confirmations: number) {
|
||||
return this.knownConfirmations.get(invoice.id) !== confirmations;
|
||||
}
|
||||
@@ -71,8 +119,100 @@ export class InvoiceManager {
|
||||
return this.knownConfirmations.get(invoice.id);
|
||||
}
|
||||
|
||||
setConfirmationCount(invoice: IInvoice, count: number) {
|
||||
socketManager.emitInvoiceEvent(invoice, 'confirmationUpdate', { count });
|
||||
return this.knownConfirmations.set(invoice.id, count);
|
||||
/**
|
||||
* Notify LibrePay about a confirmation change. If something changed, the user will be notfied.
|
||||
*
|
||||
* If the confirmation count is treated as "trusted", then the invoice will be completed.
|
||||
*/
|
||||
async setConfirmationCount(invoice: IInvoice, count: number) {
|
||||
if (this.hasConfirmationChanged(invoice, count)) {
|
||||
this.knownConfirmations.set(invoice.id, count);
|
||||
socketManager.emitInvoiceEvent(invoice, 'confirmationUpdate', { count });
|
||||
|
||||
if (count > 2) {
|
||||
logger.info(`Transaction (${invoice.transcationHash}) has reached more then 2 confirmations and can now be trusted!`);
|
||||
const sentFunds = (await providerManager.getProvider(invoice.paymentMethod).getTransaction(invoice.transcationHash)).amount;
|
||||
|
||||
// This transaction sent more then requested funds
|
||||
if (sentFunds > this.getPriceByInvoice(invoice)) {
|
||||
invoice.status = PaymentStatus.TOOMUCH;
|
||||
} else {
|
||||
invoice.status = PaymentStatus.DONE;
|
||||
}
|
||||
|
||||
await invoice.save(); // This will trigger a post save hook that will notify the user.
|
||||
this.removeInvoice(invoice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if a payment has been made in time and that the right amount was sent.
|
||||
*/
|
||||
async validatePayment(invoice: IInvoice, tx: string): Promise<void> {
|
||||
if (invoice.dueBy.getTime() < Date.now()) {
|
||||
invoice.status = PaymentStatus.TOOLATE;
|
||||
await invoice.save();
|
||||
|
||||
return; // Payment is too late
|
||||
}
|
||||
|
||||
const txInfo = await providerManager.getProvider(invoice.paymentMethod).getTransaction(tx);
|
||||
const receivedTranscation = txInfo.details.find(detail => {
|
||||
return (detail.address == invoice.receiveAddress && detail.amount > 0); // We only want receiving transactions
|
||||
});
|
||||
|
||||
const price = this.getPriceByInvoice(invoice);
|
||||
if (price === undefined) return;
|
||||
|
||||
// Transaction sent enough funds
|
||||
if (receivedTranscation.amount == price || receivedTranscation.amount > price) {
|
||||
invoice.transcationHash = tx;
|
||||
await invoice.save();
|
||||
|
||||
this.upgradeInvoice(invoice);
|
||||
} else {
|
||||
/* **Note**
|
||||
* Sending funds back is complicated since we can never know who the original sender was to 100%.
|
||||
* For Bitcoin, a transaction can have more then one input and if this is the case, you can never
|
||||
* know who the original sender was. Therefore if a customer sent not the right amount, he/she
|
||||
* should contact the support of the shop.
|
||||
*/
|
||||
logger.warning(`Transaction (${tx}) did not sent requested funds. (sent: ${receivedTranscation.amount} BTC, requested: ${price} BTC)`);
|
||||
invoice.status = PaymentStatus.TOOLITTLE;
|
||||
this.removeInvoice(invoice);
|
||||
|
||||
await invoice.save();
|
||||
|
||||
return;
|
||||
|
||||
// This is dead code and only exists because I'm yet unsure what to do with such payments.
|
||||
let sendBack = receivedTranscation.amount;
|
||||
|
||||
// If the amount was too much, mark invoice as paid and try to send remaining funds back.
|
||||
if (receivedTranscation.amount > price) {
|
||||
sendBack = price - txInfo.amount;
|
||||
|
||||
// Sent amount was too much but technically the bill is paid (will get saved in upgradeInvoice)
|
||||
invoice.transcationHash = tx;
|
||||
|
||||
this.upgradeInvoice(invoice);
|
||||
}
|
||||
|
||||
// We only have one input, we can be sure that the sender will receive the funds
|
||||
if (txInfo.details.length === 1) {
|
||||
if (txInfo.details[0].address.length !== 1) return;
|
||||
|
||||
const txBack = await providerManager.getProvider(invoice.paymentMethod).sendToAddress(txInfo.details[0].address[0], receivedTranscation.amount, null, null, true);
|
||||
logger.info(`Sent ${receivedTranscation.amount} ${invoice.paymentMethod} back to ${txInfo.details[0].address[0]}: ${txBack}`);
|
||||
} else {
|
||||
// If we cannot send the funds back, save transaction id and mark invoice as failed.
|
||||
invoice.transcationHash = tx;
|
||||
invoice.status = PaymentStatus.TOOLITTLE;
|
||||
this.removeInvoice(invoice);
|
||||
|
||||
await invoice.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { readdirSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { config } from '../../config';
|
||||
import { invoiceManager, logger } from '../app';
|
||||
import { BackendProvider } from './backendProvider';
|
||||
import { CryptoUnits } from './types';
|
||||
@@ -38,6 +39,7 @@ export class ProviderManager {
|
||||
}
|
||||
|
||||
this.cryptoProvider.set(provider.CRYPTO, provider);
|
||||
config.payment.methods.push(provider.CRYPTO);
|
||||
|
||||
// Execute onEnable() function of this provider
|
||||
provider.onEnable();
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Socket, Subscriber } from "zeromq";
|
||||
import { config } from "../../../config";
|
||||
import { invoiceManager, logger, rpcClient } from "../../app";
|
||||
import { BackendProvider, ITransaction, IRawTransaction } from "../backendProvider";
|
||||
import { InvoiceManager } from "../invoiceManager";
|
||||
import { CryptoUnits, PaymentStatus } from "../types";
|
||||
import { Subscriber } from 'zeromq';
|
||||
|
||||
import { config } from '../../../config';
|
||||
import { invoiceManager, logger, rpcClient } from '../../app';
|
||||
import { IInvoice } from '../../models/invoice/invoice.interface';
|
||||
import { BackendProvider, IRawTransaction, ITransaction, ITransactionDetails, ITransactionList } from '../backendProvider';
|
||||
import { CryptoUnits, PaymentStatus } from '../types';
|
||||
|
||||
export class Provider implements BackendProvider {
|
||||
|
||||
@@ -77,7 +78,9 @@ export class Provider implements BackendProvider {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
console.log('sendToAddress:', decoded.result);
|
||||
|
||||
resolve(decoded.result.txid);
|
||||
});
|
||||
});
|
||||
@@ -89,31 +92,20 @@ export class Provider implements BackendProvider {
|
||||
const rawtx = msg.toString('hex');
|
||||
const tx = await this.decodeRawTransaction(rawtx);
|
||||
|
||||
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.
|
||||
invoiceManager.getPendingInvoices().forEach(async invoice => {
|
||||
invoiceManager.getPendingInvoices().forEach(async invoice => {
|
||||
if (output.scriptPubKey.addresses === undefined) return; // Sometimes (weird) transaction don't have any addresses
|
||||
|
||||
logger.debug(`${output.scriptPubKey.addresses} <-> ${invoice.receiveAddress}`);
|
||||
// We found our transaction (https://developer.bitcoin.org/reference/rpc/decoderawtransaction.html)
|
||||
if (output.scriptPubKey.addresses.indexOf(invoice.receiveAddress) !== -1) {
|
||||
const senderAddress = output.scriptPubKey.addresses[output.scriptPubKey.addresses.indexOf(invoice.receiveAddress)];
|
||||
logger.info(`Transcation for invoice ${invoice.id} received! (${tx.hash})`);
|
||||
|
||||
// Change state in database
|
||||
const price = invoice.paymentMethods.find((item) => { return item.method === CryptoUnits.BITCOIN }).amount;
|
||||
if (output.value < price - config.transcations.acceptMargin) {
|
||||
const left = price - output.value;
|
||||
logger.info(`Transcation for invoice ${invoice.id} received but there are ${left} BTC missing (${tx.hash}).`);
|
||||
|
||||
const txBack = await this.sendToAddress(senderAddress, output.value, null, null, true);
|
||||
logger.info(`Sent ${output.value} BTC back to ${senderAddress}`);
|
||||
} else {
|
||||
invoice.status = PaymentStatus.UNCONFIRMED;
|
||||
invoice.transcationHash = tx.txid;
|
||||
invoice.save();
|
||||
|
||||
invoiceManager.upgradeInvoice(invoice);
|
||||
}
|
||||
invoiceManager.validatePayment(invoice, tx.txid);
|
||||
}
|
||||
})
|
||||
});
|
||||
@@ -125,32 +117,35 @@ export class Provider implements BackendProvider {
|
||||
setInterval(() => {
|
||||
invoiceManager.getUnconfirmedTransactions().forEach(async invoice => {
|
||||
if (invoice.transcationHash.length === 0) return;
|
||||
let trustworthy = true; // Will be true if all transactions are above threshold.
|
||||
|
||||
for (let i = 0; i < invoice.transcationHash.length; i++) {
|
||||
const transcation = invoice.transcationHash;
|
||||
|
||||
const tx = await this.getTransaction(transcation);
|
||||
|
||||
if (invoiceManager.hasConfirmationChanged(invoice, tx.confirmations)) {
|
||||
invoiceManager.setConfirmationCount(invoice, tx.confirmations);
|
||||
}
|
||||
|
||||
if (Number(tx.confirmations) > 0) {
|
||||
logger.info(`Transaction (${transcation}) has reached more then 2 confirmations and can now be trusted!`);
|
||||
invoiceManager.removeInvoice(invoice);
|
||||
} 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.
|
||||
}
|
||||
const transcation = invoice.transcationHash;
|
||||
|
||||
const tx = await this.getTransaction(transcation);
|
||||
invoiceManager.setConfirmationCount(invoice, tx.confirmations);
|
||||
});
|
||||
}, 2_000);
|
||||
}
|
||||
|
||||
async validateInvoices(invoices: IInvoice[]) {
|
||||
invoices.forEach(async invoice => {
|
||||
if (invoice.status === PaymentStatus.DONE || invoice.status === PaymentStatus.CANCELLED) return;
|
||||
if (invoice.paymentMethod !== CryptoUnits.BITCOIN) return;
|
||||
|
||||
rpcClient.request('listreceivedbyaddress', [0, false, false, invoice.receiveAddress], async (err, message) => {
|
||||
if (err) {
|
||||
logger.error(`There was an error while getting transcations of address ${invoice.receiveAddress}: ${err.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const res = message.result[0] as ITransactionList;
|
||||
if (res === undefined) return;
|
||||
|
||||
console.log(res);
|
||||
|
||||
res.txids.forEach(async tx => {
|
||||
invoiceManager.validatePayment(invoice, tx);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ export class SocketManager {
|
||||
}
|
||||
|
||||
emitInvoiceEvent(invoice: IInvoice, event: string, data: any) {
|
||||
logger.debug(`Broadcast ${data} to room ${invoice.selector}`);
|
||||
logger.debug(`Broadcast ${JSON.stringify(data)} to room ${invoice.selector}`);
|
||||
this.io.to(invoice.selector).emit(event, data);
|
||||
}
|
||||
}
|
||||
@@ -36,30 +36,46 @@ export enum FiatUnits {
|
||||
}
|
||||
|
||||
export enum PaymentStatus {
|
||||
|
||||
/**
|
||||
* The payment has failed because the amount that was sent is less then requested.
|
||||
*/
|
||||
TOOLITTLE = -3,
|
||||
|
||||
/**
|
||||
* The payment has failed because the payment has been issued too late.
|
||||
*/
|
||||
TOOLATE = -2,
|
||||
|
||||
/**
|
||||
* The payment has been cancelled by the user.
|
||||
*/
|
||||
CANCELLED = -2,
|
||||
CANCELLED = -1,
|
||||
|
||||
/**
|
||||
* The invoice has been requested but the payment method has to be choosen.
|
||||
*/
|
||||
REQUESTED = -1,
|
||||
REQUESTED = 0,
|
||||
|
||||
/**
|
||||
* The payment has not been yet started. The user did not initiated the transfer.
|
||||
*/
|
||||
PENDING = 0,
|
||||
PENDING = 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 is completed and the crypto is now available but the customer paid too much.
|
||||
*/
|
||||
TOOMUCH = 4
|
||||
}
|
||||
|
||||
// I'll will just leave that here
|
||||
|
||||
@@ -31,6 +31,9 @@ export interface IInvoice extends Document {
|
||||
// 3b38c3a215d4e7981e1516b2dcbf76fca58911274d5d55b3d615274d6e10f2c1
|
||||
transcationHash?: string;
|
||||
|
||||
// Is provided when transaction is unconfirmed
|
||||
confirmation?: number;
|
||||
|
||||
cart?: ICart[];
|
||||
totalPrice?: number;
|
||||
currency: FiatUnits;
|
||||
|
||||
@@ -58,12 +58,12 @@ schemaInvoice.post('validate', function (doc, next) {
|
||||
next();
|
||||
});
|
||||
|
||||
schemaInvoice.post('save', function(doc, next) {
|
||||
let self = this as IInvoice;
|
||||
|
||||
socketManager.emitInvoiceEvent(self, 'status', self.status);
|
||||
function updateStatus(doc: IInvoice, next) {
|
||||
socketManager.emitInvoiceEvent(doc, 'status', doc.status);
|
||||
next();
|
||||
})
|
||||
}
|
||||
|
||||
schemaInvoice.post('save', updateStatus);
|
||||
|
||||
export function calculateCart(cart: ICart[]): number {
|
||||
let totalPrice = 0;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Router } from "express";
|
||||
import { createInvoice, getInvoice, getPaymentMethods, setPaymentMethod } from "../controllers/invoice";
|
||||
import { createInvoice, getConfirmation, getInvoice, getPaymentMethods, setPaymentMethod } from "../controllers/invoice";
|
||||
import { MW_User } from "../controllers/user";
|
||||
|
||||
const invoiceRouter = Router()
|
||||
|
||||
invoiceRouter.get('/paymentmethods', getPaymentMethods);
|
||||
invoiceRouter.get('/:selector', getInvoice);
|
||||
invoiceRouter.get('/:selector/confirmation', getConfirmation);
|
||||
invoiceRouter.post('/:selector/setmethod', setPaymentMethod);
|
||||
invoiceRouter.get('/', MW_User, getInvoice);
|
||||
invoiceRouter.post('/', MW_User, createInvoice);
|
||||
|
||||
Reference in New Issue
Block a user