# Single Pull Payment (E-Commerce)
## Use Case
* **OnlineShop** is an online merchant
* **Alice** is a shopper at OnlineShop and an account holder at Fynbos
* **Fynbos** is Alice’s account provider
* **Uphold** is OnlineShop’s account provider
* Alice visits OnlineShop and wishes to purchase an item, paying with Interledger / OpenPayments
* OnlineShop wishes to connect to Alice's account to pay itself
### Step 1: OnlineShop creates an Incoming Payment to receive Alice's payment
When setting up its account at Uphold, OnlineShop requests a long-lived grant to create, read, delete, and complete Incoming Payments.
OnlineShop makes a Grant Request to Uphold at `https://uphold.com/auth`:
```json
{
"access_token": {
"access": [
{
"type": "incoming-payment",
"actions": [
"create",
"read",
"delete",
"complete"
],
"locations": [
"https://uphold.com/onlineshop"
]
}
]
},
"client": {
"key": {
"proof": "httpsig",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "xyz-1",
"alg": "RS256",
"n": "kOB5rR4Jv0GMeL...."
}
}
}
}
```
Since OnlineShop it logged in at Uphold when making that request, the grant is issued right away.
```json
{
"access_token": {
"value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
"manage": "https://uphold.com/auth/token/PRY5NM33OM4TB8N6BW7",
"access": [
{
"type": "incoming-payment",
"actions": [
"create",
"read",
"delete",
"complete"
],
"locations": [
"https://uphold.com/onlineshop"
]
}
]
},
"continue": {
"access_token": {
"value": "80UPRY5NM33OMUKMKSKU"
},
"uri": "https://uphold.com/auth/continue"
}
}
```
Using the access token associated with that grant, OnlineShop creates an Incoming Payment to accept Alice's payment for her purchase.
```http
POST /onlineshop 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 OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0
{
"incomingAmount": {
"amount": "4000",
"assetCode": "EUR",
"assetScale": 2
},
"externalRef": "INV2022-02-0137"
}
```
Uphold has checked that OnlineShop 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/onlineshop/fi7td6dito8yf6t"
"accountId": "https://uphold.com/onlineshop",
"state": "pending",
"incomingAmount": {
"amount": 4000,
"assetCode": "EUR",
"assetScale": 2
},
"externalRef": "INV2022-02-0137"
}
```
### Step 2: Alice grants OnlineShop access to her account for the amount of her purchase
Alice provides OnlineShop with her Payment Pointer: `https://fynbos.me/alice`.
OnlineShop 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"
}
```
OnlineShop 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": {
"createdBy" : "coil"
"receivingPayment": "https://uphold.com/onlineshop/fi7td6dito8yf6t"
}
},
{
"type": "outgoing-payment",
"actions": [
"read"
],
"locations": [
"https://fynbos.me/alice"
],
"limits": {
"createdBy" : "coil"
}
}
]
},
"client": {
"display": {
"name": "Online Shop",
"uri": "https://onlineshop.com"
},
"key": {
"proof": "httpsig",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "xyz-1",
"alg": "RS256",
"n": "kOB5rR4Jv0GMeL...."
}
}
},
"interact": {
"start": [
"redirect"
],
"finish": {
"method": "redirect",
"uri": "https://onlineshop.com/return/876FGRD8VC",
"nonce": "LKLTI25DK82FX4T4QFZC"
}
}
}
```
Fynbos sends back an instruction to OnlineShop 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"
}
}
```
OnlineShop directs Alice to `https://fynbos.dev/auth/4CF492MLVMSW9MKMXKHQ` which is a web page hosted by Fynbos.
In parallel, Fynbos has fetched the details of the incoming payment at `https://uphold.com/onlineshop/fi7td6dito8yf6t`.
Fynbos authenticates Alice and then prompts Alice to consent to giving OnlineShop access to her account to authorize the payment.
Since Alice's account is in USD and the payment Alice needs to make to complete her purchase is in EUR, Fynbos automatically quotes that payment.

