fix: display units for invoice expiry and CLTV expiry (#4223)
The PaymentRequest view rendered the decoded expiry as raw seconds (e.g. 3600) and cltv_expiry as a bare number. Display the expiry as a humanized duration via the Invoice model's existing formatter, and append the blocks unit to CLTV expiry on both the PaymentRequest and Invoice views. The model's originalTimeUntilExpiryInSeconds only worked when a bolt11 string was available to decode, but pay_req is built from a backend decodepayreq response that carries no bolt11 string. Fall back to the raw expiry/expires_at fields in that case.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
jest.mock('../stores/Stores', () => ({}));
|
||||
|
||||
import Invoice from './Invoice';
|
||||
|
||||
// decodes with timestamp 1700074718 and expiry 3600 (see Bolt11Utils.test.ts)
|
||||
const paymentRequest =
|
||||
'lnbcrt1230n1pj429x7pp57t97q4awqj3f529snr0pa6senk83sq5pp760qf5a4jzvd7xgwcksdqqcqzzsxqrrsssp57eqtv7vxr46arupna3w4ct0lkf2mqmz9wt044cwkks0rwlnhfr5s9qyyssqragwpwav7nfwv2xyuuamxxj4pnnpzv2hlw7j473repd3sq7st698ta9kmzmygt0w7tmncl56a6mnma0w7e5dlpqd0wy6x3v35rssldspjhh8p0';
|
||||
|
||||
describe('Invoice.originalTimeUntilExpiryInSeconds', () => {
|
||||
it('derives expiry from a decodable payment request', () => {
|
||||
const invoice = new Invoice({ payment_request: paymentRequest });
|
||||
expect(invoice.originalTimeUntilExpiryInSeconds).toBe(3600);
|
||||
});
|
||||
|
||||
it('falls back to the expiry field when no payment request string is present (decodepayreq response)', () => {
|
||||
const invoice = new Invoice({
|
||||
destination: '02758997f184be06f4350b136db0bed6f8',
|
||||
timestamp: '1700074718',
|
||||
expiry: '3600',
|
||||
cltv_expiry: '80'
|
||||
});
|
||||
expect(invoice.originalTimeUntilExpiryInSeconds).toBe(3600);
|
||||
});
|
||||
|
||||
it('uses expires_at with the model timestamp when no payment request string is present', () => {
|
||||
const invoice = new Invoice({
|
||||
timestamp: '1700074718',
|
||||
expires_at: 1700074718 + 600
|
||||
});
|
||||
expect(invoice.originalTimeUntilExpiryInSeconds).toBe(600);
|
||||
});
|
||||
|
||||
it('returns undefined when no expiry information is available', () => {
|
||||
const invoice = new Invoice({
|
||||
destination: '02758997f184be06f4350b136db0bed6f8'
|
||||
});
|
||||
expect(invoice.originalTimeUntilExpiryInSeconds).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Invoice.determineFormattedOriginalTimeUntilExpiry', () => {
|
||||
it('humanizes the fallback expiry seconds', () => {
|
||||
const invoice = new Invoice({
|
||||
timestamp: '1700074718',
|
||||
expiry: '3600'
|
||||
});
|
||||
invoice.determineFormattedOriginalTimeUntilExpiry('en');
|
||||
expect(invoice.formattedOriginalTimeUntilExpiry).toBe('1 hour');
|
||||
});
|
||||
|
||||
it('humanizes the expiry of a decodable payment request', () => {
|
||||
const invoice = new Invoice({ payment_request: paymentRequest });
|
||||
invoice.determineFormattedOriginalTimeUntilExpiry('en');
|
||||
expect(invoice.formattedOriginalTimeUntilExpiry).toBe('1 hour');
|
||||
});
|
||||
});
|
||||
+8
-3
@@ -387,12 +387,17 @@ export default class Invoice extends BaseModel {
|
||||
| number
|
||||
| undefined {
|
||||
const decoded = this.decodedPaymentRequest;
|
||||
if (!decoded) return undefined;
|
||||
const timestamp = decoded ? decoded.timestamp : Number(this.timestamp);
|
||||
if (this.expires_at != null) {
|
||||
// expiry is missing in payment request in Core Lightning
|
||||
return this.expires_at - decoded.timestamp;
|
||||
return !isNaN(timestamp) ? this.expires_at - timestamp : undefined;
|
||||
}
|
||||
return decoded.expiry;
|
||||
if (decoded) return decoded.expiry;
|
||||
// no bolt11 string to decode (e.g. pay_req built from a backend
|
||||
// decodepayreq response) — use the reported expiry seconds
|
||||
return this.expiry != null && this.expiry !== ''
|
||||
? Number(this.expiry)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
public determineFormattedOriginalTimeUntilExpiry(
|
||||
|
||||
+3
-1
@@ -418,7 +418,9 @@ export default class InvoiceView extends React.Component<
|
||||
keyValue={localeString(
|
||||
'views.Invoice.cltvExpiry'
|
||||
)}
|
||||
value={cltv_expiry}
|
||||
value={`${cltv_expiry} ${localeString(
|
||||
'general.blocks'
|
||||
)}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -518,6 +518,9 @@ export default class PaymentRequest extends React.Component<
|
||||
|
||||
const isZaplockerValid = isPmtHashSigValid && isRelaysSigValid;
|
||||
|
||||
const locale = SettingsStore.settings.locale;
|
||||
if (pay_req) pay_req.determineFormattedOriginalTimeUntilExpiry(locale);
|
||||
|
||||
// variables cannot be destructured traditionally here
|
||||
// due to how we clear the pay_req from the store upon
|
||||
// navigating back
|
||||
@@ -525,7 +528,7 @@ export default class PaymentRequest extends React.Component<
|
||||
pay_req && pay_req.getRequestAmount
|
||||
? pay_req.getRequestAmount
|
||||
: undefined;
|
||||
const expiry = pay_req && pay_req.expiry;
|
||||
const expiry = pay_req && pay_req.formattedOriginalTimeUntilExpiry;
|
||||
const cltv_expiry = pay_req && pay_req.cltv_expiry;
|
||||
const destination = pay_req && pay_req.destination;
|
||||
const payment_hash = pay_req && pay_req.payment_hash;
|
||||
@@ -971,7 +974,9 @@ export default class PaymentRequest extends React.Component<
|
||||
keyValue={localeString(
|
||||
'views.PaymentRequest.cltvExpiry'
|
||||
)}
|
||||
value={cltv_expiry}
|
||||
value={`${cltv_expiry} ${localeString(
|
||||
'general.blocks'
|
||||
)}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user