Invoices, payments and invoice notification

This commit is contained in:
ShahanaFarooqui 2023-11-15 21:40:43 -08:00
parent e1cc56b8d5
commit d31f1b3c70
21 changed files with 95 additions and 24 deletions

View File

@ -99,10 +99,26 @@ export const postPayment = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
const options_body = JSON.parse(JSON.stringify(req.body));
if (req.body.paymentType === 'KEYSEND') {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Keysend Payment..' });
options.url = req.session.selectedNode.ln_server_url + '/v1/keysend';
options.body = req.body;
delete options_body.uiMessage;
delete options_body.fromDialog;
delete options_body.paymentType;
delete options_body.title;
delete options_body.issuer;
delete options_body.bolt11;
delete options_body.description;
delete options_body.bolt12;
delete options_body.zeroAmtOffer;
delete options_body.pubkey;
delete options_body.riskfactor;
delete options_body.localinvreqid;
delete options_body.exclude;
delete options_body.maxfee;
delete options_body.saveToDB;
options.body = options_body;
}
else {
if (req.body.paymentType === 'OFFER') {
@ -111,7 +127,18 @@ export const postPayment = (req, res, next) => {
else {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sending Invoice Payment..' });
}
options.body = req.body;
delete options_body.uiMessage;
delete options_body.fromDialog;
delete options_body.paymentType;
delete options_body.destination;
delete options_body.extratlvs;
delete options_body.title;
delete options_body.issuer;
delete options_body.bolt12;
delete options_body.zeroAmtOffer;
delete options_body.pubkey;
delete options_body.saveToDB;
options.body = options_body;
options.url = req.session.selectedNode.ln_server_url + '/v1/pay';
}
request.post(options).then((body) => {

View File

@ -51,7 +51,7 @@ export class CLWebSocketClient {
clWsClt.selectedNode.rune_value = this.common.getRuneValue(clWsClt.selectedNode.rune_path);
}
clWsClt.webSocketClient = socketIOClient(clWsClt.selectedNode.ln_server_url, {
extraHeaders: { rune: '9ISqFS53IFIfBS0yhwgM_XaNHFAUoFU_Bzfyhe-s8u49MA==' },
extraHeaders: { rune: clWsClt.selectedNode.rune_value },
transports: ['websocket'],
secure: true,
rejectUnauthorized: false

View File

@ -163,7 +163,7 @@ export class RTLWebSocketServer {
try {
this.webSocketServer.clients.forEach((client) => {
if (+client.clientNodeIndex === +selectedNode.index) {
this.logger.log({ selectedNode: !selectedNode ? this.common.initSelectedNode : selectedNode, level: 'DEBUG', fileName: 'WebSocketServer', msg: 'Broadcasting message to client...: ' + client.clientId });
this.logger.log({ selectedNode: !selectedNode ? this.common.initSelectedNode : selectedNode, level: 'DEBUG', fileName: 'WebSocketServer', msg: 'Broadcasting message to client...: ' + client.clientId + ', Message: ' + newMessage });
client.send(newMessage);
}
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
(()=>{"use strict";var e,v={},m={};function r(e){var f=m[e];if(void 0!==f)return f.exports;var t=m[e]={id:e,loaded:!1,exports:{}};return v[e].call(t.exports,t,t.exports,r),t.loaded=!0,t.exports}r.m=v,e=[],r.O=(f,t,i,o)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,i,o]=e[n],s=!0,l=0;l<t.length;l++)(!1&o||a>=o)&&Object.keys(r.O).every(b=>r.O[b](t[l]))?t.splice(l--,1):(s=!1,o<a&&(a=o));if(s){e.splice(n--,1);var d=i();void 0!==d&&(f=d)}}return f}o=o||0;for(var n=e.length;n>0&&e[n-1][2]>o;n--)e[n]=e[n-1];e[n]=[t,i,o]},r.d=(e,f)=>{for(var t in f)r.o(f,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:f[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((f,t)=>(r.f[t](e,f),f),[])),r.u=e=>e+"."+{125:"649839951b94fd32",456:"16212571f65d0b5f",570:"a719a189ca60c55b",758:"2801e2da6f8bba94"}[e]+".js",r.miniCssF=e=>{},r.o=(e,f)=>Object.prototype.hasOwnProperty.call(e,f),(()=>{var e={},f="RTLApp:";r.l=(t,i,o,n)=>{if(e[t])e[t].push(i);else{var a,s;if(void 0!==o)for(var l=document.getElementsByTagName("script"),d=0;d<l.length;d++){var u=l[d];if(u.getAttribute("src")==t||u.getAttribute("data-webpack")==f+o){a=u;break}}a||(s=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",f+o),a.src=r.tu(t)),e[t]=[i];var c=(g,b)=>{a.onerror=a.onload=null,clearTimeout(p);var h=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),h&&h.forEach(y=>y(b)),g)return g(b)},p=setTimeout(c.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=c.bind(null,a.onerror),a.onload=c.bind(null,a.onload),s&&document.head.appendChild(a)}}})(),r.r=e=>{typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:f=>f},typeof trustedTypes<"u"&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="",(()=>{var e={666:0};r.f.j=(i,o)=>{var n=r.o(e,i)?e[i]:void 0;if(0!==n)if(n)o.push(n[2]);else if(666!=i){var a=new Promise((u,c)=>n=e[i]=[u,c]);o.push(n[2]=a);var s=r.p+r.u(i),l=new Error;r.l(s,u=>{if(r.o(e,i)&&(0!==(n=e[i])&&(e[i]=void 0),n)){var c=u&&("load"===u.type?"missing":u.type),p=u&&u.target&&u.target.src;l.message="Loading chunk "+i+" failed.\n("+c+": "+p+")",l.name="ChunkLoadError",l.type=c,l.request=p,n[1](l)}},"chunk-"+i,i)}else e[i]=0},r.O.j=i=>0===e[i];var f=(i,o)=>{var l,d,[n,a,s]=o,u=0;if(n.some(p=>0!==e[p])){for(l in a)r.o(a,l)&&(r.m[l]=a[l]);if(s)var c=s(r)}for(i&&i(o);u<n.length;u++)r.o(e,d=n[u])&&e[d]&&e[d][0](),e[d]=0;return r.O(c)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(f.bind(null,0)),t.push=f.bind(null,t.push.bind(t))})()})();
(()=>{"use strict";var e,v={},m={};function r(e){var f=m[e];if(void 0!==f)return f.exports;var t=m[e]={id:e,loaded:!1,exports:{}};return v[e].call(t.exports,t,t.exports,r),t.loaded=!0,t.exports}r.m=v,e=[],r.O=(f,t,i,o)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,i,o]=e[n],s=!0,l=0;l<t.length;l++)(!1&o||a>=o)&&Object.keys(r.O).every(b=>r.O[b](t[l]))?t.splice(l--,1):(s=!1,o<a&&(a=o));if(s){e.splice(n--,1);var d=i();void 0!==d&&(f=d)}}return f}o=o||0;for(var n=e.length;n>0&&e[n-1][2]>o;n--)e[n]=e[n-1];e[n]=[t,i,o]},r.d=(e,f)=>{for(var t in f)r.o(f,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:f[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((f,t)=>(r.f[t](e,f),f),[])),r.u=e=>e+"."+{125:"e77d478f0dec10a6",456:"16212571f65d0b5f",570:"a719a189ca60c55b",758:"2801e2da6f8bba94"}[e]+".js",r.miniCssF=e=>{},r.o=(e,f)=>Object.prototype.hasOwnProperty.call(e,f),(()=>{var e={},f="RTLApp:";r.l=(t,i,o,n)=>{if(e[t])e[t].push(i);else{var a,s;if(void 0!==o)for(var l=document.getElementsByTagName("script"),d=0;d<l.length;d++){var u=l[d];if(u.getAttribute("src")==t||u.getAttribute("data-webpack")==f+o){a=u;break}}a||(s=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",f+o),a.src=r.tu(t)),e[t]=[i];var c=(g,b)=>{a.onerror=a.onload=null,clearTimeout(p);var h=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),h&&h.forEach(y=>y(b)),g)return g(b)},p=setTimeout(c.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=c.bind(null,a.onerror),a.onload=c.bind(null,a.onload),s&&document.head.appendChild(a)}}})(),r.r=e=>{typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:f=>f},typeof trustedTypes<"u"&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="",(()=>{var e={666:0};r.f.j=(i,o)=>{var n=r.o(e,i)?e[i]:void 0;if(0!==n)if(n)o.push(n[2]);else if(666!=i){var a=new Promise((u,c)=>n=e[i]=[u,c]);o.push(n[2]=a);var s=r.p+r.u(i),l=new Error;r.l(s,u=>{if(r.o(e,i)&&(0!==(n=e[i])&&(e[i]=void 0),n)){var c=u&&("load"===u.type?"missing":u.type),p=u&&u.target&&u.target.src;l.message="Loading chunk "+i+" failed.\n("+c+": "+p+")",l.name="ChunkLoadError",l.type=c,l.request=p,n[1](l)}},"chunk-"+i,i)}else e[i]=0},r.O.j=i=>0===e[i];var f=(i,o)=>{var l,d,[n,a,s]=o,u=0;if(n.some(p=>0!==e[p])){for(l in a)r.o(a,l)&&(r.m[l]=a[l]);if(s)var c=s(r)}for(i&&i(o);u<n.length;u++)r.o(e,d=n[u])&&e[d]&&e[d][0](),e[d]=0;return r.O(c)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(f.bind(null,0)),t.push=f.bind(null,t.push.bind(t))})()})();

View File

@ -91,17 +91,44 @@ export const listPayments = (req, res, next) => {
export const postPayment = (req, res, next) => {
options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
const options_body = JSON.parse(JSON.stringify(req.body));
if (req.body.paymentType === 'KEYSEND') {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Keysend Payment..' });
options.url = req.session.selectedNode.ln_server_url + '/v1/keysend';
options.body = req.body;
delete options_body.uiMessage;
delete options_body.fromDialog;
delete options_body.paymentType;
delete options_body.title;
delete options_body.issuer;
delete options_body.bolt11;
delete options_body.description;
delete options_body.bolt12;
delete options_body.zeroAmtOffer;
delete options_body.pubkey;
delete options_body.riskfactor;
delete options_body.localinvreqid;
delete options_body.exclude;
delete options_body.maxfee;
delete options_body.saveToDB;
options.body = options_body;
} else {
if (req.body.paymentType === 'OFFER') {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sending Offer Payment..' });
} else {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sending Invoice Payment..' });
}
options.body = req.body;
delete options_body.uiMessage;
delete options_body.fromDialog;
delete options_body.paymentType;
delete options_body.destination;
delete options_body.extratlvs;
delete options_body.title;
delete options_body.issuer;
delete options_body.bolt12;
delete options_body.zeroAmtOffer;
delete options_body.pubkey;
delete options_body.saveToDB;
options.body = options_body;
options.url = req.session.selectedNode.ln_server_url + '/v1/pay';
}
request.post(options).then((body) => {

View File

@ -61,7 +61,7 @@ export class CLWebSocketClient {
clWsClt.selectedNode.rune_value = this.common.getRuneValue(clWsClt.selectedNode.rune_path);
}
clWsClt.webSocketClient = socketIOClient(clWsClt.selectedNode.ln_server_url, {
extraHeaders: { rune: '9ISqFS53IFIfBS0yhwgM_XaNHFAUoFU_Bzfyhe-s8u49MA==' },
extraHeaders: { rune: clWsClt.selectedNode.rune_value },
transports: ['websocket'],
secure: true,
rejectUnauthorized: false

View File

@ -167,7 +167,7 @@ export class RTLWebSocketServer {
try {
this.webSocketServer.clients.forEach((client) => {
if (+client.clientNodeIndex === +selectedNode.index) {
this.logger.log({ selectedNode: !selectedNode ? this.common.initSelectedNode : selectedNode, level: 'DEBUG', fileName: 'WebSocketServer', msg: 'Broadcasting message to client...: ' + client.clientId });
this.logger.log({ selectedNode: !selectedNode ? this.common.initSelectedNode : selectedNode, level: 'DEBUG', fileName: 'WebSocketServer', msg: 'Broadcasting message to client...: ' + client.clientId + ', Message: ' + newMessage });
client.send(newMessage);
}
});

View File

@ -3,7 +3,7 @@ import { createAction, props } from '@ngrx/store';
import { CLNActions } from '../../shared/services/consts-enums-functions';
import { ApiCallStatusPayload } from '../../shared/models/apiCallsPayload';
import { SelNodeChild } from '../../shared/models/RTLconfig';
import { GetInfo, Fees, Peer, Payment, QueryRoutes, Channel, FeeRates, Invoice, ListInvoices, OnChain, UTXO, SaveChannel,
import { GetInfo, Fees, Peer, Payment, QueryRoutes, Channel, FeeRates, Invoice, InvoicePaymentNotification, ListInvoices, OnChain, UTXO, SaveChannel,
GetNewAddress, DetachPeer, UpdateChannel, CloseChannel, SendPayment, GetQueryRoutes, ChannelLookup, OfferInvoice, Offer, OfferBookmark, ListForwards, FetchListForwards } from '../../shared/models/clnModels';
import { PageSettings } from '../../shared/models/pageSettings';
@ -85,11 +85,11 @@ export const fetchInvoices = createAction(CLNActions.FETCH_INVOICES_CLN);
export const setInvoices = createAction(CLNActions.SET_INVOICES_CLN, props<{ payload: ListInvoices }>());
export const saveNewInvoice = createAction(CLNActions.SAVE_NEW_INVOICE_CLN, props<{ payload: { amount_msat: number, label: string, description: string, expiry: number, exposeprivatechannels: boolean } }>());
export const saveNewInvoice = createAction(CLNActions.SAVE_NEW_INVOICE_CLN, props<{ payload: { amount_msat: number | 'any', label: string, description: string, expiry: number, exposeprivatechannels: boolean } }>());
export const addInvoice = createAction(CLNActions.ADD_INVOICE_CLN, props<{ payload: Invoice }>());
export const updateInvoice = createAction(CLNActions.UPDATE_INVOICE_CLN, props<{ payload: Invoice }>());
export const updateInvoice = createAction(CLNActions.UPDATE_INVOICE_CLN, props<{ payload: InvoicePaymentNotification }>());
export const deleteExpiredInvoice = createAction(CLNActions.DELETE_EXPIRED_INVOICE_CLN, props<{ payload?: number | null }>());

View File

@ -59,8 +59,8 @@ export class CLNEffects implements OnDestroy {
subscribe((newMessage) => {
this.logger.info('Received new message from the service: ' + JSON.stringify(newMessage));
if (newMessage && newMessage.data) {
if (newMessage.data[CLNWSEventTypeEnum.INVOICE_PAYMENT] && newMessage.data.label) {
this.store.dispatch(updateInvoice({ payload: newMessage.data }));
if (newMessage.data[CLNWSEventTypeEnum.INVOICE_PAYMENT] && newMessage.data[CLNWSEventTypeEnum.INVOICE_PAYMENT].label) {
this.store.dispatch(updateInvoice({ payload: newMessage.data[CLNWSEventTypeEnum.INVOICE_PAYMENT] }));
}
}
});

View File

@ -146,7 +146,14 @@ export const CLNReducer = createReducer(initCLNState,
})),
on(updateInvoice, (state, { payload }) => {
const modifiedInvoices = state.invoices;
modifiedInvoices.invoices = modifiedInvoices.invoices?.map((invoice) => ((invoice.label === payload.label) ? payload : invoice));
modifiedInvoices.invoices = modifiedInvoices.invoices?.map((invoice) => {
if (invoice.label === payload.label) {
invoice.amount_received_msat = +payload.msat.substring(0, payload.msat.length - 4);
invoice.payment_preimage = payload.preimage;
invoice.status = 'paid';
}
return invoice;
});
return {
...state,
invoices: modifiedInvoices

View File

@ -77,7 +77,7 @@ export class CLNCreateInvoiceComponent implements OnInit, OnDestroy {
}
this.store.dispatch(saveNewInvoice({
payload: {
label: ('ulbl' + Math.random().toString(36).slice(2) + Date.now()), amount_msat: this.invoiceValue * 1000, description: this.description, expiry: expiryInSecs, exposeprivatechannels: this.private
label: ('ulbl' + Math.random().toString(36).slice(2) + Date.now()), amount_msat: (this.invoiceValue ? this.invoiceValue * 1000 : 'any'), description: this.description, expiry: expiryInSecs, exposeprivatechannels: this.private
}
}));
}

View File

@ -33,7 +33,7 @@
<div fxFlex="50">
<h4 fxLayoutAlign="start" class="font-bold-500">{{screenSize === screenSizeEnum.XS ? 'Amount' : 'Amount Requested'}}</h4>
<span class="foreground-secondary-text">
{{(invoice?.amount_msat/1000 || 0) | number}} Sats<ng-container *ngIf="!invoice?.amount_msat || invoice?.amount_msat === '0'"> (zero amount) </ng-container>
{{(invoice?.amount_msat/1000 || 0) | number}} Sats<ng-container *ngIf="!invoice?.amount_msat || invoice?.amount_msat === '0' || invoice?.amount_msat === 'any'"> (zero amount) </ng-container>
</span>
</div>
<div fxFlex="50">

View File

@ -28,6 +28,7 @@ export class CLNInvoiceInformationComponent implements OnInit, OnDestroy {
public showAdvanced = false;
public newlyAdded = false;
public invoice: Invoice;
public invoiceStatus = '';
public qrWidth = 240;
public screenSize = '';
public screenSizeEnum = ScreenSizeEnum;
@ -38,6 +39,7 @@ export class CLNInvoiceInformationComponent implements OnInit, OnDestroy {
ngOnInit() {
this.invoice = this.data.invoice;
this.invoiceStatus = this.invoice.status;
this.newlyAdded = !!this.data.newlyAdded;
this.screenSize = this.commonService.getScreenSize();
if (this.screenSize === ScreenSizeEnum.XS) {
@ -45,14 +47,16 @@ export class CLNInvoiceInformationComponent implements OnInit, OnDestroy {
}
this.store.select(listInvoices).pipe(takeUntil(this.unSubs[1])).
subscribe((invoicesSelector: { listInvoices: ListInvoices, apiCallStatus: ApiCallStatusPayload }) => {
const invoiceStatus = this.invoice.status;
const invoices = invoicesSelector.listInvoices.invoices || [];
const foundInvoice = invoices?.find((invoice) => invoice.payment_hash === this.invoice.payment_hash) || null;
if (foundInvoice) { this.invoice = foundInvoice; }
if (invoiceStatus !== this.invoice.status && this.invoice.status === 'paid') {
if (this.invoiceStatus !== this.invoice.status && this.invoice.status === 'paid') {
this.flgInvoicePaid = true;
setTimeout(() => { this.flgInvoicePaid = false; }, 4000);
}
this.logger.info(this.invoice);
this.logger.info(this.invoiceStatus);
this.logger.info(foundInvoice);
this.logger.info(invoicesSelector);
});
}

View File

@ -152,7 +152,7 @@ export class CLNLightningInvoicesTableComponent implements OnInit, AfterViewInit
this.newlyAddedInvoiceValue = this.invoiceValue;
this.store.dispatch(saveNewInvoice({
payload: {
label: this.newlyAddedInvoiceMemo, amount_msat: this.invoiceValue * 1000, description: this.description, expiry: expiryInSecs, exposeprivatechannels: this.private
label: this.newlyAddedInvoiceMemo, amount_msat: (this.invoiceValue ? this.invoiceValue * 1000 : 'any'), description: this.description, expiry: expiryInSecs, exposeprivatechannels: this.private
}
}));
this.resetData();

View File

@ -6,7 +6,7 @@
<mat-hint *ngIf="paymentRequest && paymentDecodedHint !== ''">{{paymentDecodedHint}}</mat-hint>
<mat-error *ngIf="!paymentRequest">Payment request is required.</mat-error>
</mat-form-field>
<div fxLayout="row" class="mt-1">
<div fxLayout="row" class="mt-3">
<button class="mr-1" mat-stroked-button color="primary" tabindex="2" type="reset" (click)="resetData()">Clear Field</button>
<button mat-flat-button color="primary" tabindex="3" (click)="onSendPayment()">Send Payment</button>
</div>

View File

@ -107,6 +107,12 @@ export interface OfferBookmark {
description?: string;
}
export interface InvoicePaymentNotification {
msat?: string;
preimage?: string;
label?: string;
}
export interface ListInvoices {
invoices?: Invoice[];
last_index_offset?: string;