Pull Payments

We want to enable pull payments on top of ILP for subscriptions and (potentially) for our merchant checkout experience on the Web or in person. We need to come to some agreement about the high-level design of this experience and protocol components before debating the details of the exact format.

This is a continuation of the discussions started in interledger/rfcs/issues/499 and interledger/rfcs/pull/501.

As I see it, the main parts of the pull payment experience are:

  1. Merchant requests a token with some parameters
    a. What parameters can they request?
    b. What format is the merchant’s request expressed in (JSON, etc)?
    c. Should there be a defined communication transport for this request (HTTP, part of an OAuth-like flow, etc)?
  2. User is prompted to approve the request
    a. Where / how are they prompted? Is that dialogue part of the user’s web browser, a separate app, or something else?
  3. User’s agent returns a token to the merchant
    a. Should the merchant be able to understand the parameters the user agent encoded in the token or should it be opaque to them?
  4. The merchant uses the token to create a STREAM connection
    a. I was convinced in interledger/rfcs/issues/499 that the value returned in step 3 should be a Payment Pointer with an opaque token included in it (for example in the path: $user.provider.example/secret-token-12345)
  5. The user’s STREAM server sends money to the merchant
    a. Should the merchant request how much money they want or should the server just push as much money as it can given the limits of the token?
    b. If the merchant will request a specific amount, how do they communicate that to the server?
    c. Does it matter what stream ID in the STREAM protocol the server uses to send the money, or should all of them be treated interchangeably?
  6. The merchant may want to be able to query details about the token they were given
    a. This is largely what the discussion in interledger/rfcs/pull/501 is about
    b. Would a merchant care to query the parameters of the token, or would they just try to use it to pull the amount of money they want? Note that the result of the query would not be binding so the only way to know for sure if a token will yield the money you want is to try it
1 Like

I don’t think we need to standardize steps 1 - 3. Some standard flows exist already but ultimately this is channel specific.

Token

I agree that the token should be a payment pointer, or more generally I think it should be/or include a URL that is used by the merchant to redeem the token to get paid. So if it’s a JWT or some other thing, one of the properties should be a URL that is used to redeem it.

The problem with a Payment Pointer as the only option is that you may want a more sophisticated authentication of the merchant than simply a bearer token so some token that is bound somehow to a particular merchant might be useful. I think there ARE ways to do this with Payment Pointers and standard HTTP auth mechanisms but they do assume that the payer is able to go online to generate the token.

I’d be interested in hearing ideas for ways a token can be generated offline. E.g. Imagine I have a wallet system that I want to be able to serve pull payments for me but I want to be able to generate the token on my phone quickly without needing to request it from the wallet. Definitely possible but needs some work to standardize.

Payment

I like the idea of a simple protocol over the STREAM data channel to do things like requesting a payment once the connection is established. See my comment on the AnyPay question under Option 2.

I think you could use the same mechanism to query the details about the token however this does mean you need to establish a STREAM connection to perform the query.

Disclaimer: I’m thinking about this with Codius use case tunnel vision

STREAM

Working backwards from the STREAM connection, this is what I have in mind.
The payer’s STREAM server would use the following stored information to determine its sendMax:

  • startTime
  • price - an amount per second
  • assetInfo - code and scale for price which is not necessarily the same asset used by either the payer or payee
  • totalPaid - total amount pulled to date, denominated in the same assetInfo as price

The payer’s sendMax would be (secondsSinceStartTime * price - totalPaid) converted to the payer’s asset. The payer would update totalPaid (using the same exchange rate used to calculate sendMax) each time totalSent increases in the stream.
Using a per second price (as opposed to some other specified interval) allows the payee to have the choice of either a “constant” stream or less frequent (ex. monthly) pull payments. You could argue that a constant stream could be accomplished using push payments but that would require the payer to remain online.

Alternatively, the payer’s STREAM server could be made to determine its sendMax based on:

  • balance
  • assetInfo

With Codius, this could happen when a user (payer) tells a Codius host (payee) to terminate a running deployment. The payer would store balance as secondsSinceStartTime * price - totalPaid. The payer’s sendMax would then be balance converted to the payer’s asset. This allows the payee to seek (final) payment after the fact.

