# Fixed Recurring Pull Payment (Subscription)
## Use Case
- **APlusVideo** is a video streaming service that wishes to connect to Alice's Open Payments account
- **Alice** is an account holder at Fynbos and a user at APlusVideo
- **Fynbos** is Alice's account provider
- **Uphold** is APlusVideo's account provider
- Alice is visiting APlusVideo's website and wishes to pay for a subscription
### Step 1: Alice connects her Fynbos account to APlusVideo
Alice wishes to give APlusVideo the ability to pull the subscription fee from her Fynbos account.
Alice provides APlusVideo with her Payment Pointer: `https://fynbos.me/alice`
APlusVideo queries the Payment Pointer to get the grant request endpoint URL.
```http
GET /alice HTTP/1.1
Host: fynbos.me
Accept: application/op-account-v1+json
```
Fynbos responds:
```http
HTTP/1.1 200 Success
Content-Type: application/op-alice-v1+json
{
"id": "https://fynbos.me/alice",
"publicName": "Alice"
"assetCode": "USD",
"assetScale": 2
"authServer": "https://fynbos.dev/auth"
}
```
APlusVideo makes a Grant Request to Fynbos at `https://fynbos.dev/auth`:
```json
{
"access_token": {
"access": [
{
"type": "outgoing-payment",
"actions": [
"create",
"authorize"
],
"locations": [
"https://fynbos.me/alice"
],
"limits": {
"startAt": "2022-01-01T18:25:43.511Z",
"interval": "P1M",
"receiveAmount": {
"amount": 500,
"assetCode": "EUR",
"assetScale": 2
},
"receivingAccount": "https://uphold.com/aplusvideo"
}
}
]
},
"client": {
"display": {
"name": "APlusVideo",
"uri": "https://aplusvideo.com"
},
"key": {
"proof": "httpsig",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "xyz-1",
"alg": "RS256",
"n": "kOB5rR4Jv0GMeL...."
}
}
},
"interact": {
"start": [
"redirect"
],
"finish": {
"method": "redirect",
"uri": "https://aplusvideo.com/return/876FGRD8VC",
"nonce": "LKLTI25DK82FX4T4QFZC"
}
}
}
```
Fynbos sends back an instruction to APlusVideo to redirect Alice to a Fynbos web page to authenticate herself and consent to the grant request.
```json
{
"interact": {
"redirect": "https://fynbos.dev/auth/4CF492MLVMSW9MKMXKHQ",
"finish": "MBDOFXG4Y5CVJCX821LH"
},
"continue": {
"access_token": {
"value": "80UPRY5NM33OMUKMKSKU"
},
"uri": "https://fynbos.dev/auth/continue"
}
}
```
APlusVideo directs Alice to `https://fynbos.dev/auth/4CF492MLVMSW9MKMXKHQ` which is a web page hosted by Fynbos.
Fynbos authenticates Alice and then prompts Alice to consent to giving APlusVideo access to her account.
Fynbos allows Alice to specify an expiry for the subscription.

