mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-19 01:43:36 +01:00
offer: allow re-enabling a previously disabled offer
Sometimes, for various reasons, a user disables an offer and then wants to re-enable it. This should be allowed because, from the CLN point of view, it is just an internal state. If a user has constraints on the description of the invoice because they are using services that link some sort of user ID to an offer, it is important for the user to be able to re-enable the offer, not create a new one. Creating a new offer would require a different description. Link: https://github.com/ElementsProject/lightning/issues/7360 Co-Developed-by: Rusty Russell <rusty@rustcorp.com.au> Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
This commit is contained in:
parent
47e7127b19
commit
1e1edfd073
@ -10757,6 +10757,7 @@
|
||||
"Rusty Russell <<rusty@rustcorp.com.au>> is mainly responsible."
|
||||
],
|
||||
"see_also": [
|
||||
"lightning-enableoffer(7)",
|
||||
"lightning-offer(7)",
|
||||
"lightning-listoffers(7)"
|
||||
],
|
||||
@ -10921,6 +10922,112 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"lightning-enableoffer.json": {
|
||||
"$schema": "../rpc-schema-draft.json",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"rpc": "disableoffer",
|
||||
"title": "Command for re-enabling an offer",
|
||||
"warning": "experimental-offers only",
|
||||
"description": [
|
||||
"The **enableoffer** RPC command enables an offer, after it has been disabled."
|
||||
],
|
||||
"request": {
|
||||
"required": [
|
||||
"offer_id"
|
||||
],
|
||||
"properties": {
|
||||
"offer_id": {
|
||||
"type": "hash",
|
||||
"description": [
|
||||
"The id we use to identify this offer."
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"required": [
|
||||
"offer_id",
|
||||
"active",
|
||||
"single_use",
|
||||
"bolt12",
|
||||
"used"
|
||||
],
|
||||
"properties": {
|
||||
"offer_id": {
|
||||
"type": "hash",
|
||||
"description": [
|
||||
"The merkle hash of the offer."
|
||||
]
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean",
|
||||
"enum": [
|
||||
true
|
||||
],
|
||||
"description": [
|
||||
"Whether the offer can produce invoices/payments."
|
||||
]
|
||||
},
|
||||
"single_use": {
|
||||
"type": "boolean",
|
||||
"description": [
|
||||
"Whether the offer is disabled after first successful use."
|
||||
]
|
||||
},
|
||||
"bolt12": {
|
||||
"type": "string",
|
||||
"description": [
|
||||
"The bolt12 string representing this offer."
|
||||
]
|
||||
},
|
||||
"used": {
|
||||
"type": "boolean",
|
||||
"description": [
|
||||
"Whether the offer has had an invoice paid / payment made."
|
||||
]
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": [
|
||||
"The label provided when offer was created."
|
||||
]
|
||||
}
|
||||
},
|
||||
"pre_return_value_notes": [
|
||||
"Note: the returned object is the same format as **listoffers**."
|
||||
]
|
||||
},
|
||||
"author": [
|
||||
"Rusty Russell <<rusty@rustcorp.com.au>> is mainly responsible."
|
||||
],
|
||||
"see_also": [
|
||||
"lightning-offer(7)",
|
||||
"lightning-disableoffer(7)",
|
||||
"lightning-listoffers(7)"
|
||||
],
|
||||
"resources": [
|
||||
"Main web site: <https://github.com/ElementsProject/lightning>"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"request": {
|
||||
"id": "example:enableoffer#1",
|
||||
"method": "enableoffer",
|
||||
"params": {
|
||||
"offer_id": "713a16ccd4eb10438bdcfbc2c8276be301020dd9d489c530773ba64f3b33307d"
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"offer_id": "053a5c566fbea2681a5ff9c05a913da23e45b95d09ef5bd25d7d408f23da7084",
|
||||
"active": true,
|
||||
"single_use": false,
|
||||
"bolt12": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqvqcdgq2z9pk7enxv4jjqen0wgs8yatnw3ujz83qkc6rvp4j28rt3dtrn32zkvdy7efhnlrpr5rp5geqxs783wtlj550qs8czzku4nk3pqp6m593qxgunzuqcwkmgqkmp6ty0wyvjcqdguv3pnpukedwn6cr87m89t74h3auyaeg89xkvgzpac70z3m9rn5xzu28c",
|
||||
"used": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"lightning-feerates.json": {
|
||||
"$schema": "../rpc-schema-draft.json",
|
||||
"type": "object",
|
||||
|
@ -80,6 +80,7 @@
|
||||
"Rusty Russell <<rusty@rustcorp.com.au>> is mainly responsible."
|
||||
],
|
||||
"see_also": [
|
||||
"lightning-enableoffer(7)",
|
||||
"lightning-offer(7)",
|
||||
"lightning-listoffers(7)"
|
||||
],
|
||||
|
106
doc/schemas/lightning-enableoffer.json
Normal file
106
doc/schemas/lightning-enableoffer.json
Normal file
@ -0,0 +1,106 @@
|
||||
{
|
||||
"$schema": "../rpc-schema-draft.json",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"rpc": "disableoffer",
|
||||
"title": "Command for re-enabling an offer",
|
||||
"warning": "experimental-offers only",
|
||||
"description": [
|
||||
"The **enableoffer** RPC command enables an offer, after it has been disabled."
|
||||
],
|
||||
"request": {
|
||||
"required": [
|
||||
"offer_id"
|
||||
],
|
||||
"properties": {
|
||||
"offer_id": {
|
||||
"type": "hash",
|
||||
"description": [
|
||||
"The id we use to identify this offer."
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"required": [
|
||||
"offer_id",
|
||||
"active",
|
||||
"single_use",
|
||||
"bolt12",
|
||||
"used"
|
||||
],
|
||||
"properties": {
|
||||
"offer_id": {
|
||||
"type": "hash",
|
||||
"description": [
|
||||
"The merkle hash of the offer."
|
||||
]
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean",
|
||||
"enum": [
|
||||
true
|
||||
],
|
||||
"description": [
|
||||
"Whether the offer can produce invoices/payments."
|
||||
]
|
||||
},
|
||||
"single_use": {
|
||||
"type": "boolean",
|
||||
"description": [
|
||||
"Whether the offer is disabled after first successful use."
|
||||
]
|
||||
},
|
||||
"bolt12": {
|
||||
"type": "string",
|
||||
"description": [
|
||||
"The bolt12 string representing this offer."
|
||||
]
|
||||
},
|
||||
"used": {
|
||||
"type": "boolean",
|
||||
"description": [
|
||||
"Whether the offer has had an invoice paid / payment made."
|
||||
]
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": [
|
||||
"The label provided when offer was created."
|
||||
]
|
||||
}
|
||||
},
|
||||
"pre_return_value_notes": [
|
||||
"Note: the returned object is the same format as **listoffers**."
|
||||
]
|
||||
},
|
||||
"author": [
|
||||
"Rusty Russell <<rusty@rustcorp.com.au>> is mainly responsible."
|
||||
],
|
||||
"see_also": [
|
||||
"lightning-offer(7)",
|
||||
"lightning-disableoffer(7)",
|
||||
"lightning-listoffers(7)"
|
||||
],
|
||||
"resources": [
|
||||
"Main web site: <https://github.com/ElementsProject/lightning>"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"request": {
|
||||
"id": "example:enableoffer#1",
|
||||
"method": "enableoffer",
|
||||
"params": {
|
||||
"offer_id": "713a16ccd4eb10438bdcfbc2c8276be301020dd9d489c530773ba64f3b33307d"
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"offer_id": "053a5c566fbea2681a5ff9c05a913da23e45b95d09ef5bd25d7d408f23da7084",
|
||||
"active": true,
|
||||
"single_use": false,
|
||||
"bolt12": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqvqcdgq2z9pk7enxv4jjqen0wgs8yatnw3ujz83qkc6rvp4j28rt3dtrn32zkvdy7efhnlrpr5rp5geqxs783wtlj550qs8czzku4nk3pqp6m593qxgunzuqcwkmgqkmp6ty0wyvjcqdguv3pnpukedwn6cr87m89t74h3auyaeg89xkvgzpac70z3m9rn5xzu28c",
|
||||
"used": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -231,6 +231,48 @@ static const struct json_command disableoffer_command = {
|
||||
};
|
||||
AUTODATA(json_command, &disableoffer_command);
|
||||
|
||||
static struct command_result *json_enableoffer(struct command *cmd,
|
||||
const char *buffer,
|
||||
const jsmntok_t *obj UNNEEDED,
|
||||
const jsmntok_t *params)
|
||||
{
|
||||
struct json_stream *response;
|
||||
struct sha256 *offer_id;
|
||||
struct wallet *wallet = cmd->ld->wallet;
|
||||
const char *b12;
|
||||
const struct json_escape *label;
|
||||
enum offer_status status;
|
||||
|
||||
if (!param_check(cmd, buffer, params,
|
||||
p_req("offer_id", param_sha256, &offer_id),
|
||||
NULL))
|
||||
return command_param_failed();
|
||||
|
||||
b12 = wallet_offer_find(tmpctx, wallet, offer_id, &label, &status);
|
||||
if (!b12)
|
||||
return command_fail(cmd, LIGHTNINGD, "Unknown offer");
|
||||
|
||||
if (offer_status_active(status))
|
||||
return command_fail(cmd, OFFER_ALREADY_DISABLED,
|
||||
"offer already active");
|
||||
|
||||
if (command_check_only(cmd))
|
||||
return command_check_done(cmd);
|
||||
|
||||
status = wallet_offer_enable(wallet, offer_id, status);
|
||||
|
||||
response = json_stream_success(cmd);
|
||||
json_populate_offer(response, offer_id, b12, label, status);
|
||||
return command_success(cmd, response);
|
||||
}
|
||||
|
||||
static const struct json_command enableoffer_command = {
|
||||
"enableoffer",
|
||||
json_enableoffer,
|
||||
};
|
||||
AUTODATA(json_command, &enableoffer_command);
|
||||
|
||||
|
||||
/* We do some sanity checks now, since we're looking up prev payment anyway,
|
||||
* but our main purpose is to fill in prev_basetime tweak. */
|
||||
static struct command_result *prev_payment(struct command *cmd,
|
||||
|
@ -5966,3 +5966,33 @@ def test_fetch_no_description_with_amount(node_factory):
|
||||
err = r'description is required for the user to know what it was they paid for'
|
||||
with pytest.raises(RpcError, match=err) as err:
|
||||
_ = l2.rpc.call('offer', {'amount': '2msat'})
|
||||
|
||||
|
||||
def test_enableoffer(node_factory):
|
||||
l1, l2 = node_factory.line_graph(2, opts={'experimental-offers': None})
|
||||
|
||||
# Normal offer, works as expected
|
||||
offer1 = l2.rpc.call('offer', {'amount': '2msat',
|
||||
'description': 'test_disableoffer_reenable'})
|
||||
assert offer1['created'] is True
|
||||
l1.rpc.fetchinvoice(offer=offer1['bolt12'])
|
||||
|
||||
l2.rpc.disableoffer(offer_id=offer1['offer_id'])
|
||||
|
||||
with pytest.raises(RpcError, match="Offer no longer available"):
|
||||
l1.rpc.fetchinvoice(offer=offer1['bolt12'])
|
||||
|
||||
with pytest.raises(RpcError, match="1000.*Already exists, but isn't active"):
|
||||
l2.rpc.call('offer', {'amount': '2msat',
|
||||
'description': 'test_disableoffer_reenable'})
|
||||
|
||||
l2.rpc.enableoffer(offer_id=offer1['offer_id'])
|
||||
l1.rpc.fetchinvoice(offer=offer1['bolt12'])
|
||||
|
||||
# Can't enable twice.
|
||||
with pytest.raises(RpcError, match="1001.*offer already active"):
|
||||
l2.rpc.enableoffer(offer_id=offer1['offer_id'])
|
||||
|
||||
# Can't enable unknown.
|
||||
with pytest.raises(RpcError, match="Unknown offer"):
|
||||
l1.rpc.enableoffer(offer_id=offer1['offer_id'])
|
||||
|
@ -5569,6 +5569,20 @@ enum offer_status wallet_offer_disable(struct wallet *w,
|
||||
return newstatus;
|
||||
}
|
||||
|
||||
enum offer_status wallet_offer_enable(struct wallet *w,
|
||||
const struct sha256 *offer_id,
|
||||
enum offer_status s)
|
||||
{
|
||||
enum offer_status newstatus;
|
||||
|
||||
assert(!offer_status_active(s));
|
||||
|
||||
newstatus = offer_status_in_db(s | OFFER_STATUS_ACTIVE_F);
|
||||
offer_status_update(w->db, offer_id, s, newstatus);
|
||||
|
||||
return newstatus;
|
||||
}
|
||||
|
||||
void wallet_offer_mark_used(struct db *db, const struct sha256 *offer_id)
|
||||
{
|
||||
struct db_stmt *stmt;
|
||||
|
@ -1466,6 +1466,18 @@ enum offer_status wallet_offer_disable(struct wallet *w,
|
||||
enum offer_status s)
|
||||
NO_NULL_ARGS;
|
||||
|
||||
/**
|
||||
* Enable an offer in the database.
|
||||
* @w: the wallet
|
||||
* @offer_id: the merkle root, as used for signing (must be unique)
|
||||
* @s: the current status (must be active).
|
||||
*
|
||||
* Must exist. Returns new status. */
|
||||
enum offer_status wallet_offer_enable(struct wallet *w,
|
||||
const struct sha256 *offer_id,
|
||||
enum offer_status s)
|
||||
NO_NULL_ARGS;
|
||||
|
||||
/**
|
||||
* Mark an offer in the database used.
|
||||
* @w: the wallet
|
||||
|
Loading…
Reference in New Issue
Block a user