Save exchange rate with invoice

- Fix issue where completed invoices were flagged as expired
This commit is contained in:
2021-01-02 22:14:59 +01:00
parent b356f3ee70
commit fc71aed660
8 changed files with 55 additions and 44 deletions

View File

@@ -72,7 +72,6 @@ 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()}`, {
responseType: 'json'
});
console.log(request.body);
// Calulate total price, if cart is provided
if (cart !== undefined && totalPrice === undefined) {
@@ -82,12 +81,10 @@ export async function createInvoice(req: Request, res: Response) {
let paymentMethods: IPaymentMethod[] = [];
cgFormat.forEach(coinFullName => {
console.log(coinFullName);
const coin = CryptoUnits[coinFullName.toUpperCase()];
const exRate = Number(request.body[coinFullName][currency.toLowerCase()]);
paymentMethods.push({ method: coin, amount:
roundNumber(totalPrice / Number(request.body[coinFullName][currency.toLowerCase()]), decimalPlaces.get(coin))
});
paymentMethods.push({ exRate, method: coin, amount: roundNumber(totalPrice / exRate, decimalPlaces.get(coin))});
});
const dueBy = new Date(Date.now() + 1000 * 60 * 15);
@@ -103,7 +100,7 @@ export async function createInvoice(req: Request, res: Response) {
dueBy
}, (error, invoice: IInvoice) => {
if (error) {
res.status(500).send({error: error.message});
res.status(500).send({message: error.message});
return;
}
@@ -220,7 +217,6 @@ export async function setPaymentMethod(req: Request, res: Response) {
return;
}
invoice.status = PaymentStatus.PENDING;
invoice.paymentMethod = CryptoUnits[findCryptoBySymbol(method)];
invoice.receiveAddress = await providerManager.getProvider(invoice.paymentMethod).getNewAddress();

View File

@@ -79,7 +79,7 @@ export abstract class BackendProvider {
*
* *Mainly used when LibrePay starts.*
*/
abstract validateInvoices(invoices: IInvoice[]): void;
abstract validateInvoice(invoices: IInvoice): void;
}
export interface ITransactionDetails {

View File

@@ -16,16 +16,13 @@ export class InvoiceManager {
this.pendingInvoices = [];
this.knownConfirmations = new Map<string, number>();
// Get all pending transcations
Invoice.find({ status: PaymentStatus.PENDING }).then(invoices => {
logger.info(`There are ${invoices.length} invoices pending`);
providerManager.getProvider(CryptoUnits.BITCOIN).validateInvoices(invoices);
});
// Get all pending and unconfirmed transcations
Invoice.find({ $or: [ { status: PaymentStatus.PENDING }, { status: PaymentStatus.UNCONFIRMED } ]}).then(invoices => {
logger.info(`There are ${invoices.length} invoices that are pending or unconfirmed`);
// Get all unconfirmed transactions
Invoice.find({ status: PaymentStatus.UNCONFIRMED }).then(invoices => {
logger.info(`There are ${invoices.length} invoices unconfirmed`);
providerManager.getProvider(CryptoUnits.BITCOIN).validateInvoices(invoices);
invoices.forEach(invoice => {
providerManager.getProvider(invoice.paymentMethod).validateInvoice(invoice);
});
});
this.expireScheduler();
@@ -36,6 +33,7 @@ export class InvoiceManager {
*/
private expireScheduler() {
setInterval(async () => {
// Find invoices that are pending or requested and reached there EOF date
const expiredInvoices = await Invoice.find({
dueBy: { $lte: new Date() },
$or: [ { status: PaymentStatus.PENDING }, { status: PaymentStatus.REQUESTED } ]
@@ -180,7 +178,6 @@ export class InvoiceManager {
*/
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();

View File

@@ -0,0 +1,12 @@
import { BackendProvider } from "../backendProvider";
import { CryptoUnits } from "../types";
export class Provider implements BackendProvider {
NAME = 'Bitcoin Blockchain';
DESCRIPTION = 'This provider queries the API backend provider by blockchain.com';
AUTHOR = 'LibrePay Team';
VERSION = '0.1'
CRYPTO = CryptoUnits.BITCOIN;
}

View File

@@ -1,9 +1,8 @@
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 { BackendProvider, IRawTransaction, ITransaction, ITransactionList } from '../backendProvider';
import { CryptoUnits, PaymentStatus } from '../types';
export class Provider implements BackendProvider {
@@ -79,8 +78,6 @@ export class Provider implements BackendProvider {
return;
}
console.log('sendToAddress:', decoded.result);
resolve(decoded.result.txid);
});
});
@@ -125,8 +122,7 @@ export class Provider implements BackendProvider {
}, 2_000);
}
async validateInvoices(invoices: IInvoice[]) {
invoices.forEach(async invoice => {
async validateInvoice(invoice: IInvoice) {
if (invoice.status === PaymentStatus.DONE || invoice.status === PaymentStatus.CANCELLED) return;
if (invoice.paymentMethod !== CryptoUnits.BITCOIN) return;
@@ -139,13 +135,10 @@ export class Provider implements BackendProvider {
const res = message.result[0] as ITransactionList;
if (res === undefined) return;
console.log(res);
res.txids.forEach(async tx => {
invoiceManager.validatePayment(invoice, tx);
});
});
});
}
}

View File

@@ -10,7 +10,8 @@ export interface ICart {
export interface IPaymentMethod {
method: CryptoUnits;
amount: number
amount: number;
exRate: number;
}
export interface IInvoice extends Document {

View File

@@ -1,5 +1,5 @@
import { Schema } from 'mongoose';
import { socketManager } from '../../app';
import { invoiceManager, socketManager } from '../../app';
import { CryptoUnits, FiatUnits, PaymentStatus } from '../../helper/types';
import { ICart, IInvoice } from './invoice.interface';
@@ -14,7 +14,8 @@ const schemaCart = new Schema({
const schemaPaymentMethods = new Schema({
method: { type: String, enum: Object.values(CryptoUnits), required: true },
amount: { type: Number, required: false }
amount: { type: Number, required: false },
exRate: { type: Number, required: true }, // Exchange rate at creation
}, { _id: false });
const schemaInvoice = new Schema({
@@ -60,6 +61,12 @@ schemaInvoice.post('validate', function (doc, next) {
function updateStatus(doc: IInvoice, next) {
socketManager.emitInvoiceEvent(doc, 'status', doc.status);
// If a status has a negative value, then this invoice has failed.
if (doc.status < 0) {
invoiceManager.removeInvoice(doc);
}
next();
}

View File

@@ -1,13 +1,18 @@
import { Router } from "express";
import { createInvoice, getConfirmation, getInvoice, getPaymentMethods, setPaymentMethod } from "../controllers/invoice";
import { cancelInvoice, createInvoice, getConfirmation, getInvoice, getPaymentMethods, setPaymentMethod } from "../controllers/invoice";
import { MW_User } from "../controllers/user";
const invoiceRouter = Router()
// Get general information
invoiceRouter.get('/paymentmethods', getPaymentMethods);
// Actions related to specific invoices
invoiceRouter.get('/:selector', getInvoice);
invoiceRouter.delete('/:selector', cancelInvoice);
invoiceRouter.get('/:selector/confirmation', getConfirmation);
invoiceRouter.post('/:selector/setmethod', setPaymentMethod);
invoiceRouter.get('/', MW_User, getInvoice);
invoiceRouter.post('/', MW_User, createInvoice);