In each case, the payee would likely store comparable information. You could expect (at least for Codius) their calculated receiveTotal to cause the payee’s totalSent to approach sendMax.

SPSP

Also in favor of the STREAM connection being initiated using the ILP address and shared secret returned from an SPSP endpoint.
I’m wondering if such a pull payment endpoint still needs a different shared secret in each query response (could it return with Cache-Control: immutable?).

Token

An opaque token with the payment pointer is sufficient for Codius. What do you think about moving forward with opaque tokens, and we can eventually extend functionality for offline token generation? (Could it be as simple as the payee submitting the payer-provided authorization to an SPSP server and getting back an opaque token?)

I’m also thinking we might not need to standardize how the payee requests and gets a token. For Codius, we’re imagining:

  1. Payer requests to deploy to host
  2. Host responds with price and assetInfo (startTime can be assumed to be now)
  3. Payer sends host payment pointer with token
1 Like

I agree. This will depend on the use case.

I would like to clarify that because this has confused me in the past. I think we have two possibilities but correct me if there is another possibility:

  1. A pull agreement is manifested as a payment pointer, let’s say $example.com/123456789 where 123456789 is the token corresponding to that pull agreement. If the client knows the token, it can connect to the endpoint the payment pointer resolves to and pull. The server stores all information about the pull agreement as well as balances.
  2. A pull agreement is manifested within the token, e.g. a JWT. The client connects to $example.com/ and sends a data packet containing this JWT. The server validates this JWT and if it is valid, it starts streaming to the client, i.e. fulfilling the pull payment request. All information needed for the pull payment are encoded in the token. However, the server will need to keep track of balances.

Especially the latter case should make an offline generation of tokens possible. JWT signatures are usually created by hashing header.payload together with a secret. I wonder if we could hash header.payload.clientIlpAddress together with a secret. That way, one could make sure that only the dedicated client can use this particular token.

I do, too. This makes partial payments possible.

Again, I think we have two possibilities:

  1. Having refill intervals: This is more traditional and is especially valuable for one time pull payments. It punishes the client for not pulling within an interval.
  2. Having a bucket: This is more Interledger’y, however, the client would have to wait before it can make a one-time pull payment. This model is more tailored towards pay-as-you-go use cases (like codius).

I feel like the first option allows for more use cases but I could be wrong.

1 Like

I just want to clarify - I don’t think all of those steps need to be part of a single protocol or the same for all use cases. However, if we want this to be used in any use case, we need to define how the flow works for that use case.

I think is an important question for any use case. Do tokens refill? On an arbitrary schedule or on specific intervals that are easy for humans to understand (like every day or month)? Can a merchant request a specific amount? If so, is that amount denominated in the merchant’s asset or the sender’s? How do we account for exchange rates changing over that time frame?

I think that’s totally fine, since we should design protocols like this with specific use cases in mind! (One possible conclusion, though, is that certain use cases should be excluded from a given protocol)

If the price were denominated in a 3rd asset, wouldn’t that just make both parties unhappy because they’d both have currency risk (rather than just one of them)?

If you’re using authentication to ensure that only a specific user gets that shared secret, that might be okay. That said, what would the benefit of that be? (The minor issue I see, aside from giving out the same “shared secret” multiple times just being a bit of a footgun, is that of the Maximum Number of Packets per STREAM connection. It may be hard to keep track of this if multiple connections at different times use the same secret)

Important clarification: opaque does not mean it must be a simple bearer token, it only means that the receiver cannot assume they will understand the details. JWTs and Macaroons fit in this model as well, and would allow offline generation.

The JWT can just be encoded in the Payment Pointer / URL the same way the opaque token would be: $example.com/asldkfjldsgkoidsoasdfljkasjldkfjlsdkjfalskdj....

I think this is a good idea for certain use cases when having an interactive setup between the sender and receiver isn’t a problem. (This was what I was envisioning with the caveats in the Macaroon-based format.) It wouldn’t work so well if you exchange the token with something like a QR code.

If you have a start time, duration, and number of repetitions, it handles both recurring and one-off payments. However, that method shouldn’t be used to express payment bandwidth (for example, using very short “durations”). The congestion control algorithms we want to use for STREAM don’t handle chunky refills with unknown intervals as well as having a total amount you can send that increases at a predictable rate.