Fynbos redirects Alice to `https://aplusvideo.com/return/876FGRD8VC?hash=p28jsq0Y2KK3WS__a42tavNC64ldGTBroywsWxT4md_jZQ1R2HZT8BOWYHcLmObM7XHPAdJzTZMtKBsaraJ64A
&interact_ref=4IFWWIKYBC2PQ6U56NL1`
- The URL was provided by APlusVideo in the grant request
- The `interact_ref` is a random value generated by Fynbos
- The `hash` was calculated by Fynbos based on:
- the `nonce` APlusVideo provided in the original request (`LKLTI25DK82FX4T4QFZC`),
- the `nonce` Fynbos provided in the response (`MBDOFXG4Y5CVJCX821LH`)
- the `interact_ref`, and
- the URL of the grant request endpoint `https://fynbos.dev/auth`.
APlusVideo validates the hash and then makes a continue request to Fynbos at `https://fynbos.dev/auth/continue` using the access token `80UPRY5NM33OMUKMKSKU`.
Fynbos responds to APlusVideo with the following:
```json
{
"access_token": {
"value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
"manage": "https://fynbos.dev/auth/token/PRY5NM33OM4TB8N6BW7",
"expires_in": 762534,
"access": [
{
"type": "outgoing-payment",
"actions": [
"create",
"authorize",
],
"locations": [
"https://fynbos.me/alice"
],
"limits": {
"startAt": "2022-01-01T18:25:43.511Z",
"expiresAt": "2023-01-01T18:25:43.511Z",
"interval": "P1M",
"receiveAmount": {
"amount": 500,
"assetCode": "EUR",
"assetScale": 2
},
"sendAmount": {
"amount": 811,
"assetCode": "USD",
"assetScale": 2
},
"receivingAccount": "https://uphold.com/aplusvideo"
}
}]
},
"continue": {
"access_token": {
"value": "YT98RY5NM33OMUKMK65U"
},
"uri": "https://fynbos.dev/auth/continue"
}
}
```
APlusVideo now has a grant to create and authorize outgoing payments from Alice's account up to a value of 5 EUR or 8.11 USD per month.
### Step 2: APlusVideo sends the subscription fee from Alice's account to itself
When setting up its account at Uphold, APlusVideo requested a long-lived grant to create, read, delete, and complete Incoming Payments (see [Single Pull Payment](https://hackmd.io/ywszEIRYTcuBUOWhj8cBHA) for details).
Using the access token associated with that grant, APlusVideo creates an incoming payment for one payment of the subscription fee.
```http
POST /aplusvideo HTTP/1.1
Host: uphold.com
Content-Type: application/op-incoming-payment-v1+json
Accept: application/op-incoming-payment-v1+json
Signature-Input: gnap=("@request-target" "host" "authorization");created=1624564850;keyid="xyz-client"
Signature: gnap=:EN/rExQ/knVi61P5AFhyMGN7aVPzk/9C7nsYAWF2RvzsoV1uNxGZklM55qCIQpuhoNty4EhiH7iwuzZBbRCQcQ==:
Authorization: GNAP CDFONP219RP1LT0OS9M2PMHKUR64TB8N6BW7OZB8
{
"incomingAmount": {
"amount": "500",
"assetCode": "EUR",
"assetScale": 2
},
"externalRef": "INV2022-01-3456"
}
```
Uphold has checked that APlusVideo is authorised to create the payment. It returns a response:
```http
HTTP/1.1 201 Created
Content-Type: application/op-incoming-payment-v1+json
{
"id": "https://uphold.com/aplusvideo/fi7td6dito8yf6t"
"accountId": "https://uphold.com/aplusvideo",
"state": "pending",
"incomingAmount": {
"amount": "500",
"assetCode": "EUR",
"assetScale": 2
},
"externalRef": "INV2022-01-3456"
}
```
Next, APlusVideo creates the outgoing payment at Alice's Payment Pointer:
```http
POST /alice HTTP/1.1
Host: fynbos.me
Content-Type: application/op-outgoing-payment-v1+json
Accept: application/op-outgoing-payment-v1+json
Signature-Input: gnap=("@request-target" "host" "authorization");created=1624564850;keyid="xyz-client"
Signature: gnap=:EN/rExQ/knVi61P5AFhyMGN7aVPzk/9C7nsYAWF2RvzsoV1uNxGZklM55qCIQpuhoNty4EhiH7iwuzZBbRCQcQ==:
Authorization: GNAP OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0
{
"state": "authorized",
"receivingPayment": "https://uphold.com/aplusvideo/fi7td6dito8yf6t"
}
```
Within Fynbos, the resource server (Rafiki) checks the grant associated with the provided token and gets the following response.
```json
{
"active": true,
"grant": "PRY5NM33OM4TB8N6BW7",
"access": [
{
"type": "outgoing-payment",
"actions": [
"create"
"authorize"
],
"locations": [
"https://fynbos.me/alice/"
],
"limits": {
"startAt": "2022-01-01T18:25:43.511Z",
"expiresAt": "2023-01-01T18:25:43.511Z",
"interval": "P1M",
"receiveAmount": {
"amount": 500,
"assetCode": "EUR",
"assetScale": 2
},
"sendAmount": {
"amount": 811,
"assetCode": "USD",
"assetScale": 2
},
"receivingAccount": "https://uphold.com/aplusvideo"
}
}
],
"key": {
"proof": "httpsig",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "xyz-1",
"alg": "RS256",
"n": "kOB5rR4Jv0GMeL...."
}
},
}
```
The RS keeps balances for each time interval for every `grant`.
Even though the AS confirms that this access token is valid, the RS may decline the client's request if the amount that it wants to send exceeds the limit per interval.
In this case, Fynbos has no record of any transactions using this grant in the current month so it starts a new balance to track these.
The balance of `PRY5NM33OM4TB8N6BW7_2022-01-01T18:27:16.936Z` is $0.
Fynbos has checked that APlusVideo is authorised to create the payment. It returns a response:
```http
HTTP/1.1 201 Created
Content-Type: application/op-outgoing-payment-v1+json
{
"id": "https://fynbos.me/alice/6tfi7td6dito8yf"
"accountId": "https://fynbos.me/alice/",
"state": "authorized",
"receivingPayment": "https://uphold.com/aplusvideo/fi7td6dito8yf6t"
}
```
Since the payment is authorised, Fynbos will automatically start sending the money to `https://uphold.com/aplusvideo/fi7td6dito8yf6t`. Therefore, it will query the receiving endpoint
```http
GET /aplusvideo/fi7td6dito8yf6t HTTP/1.1
Host: uphold.com
Accept: application/op-incoming-payment-v1+json
```
and receive the incoming payment details including the STREAM credentials
```http
HTTP/1.1 200 OK
Content-Type: application/op-incoming-payment-v1+json
{
"id": "https://uphold.com/aplusvideo/fi7td6dito8yf6t"
"accountId": "https://uphold.com/aplusvideo",
"state": "pending",
"incomingAmount": {
"amount": "500",
"assetCode": "EUR",
"assetScale": 2
},
"externalRef": "INV2022-01-3456"
"ilpAddress": "g.uphold.Cty6C+YB5X9FhSOUPCL",
"sharedSecret": "6jR5iNIVRvqeasJeCty6C+YB5X9FhSOUPCL/5nha5Vs=",
"receiptsEnabled": false
}
```
Fynbos opens a STREAM connection and starts sending until it has sent 5 EUR (Uphold does not accept any more packets) or 8.11 USD.
Once the subsciption amount of 5 EUR is in APlusVideo's account, it will mark the incoming payment as `complete`.
```http
PUT /aplusvideo/fi7td6dito8yf6t HTTP/1.1
Host: uphold.com
Content-Type: application/op-incoming-payment-v1+json
Accept: application/op-incoming-payment-v1+json
Signature-Input: gnap=("@request-target" "host" "authorization");created=1624564850;keyid="xyz-client"
Signature: gnap=:EN/rExQ/knVi61P5AFhyMGN7aVPzk/9C7nsYAWF2RvzsoV1uNxGZklM55qCIQpuhoNty4EhiH7iwuzZBbRCQcQ==:
Authorization: GNAP 1LT0OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0
{
"status": "complete"
}
```
Uphold responds:
```http
HTTP/1.1 200 Success
Content-Type: application/op-incoming-payment-v1+json
{
"id": "https://uphold.com/aplusvideo/fi7td6dito8yf6t",
"accountId": "https://uphold.com/aplusvideo",
"state": "completed",
"incomingAmount": {
"amount": "500",
"assetCode": "EUR",
"assetScale": 2
},
"receivedAmount": {
"amount": 500,
"assetCode": "EUR",
"assetScale": 2
},
"externalRef": "INV2022-01-3456"
}
```
### Edge Case: Exchange rate changes
At a future point in time, the exchange rate changes and 8.11 USD do not cover the subscription fee of 5 EUR anymore. At this point, Alice needs to consent to an updated grant in order to keep her subscription for APlusVideo.
Either via Email or on Login, APlusVideo will prompt Alice to update the grant.
APlusVideo continues its grant request with the AS
```http
POST auth/continue HTTP/1.1
Host: fynbos.dev
Content-Type: application/json
Authorization: GNAP YT98RY5NM33OMUKMK65U
Signature-Input: sig1=...
Signature: sig1=...
Digest: sha256=...
{
"access_token": {
"access": [
{
"type": "outgoing-payment",
"actions": [
"create",
"authorize"
],
"locations": [
"https://fynbos.me/alice"
],
"limits": {
"startAt": "2022-05-01T18:25:43.511Z"
"interval": "P1M",
"receiveAmount": {
"amount": 500,
"assetCode": "EUR",
"assetScale": 2
},
"receivingAccount": "https://uphold.com/aplusvideo"
}
}
]
},
"client": {
"display": {
"name": "APlusVideo",
"uri": "https://aplusvideo.com"
},
"key": {
"proof": "httpsig",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "xyz-1",
"alg": "RS256",
"n": "kOB5rR4Jv0GMeL...."
}
}
},
"interact": {
"start": [
"redirect"
],
"finish": {
"method": "redirect",
"uri": "https://coil.com/return/876FGRD8VC",
"nonce": "LKLTI25DK82FX4T4QFZC"
}
}
}
```
Fynbos sends back an instruction to APlusVideo to redirect Alice to a Fynbos web page to authenticate herself and consent to the grant request.
```json
{
"interact": {
"redirect": "https://fynbos.dev/auth/HQ4CF492MLVMSW9MKMXK",
"finish": "LHMBDOFXG4Y5CVJCX821"
},
"continue": {
"access_token": {
"value": "KU80UPRY5NM33OMUKMKS"
},
"uri": "https://fynbos.dev/auth/continue"
}
}
```
APlusVideo directs Alice to https://fynbos.dev/auth/HQ4CF492MLVMSW9MKMXK which is a web page hosted by Fynbos.
Fynbos authenticates Alice and then prompts Alice to consent to giving APlusVideo access to her account wth the updated exchange rate. She can also change the expiry:

She consents. Fynbos responds to APlusVideo with the following:
```json
{
"access_token": {
"value": "R64TB8N6BW7OZB8CDFONP219RP1LT0OS9M2PMHKU",
"manage": "https://fynbos.dev/auth/token/W7PRY5NM33OM4TB8N6B",
"expires_in": 762534,
"access": [
{
"type": "outgoing-payment",
"actions": [
"create",
"authorize",
],
"locations": [
"https://fynbos.me/alice"
],
"limits": {
"startAt": "2022-05-01T18:25:43.511Z",
"expiresAt": "2022-12-01T18:25:43.511Z",
"interval": "P1M",
"receiveAmount": {
"amount": 500,
"assetCode": "EUR",
"assetScale": 2
},
"sendAmount": {
"amount": 937,
"assetCode": "EUR",
"assetScale": 2
},
"receivingAccount": "https://uphold.com/aplusvideo"
}
}]
},
"continue": {
"access_token": {
"value": "5UYT98RY5NM33OMUKMK6"
},
"uri": "https://fynbos.dev/auth/continue"
}
}
```
Going forward, APlusVideo has a grant to create and authorize outgoing payments from Alice's account up to a value of 5 EUR or 9.37 USD.