invoice cb

This commit is contained in:
hatim boufnichel 2021-01-17 17:07:42 +01:00
parent c877c806b4
commit 239a5603f4
2 changed files with 153 additions and 69 deletions

View file

@ -14,7 +14,6 @@ const {
} = Common } = Common
const SchemaManager = require('../../../schema') const SchemaManager = require('../../../schema')
const LightningServices = require('../../../../utils/lightningServices') const LightningServices = require('../../../../utils/lightningServices')
const LNDHealthManager = require('../../../../utils/lightningServices/errors')
const Key = require('../key') const Key = require('../key')
const Utils = require('../utils') const Utils = require('../utils')
@ -105,7 +104,7 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
return return
} }
const listenerStartTime = performance.now() //const listenerStartTime = performance.now()
ordersProcessed.add(orderID) ordersProcessed.add(orderID)
@ -228,74 +227,113 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
const invoicePutEndTime = performance.now() - invoicePutStartTime const invoicePutEndTime = performance.now() - invoicePutStartTime
// invoices should be settled right away so we can rely on this single
// subscription instead of life-long all invoices subscription
/**
* @type {string|null}
*/
let maybePostId = null
if (order.targetType === 'post') {
const { postID } = order
maybePostId = postID || null
if (!Common.isPopulatedString(postID)) {
throw new TypeError(`postID not a a populated string`)
}
}
const { r_hash } = invoice
// A post tip order lifecycle is short enough that we can do it like this.
const stream = LightningServices.invoices.subscribeSingleInvoice({
r_hash
})
/**
* @param {Common.Invoice & {r_hash:Buffer}} invoice
*/
const invoiceSubCb = invoice => {
if (!invoice.settled) {
return
}
if (order.targetType === 'post' && typeof maybePostId === 'string') {
getUser()
.get('postToTipCount')
.get(maybePostId)
.set(null) // each item in the set is a tip
}
const myLndPub = LNDHealthManager.lndPub
const myGunPub = getUser()._.sea.pub
if (!myLndPub) {
return //should never happen but just to be safe
}
SchemaManager.AddOrder({
amount: parseInt(invoice.amt_paid, 10),
coordinateHash: invoice.r_hash.toString('hex'),
coordinateIndex: parseInt(invoice.add_index, 10),
toLndPub: myLndPub,
inbound: true,
type: 'other', //TODO better this
fromGunPub: order.from,
toGunPub: myGunPub,
invoiceMemo: invoice.memo
})
}
stream.on('data', invoiceSubCb)
stream.on('status', (/** @type {any} */ status) => {
logger.info(`${r_hash}, invoice status:`, status)
})
stream.on('end', () => {
logger.warn(`${r_hash}, invoice stream ended`)
})
stream.on('error', (/** @type {any} */ e) => {
logger.warn(`${r_hash}, error:`, e)
})
logger.info(`[PERF] Added invoice to GunDB in ${invoicePutEndTime}ms`) logger.info(`[PERF] Added invoice to GunDB in ${invoicePutEndTime}ms`)
const listenerEndTime = performance.now() - listenerStartTime /**
*
* @type {Common.Schema.InvoiceWhenListed & {r_hash:Buffer,payment_addr:string}}
*/
const paidInvoice = await new Promise(res => {
SchemaManager.addListenInvoice(invoice.r_hash, res)
})
const hashString = paidInvoice.r_hash.toString('hex')
const {
amt_paid_sat: amt,
add_index: addIndex,
payment_addr: paymentAddr
} = paidInvoice
/**@type {'spontaneousPayment' | 'tip' | 'service' | 'product' | 'other'}*/
//@ts-expect-error to fix
const orderType = order.targetType
//@ts-expect-error to fix
const { ackInfo } = order //a string representing what has been requested
switch (orderType) {
case 'tip': {
if (!Common.isPopulatedString(ackInfo)) {
throw new Error('ackInfo for postID not a populated string')
} else {
getUser()
.get('postToTipCount')
.get(ackInfo)
.set(null) // each item in the set is a tip
}
break
}
case 'spontaneousPayment': {
//no action required
break
}
case 'product': {
//assuming digital product that only requires to be unlocked
const ackData = { productFinalRef: '' } //find ref by decrypting it base on "ackInfo" provided information
const toSend = JSON.stringify(ackData)
const encrypted = await SEA.encrypt(toSend, secret)
const ordResponse = {
type: 'orderAck',
content: encrypted
}
await new Promise((res, rej) => {
getUser()
.get(Key.ORDER_TO_RESPONSE)
.get(orderID)
.put(ordResponse, ack => {
if (ack.err && typeof ack.err !== 'number') {
rej(
new Error(
`Error saving encrypted orderAck to order to response usergraph: ${ack}`
)
)
} else {
res()
}
})
})
break
}
case 'service': {
const ackData = { serviceFinalRef: '' } //find ref by decrypting it base on "ackInfo" provided information
const toSend = JSON.stringify(ackData)
const encrypted = await SEA.encrypt(toSend, secret)
const serviceResponse = {
type: 'orderAck',
content: encrypted
}
await new Promise((res, rej) => {
getUser()
.get(Key.ORDER_TO_RESPONSE)
.get(orderID)
.put(serviceResponse, ack => {
if (ack.err && typeof ack.err !== 'number') {
rej(
new Error(
`Error saving encrypted orderAck to order to response usergraph: ${ack}`
)
)
} else {
res()
}
})
})
break
}
case 'other': //not implemented yet but save them as a coordinate anyways
break
default:
return //exit because not implemented
}
const myGunPub = getUser()._.sea.pub
SchemaManager.AddOrder({
type: orderType,
coordinateHash: hashString,
coordinateIndex: parseInt(addIndex, 10),
inbound: true,
amount: parseInt(amt, 10),
logger.info(`[PERF] Invoice generation completed in ${listenerEndTime}ms`) toLndPub: paymentAddr,
fromGunPub: order.from,
toGunPub: myGunPub,
invoiceMemo: memo
})
} catch (err) { } catch (err) {
logger.error( logger.error(
`error inside onOrders, orderAddr: ${addr}, orderID: ${orderID}, order: ${JSON.stringify( `error inside onOrders, orderAddr: ${addr}, orderID: ${orderID}, order: ${JSON.stringify(

View file

@ -1,5 +1,5 @@
const Crypto = require('crypto') const Crypto = require('crypto')
const { Utils: CommonUtils } = require('shock-common') const Common = require('shock-common')
const getGunUser = () => require('../gunDB/Mediator').getUser() const getGunUser = () => require('../gunDB/Mediator').getUser()
const Key = require('../gunDB/contact-api/key') const Key = require('../gunDB/contact-api/key')
/** /**
@ -259,7 +259,7 @@ class SchemaManager {
if (!coordinates) { if (!coordinates) {
return orders return orders
} }
await CommonUtils.asyncForEach(coordinates, async coordinateSHA256 => { await Common.Utils.asyncForEach(coordinates, async coordinateSHA256 => {
const encryptedOrderString = await getGunUser() const encryptedOrderString = await getGunUser()
.get(Key.COORDINATES) .get(Key.COORDINATES)
.get(coordinateSHA256) .get(coordinateSHA256)
@ -282,6 +282,52 @@ class SchemaManager {
return orderedOrders return orderedOrders
} }
/**
* @typedef {Common.Schema.InvoiceWhenListed & {r_hash:Buffer,payment_addr:string}} Invoice
*/
/**
* @type {Record<string,(invoice:Invoice) =>void>}
*/
_listeningInvoices = {}
/**
*
* @param {Buffer} r_hash
* @param {(invoice:Invoice) =>void} done
*/
addListenInvoice(r_hash, done) {
const hashString = r_hash.toString("hex")
this._listeningInvoices[hashString] = done
}
/**
*
* @param {Common.Schema.InvoiceWhenListed & {r_hash:Buffer,payment_addr:string}} data
*/
invoiceStreamDataCb(data) {
if (!data.settled) {
//invoice not paid yet
return
}
const hashString = data.r_hash.toString('hex')
const amt = parseInt(data.amt_paid_sat, 10)
if (this._listeningInvoices[hashString]) {
const done = this._listeningInvoices[hashString]
delete this._listeningInvoices[hashString]
done(data)
} else {
this.AddOrder({
type: 'invoice',
coordinateHash: hashString,
coordinateIndex: parseInt(data.add_index, 10),
inbound: true,
amount: amt,
toLndPub: data.payment_addr,
invoiceMemo: data.memo
})
}
}
/** /**
* *
* @param {string} address * @param {string} address