Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export default defineConfig({
integrations: [
starlight({
title: "Payment Pointers",
customCss: [
'./src/styles/custom.css'
],
description:
"Payment Pointers are a standardized identifier for payment accounts. In the same way that an email address provides an identifier for a mailbox in the email ecosystem a payment pointer is used by an account holder to share the details of their account with a counter-party.",
head: [
Expand Down Expand Up @@ -40,12 +43,12 @@ export default defineConfig({
github: "https://github.com/interledger/paymentpointers.org",
},
sidebar: [
{ label: "Explainer", link: "/" },
{ label: "Design Goals", link: "/goals" },
{ label: "Flow", link: "/flow" },
{ label: "Syntax and Resolution", link: "/syntax" },
{ label: "IANA Considerations", link: "/iana" },
{ label: "Security Considerations", link: "/security" },
{ label: "Overview", link: "overview" },
{ label: "How it works", link: "how-it-works" },
{ label: "Syntax and resolution", link: "/syntax" },
{ label: "Security considerations", link: "/security" },
{ label: "Example implementation - Interledger", link: "/examples/ilp-implementation" },
{ label: "IANA considerations", link: "/iana" },
{ label: "About", link: "/about" },
],
}),
Expand Down
22 changes: 21 additions & 1 deletion src/content/docs/about.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,24 @@
title: About
---

The **Payment Pointers** specification is published by the [Interledger Community Group](https://interledger.org) at [W3C](https://w3.org).
This payment pointers specification is published by the <a href="https://interledger.org" target="_blank">Interledger Community Group</a> at <a href="https://w3.org" target="_blank">W3C</a>.

## Design goals

Payment pointers are designed with very specific goals in mind.

### Unique and easily recognizable

Various standardized and defacto-standardized identifiers are widely used on the Internet today, such as email addresses and social media handles. Payment pointers must be obviously unique so that they aren't confused with another type of identifier and also so that other identifiers aren't assumed to be payment pointers.

### Simple transcription

It should be easy for someone to exchange their payment pointer with a counterparty by saying it, writing it down, or sending it in a digital message.

### Flexible

Payment pointers shouldn't be tightly coupled to a specific payment service. It should be possible for any new payment service to leverage payment pointers by implementing Interledger's <a href="https://interledger.org/developers/rfcs/simple-payment-setup-protocol/" target="_blank">Simple Payment Setup Protocol</a>.

### Widely usable

It should be simple for individuals or small companies to host their own payment service endpoints at a URL that can resolve via a simple and easily recognizable payment pointer. Likewise, it should be possible for a payment services provider to host payment service endpoints for multiple entities without the risk of hosting them at endpoint URLs that conflict with their other services. To that end, the provider should have the option of hosting different endpoints under the same domain and a different path or at a different sub-domain for each user.
80 changes: 80 additions & 0 deletions src/content/docs/examples/ilp-implementation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
title: 'Example implementation - Interledger'
---

This page provides a high-level description of how the <a href="https://interledger.org/developers/get-started/" target="_blank">Interledger Protocol (ILP)</a> has implemented payment pointers. ILP is an open suite of protocols for transferring packets of value across different payment networks and ledgers.

## Interledger's Simple Payment Setup Protocol (SPSP)

Interledger's <a href="https://interledger.org/developers/rfcs/simple-payment-setup-protocol/" target="_blank">SPSP</a> is a protocol used by clients, such as mobile and web apps, to obtain the destination account and shared secret details required to set up an Interledger payment.

The details are obtained via an HTTPS request to an SPSP server endpoint. Interledger uses payment pointers as a user-friendly way to express the endpoint URLs.

### Example: Sending an Interledger payment

You use a payment app to send a payment to a friend. You enter an amount and your friend’s payment pointer into the app. Your friend's payment pointer is `$alice.wallet.example`.

First, the app follows the [syntax and resolution](/syntax) rules to convert the payment pointer to an HTTPS URL. The URL is the endpoint for an SPSP server:

```http
https://alice.wallet.example/.well-known/pay
```

The app queries the URL by sending a `GET` request with a MIME type of `application/spsp4+json` included in the header.

```http title="Example request"
curl --request GET \
--url https://alice.wallet.example/.well-known/pay \
--header 'accept: application/spsp4+json'
```

The SPSP server responds with the following connection details.

* `destination_account` - An ILP address, which provides a way to route ILP packets to their intended destination. ILP addresses aren't meant to be user-facing.
* `shared_secret` - A shared secret between the app and the SPSP server.

```json title="Example response" wrap
{
"destination_account":"example.dev.0.wallet.cloudnine.swx0a0.~ipr.cdfa5e16-e759",
"shared_secret":"7h0s7EpQDqcgzqmX-mwrNHFHinPvJq8Jw",
},
```

After the app has the destination account and shared secret, it no longer needs the payment pointer. The next step is for the app to use the details to set up a <a href="https://interledger.org/developers/rfcs/stream-protocol/" target="_blank">STREAM</a> connection. STREAM is Interledger's transport layer protocol that creates and transmits the Interledger packets that make up a payment.

## Interledger and Open Payments

<a href="https://openpayments.dev" target="_blank">Open Payments</a> is an open API standard for implementation by banks, mobile money providers, and other account servicing entities (ASEs). It allows developers to build payment capabilities into their clients by calling Open Payments APIs to issue payment instructions to ASEs without needing custom integrations.

Open Payments is an alternative to Interledger’s SPSP. As such, getting the details needed to set up a payment is done through an Open Payments API call.

While Open Payments doesn’t use SPSP, it does use Interledger’s STREAM protocol. The Open Payments standard is designed so that it can relay payment instructions between transacting parties atop any payment method. However, Interledger is the only method that’s currently integrated with Open Payments.

### Payment pointers and wallet addresses

Instead of payment pointers, the Open Payments standard uses wallet addresses to identify Open Payments-enabled accounts. Wallet addresses are expressed as HTTPS URLs which makes them identical to the URL form of payment pointers. However, wallet addresses resolve to Open Payments API endpoints instead of SPSP server endpoints.

```http title="Example wallet address"
https://wallet.example.com/bob
```

You can query the URL by sending a `GET` request with `application/json` in the header.

```http title="Example request"
curl --request GET \
--url https://wallet.example.com/bob \
--header `accept: application/json`
```

You can also use `application/spsp4+json` since Open Payments uses Interledger as its payment method, but in either case you'll receive an Open Payments response.

```json title="Example response"
{
"id": "https://wallet.example.com/bob",
"publicName": "Bob",
"assetCode": "USD",
"assetScale": 2,
"authServer": "https://auth.wallet.example.com",
"resourceServer": "https://wallet.example.com",
}
```
21 changes: 0 additions & 21 deletions src/content/docs/goals.md

This file was deleted.

182 changes: 182 additions & 0 deletions src/content/docs/how-it-works.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
---
title: How it works
---

A payment service endpoint provides the information that's necessary for interacting with a payment pointer's underlying payment account. Payment service endpoints **MUST** accept HTTP GET requests and be identifiable by an HTTPS URI as defined in <a href="https://datatracker.ietf.org/doc/html/rfc7230" target="_blank">RFC7230</a>.

:::note
In addition to payment service endpoint requirements, you should familiarize yourself with the [syntax and resolution](/syntax) requirements of payment pointers.
:::

Payment pointers aren't meant to be tightly coupled to any specific payment service. Individuals and small companies can host their own payment service endpoints at URLs that resolve to payment pointers.

Likewise, payment service providers can host payment service endpoints for multiple entities without the risk of hosting them at endpoint URLs that conflict with their other services. To that end, providers have the option of hosting different endpoints under the same domain and a different path, or at a different sub-domain for each of their users.

## Step 1: Resolve server meta-data URL

Let's say you are using a payment app to send a payment to a friend. You enter your friend's payment pointer into the app.

The first step a client (for example, your payment app) must perform is to decode the endpoint URL from the payment pointer using the [rules](/syntax#resolution) defined in this specification.

<div class="pp-converter not-content">
<div class="input-wrapper">
<label class="payment-pointer">
<p>Payment Pointer</p>
<input id="pp-input" value="$alice.wallet.example" placeholder="$alice.wallet.example" />
</label>
<label class="url">
<p>URL</p>
<input id="url-input" value="https://alice.wallet.example/.well-known/pay" readonly />
</label>
</div>
<p id="error" class="error-msg"></p>
</div>

## Step 2: Discover endpoints

The client then uses the HTTP protocol to query (`GET`) the resolved endpoint URL. The client uses the `Accept` header to express the media types of the protocol messages it supports.

```http title="Example"
GET /.well-known/pay HTTP/1.1
Host: alice.wallet.example
Accept: application/json
```

The response contains details the client needs to discover the payment service endpoints for interacting with the underlying account.

The resolved endpoint **MAY** redirect the client to another URL but the client **MUST** ensure it affords the sender an opportunity to verify both the originally resolved and ultimate endpoint hosts.

## Step 3: Initiate payment

Having discovered the available endpoint, the client initiates the payment using the payment setup protocol appropriate to the use case.

<script>
function resolveUrl(pointer) {
if (typeof pointer !== "string") {
throw new Error("Payment pointer must be a string");
}
if (pointer.charAt(0) !== "$") {
throw new Error('Payment pointer must start with "$"');
}
const url = new URL("https://" + pointer.slice(1));
if (url.port) {
throw new Error("Payment pointers cannot be defined with a port");
}
if (url.username || url.password) {
throw new Error("Payment pointers cannot be defined with userinfo");
}
if (url.search) {
throw new Error("Payment pointers cannot be defined with query parameters");
}
if (url.hash) {
throw new Error("Payment pointers cannot be defined with a fragment");
}
if (url.pathname === "" || url.pathname === "/") {
url.pathname = "/.well-known/pay";
}
return url.href;
}

function createPaymentPointer(url) {
const u = typeof url === "string" ? new URL(url) : url;
if (u instanceof URL) {
if (u.protocol !== "https:") {
throw new Error(
'Payment pointers can only point to URLs with a protocol of "https"'
);
}
if (u.port) {
throw new Error(
"Payment pointers cannot point to URLs with a custom port"
);
}
if (u.username || u.password) {
throw new Error(
"Payment pointers cannot point to URLs containing `userinfo`"
);
}
if (u.search) {
throw new Error(
"Payment pointers cannot point to URLs with query parameters"
);
}
if (u.hash) {
throw new Error("Payment pointers cannot point to URLs with a fragment");
}
const path = u.pathname.endsWith("/")
? u.pathname.slice(0, -1)
: u.pathname;
if (path === "") {
throw new Error(
"Payment pointers cannot point to URLs with an empty path"
);
}
return "$" + u.hostname + (path === "/.well-known/pay" ? "" : path);
}
throw new Error("url must be a valid URL string or URL object");
}

function toggleError(msg) {
const error = document.getElementById("error");
if (msg) {
error.innerHTML = msg;
} else {
error.innerHTML = "";
}
}

document.getElementById("url-input").addEventListener("keyup", (event) => {
const url = event.srcElement.value;
try {
if (url.length > 8) {
const pp = createPaymentPointer(url);
document.getElementById("pp-input").value = pp;
}
toggleError();
} catch (e) {
toggleError(e.message);
}
});

document.getElementById("pp-input").addEventListener("keyup", (event) => {
const pp = event.srcElement.value;
try {
if (pp.length > 3) {
const url = resolveUrl(pp);
document.getElementById("url-input").value = url;
}
toggleError();
} catch (e) {
toggleError(e.message);
}
});
</script>

<style>
.input-wrapper {
display: flex;
flex-direction: column;
gap: 1em;
}
@media screen and (min-width: 550px) {
.input-wrapper {
flex-direction: row;
}
}
label p {
font-weight: 700;
}
input { width: 100% }
.payment-pointer {
flex: 1 1 0;
}
.url.url {
flex: 2 1 0;
margin-top: 0;
}
.pp-converter .error-msg.error-msg {
margin-top: var(--space-3xs);
color: firebrick;
font-size: var(--step--1);
}
</style>
2 changes: 1 addition & 1 deletion src/content/docs/iana.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: IANA Considerations
---

## Well-Known URI
## Well-known URI

This specification registers a new well-known URI.

Expand Down
Loading