Skip to content

Latest commit

 

History

History
517 lines (365 loc) · 20.1 KB

paymentpointers.md

File metadata and controls

517 lines (365 loc) · 20.1 KB

Payment Pointers

This document consolidates the content from the Payment Pointers website documentation and project README. It provides an overview, goals, syntax, flow, security, and related information about Payment Pointers.


Table of Contents

Payment Pointers are a standardized identifier for payment accounts that support Interledger payments. They are designed to be simple, user-friendly, and compatible with existing web infrastructure. The RFC for payment pointers can be found here.


Goals

  • Provide a simple, standardized way to identify payment accounts on the web.
  • Enable interoperability between different payment systems and providers.

A payment pointer is a URL-like identifier that starts with a $ sign, followed by a hostname and optional path. For example:

Flow


- Payment pointers must resolve to an HTTPS URL that serves as an SPSP endpoint.
- They are case-insensitive.
- The path component is optional.

---

## Flow

The typical flow for using a payment pointer is as follows:

1. The sender obtains the recipient's payment pointer (e.g., `$wallet.example.com/alice`).
2. The sender's wallet resolves the payment pointer to an SPSP endpoint.
3. The sender's wallet requests payment setup information from the endpoint.
4. The payment is sent over the Interledger network using the provided details.

---

## IANA Considerations

Payment pointers are not a new URI scheme, but a standardized way to represent HTTPS URLs for payment accounts. They leverage existing web infrastructure and do not require new protocol registrations.

---

### Syntax

---
## Project Structure