Fynbos redirects Alice to `https://onlineshop.com/return/876FGRD8VC?hash=p28jsq0Y2KK3WS__a42tavNC64ldGTBroywsWxT4md_jZQ1R2HZT8BOWYHcLmObM7XHPAdJzTZMtKBsaraJ64A&interact_ref=4IFWWIKYBC2PQ6U56NL1`
- The URL was provided by OnlineShop in the grant request
- The `interact_ref` is a random value generated by Fynbos
- The `hash` was calculated by Fynbos based on:
- the `nonce` OnlineShop 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`.
OnlineShop 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 OnlineShop with the following:
```json
{
"access_token": {
"value": "T0OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1L",
"manage": "https://fynbos.dev/auth/token/W7PRY5NM33OM4TB8N6B",
"expires_in": 762534,
"access": [
{
"type": "outgoing-payment",
"actions": [
"create",
"authorize",
],
"locations": [
"https://fynbos.me/alice"
],
"limits": {
"sendAmount": {
"amount": 4596,
"assetCode": "USD",
"assetScale": 2
},
"receivingPayment": "https://uphold.com/onlineshop/fi7td6dito8yf6t"
}
}]
},
"continue": {
"access_token": {
"value": "5UYT98RY5NM33OMUKMK6"
},
"uri": "https://fynbos.dev/auth/continue"
}
}
```
OnlineShop now has a grant to create and authorize an outgoing payment from Alice's account to its own account, specifically to the incoming payment it created in [Step 1](#Step-1-OnlineShop-creates-an-Incoming-Payment-to-receive-Alice%E2%80%99s-payment).
### Step 3: OnlineShop sends the payment from Alice's account into its account
OnlineShop 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 T0OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1L
{
"state": "authorized",
"receivingPayment": "https://uphold.com/onlineshop/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": {
"sendAmount": {
"amount": 4596,
"assetCode": "USD",
"assetScale": 2
},
"receivingPayment": "https://uphold.com/onlineshop/fi7td6dito8yf6t"
}
}
],
"key": {
"proof": "httpsig",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "xyz-1",
"alg": "RS256",
"n": "kOB5rR4Jv0GMeL...."
}
},
}
```
Fynbos has checked that OnlineShop 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/onlineshop/fi7td6dito8yf6t"
}
```
Since the payment is authorised, Fynbos will automatically start sending the money to `https://uphold.com/onlineshop/fi7td6dito8yf6t`. Therefore, it will query the receiving endpoint
```http
GET /onlineshop/fi7td6dito8yf6t HTTP/1.1
Host: uphold.com
Accept: application/op-incoming-payment-v1+json
```
and receive the incoming payment details including STREAM credentials
```http
HTTP/1.1 200 OK
Content-Type: application/op-incoming-payment-v1+json
{
"id": "https://uphold.com/onlineshop/fi7td6dito8yf6t"
"accountId": "https://uphold.com/onlineshop",
"state": "pending",
"incomingAmount": {
"amount": 4000,
"assetCode": "EUR",
"assetScale": 2
},
"externalRef": "INV2022-02-0137",
"ilpAddress": "g.uphold.Cty6C+YB5X9FhSOUPCL",
"sharedSecret": "6jR5iNIVRvqeasJeCty6C+YB5X9FhSOUPCL/5nha5Vs=",
"receiptsEnabled": false
}
```
Fynbos opens a STREAM connection and starts sending until it has sent 40 EUR (Uphold does not accept any more packets) or 45.96 USD.
### Edge Case: The quoted amount does not cover the incoming payment receive amount
Fynbos has sent 45.96 USD out of Alice's account to OnlineShop's account at `https://uphold.com/onlineshop/fi7td6dito8yf6t`. Unfortunately, the quoted and authorized amount of 45.96 USD does not result in a received amount of 40 EUR by OnlineShop. It only received 39.98 EUR.
#### Case 1: OnlineShop completes the underpaid incoming payment
Underpayment should rarely ever happen because account providers add an extensive slippage to their quote when asking for user consent.
For user experience it is beneficial if OnlineShop accepts the incoming payment as completed, even though it has not been paid in full.
After a certain amount of time has passed, it will mark the payment as complete, using an access token it has obtained after rotating the old token.
```http
PUT /onlineshop/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/onlineshop/fi7td6dito8yf6t",
"accountId": "https://uphold.com/onlineshop",
"state": "completed",
"incomingAmount": {
"amount": 4000,
"assetCode": "EUR",
"assetScale": 2
},
"receivedAmount": {
"amount": 3998,
"assetCode": "EUR",
"assetScale": 2
},
"externalRef": "INV2022-02-0137"
}
```
#### Case 2: Alice is required to pay the difference
OnlineShop notifies Alice (either while she is still on its page or via email) that the invoice amount has not been fully paid. It asks her to pay the difference in order to complete the incoming payment.
Hence, OnlineShop wants to create a new outgoing payment from Alice's account for 0.02 EUR. The process is the same as described in [Step 2](#Step-2-Alice-grants-OnlineShop-access-to-her-account-for-the-amount-of-her-purchase) and [Step 3](#Step-3-OnlineShop-sends-the-payment-from-Alice%E2%80%99s-account-into-its-account).
Once the full amount has reached OnlineShop's account, it marks the incoming payment as complete.
```http
PUT /onlineshop/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/onlineshop/fi7td6dito8yf6t",
"accountId": "https://uphold.com/onlineshop",
"state": "completed",
"incomingAmount": {
"amount": 4000,
"assetCode": "EUR",
"assetScale": 2
},
"receivedAmount": {
"amount": 4000,
"assetCode": "EUR",
"assetScale": 2
},
"externalRef": "INV2022-02-0137"
}
```