Proper handling of unequal payments

- Cryptocurrencies are enabled dynamically
- Invoice handler handles now more
- Better status change handling
This commit is contained in:
2021-01-01 19:39:38 +01:00
parent aa2147d509
commit b356f3ee70
12 changed files with 305 additions and 120 deletions

View File

@@ -21,13 +21,9 @@ export const config: IConfig = {
acceptMargin: 0.00000001 acceptMargin: 0.00000001
}, },
payment: { payment: {
// This is a list of cryptocurrencies that you want to accpet. // This has to stay empty since it will be filled automatically in runtime.
methods: [ // If you want to accept a specifc cryptocurrency, add a provider in src/helper/providers
CryptoUnits.BITCOIN, methods: []
CryptoUnits.DOGECOIN,
CryptoUnits.ETHEREUM,
CryptoUnits.MONERO
]
} }
} }
/** /**

View File

@@ -37,6 +37,7 @@ async function run() {
const { combine, timestamp, label, printf, prettyPrint } = winston.format; const { combine, timestamp, label, printf, prettyPrint } = winston.format;
const myFormat = printf(({ level, message, label, timestamp }) => { const myFormat = printf(({ level, message, label, timestamp }) => {
if (label !== undefined) return `${timestamp} ${level} (${label}) ${message}`;
return `${timestamp} ${level} ${message}`; return `${timestamp} ${level} ${message}`;
}); });

View File

@@ -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({ Invoice.create({
selector: randomString(128), selector: randomString(128),
@@ -131,23 +131,7 @@ export async function getInvoice(req: Request, res: Response) {
return; 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; return;
} }
@@ -176,12 +160,34 @@ export async function getInvoice(req: Request, res: Response) {
res.status(200).send(invoices); 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 // DELETE /invoice/:selector
export async function cancelInvoice(req: Request, res: Response) { export async function cancelInvoice(req: Request, res: Response) {
const selector = req.params.selector; const selector = req.params.selector;
// If an id is provided
if (selector !== undefined) {
const invoice = await Invoice.findOne({ selector: selector }); const invoice = await Invoice.findOne({ selector: selector });
if (invoice === null) { if (invoice === null) {
res.status(404).send(); res.status(404).send();
@@ -192,7 +198,6 @@ export async function cancelInvoice(req: Request, res: Response) {
await invoice.save(); await invoice.save();
return; return;
} }
}
// POST /invoice/:selector/setmethod // POST /invoice/:selector/setmethod
export async function setPaymentMethod(req: Request, res: Response) { export async function setPaymentMethod(req: Request, res: Response) {
@@ -222,6 +227,8 @@ export async function setPaymentMethod(req: Request, res: Response) {
await invoice.save(); await invoice.save();
invoiceManager.addInvoice(invoice)
res.status(200).send({ res.status(200).send({
receiveAddress: invoice.receiveAddress receiveAddress: invoice.receiveAddress
}); });

View File

@@ -1,3 +1,4 @@
import { IInvoice } from '../models/invoice/invoice.interface';
import { InvoiceManager } from './invoiceManager'; import { InvoiceManager } from './invoiceManager';
import { CryptoUnits } from './types'; import { CryptoUnits } from './types';
@@ -68,6 +69,26 @@ export abstract class BackendProvider {
* Keep track of unconfirmed transactions. * Keep track of unconfirmed transactions.
*/ */
abstract watchConfirmations(): void; 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 { export interface ITransaction {
@@ -75,16 +96,19 @@ export interface ITransaction {
fee: number; fee: number;
confirmations: number; confirmations: number;
time: number; // Unix timestamp time: number; // Unix timestamp
details: { details: ITransactionDetails[];
address: string;
category: 'send' | 'receive' | 'generate' | 'immature' | 'orphan'
vout: number;
fee: number;
abandoned: boolean
}[];
hex: string; hex: string;
} }
// Special interface for RPC call `listreceivedbyaddress`
export interface ITransactionList {
address: string;
amount: number;
confirmation: number;
label: string;
txids: string[];
}
export interface IRawTransaction { export interface IRawTransaction {
txid: string; txid: string;
hash: string; hash: string;

View File

@@ -1,10 +1,7 @@
import { IInvoice } from "../models/invoice/invoice.interface"; import { logger, providerManager, socketManager } from '../app';
import { Subscriber } from 'zeromq'; import { IInvoice } from '../models/invoice/invoice.interface';
import { logger, rpcClient, socketManager } from "../app"; import { Invoice } from '../models/invoice/invoice.model';
import { invoiceRouter } from "../routes/invoice"; import { CryptoUnits, PaymentStatus } from './types';
import { Invoice } from "../models/invoice/invoice.model";
import { CryptoUnits, PaymentStatus } from "./types";
import { config } from "../../config";
/** /**
* This invoice manager keeps track of the status of each transaction. * This invoice manager keeps track of the status of each transaction.
@@ -21,27 +18,58 @@ 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); logger.info(`There are ${invoices.length} invoices pending`);
providerManager.getProvider(CryptoUnits.BITCOIN).validateInvoices(invoices);
this.pendingInvoices = invoices;
}); });
// Get all unconfirmed transactions // Get all unconfirmed transactions
Invoice.find({ status: PaymentStatus.UNCONFIRMED }).then(invoices => { 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. * 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) { 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}`) logger.info(`A new invoice has been created: ${invoice.id}`)
this.pendingInvoices.push(invoice); this.pendingInvoices.push(invoice);
} }
}
removeInvoice(invoice: IInvoice) { 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) { upgradeInvoice(invoice: IInvoice) {
const target = this.pendingInvoices.find(item => { return item.id = invoice.id }); const target = this.pendingInvoices.find(item => { return item.id = invoice.id });
if (target !== undefined) { if (target !== undefined) {
this.pendingInvoices.push(invoice); this.unconfirmedTranscations.push(invoice);
this.pendingInvoices.splice(this.pendingInvoices.indexOf(invoice), 1); 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() { getPendingInvoices() {
@@ -63,6 +97,20 @@ export class InvoiceManager {
return this.unconfirmedTranscations; 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) { hasConfirmationChanged(invoice: IInvoice, confirmations: number) {
return this.knownConfirmations.get(invoice.id) !== confirmations; return this.knownConfirmations.get(invoice.id) !== confirmations;
} }
@@ -71,8 +119,100 @@ export class InvoiceManager {
return this.knownConfirmations.get(invoice.id); return this.knownConfirmations.get(invoice.id);
} }
setConfirmationCount(invoice: IInvoice, count: number) { /**
* 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 }); socketManager.emitInvoiceEvent(invoice, 'confirmationUpdate', { count });
return this.knownConfirmations.set(invoice.id, 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();
}
}
} }
} }

View File

@@ -1,5 +1,6 @@
import { readdirSync } from 'fs'; import { readdirSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import { config } from '../../config';
import { invoiceManager, logger } from '../app'; import { invoiceManager, logger } from '../app';
import { BackendProvider } from './backendProvider'; import { BackendProvider } from './backendProvider';
import { CryptoUnits } from './types'; import { CryptoUnits } from './types';
@@ -38,6 +39,7 @@ export class ProviderManager {
} }
this.cryptoProvider.set(provider.CRYPTO, provider); this.cryptoProvider.set(provider.CRYPTO, provider);
config.payment.methods.push(provider.CRYPTO);
// Execute onEnable() function of this provider // Execute onEnable() function of this provider
provider.onEnable(); provider.onEnable();

View File

@@ -1,9 +1,10 @@
import { Socket, Subscriber } from "zeromq"; import { Subscriber } from 'zeromq';
import { config } from "../../../config";
import { invoiceManager, logger, rpcClient } from "../../app"; import { config } from '../../../config';
import { BackendProvider, ITransaction, IRawTransaction } from "../backendProvider"; import { invoiceManager, logger, rpcClient } from '../../app';
import { InvoiceManager } from "../invoiceManager"; import { IInvoice } from '../../models/invoice/invoice.interface';
import { CryptoUnits, PaymentStatus } from "../types"; import { BackendProvider, IRawTransaction, ITransaction, ITransactionDetails, ITransactionList } from '../backendProvider';
import { CryptoUnits, PaymentStatus } from '../types';
export class Provider implements BackendProvider { export class Provider implements BackendProvider {
@@ -78,6 +79,8 @@ export class Provider implements BackendProvider {
return; return;
} }
console.log('sendToAddress:', decoded.result);
resolve(decoded.result.txid); resolve(decoded.result.txid);
}); });
}); });
@@ -89,31 +92,20 @@ export class Provider implements BackendProvider {
const rawtx = msg.toString('hex'); const rawtx = msg.toString('hex');
const tx = await this.decodeRawTransaction(rawtx); 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. // 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 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) // We found our transaction (https://developer.bitcoin.org/reference/rpc/decoderawtransaction.html)
if (output.scriptPubKey.addresses.indexOf(invoice.receiveAddress) !== -1) { if (output.scriptPubKey.addresses.indexOf(invoice.receiveAddress) !== -1) {
const senderAddress = output.scriptPubKey.addresses[output.scriptPubKey.addresses.indexOf(invoice.receiveAddress)]; const senderAddress = output.scriptPubKey.addresses[output.scriptPubKey.addresses.indexOf(invoice.receiveAddress)];
logger.info(`Transcation for invoice ${invoice.id} received! (${tx.hash})`); logger.info(`Transcation for invoice ${invoice.id} received! (${tx.hash})`);
// Change state in database // Change state in database
const price = invoice.paymentMethods.find((item) => { return item.method === CryptoUnits.BITCOIN }).amount; invoiceManager.validatePayment(invoice, tx.txid);
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);
}
} }
}) })
}); });
@@ -125,32 +117,35 @@ export class Provider implements BackendProvider {
setInterval(() => { setInterval(() => {
invoiceManager.getUnconfirmedTransactions().forEach(async invoice => { invoiceManager.getUnconfirmedTransactions().forEach(async invoice => {
if (invoice.transcationHash.length === 0) return; 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 transcation = invoice.transcationHash;
const tx = await this.getTransaction(transcation); const tx = await this.getTransaction(transcation);
if (invoiceManager.hasConfirmationChanged(invoice, tx.confirmations)) {
invoiceManager.setConfirmationCount(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.
}
}); });
}, 2_000); }, 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);
});
});
});
}
} }

View File

@@ -47,7 +47,7 @@ export class SocketManager {
} }
emitInvoiceEvent(invoice: IInvoice, event: string, data: any) { 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); this.io.to(invoice.selector).emit(event, data);
} }
} }

View File

@@ -36,30 +36,46 @@ export enum FiatUnits {
} }
export enum PaymentStatus { 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. * 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. * 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. * 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. * 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. * 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 // I'll will just leave that here

View File

@@ -31,6 +31,9 @@ export interface IInvoice extends Document {
// 3b38c3a215d4e7981e1516b2dcbf76fca58911274d5d55b3d615274d6e10f2c1 // 3b38c3a215d4e7981e1516b2dcbf76fca58911274d5d55b3d615274d6e10f2c1
transcationHash?: string; transcationHash?: string;
// Is provided when transaction is unconfirmed
confirmation?: number;
cart?: ICart[]; cart?: ICart[];
totalPrice?: number; totalPrice?: number;
currency: FiatUnits; currency: FiatUnits;

View File

@@ -58,12 +58,12 @@ schemaInvoice.post('validate', function (doc, next) {
next(); next();
}); });
schemaInvoice.post('save', function(doc, next) { function updateStatus(doc: IInvoice, next) {
let self = this as IInvoice; socketManager.emitInvoiceEvent(doc, 'status', doc.status);
socketManager.emitInvoiceEvent(self, 'status', self.status);
next(); next();
}) }
schemaInvoice.post('save', updateStatus);
export function calculateCart(cart: ICart[]): number { export function calculateCart(cart: ICart[]): number {
let totalPrice = 0; let totalPrice = 0;

View File

@@ -1,11 +1,12 @@
import { Router } from "express"; 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"; 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.get('/:selector/confirmation', getConfirmation);
invoiceRouter.post('/:selector/setmethod', setPaymentMethod); 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);