This project is the source code for the website hosted at [paymentpointers.org](https://paymentpointers.org). It is built with [Starlight](https://starlight.astro.build/getting-started/), a documentation framework based on [Astro](https://astro.build/).

About


title: About

The Payment Pointers specification is published by the Interledger Community Group at W3C.

Index

│ └── env.d.ts └── tsconfig.json


Starlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. Each file is exposed as a route based on its file name.

Static assets, like favicons or images, can be placed in the `public/` directory. When referencing these assets in your markdown, you do not have to include `public/` in the file path, so an image would have a path like:

```md
![A lovely description of your beautiful image](/img/YOUR_BEAUTIFUL_IMAGE.png)

Local Development

We are using Bun in this repository, but you could theoretically use the package manager of your choice. To install Bun, run:

Commands

All commands are run from the root of the project, from a terminal:

Command Action
bun install Installs dependencies
bun run start Starts local dev server at localhost:1102
bun run build Build your production site to ./dist/
bun run preview Preview your build locally, before deploying
bun run astro ... Run CLI commands like astro add, astro check

You can substitute the bun commands with whatever package manager of your choice uses.


Learn More

Check out Starlight’s docs, read the Astro documentation, or jump into the Astro Discord server.


Appendix: Documentation Pages

About


Goals

Payment Pointers were 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. The Payment Pointer must be obviously unique so that it is not confused with another type of identifier and also so that other identifiers are not assumed to be Payment Pointers.

Simple Transcription

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

Flexible

The payment pointer should not be tightly coupled to a specific payment service. It should be possible for any new payment service to leverage payment pointers by implementing the Open Payments standard.

Widely Usable

It should be simple for individuals or small companies to host their own payment service endpoints at a URL that can be resolved 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.


Syntax

This specification uses the Augmented Backus-Naur Form (ABNF) notation of RFC2234 and imports the following rules from RFC3986: host and path-abempty.

The syntax of a payment pointer is:

"$" host path-abempty

Example: $example.com/bob

ASCII

Note that the character set of a Payment Pointer, as with a valid URL, is limited to a subset of valid ASCII characters.

Further work may be done to define mappings from other character sets that support international characters (similar to the rules for Internationalized Domain Names) however, such mappings are not defined in this specification. Implementations that attempt to interpret a Payment Pointer that contains non-ASCII characters should be aware of the security risks.

Resolution

Given a payment pointer of the form "$" host path-abempty the endpoint URL that it resolves to is:

"https://" host path-abempty

If path-abempty is empty or equal to / then it is assigned the value /.well-known/pay.

Note that this is a restricted profile of the data allowed in a full https URL as defined in RFC3986.

The URL syntax supports an authority, however the Payment Pointer syntax only supports a host which excludes the userinfo and port. The Payment Pointer syntax also excludes the query and fragment parts that are allowed in the URL syntax.

Payment Pointers that do not meet the limited syntax of this profile MUST be considered invalid and should not be used to resolve a URL.

Examples:

The following payment pointers resolve to the specified endpoint URLs:

$example.com                  ->  https://example.com/.well-known/pay
$example.com/invoices/12345   ->  https://example.com/invoices/12345
$bob.example.com              ->  https://bob.example.com/.well-known/pay
$example.com/bob              ->  https://example.com/bob

Requirements

Payment Pointers MUST resolve to an https URL as defined in RFC7230.

Clients MUST support HTTP redirects as a result of 3XX responses per RFC 7231 however these MUST always redirect to a secure (https) URL.

Servers SHOULD be cautious with the use of permanent (301/308) redirect response codes as clients may stop querying the original Payment Pointer and always query the new URL for future payments.


Flow

When a user wishes to make or receive a payment, and is engaging with a counter-party that is able to process Payment Pointers, it provides a Payment Pointer to the counter-party.

The counter-party's client then uses this Payment Pointer as described below to discover the services and accounts linked to the Payment Pointer and interact with these to send or request a payment.

This is analogous to how a user may provide a credit card number to a merchant to make a payment online, or provide their bank account details to a payer to receive a payment. However, in the case of Payment Pointers the pointer and the services are loosely coupled so a variety of service types could be discovered and payments can be both pushed and pulled from the user's account initiated via the same Payment Pointer.

Step 1: Resolve Open Payments Server Meta-Data URL

The first step the client performs is to decode the Payment Pointer URL from the Payment Pointer using the rules defined in this specification.

Payment Pointer

URL

Step 2: Discover Open Payments Endpoints

The client then uses the HTTP protocol to query the resolved Open Payments account URL and discover the Open Payments services endpoints.

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.

Example:

GET /.well-known/pay HTTP/1.1
Host: alice.wallet.example
Accept: application/json

Step 3: Initiate Payment

Having discovered the available endpoints, the client initiates the payment using one of the supported Open Payments protocols 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.host + path; } throw new Error("Invalid URL"); } const ppInput = document.getElementById("pp-input"); const urlInput = document.getElementById("url-input"); const errorMsg = document.getElementById("error"); function updateUrl() { try { const url = resolveUrl(ppInput.value); urlInput.value = url; errorMsg.textContent = ""; } catch (e) { errorMsg.textContent = e.message; urlInput.value = ""; } } ppInput.addEventListener("input", updateUrl); updateUrl(); </script>

IANA Considerations


title: IANA Considerations

Well-Known URI

This specification registers a new well-known URI.

URI suffix: "pay"

Change controller: Interledger Payments Community Group (W3C)

Specification document(s): This document


Syntax

Payment pointers are not a new URI scheme, but a standardized way to represent HTTPS URLs for payment accounts. They leverage existing web infrastructure and do not require new protocol registrations.


Security Considerations

Payment pointers have many of the same security risks as URLs so it follows that the security considerations are the same even though payment pointers do not in and of themselves pose a security threat.

However, as payment pointers are used to provide a compact set of instructions for initiation of a payment, care must be taken to properly interpret the data within a payment pointer, to prevent payments being made to the wrong recipient or leaking of sensitive data.

Reliability and Consistency

There is no guarantee that once a Payment Pointer has been used resolve a payment initiation service endpoint, the same service will be hosted at that URI in the future.

Semantic Attacks and Phishing

Because Payment Pointers only support a limited profile of the data in a URL, not all of the attacks described in RFC3986 apply to payment pointers.

However, it is possible for a malicious actor to construct a payment pointer that appears to point to a trusted receiver but in fact points to a malicious actor. As a result the pointer is constructed that is intended to mislead a human user by appearing to identify one (trusted) naming authority while actually identifying a different authority hidden behind the noise.

As detailed in [RFC7230], the "https" scheme (Section 2.7.2) is intended to prevent (or at least reveal) many of these potential attacks on establishing the authority behind a pointer, provided that the negotiated TLS connection is secured and the client properly verifies that the communicating server's identity matches the target URIs authority component (see [RFC2818]). Correctly implementing such verification can be difficult (see The Most Dangerous Code in the World).

Payment Initiation Services should take heed of this risk in their design and provide a mechanism for the parties to reliably verify the counter-party's identity.

Agents of a user that is sending to a counter-party using a Payment Pointer MUST provide a way for the party, after processing the Payment Pointer, to verify that the entity they are sending to (or at least the entity hosting the payment initiation service) is who they intended otherwise senders can easily be fooled into sending money to the wrong receiver. An example of such a mechanism is the use of extended validation certificates at the endpoint.

References

The Most Dangerous Code in the World: Validating SSL Certificates in Non-browser Software

Georgiev, M., Iyengar, S., Jana, S., Anubhai, R., Boneh, D., and V. Shmatikov, "The Most Dangerous Code in the World: Validating SSL Certificates in Non-browser Software", In Proceedings of the 2012 ACM Conference on Computer and Communications Security (CCS'12), pp. 38-49, October 2012, http://doi.acm.org/10.1145/2382196.2382204.


Index

Explainer

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.

A Payment Pointer resolves to a URL (with the https scheme) that can be used to discover the Open Payments endpoints for interacting with the account. Using the Open Payments protocol the counter-party can initiate a payment to or from the owner of the Payment Pointer.

Payment Pointer

URL

Syntax

Payment Pointers start with a $ character to distinguish them from other identifiers and make it obvious that they are related to payments. To convert a Payment Pointer to a URL the $ is replaced with the standard prefix of a secure URL, https://.

Payment Pointers don't contain query strings or fragments, however Open Payments MAY define standard parameters that can be used by a client when connecting to an Open Payments service endpoint.

More details...

Flow

When making or a receiving a payment, a user passes a Payment Pointer to the counter-party who decodes it to the corresponding URL.

That URL represents an account at a wallet and the client begins an interaction with the wallet using the Open Payments protocol.

More details...

<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.host + path; } throw new Error("Invalid URL"); } const ppInput = document.getElementById("pp-input"); const urlInput = document.getElementById("url-input"); const errorMsg = document.getElementById("error"); function updateUrl() { try { const url = resolveUrl(ppInput.value); urlInput.value = url; errorMsg.textContent = ""; } catch (e) { errorMsg.textContent = e.message; urlInput.value = ""; } } ppInput.addEventListener("input", updateUrl); updateUrl(); </script>

End of consolidated Payment Pointers documentation.