lnurl / luds

lnurl specifications
602 stars 142 forks source link

Recurrent payments outline #77

Open akumaigorodski opened 3 years ago

akumaigorodski commented 3 years ago

Here are my thoughts on how this should look like before getting to PR:

SPEC SIDE:

  1. Recurrence points may happen MONTHLY (1st/2nd/3rd/last WEEKDAY) or YEARLY (1st/2nd/3rd/last WEEKDAY of MONTH), this is a subset of RRULE of iCal format.

  2. LNURL-PAY has an optional RECURRENCE_FIELD which contains "MONTHLY/YEARLY; PATIENCE_PERIOD_DAYS; SERVER_TIMEZONE" data. Recurrence point is determined by both WALLET and SERVICE on the day of the first successful payment (SERVER_TIMEZONE is provided to ensure that point is the same for server and client). PATIENCE_PERIOD_DAYS means "you have this many days to pay since next recurrence point until I do something about it on my side", what that something exactly is specific to service, wallet mainly needs this for grouping of events into batches and maybe warnings.

  3. metadata may contain a THIS_OCCURRENCE_NOTE field relevant for this particular occurrence which may contain free form text ("we're raising amount for you this time only because..." or "next payments will be happening annually" accompanied with MONTHLY -> YEARLY change in RECURRENCE_FIELD).

  4. Pay Json response may have an optional currentBalance field (similar to https://github.com/fiatjaf/lnurl-rfc/pull/76). If for example user has paid 3x of what was requested last time for some reason, it's up to wallet how to react to this, for example if pay Json returns 500 as payment amount but balance is 1500 then wallet may inform user that next payment is not due and allow to uncheck it for this one time. Or if user has tapped a stored pay link with recurrence, wallet may show server response along with warning that next payment is not due yet accompanied with balance, but allow user to pay anyway if user insists.

  5. Overall it is expected that WALLET re-queries a static LNURL-PAY after each recurrence point, how much is to be paid this time is defined by SERVICE each time (along with explanations in THIS_OCCURRENCE_NOTE if necessary), recurrence parameters can change (for example user has opted for YEARLY with discount whereas last time it was MONTHLY).

Recommendations for WALLET SIDE (does not go to spec but justifies spec decisions):

WALLET should convert recurrence points to days of month for each month (exact dates will differ from month to month), then try to group overlapping payments into batches such that there are as few monthly calendar reminders as possible, ideally one reminder for all recurrent events (guaranteed if all events have PATIENCE_PERIOD_DAYS=31days, but this is up to SERVICE). For example if there are two payments: first on 3rd of May with PATIENCE_PERIOD_DAYS=10days and second on 9th of May with PATIENCE_PERIOD_DAYS=5days then there will be one calendar reminder on 9th of May.

WALLET should store payment history and balance changes if applicable to provide user with context, so user can compare new info with history and for example see that "this time SERVICE wants 1000 SAT while last time it was 1100 SAT because we agreed a sum to be denominated in USD and I see that BTC has appreciated since then".

fiatjaf commented 3 years ago

The idea of writing code that translates all these weekdays and timezone magic into actual date objects or timestamps frightens me. Can we replace all that with just raw unix timestamps and intervals in seconds instead?

fiatjaf commented 3 years ago

I always thought we could count on services keeping balances for users and lnurl-pays just recharging these, and you've included that currentBalance field there for similar purpose.

But now I'm thinking maybe services want to measure that balance in something else than satoshis: "days", for example, or some form of "usage credits", but it also doesn't make sense to include these custom things in the spec.


Instead I propose the following: lnurl-pay returns a field like EXPIRES_IN: <unix timestamp>, meaning that on that date the service will stop being provided (as in the end of PATIENCE_DAYS in the scheme above). Just that.

Then some days before that timestamp the wallet can start prompting the user to pay again, and doing some smart calculations to group that prompt with other prompts that happen to be expiring in a near date.

What am I missing here?

akumaigorodski commented 3 years ago

The idea of writing code that translates all these weekdays and timezone magic into actual date objects or timestamps frightens me. Can we replace all that with just raw unix timestamps and intervals in seconds instead?

This does not quite work with months because they have varying number of days + leap years. For example, if first payment happens on Jan 31th there's no way to translate it strictly to Feb since it only has 28 days except leap years, that's why RRULE stuff.

It may sound complicated but all languages have specialized Calendar objects which should be relied to do these calculations.

fiatjaf commented 3 years ago

That's true. I forgot about those oddities.

But what do you think of the EXPIRES_IN proposal? Using that services can opt to whatever weird interval they want. They can just sell you a subscription for 22 days and a half, and then next time you're paying they change it to 64 days and adjust the price accordingly and so on. And they can do the weird datetime calculations on their servers.

takinbo commented 3 years ago

Can we also leave out the SERVER_TIMEZONE parameter and just use one singular timezone? Using a unix timestamp is probably best but if it can't be avoided, we should use the +0000 timezone. Not only is it easier to standardize but would also help with privacy.

HamishMacEwan commented 3 years ago

I believe recurrences are appealing and wrong. BOLT12's mistake need not be aped by LNURL.

I mean, which calendar?

https://www.worldatlas.com/articles/calendars-used-around-the-world.html

If BOLT12 finalises some recurrence structure, then adopt it.

akumaigorodski commented 3 years ago

@fiatjaf

Instead I propose the following: lnurl-pay returns a field like EXPIRES_IN: , meaning that on that date the service will stop being provided (as in the end of PATIENCE_DAYS in the scheme above).

I don't like that it gives a service too much capability, in particular service can occasionally reduce next timestamp from being a year away to, say, 350 days away and hope a careless user won't notice while with recurrence rule the only thing a service can change is recurrence period (monthly/yearly) which is very noticeable.

Other than that it's more vague than having a recurrence point + patience, does not let user see future payment points in a calendar (and do some planning based on that if needed), does not seem to make things easier in general.

@takinbo Yes and more, on a second thought timezone seems useless: both service and client should define a first recurrence point as their local time and it does not matter if for example the weekday happens to be 3rd Monday of month for client yet still 2nd Sunday for service. What's important is standard calendar software should be relied upon at all times to do these calculations so edge cases like user/service changing a timezone are handled automatically.

@HamishMacEwan

I mean, which calendar?

Gregorian calendar to be compatible with Apple, Google and pretty much every other calendar software.

If BOLT12 finalises some recurrence structure, then adopt it.

Thankfully this work has already been done for us in iCal format, no need to reinvent the wheel.

aeschylus commented 3 years ago

Why not use blockheight as the measure of time?

aeschylus commented 3 years ago

Blockheight would be precise to a resolution of around 10 minutes, and guaranteed to be synchronised across the network.

image

akumaigorodski commented 3 years ago

We want to be a drop-in replacement/complementation of existing schemes which are based on conventional calendars.

pseudozach commented 3 years ago

I agree on enforcing UTC for all communication which unix timestamp should cover. Wallet and service GUI can show start/end datetime as they please.

Recurrence point is determined by both WALLET and SERVICE on the day of the first successful payment (SERVER_TIMEZONE is provided to ensure that point is the same for server and client)

I am a little confused on the wording Recurrence point is determined by both WALLET and SERVICE, when considering for instance a substack/gym membership, wallet requests to subscribe and service should return amount to pay and expiry time along with patience_days. This will cover cases like 1st month is free (or a symbolic 1 sat payment) but on 5th of next month user needs to pay.

akumaigorodski commented 3 years ago

@pseudozach agreed, it's too rigid as currently outlined, first recurrence point should be movable into future.

joostjager commented 3 years ago

Instead I propose the following: lnurl-pay returns a field like EXPIRES_IN: , meaning that on that date the service will stop being provided (as in the end of PATIENCE_DAYS in the scheme above). Just that.

+1 for this because of simplicity and flexibility.

I don't like that it gives a service too much capability, in particular service can occasionally reduce next timestamp from being a >year away to, say, 350 days away and hope a careless user won't notice while with recurrence rule the only thing a service can >change is recurrence period (monthly/yearly) which is very noticeable.

Other than that it's more vague than having a recurrence point + patience, does not let user see future payment points in a >calendar (and do some planning based on that if needed), does not seem to make things easier in general.

Perhaps wallets can help here by showing history and the next expiry in the approval screen.

joostjager commented 3 years ago

Overall it is expected that WALLET re-queries a static LNURL-PAY after each recurrence point

Perhaps it would be good to somehow send along an identifier tied to the initial lnurl-pay request that the user approved?

ok300 commented 1 year ago

@aeschylus said

Why not use blockheight as the measure of time?

That's what I ended up using for recurring payments on Oak Node: https://oak-node.net/doc/trunk/doc/bcron.md

The UI translates it to "approximate human time", like "in ~2 days" or "every ~6h".

Screenshot from 2023-03-04 10-55-21

IMO a blockheight-based approach would be a good starting point for a recurrence spec.

(UTC epoch is also good, the trouble I had there was specifying intervals, from and until. "Every 3rd Monday of the month until end of year" brings back the calendar chaos.)