1 Like

I would argue that the amount can even be denominated in a third assets. For example, the sender has XRP, the receiver prefers ETH, but the agreement is in USD.

It does guarantee a stable price of a service. In practice, the merchant would probably want to have a token that corresponds to her asset. However, it should not have to be that way. A merchant could be consumer friendly and offer different rates for different currency agreements.

In case of individual streams for each pull, both sender and receiver should check the exchange rate of agreed upon asset to their asset at the time of the establishment of the connection and set their sendMax and receiveMax accordingly. If we allow for constant pull payments, i.e. streaming payments, the exchange should also continuously be adjusted by both parties (maybe every second).

That is correct. Thanks for pointing that out. So either way, it could be in the URL or not.

Well, it would mean scanning 2 QR codes, the customer scans the merchant’s QR code to get her ILP address, she generates a token, and the merchant scans that corresponding QR code to receive this token.

Can you elaborate on that? I don’t really understand that point. I was thinking along the lines of paying for my book by letting amazon pull from my wallet and how a bucket that fills up every second would not really work here.

1 Like

One QR code is bad enough. I would argue that scanning two is a deal-breaker. That type of exchange would be more reasonable with a protocol over something like NFC.

By paying for a book, do you mean renting one or a single one-off payment? The start start time + duration + amount + repetitions method would work for a one-off payment because you would just set repetitions to 1 so the duration would act like an expiry. It would also work for something like a traditional subscription where you would set it for $10 per month. The only thing it wouldn’t work well for is expressing a rate for a very small unit of time like a second.

1 Like

I was thinking of a single one-off payment.

I guess that would mean that in the case of pay-as-you-go you would create a token bucket that fills up every second the service is used. This could be capped or not.
In the case of one-off payments one could have a bucket that is full in the beginning and never refills.
Subscriptions just fill up over any interval, also capped or not. Merchants could even come up with different rates for different intervals.

I think one would need the following parameters specified: start time, amount per interval, refill interval (can be as low as a second), duration or # of repetitions or expiry time (any would work but personally I prefer # of repetitions = cycles), and cap.

1 Like

That ilp-spsp-pull-token is a helpful reference. Thanks for putting that together.

Do these congestion controls already exist? If so, where?

Agreed.

One question I couldn’t decide on while working on the macaroon-based pull token was whether it’s worth supporting “multi-level” tokens.

The idea of the Macaroon format is that you can create a token, give it to another party with some “caveats” or restrictions, and they can give it to yet another party with further restrictions added.

If we allow this for pull tokens, I could have a master macaroon living on my phone that has some limits added. I could use the master token to give, say, $10 per month to a merchant, who could then “pay” a service that underpins their offering by giving a further restricted sub-token from my original one.

The big complication comes from tracking each level of the token separately to make sure that spending a certain amount doesn’t exceed any of the limits. A small advantage is that the overall amount cap can be expressed as a start time + a very long duration + the total amount + only 1 repetition. Alternatively, we could have a single limit defined, as you said, by the start time, duration, amount, number of repetitions, and total cap.

(As I’m writing this, I’m thinking that we don’t need the extra complications of multi-level tokens)

There are two different ways of implementing the refill strategy, and one is arguably more appropriate for expressing bandwidth and the other is better for subscriptions.

The key question is: at the refill time, does the amount reset to the maximum or is the given amount added to the total that can be spent? For example, if I give you a token for $10 / month and you only spend $5, does that mean next month you can spend $15 or $10? Bandwidth is arguably best expressed as the average amount you send per time, which means if your payment bandwidth is $0.01 / second you shouldn’t lose it if you don’t use it that second. In contrast, a subscription is arguably better modeled with the “use it or lose it” method – if a newspaper doesn’t charge me the $10 this month, should they be able to charge me $20 next month? I think with subscriptions (which have larger durations), you want more predictability about when they are going to come collect.

Right now, the way the JS implementation of STREAM determines the packet amount to send is very naive, and it backs off both in amount and in time when a packet is rejected.

In the Rust implementation I’ve been working on an implementation that works more similarly to how TCP does congestion control, where it only backs off in amount.

