dont lose payments

This commit is contained in:
boufni95 2023-11-18 22:51:49 +01:00
parent 0d37b1d72b
commit 5969e8fecf
9 changed files with 59 additions and 29 deletions

View file

@ -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 => {

View file

@ -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

View file

@ -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) {

View file

@ -126,11 +126,6 @@ export default class {
}
}
async PayInvoiceInternal(app: Application, userId: string, invoice: UserReceivingInvoice, amount: number, action: Types.UserOperationType): Promise<Types.PayAddressResponse> {
this.invoicePaidCb(invoice.invoice, amount)
return { txId: "" }
}
async PayInvoice(userId: string, req: Types.PayInvoiceRequest, linkedApplication: Application): Promise<Types.PayInvoiceResponse> {
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
}

View file

@ -28,6 +28,9 @@ export class AddressReceivingTransaction {
@Column()
paid_at_unix: number
@Column()
internal: boolean
@CreateDateColumn()
created_at: Date

View file

@ -27,6 +27,9 @@ export class UserInvoicePayment {
@Column()
paid_at_unix: number
@Column()
internal: boolean
@CreateDateColumn()
created_at: Date

View file

@ -23,6 +23,12 @@ export class UserReceivingInvoice {
@Column({ default: 0 })
paid_at_unix: number
@Column()
internal: boolean
@Column()
paidByLnd: boolean
@Column({ default: "" })
callbackUrl: string

View file

@ -33,6 +33,9 @@ export class UserTransactionPayment {
@Column()
paid_at_unix: number
@Column()
internal: boolean
@CreateDateColumn()
created_at: Date

View file

@ -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<UserReceivingInvoice> = { 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<UserReceivingInvoice[]> {
@ -105,14 +110,15 @@ export default class {
})
}
async AddUserInvoicePayment(userId: string, invoice: string, amount: number, routingFees: number, serviceFees: number, entityManager = this.DB): Promise<UserInvoicePayment> {
async AddUserInvoicePayment(userId: string, invoice: string, amount: number, routingFees: number, serviceFees: number, internal: boolean, entityManager = this.DB): Promise<UserInvoicePayment> {
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<UserTransactionPayment> {
async AddUserTransactionPayment(userId: string, address: string, txHash: string, txOutput: number, amount: number, chainFees: number, serviceFees: number, internal: boolean, entityManager = this.DB): Promise<UserTransactionPayment> {
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)
}