From 40e8a209257af4f7c1d767cbe8c64c3ccc48c021 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 2 Jul 2021 09:41:35 +0930 Subject: [PATCH] invoices: display the payer note if it's for local offer, allow in fetchinvoice. We don't do it for sendinvoice (yet?). Signed-off-by: Rusty Russell Changelog-EXPERIMENTAL: `fetchinvoice` can take a payer note, and `listinvoice` will show the payer_notes received. --- doc/lightning-createinvoice.7 | 4 +++- doc/lightning-createinvoice.7.md | 3 ++- doc/lightning-delinvoice.7 | 4 +++- doc/lightning-delinvoice.7.md | 3 ++- doc/lightning-listinvoices.7 | 4 +++- doc/lightning-listinvoices.7.md | 3 ++- doc/schemas/createinvoice.schema.json | 4 ++++ doc/schemas/delinvoice.schema.json | 6 ++++++ doc/schemas/listinvoices.schema.json | 6 ++++++ lightningd/invoice.c | 16 +++++++++++++++- plugins/fetchinvoice.c | 9 ++++++++- tests/test_pay.py | 6 +++++- 12 files changed, 59 insertions(+), 9 deletions(-) diff --git a/doc/lightning-createinvoice.7 b/doc/lightning-createinvoice.7 index 233f9ebe71fc..1bcef0fc68a3 100644 --- a/doc/lightning-createinvoice.7 +++ b/doc/lightning-createinvoice.7 @@ -60,6 +60,8 @@ On success, an object is returned, containing: \fBpayment_preimage\fR (hex, optional): the proof of payment: SHA256 of this \fBpayment_hash\fR (always 64 characters) .IP \[bu] \fBlocal_offer_id\fR (hex, optional): the \fIid\fR of our offer which created this invoice (\fBexperimental-offers\fR only)\. (always 64 characters) +.IP \[bu] +\fBpayer_note\fR (string, optional): the optional \fIpayer_note\fR from invoice_request which created this invoice (\fBexperimental-offers\fR only)\. .RE @@ -91,4 +93,4 @@ Rusty Russell \fI is mainly responsible\. Main web site: \fIhttps://github.com/ElementsProject/lightning\fR -\" SHA256STAMP:fd9275c58b67eca68aafee27cb79a558669b12b8cda56872f1db798637006278 +\" SHA256STAMP:98d930c694694138e37f3db934e2e8d5f4454c6761da5dfae667dd9b869d972c diff --git a/doc/lightning-createinvoice.7.md b/doc/lightning-createinvoice.7.md index 03184ba303bf..3a848bf662bf 100644 --- a/doc/lightning-createinvoice.7.md +++ b/doc/lightning-createinvoice.7.md @@ -45,6 +45,7 @@ On success, an object is returned, containing: - **paid_at** (u64, optional): UNIX timestamp of when invoice was paid (**status** *paid* only) - **payment_preimage** (hex, optional): the proof of payment: SHA256 of this **payment_hash** (always 64 characters) - **local_offer_id** (hex, optional): the *id* of our offer which created this invoice (**experimental-offers** only). (always 64 characters) +- **payer_note** (string, optional): the optional *payer_note* from invoice_request which created this invoice (**experimental-offers** only). [comment]: # (GENERATE-FROM-SCHEMA-END) On failure, an error is returned and no invoice is created. If the @@ -72,4 +73,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:948d344d5f589050127bd5181689882c6fad036799fa6ff039a83194ff5fd098) +[comment]: # ( SHA256STAMP:72b3b200462e9e16c72dd5e3281d35d9ec2eded62e6a7d87e9361573c85dcc32) diff --git a/doc/lightning-delinvoice.7 b/doc/lightning-delinvoice.7 index 6aeaa82c48e6..be9a52017136 100644 --- a/doc/lightning-delinvoice.7 +++ b/doc/lightning-delinvoice.7 @@ -46,6 +46,8 @@ If \fBbolt12\fR is present: .RS .IP \[bu] \fBlocal_offer_id\fR (hex, optional): offer for which this invoice was created +.IP \[bu] +\fBpayer_note\fR (string, optional): the optional \fIpayer_note\fR from invoice_request which created this invoice .RE @@ -93,4 +95,4 @@ Rusty Russell \fI is mainly responsible\. Main web site: \fIhttps://github.com/ElementsProject/lightning\fR -\" SHA256STAMP:59f9becf74ee1b70ad9eb4316baaed1d26a95e34c3a5000e095fc8b7bfa291cf +\" SHA256STAMP:67e7b489e78df3ad58efad5cad431d5b927a9294b7cbe2ebfef6903fbd2be170 diff --git a/doc/lightning-delinvoice.7.md b/doc/lightning-delinvoice.7.md index 5b3cb5db9366..7dfb3b35b1c1 100644 --- a/doc/lightning-delinvoice.7.md +++ b/doc/lightning-delinvoice.7.md @@ -33,6 +33,7 @@ On success, an object is returned, containing: If **bolt12** is present: - **local_offer_id** (hex, optional): offer for which this invoice was created + - **payer_note** (string, optional): the optional *payer_note* from invoice_request which created this invoice If **status** is "paid": - **pay_index** (u64): unique index for this invoice payment @@ -71,4 +72,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:62a3bdd502ed0b3b555f3cfda6a0e5f7c2417ce0f5b23202d613216de58a23f3) +[comment]: # ( SHA256STAMP:65c14a16b939461a06d81b0ff41e976ea4c7bf963cccdeba47739fe9ca1658a1) diff --git a/doc/lightning-listinvoices.7 b/doc/lightning-listinvoices.7 index d7de29ee256e..d77d5b792306 100644 --- a/doc/lightning-listinvoices.7 +++ b/doc/lightning-listinvoices.7 @@ -39,6 +39,8 @@ On success, an object containing \fBinvoices\fR is returned\. It is an array of \fBbolt12\fR (string, optional): the BOLT12 string (always present unless \fIbolt11\fR is) .IP \[bu] \fBlocal_offer_id\fR (hex, optional): the \fIid\fR of our offer which created this invoice (\fBexperimental-offers\fR only)\. (always 64 characters) +.IP \[bu] +\fBpayer_note\fR (string, optional): the optional \fIpayer_note\fR from invoice_request which created this invoice (\fBexperimental-offers\fR only)\. .RE @@ -67,4 +69,4 @@ Rusty Russell \fI is mainly responsible\. Main web site: \fIhttps://github.com/ElementsProject/lightning\fR -\" SHA256STAMP:d6bc8b6c09aee1f5e29900fffacbc9d3fdf1e32d27e7b90b0709a5f50ecb5c13 +\" SHA256STAMP:39640db51e7edd166966ff0f68bc1448328016fa23b56cf2e6e93961e62f458a diff --git a/doc/lightning-listinvoices.7.md b/doc/lightning-listinvoices.7.md index a6e1fd908b66..48cc73b9abb2 100644 --- a/doc/lightning-listinvoices.7.md +++ b/doc/lightning-listinvoices.7.md @@ -31,6 +31,7 @@ On success, an object containing **invoices** is returned. It is an array of ob - **bolt11** (string, optional): the BOLT11 string (always present unless *bolt12* is) - **bolt12** (string, optional): the BOLT12 string (always present unless *bolt11* is) - **local_offer_id** (hex, optional): the *id* of our offer which created this invoice (**experimental-offers** only). (always 64 characters) +- **payer_note** (string, optional): the optional *payer_note* from invoice_request which created this invoice (**experimental-offers** only). If **status** is "paid": - **pay_index** (u64): Unique incrementing index for this payment @@ -54,4 +55,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:cc6d7b4549d2ab905ab18041f34d13143e4ffed2bb6cc7c1df0307800e59a2fa) +[comment]: # ( SHA256STAMP:99dd5cb3f84e8201903e531b601a04dafde3f6eae3f582b538caea7fac20aad0) diff --git a/doc/schemas/createinvoice.schema.json b/doc/schemas/createinvoice.schema.json index b1e6aa13107f..8e79c8094966 100644 --- a/doc/schemas/createinvoice.schema.json +++ b/doc/schemas/createinvoice.schema.json @@ -62,6 +62,10 @@ "description": "the *id* of our offer which created this invoice (**experimental-offers** only).", "maxLength": 64, "minLength": 64 + }, + "payer_note": { + "type": "string", + "description": "the optional *payer_note* from invoice_request which created this invoice (**experimental-offers** only)." } } } diff --git a/doc/schemas/delinvoice.schema.json b/doc/schemas/delinvoice.schema.json index 4840d4610f36..bbcf61fca5a2 100644 --- a/doc/schemas/delinvoice.schema.json +++ b/doc/schemas/delinvoice.schema.json @@ -67,6 +67,10 @@ "local_offer_id": { "type": "hex", "description": "offer for which this invoice was created" + }, + "payer_note": { + "type": "string", + "description": "the optional *payer_note* from invoice_request which created this invoice" } } }, @@ -111,6 +115,7 @@ "amount_msat": { }, "description": { }, "payment_hash": { }, + "payer_note": { }, "local_offer_id": { }, "pay_index": { "type": "u64", @@ -145,6 +150,7 @@ "payment_hash": { }, "expires_at": { }, "pay_index": { }, + "payer_note": { }, "local_offer_id": { } } } diff --git a/doc/schemas/listinvoices.schema.json b/doc/schemas/listinvoices.schema.json index a2edbeb7f444..0de5af67f69c 100644 --- a/doc/schemas/listinvoices.schema.json +++ b/doc/schemas/listinvoices.schema.json @@ -54,6 +54,10 @@ "description": "the *id* of our offer which created this invoice (**experimental-offers** only).", "maxLength": 64, "minLength": 64 + }, + "payer_note": { + "type": "string", + "description": "the optional *payer_note* from invoice_request which created this invoice (**experimental-offers** only)." } }, "allOf": [ @@ -79,6 +83,7 @@ "bolt11": { }, "bolt12": { }, "local_offer_id": { }, + "payer_note": { }, "expires_at": { }, "pay_index": { "type": "u64", @@ -115,6 +120,7 @@ "bolt11": { }, "bolt12": { }, "local_offer_id": { }, + "payer_note": { }, "expires_at": { } } } diff --git a/lightningd/invoice.c b/lightningd/invoice.c index 9ada32b58004..11f97fe8170a 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -72,8 +72,22 @@ static void json_add_invoice(struct json_stream *response, json_add_string(response, "description", inv->description); json_add_u64(response, "expires_at", inv->expiry_time); - if (inv->local_offer_id) + if (inv->local_offer_id) { + char *fail; + struct tlv_invoice *tinv; + json_add_sha256(response, "local_offer_id", inv->local_offer_id); + + /* Everyone loves seeing their own payer notes! + * Well: they will. Trust me. */ + tinv = invoice_decode(tmpctx, + inv->invstring, strlen(inv->invstring), + NULL, NULL, &fail); + if (tinv && tinv->payer_note) + json_add_stringn(response, "payer_note", + tinv->payer_note, + tal_bytelen(tinv->payer_note)); + } } static struct command_result *tell_waiter(struct command *cmd, diff --git a/plugins/fetchinvoice.c b/plugins/fetchinvoice.c index 50af60afb48f..4e1270097479 100644 --- a/plugins/fetchinvoice.c +++ b/plugins/fetchinvoice.c @@ -830,7 +830,7 @@ static struct command_result *json_fetchinvoice(struct command *cmd, const jsmntok_t *params) { struct amount_msat *msat; - const char *rec_label; + const char *rec_label, *payer_note; struct out_req *req; struct tlv_invoice_request *invreq; struct sent *sent = tal(cmd, struct sent); @@ -847,6 +847,7 @@ static struct command_result *json_fetchinvoice(struct command *cmd, &invreq->recurrence_start), p_opt("recurrence_label", param_string, &rec_label), p_opt_def("timeout", param_number, &timeout, 60), + p_opt("payer_note", param_string, &payer_note), NULL)) return command_param_failed(); @@ -997,6 +998,12 @@ static struct command_result *json_fetchinvoice(struct command *cmd, invreq->features = plugin_feature_set(cmd->plugin)->bits[BOLT11_FEATURE]; + /* invreq->payer_note is not a nul-terminated string! */ + if (payer_note) + invreq->payer_note = tal_dup_arr(invreq, utf8, + payer_note, strlen(payer_note), + 0); + /* Make the invoice request (fills in payer_key and payer_info) */ req = jsonrpc_request_start(cmd->plugin, cmd, "createinvoicerequest", &invreq_done, diff --git a/tests/test_pay.py b/tests/test_pay.py index 0211d42daea8..27403effe469 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -3992,7 +3992,8 @@ def test_fetchinvoice(node_factory, bitcoind): 'description': 'simple test'}) inv1 = l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12']}) - inv2 = l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12_unsigned']}) + inv2 = l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12_unsigned'], + 'payer_note': 'Thanks for the fish!'}) assert inv1 != inv2 assert 'next_period' not in inv1 assert 'next_period' not in inv2 @@ -4005,6 +4006,9 @@ def test_fetchinvoice(node_factory, bitcoind): # listinvoices will show these on l3 assert [x['local_offer_id'] for x in l3.rpc.listinvoices()['invoices']] == [offer1['offer_id'], offer1['offer_id']] + assert 'payer_note' not in only_one(l3.rpc.call('listinvoices', {'invstring': inv1['invoice']})['invoices']) + assert only_one(l3.rpc.call('listinvoices', {'invstring': inv2['invoice']})['invoices'])['payer_note'] == 'Thanks for the fish!' + # We can also set the amount explicitly, to tip. inv1 = l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12'], 'msatoshi': 3}) assert l1.rpc.call('decode', [inv1['invoice']])['amount_msat'] == 3