The reason I brought this up is that if you have the “use it or lose it” refill method and/or the refill is applied at a distinct point in time rather than being applied constantly like the JS implementation does, the congestion controller needs to start guessing when the refill happens rather than estimating the rate it can send at. Does that make sense?

As I was reading that, I thought the same.

This I what I considered to be specified by the cap. I was not thinking of a total cap (which is defined by start time, interval, amount per interval, and # of repetitions) but of a cap per refill interval, i.e. the pullable amount is set to amount per interval (= cap) vs the amount per interval is added to the pullable amount (= no cap).

Just for clarification: STREAM / the congestion controller wants token buckets that refill every second and are not capped?

Gotcha. I misunderstood but thought that an overall cap might also be useful. It might make more sense under a bandwidth model rather than the subscription bucket model (like you can use $0.01 / sec up to a total amount of $10).

No, sorry for the confusion.

Both of these would work fine:

  • A subscription-oriented model in which there is a cap per time period and the pullable amount resets to the cap at a fixed interval on the order of hours, days, or months. Note this works because the assumption is that the total amount will be pulled in a relatively small amount of time compared with the period duration (it might take seconds or a minute to pull the total amount allowed for the month, and you’d expect it to be taken all at once rather than continuously over the month).
  • A bandwidth-oriented model that specifies a rate expressed as amount per small unit of time (like second or minute) where the pro-rated amount is added cumulatively (for example, if it’s been 15 seconds and the rate is $0.10 / second, the pullable amount would be $1.50)

The only thing that would not work well is:

  • A cap per time period where the pullable amount resets to the cap at a fixed interval on the order of milliseconds or seconds (the same order of magnitude as the packet Round Trip Time)

I think that the main use case we are designing for is similar to a traditional subscription (@wilsonianb feel free to push back on this) that would be best suited to a resetting cap with durations specified in hours or days.

Does that make sense? Anyone should feel free to disagree about the design goal, I could be convinced otherwise about this.

I see what you mean. But the bandwidth case would be very different in anyways because the ‘refill amount’ could vary depending on how much was used, right? We could not capture that scenario using the parameters we defined above, I think.

Thanks for the clarification, makes total sense. This means if we want to have a refill cap, we have to make sure that it is reasonable compared to the refill amount.
Or there is indeed a total cap and no refill cap where total cap = theoretical refill cap * # of repetitions. However, this means that the pulling entity could pull everything at start time and stop its services even though the customer payed for let’s say a month (thinking about codius).

Agreed. I also don’t think we have a use case for this right now.

I think it’s better to have a cap per interval, because you may expect the service to be ongoing and you’d want to cancel your subscription rather than let them take the money upfront.

1 Like

This allows us to come back to the discussion on what kind of token we want:

  1. What kind of token do we use? Opaque string, JWT, Macaroon, …?
  2. What information is stored within the SPSP endpoint or the token.
  3. Is the token in the URL, the header, or send via STREAM?
  4. Is there a specific endpoint/payment pointer for pull payments, e.g. $example.com/pull, or in the case of token in URL $example.com/pull/token?
  5. Should one be able to generate tokens offline?
  6. Can we make sure that only the intended entity can pull using this token, especially when tokens are created offline?

My take:

  1. Token is opaque to the merchant. This is an important point to be flexible on. It provides no benefit to the merchant to inspect the details, since they can’t rely on it (the details can be changed on the server). And certain providers may want to use different types of tokens. Making it non-opaque means we need to pick one type of token now and stick with it forever.
  2. I don’t see much value in querying details about the token you have. Just try it and see if it works. For me this fits into a similar category as looking at the details of the token - it’s more to standardize and you can’t rely on it anyway.
  3. In the URL, because then we don’t need any special handling of these payment pointers. You just connect and it works
  4. No specific endpoint for pull payments – this is up to the provider and URL parsing is generally considered an antipattern.
  5. In some cases yes, but that’s part of a different spec that need not be standardized because it’s between the customer and their provider.
  6. Technically there’s no way to guarantee the money will be sent to the right party. You could try to restrict the ILP address prefixes it can be sent to but a) you need an ILP address and a shared secret that you got from an authenticated connection in order to be sure the packets will go to the right place and b) if you have a long-lived subscription this could get annoying because a merchant’s ILP address might change. Generally I’m all for making things like this more secure but it doesn’t seem like we can do it without a lot more overhead and it requires the protocol for getting these tokens to be interactive.

