From 5969e8fecf29eb42cdfe2e71d2d87e4b333d2e65 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Sat, 18 Nov 2023 22:51:49 +0100 Subject: [PATCH] dont lose payments --- src/services/lnd/lnd.ts | 4 +-- src/services/lnd/settings.ts | 4 +-- src/services/main/index.ts | 26 +++++++++++++------ src/services/main/paymentManager.ts | 16 +++++------- .../entity/AddressReceivingTransaction.ts | 3 +++ .../storage/entity/UserInvoicePayment.ts | 3 +++ .../storage/entity/UserReceivingInvoice.ts | 6 +++++ .../storage/entity/UserTransactionPayment.ts | 3 +++ src/services/storage/paymentStorage.ts | 23 ++++++++++------ 9 files changed, 59 insertions(+), 29 deletions(-) diff --git a/src/services/lnd/lnd.ts b/src/services/lnd/lnd.ts index 1076f32b..333bf3b4 100644 --- a/src/services/lnd/lnd.ts +++ b/src/services/lnd/lnd.ts @@ -111,7 +111,7 @@ export default class { tx.outputDetails.forEach(output => { if (output.isOurAddress) { this.log("received chan TX", Number(output.amount), "sats") - this.addressPaidCb({ hash: tx.txHash, index: Number(output.outputIndex) }, output.address, Number(output.amount)) + this.addressPaidCb({ hash: tx.txHash, index: Number(output.outputIndex) }, output.address, Number(output.amount), false) } }) } @@ -133,7 +133,7 @@ export default class { if (invoice.state === Invoice_InvoiceState.SETTLED) { this.log("An invoice was paid for", Number(invoice.amtPaidSat), "sats") this.latestKnownSettleIndex = Number(invoice.settleIndex) - this.invoicePaidCb(invoice.paymentRequest, Number(invoice.amtPaidSat)) + this.invoicePaidCb(invoice.paymentRequest, Number(invoice.amtPaidSat), false) } }) stream.responses.onError(error => { diff --git a/src/services/lnd/settings.ts b/src/services/lnd/settings.ts index da71c224..c2fe562f 100644 --- a/src/services/lnd/settings.ts +++ b/src/services/lnd/settings.ts @@ -11,8 +11,8 @@ type TxOutput = { index: number } -export type AddressPaidCb = (txOutput: TxOutput, address: string, amount: number) => void -export type InvoicePaidCb = (paymentRequest: string, amount: number) => void +export type AddressPaidCb = (txOutput: TxOutput, address: string, amount: number, internal: boolean) => void +export type InvoicePaidCb = (paymentRequest: string, amount: number, internal: boolean) => void export type NodeInfo = { alias: string diff --git a/src/services/main/index.ts b/src/services/main/index.ts index 2a102a10..d07106b4 100644 --- a/src/services/main/index.ts +++ b/src/services/main/index.ts @@ -59,14 +59,23 @@ export default class { this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager) } - addressPaidCb: AddressPaidCb = (txOutput, address, amount) => { + addressPaidCb: AddressPaidCb = (txOutput, address, amount, internal) => { this.storage.StartTransaction(async tx => { const userAddress = await this.storage.paymentStorage.GetAddressOwner(address, tx) if (!userAddress) { return } - const fee = this.paymentManager.getServiceFee(Types.UserOperationType.INCOMING_TX, amount, false) + const log = getLogger({}) + if (!userAddress.linkedApplication) { + log("ERROR", "an address was paid, that has no linked application") + return + } + const isAppUserPayment = userAddress.user.user_id !== userAddress.linkedApplication.owner.user_id + let fee = this.paymentManager.getServiceFee(Types.UserOperationType.INCOMING_TX, amount, isAppUserPayment) + if (userAddress.linkedApplication && userAddress.linkedApplication.owner.user_id === userAddress.user.user_id) { + fee = 0 + } try { // This call will fail if the transaction is already registered - const addedTx = await this.storage.paymentStorage.AddAddressReceivingTransaction(userAddress, txOutput.hash, txOutput.index, amount, fee, tx) + const addedTx = await this.storage.paymentStorage.AddAddressReceivingTransaction(userAddress, txOutput.hash, txOutput.index, amount, fee, internal, tx) await this.storage.userStorage.IncrementUserBalance(userAddress.user.user_id, addedTx.paid_amount - fee, tx) this.triggerSubs(userAddress.user.user_id, { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_TX, identifier: userAddress.address }) } catch { @@ -75,11 +84,13 @@ export default class { }) } - invoicePaidCb: InvoicePaidCb = (paymentRequest, amount) => { + invoicePaidCb: InvoicePaidCb = (paymentRequest, amount, internal) => { this.storage.StartTransaction(async tx => { - const userInvoice = await this.storage.paymentStorage.GetInvoiceOwner(paymentRequest, tx) - if (!userInvoice || userInvoice.paid_at_unix > 0) { return } const log = getLogger({}) + const userInvoice = await this.storage.paymentStorage.GetInvoiceOwner(paymentRequest, tx) + if (!userInvoice) { return } + if (userInvoice.paid_at_unix > 0 && internal) { log("cannot pay internally, invoice already paid"); return } + if (userInvoice.paid_at_unix > 0 && !internal && userInvoice.paidByLnd) { log("invoice already paid by lnd"); return } if (!userInvoice.linkedApplication) { log("ERROR", "an invoice was paid, that has no linked application") return @@ -90,8 +101,7 @@ export default class { fee = 0 } try { - // This call will fail if the invoice is already registered - await this.storage.paymentStorage.FlagInvoiceAsPaid(userInvoice, amount, fee, tx) + await this.storage.paymentStorage.FlagInvoiceAsPaid(userInvoice, amount, fee, internal, tx) await this.storage.userStorage.IncrementUserBalance(userInvoice.user.user_id, amount - fee, tx) if (isAppUserPayment && fee > 0) { diff --git a/src/services/main/paymentManager.ts b/src/services/main/paymentManager.ts index 55efca02..22e7f27e 100644 --- a/src/services/main/paymentManager.ts +++ b/src/services/main/paymentManager.ts @@ -126,11 +126,6 @@ export default class { } } - async PayInvoiceInternal(app: Application, userId: string, invoice: UserReceivingInvoice, amount: number, action: Types.UserOperationType): Promise { - - this.invoicePaidCb(invoice.invoice, amount) - return { txId: "" } - } async PayInvoice(userId: string, req: Types.PayInvoiceRequest, linkedApplication: Application): Promise { const decoded = await this.lnd.DecodeInvoice(req.invoice) if (decoded.numSatoshis !== 0 && req.amount !== 0) { @@ -157,14 +152,17 @@ export default class { throw err } } else { + if (internalInvoice.paid_at_unix > 0) { + throw new Error("this invoice was already paid") + } await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement) - this.invoicePaidCb(req.invoice, payAmount) + this.invoicePaidCb(req.invoice, payAmount, true) } if (isAppUserPayment && serviceFee > 0) { await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, serviceFee) } const routingFees = payment ? payment.feeSat : 0 - await this.storage.paymentStorage.AddUserInvoicePayment(userId, req.invoice, payAmount, routingFees, serviceFee) + await this.storage.paymentStorage.AddUserInvoicePayment(userId, req.invoice, payAmount, routingFees, serviceFee, !!internalInvoice) return { preimage: payment ? payment.paymentPreimage : "", amount_paid: payment ? Number(payment.valueSat) : payAmount @@ -195,13 +193,13 @@ export default class { } } else { await this.storage.userStorage.DecrementUserBalance(userId, req.amoutSats + serviceFee) - this.addressPaidCb({ hash: crypto.randomBytes(32).toString("hex"), index: 0 }, req.address, req.amoutSats) + this.addressPaidCb({ hash: crypto.randomBytes(32).toString("hex"), index: 0 }, req.address, req.amoutSats, true) } if (isAppUserPayment && serviceFee > 0) { await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, serviceFee) } - await this.storage.paymentStorage.AddUserTransactionPayment(userId, req.address, txId, 0, req.amoutSats, chainFees, serviceFee) + await this.storage.paymentStorage.AddUserTransactionPayment(userId, req.address, txId, 0, req.amoutSats, chainFees, serviceFee, !!internalAddress) return { txId: txId } diff --git a/src/services/storage/entity/AddressReceivingTransaction.ts b/src/services/storage/entity/AddressReceivingTransaction.ts index acac712b..700c7dea 100644 --- a/src/services/storage/entity/AddressReceivingTransaction.ts +++ b/src/services/storage/entity/AddressReceivingTransaction.ts @@ -28,6 +28,9 @@ export class AddressReceivingTransaction { @Column() paid_at_unix: number + @Column() + internal: boolean + @CreateDateColumn() created_at: Date diff --git a/src/services/storage/entity/UserInvoicePayment.ts b/src/services/storage/entity/UserInvoicePayment.ts index b7489d48..5c49e2fd 100644 --- a/src/services/storage/entity/UserInvoicePayment.ts +++ b/src/services/storage/entity/UserInvoicePayment.ts @@ -27,6 +27,9 @@ export class UserInvoicePayment { @Column() paid_at_unix: number + @Column() + internal: boolean + @CreateDateColumn() created_at: Date diff --git a/src/services/storage/entity/UserReceivingInvoice.ts b/src/services/storage/entity/UserReceivingInvoice.ts index 0df576d3..af70cc62 100644 --- a/src/services/storage/entity/UserReceivingInvoice.ts +++ b/src/services/storage/entity/UserReceivingInvoice.ts @@ -23,6 +23,12 @@ export class UserReceivingInvoice { @Column({ default: 0 }) paid_at_unix: number + @Column() + internal: boolean + + @Column() + paidByLnd: boolean + @Column({ default: "" }) callbackUrl: string diff --git a/src/services/storage/entity/UserTransactionPayment.ts b/src/services/storage/entity/UserTransactionPayment.ts index 4be0a02f..078faac9 100644 --- a/src/services/storage/entity/UserTransactionPayment.ts +++ b/src/services/storage/entity/UserTransactionPayment.ts @@ -33,6 +33,9 @@ export class UserTransactionPayment { @Column() paid_at_unix: number + @Column() + internal: boolean + @CreateDateColumn() created_at: Date diff --git a/src/services/storage/paymentStorage.ts b/src/services/storage/paymentStorage.ts index 6567cb3b..e7270da4 100644 --- a/src/services/storage/paymentStorage.ts +++ b/src/services/storage/paymentStorage.ts @@ -20,14 +20,15 @@ export default class { this.DB = DB this.userStorage = userStorage } - async AddAddressReceivingTransaction(address: UserReceivingAddress, txHash: string, outputIndex: number, amount: number, serviceFee: number, entityManager = this.DB) { + async AddAddressReceivingTransaction(address: UserReceivingAddress, txHash: string, outputIndex: number, amount: number, serviceFee: number, internal: boolean, entityManager = this.DB) { const newAddressTransaction = entityManager.getRepository(AddressReceivingTransaction).create({ user_address: address, tx_hash: txHash, output_index: outputIndex, paid_amount: amount, service_fee: serviceFee, - paid_at_unix: Math.floor(Date.now() / 1000) + paid_at_unix: Math.floor(Date.now() / 1000), + internal }) return entityManager.getRepository(AddressReceivingTransaction).save(newAddressTransaction) } @@ -57,8 +58,12 @@ export default class { return entityManager.getRepository(UserReceivingAddress).save(newUserAddress) } - async FlagInvoiceAsPaid(invoice: UserReceivingInvoice, amount: number, serviceFee: number, entityManager = this.DB) { - return entityManager.getRepository(UserReceivingInvoice).update(invoice.serial_id, { paid_at_unix: Math.floor(Date.now() / 1000), paid_amount: amount, service_fee: serviceFee }) + async FlagInvoiceAsPaid(invoice: UserReceivingInvoice, amount: number, serviceFee: number, internal: boolean, entityManager = this.DB) { + const i: Partial = { paid_at_unix: Math.floor(Date.now() / 1000), paid_amount: amount, service_fee: serviceFee, internal } + if (!internal) { + i.paidByLnd = true + } + return entityManager.getRepository(UserReceivingInvoice).update(invoice.serial_id, i) } GetUserInvoicesFlaggedAsPaid(userId: string, fromIndex: number, entityManager = this.DB): Promise { @@ -105,14 +110,15 @@ export default class { }) } - async AddUserInvoicePayment(userId: string, invoice: string, amount: number, routingFees: number, serviceFees: number, entityManager = this.DB): Promise { + async AddUserInvoicePayment(userId: string, invoice: string, amount: number, routingFees: number, serviceFees: number, internal: boolean, entityManager = this.DB): Promise { const newPayment = entityManager.getRepository(UserInvoicePayment).create({ user: await this.userStorage.GetUser(userId), paid_amount: amount, invoice, routing_fees: routingFees, service_fees: serviceFees, - paid_at_unix: Math.floor(Date.now() / 1000) + paid_at_unix: Math.floor(Date.now() / 1000), + internal }) return entityManager.getRepository(UserInvoicePayment).save(newPayment) } @@ -132,7 +138,7 @@ export default class { }) } - async AddUserTransactionPayment(userId: string, address: string, txHash: string, txOutput: number, amount: number, chainFees: number, serviceFees: number, entityManager = this.DB): Promise { + async AddUserTransactionPayment(userId: string, address: string, txHash: string, txOutput: number, amount: number, chainFees: number, serviceFees: number, internal: boolean, entityManager = this.DB): Promise { const newTx = entityManager.getRepository(UserTransactionPayment).create({ user: await this.userStorage.GetUser(userId), address, @@ -141,7 +147,8 @@ export default class { output_index: txOutput, tx_hash: txHash, service_fees: serviceFees, - paid_at_unix: Math.floor(Date.now() / 1000) + paid_at_unix: Math.floor(Date.now() / 1000), + internal }) return entityManager.getRepository(UserTransactionPayment).save(newTx) }