Save exchange rate with invoice
- Fix issue where completed invoices were flagged as expired
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
12
src/helper/providers/bitcoinBlockchain.js
Normal file
12
src/helper/providers/bitcoinBlockchain.js
Normal 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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ export interface ICart {
|
||||
|
||||
export interface IPaymentMethod {
|
||||
method: CryptoUnits;
|
||||
amount: number
|
||||
amount: number;
|
||||
exRate: number;
|
||||
}
|
||||
|
||||
export interface IInvoice extends Document {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user