Could we also outline some of the flows we might expect people to go through to get one of these tokens? Even if it’s not part of the same spec, we’re going to need to define those flows and thinking through the use cases we want to support may change our answers to the above questions.

Some use cases off the top of my head:

  • One-off payment via the Web Payments API - the merchant requests the payment using that API and the response is a normal payment pointer with a pull token included (this would probably be a good use case for offline generation, even if the browser is technically online, because it would be much faster)
  • Recurring payment via the Web Payments API? (@adrianhopebailie does that API support subscriptions?)
  • Codius (@wilsonianb how exactly would the setup work? Would the parameters we’re talking about fit your needs?)
  • Point of Sale + NFC - Merchant communicates a request for payment (in some to-be-defined format) to the customer’s phone or watch and the device returns a payment pointer with the token included
  • Point of Sale + QR code - Merchant tells the user how much, user types the amount into their device, the device generates a QR code from the payment pointer, and the merchant scans the QR code… This doesn’t seem like a great experience.
  • Any others?

One more note: offline merchant payments are much more complicated but we may want to keep them on the back burner in this discussion. Credit cards today handle this by having the terminals trust cards for a certain amount if they’re offline and using (I think) blacklists or bloom filters that are updated when they’re online to make sure that fraudulent cards are rejected. This would be a lot more difficult in an Interledger world because you’d need a federated trust model between the providers of this service and trustworthy devices, but we might need something like that if we wanted to have a solution that’s comparable to credit cards.

@emschwartz I don’t object to your answers to 1-6.

Some of my thoughts:
If it should be possible to create tokens offline, we need some sort of signed token that includes information such that the server knows how to handle it the first time it sees it, probably a JWT because that can be easily decoded. This token could still be opaque to the merchant. The secret is shared between the server and the offline app. When the merchant presents the token to the server for the first time, the server creates a DB entry for this token to keep track of balances or to change token parameters. This means, the information stored within the token is only valid when the token is first used.

We can. Should that be different specs or more of a recommendation?

It’s probably a similar scenario as subscriptions/recurring payments but one more thing I thought of was loan repayments. There, it should not be possible to revoke pulling permissions.

Regarding offline merchant payments: I agree that we should think about and discuss that but that should be the very last step once we have all the other parts spec’d out. I would like to have consensus on the ‘low hanging fruit’ first before starting a discussion on more rare/technically difficult scenarios.

After considering the use cases below, I think we’d be good with:
-start time
-amount (per interval, including asset info)
-refill interval (optional)
-cap (optional)

In each case, the amount would immediately be available at start, as opposed to after the first interval

Bucket
unknown expected usage, such AWS lambda, price per API call

Example: $10/month

{
  start: now,
  amount: 10,
  interval: month,
  cap: 10
}

Subscription
Agreed upon amount. In my opinion, the payee shouldn’t be expected to use it or lose it if there’s agreed upon price.

Example: $10/month

{
  start: now,
  amount: 10,
  inteval: month
}

To limit to 12 months, you would include cap: 120 Edit: this would require different logic from the bucket cap :confused:

Bandwidth
Also agreed upon amount. Identical to subscription, just with short interval.

Example $.01/second

{
  start: now,
  amount: .01,
  interval: second
}

The payer could be able to tell their STREAM server to stop refilling/incrementing (such as when they stop a Codius deployment), causing the server to apply a cap based the amount of time passed since start time.

I think this is missing some termination condition like # of repetitions. Maybe it is not relevant for the codius use case but definitely for other use cases. Therefore, it should be optional.

Also, what is the default refill interval?

And another question: How would we do intervals that are not 1 second/minute/hour/…, e.g. bi-weekly refill intervals?

# of repetitions should be mandatory but 1 (or 0, whatever means there’s only 1 period of time) should be an option.

I don’t think there should be a default. That should be mandatory.

I would put the duration as an integer denominated in a unit of time. As I said above, I don’t think 1 second will work well with the resetting bucket method so I think I would advocate for the time to be denominated in hours.