Skip to content
GitHub

Make recurring payments

The Open Payments (OP) API facilitates multiple uses cases for payments to and from different OP-enabled wallets. In this guide we look at the steps for making recurring payments.

An example use case for making recurring payments may be a Buy Now Pay Later (BNPL) scheme where a payer purchases a high priced item but pays for it in installments over regularly scheduled intervals.

Dependencies

Endpoints

Additional configuration

Add "type": "module" to package.json

Add the following to tsconfig.json

{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022"
}
}

Steps

1. Import dependencies

Import createAuthenticatedClient from the Open Payments SDK package.

Import dependencies

import { createAuthenticatedClient } from "@interledger/open-payments";

2. Create an authenticated Open Payments client

Create an OP authenticated client by providing the following properties:

  • walletAddressURL : your OP-enabled wallet address that your client will use to authenticate itself to one or more Authorization Servers (AS).
  • privateKey : the EdDSA-Ed25519 key or preferably the absolute or relative file path to the key that is bound to your wallet address. A public key signed with this private key must be made available as a public JWK document at {walletAddressUrl}/jwks.json url.
  • keyId : the identifier of the private key and the corresponding public key.

Initialize Open Payments client

const client = await createAuthenticatedClient({
  walletAddressUrl: WALLET_ADDRESS,
  privateKey: PRIVATE_KEY_PATH,
  keyId: KEY_ID,
});

3. Request an Incoming Payment grant

Request an incomingPayment grant from the payee wallet’s AS.

Request incoming payment grant

const grant = await client.grant.request(
  {
    url: walletAddress.authServer,
  },
  {
    access_token: {
      access: [
        {
          type: "incoming-payment",
          actions: [
            "list",
            "list-all",
            "read",
            "read-all",
            "complete",
            "create",
          ],
        },
      ],
    },
  },
);

4. Create an Incoming Payment

Create an incomingPayment on the payee wallet’s Resource Server (RS) using the access token returned by the AS in the grant request and specify the walletAddress with the URL of the payee’s wallet.

Add the incomingAmount object and define the following properties:

  • value : the maximum allowable amount that will be paid to the payee’s wallet address.
  • assetCode : the ISO 4217 currency code representing the underlying asset used to make the payment.
  • assetScale : the scale of amounts denoted in the corresponding asset code.

Optionally you may add the expiresAt property which is the date and time after which payments to the incoming payments will no longer be accepted.

Create incoming payment

const incomingPayment = await client.incomingPayment.create(
  {
    url: new URL(WALLET_ADDRESS).origin,
    accessToken: INCOMING_PAYMENT_ACCESS_TOKEN,
  },
  {
    walletAddress: WALLET_ADDRESS,
    incomingAmount: {
      value: "1000",
      assetCode: "USD",
      assetScale: 2,
    },
    expiresAt: new Date(Date.now() + 60_000 * 10).toISOString(),
  },
);

5. Request a Quote grant

Request a quote grant from the payer wallet’s AS.

Request quote grant

const grant = await client.grant.request(
  {
    url: walletAddress.authServer,
  },
  {
    access_token: {
      access: [
        {
          type: "quote",
          actions: ["create", "read", "read-all"],
        },
      ],
    },
  },
);

6. Create a Quote

Create a quote on the payer wallet’s RS using the access token returned by the AS in the grant request.

Add the following properties:

  • method : the payment method used to facilitate the payment. Set this property to ilp as Open Payments only supports Interledger payments at this time.
  • walletAddress : the URL of the payer’s wallet address.
  • receiver : the URL of the incoming payment that will receive the payment.

Create quote

const quote = await client.quote.create(
  {
    url: new URL(WALLET_ADDRESS).origin,
    accessToken: QUOTE_ACCESS_TOKEN,
  },
  {
    method: "ilp",
    walletAddress: WALLET_ADDRESS,
    receiver: INCOMING_PAYMENT_URL,
  },
);

7. Request an Outgoing Payment grant

Request an outgoingPayment grant from the payer wallet’s AS.

Add the limits object with the following properties:

  • debitAmount : the maximum amount to be deducted from the payer’s wallet.
  • receiveAmount : the maximum amount to be received in the payee’s wallet.
  • interval : the interval period conforming to the ISO8601 repeating interval string format.

Request outgoing payment grant with interval

const grant = await client.grant.request(
  {
    url: walletAddress.authServer,
  },
  {
    access_token: {
      access: [
        {
          identifier: walletAddress.id,
          type: "outgoing-payment",
          actions: ["list", "list-all", "read", "read-all", "create"],
          limits: {
            debitAmount: DEBIT_AMOUNT,
            receiveAmount: RECEIVE_AMOUNT,
            interval: "R/2016-08-24T08:00:00Z/P1D",
          },
        },
      ],
    },
    interact: {
      start: ["redirect"],
      finish: {
        method: "redirect",
        uri: "http://localhost:3344",
        nonce: NONCE,
      },
    },
  },
);

8. Create an initial Outgoing Payment

Create an outgoingPayment on the payer wallet’s RS using the access token returned by the AS in the grant request.

Add the following properties:

  • walletAddress : the URL of the payer’s wallet address.
  • quoteId : the URL of the quote specifying the payment amount.

Create outgoing payment

const outgoingPayment = await client.outgoingPayment.create(
  {
    url: new URL(WALLET_ADDRESS).origin,
    accessToken: OUTGOING_PAYMENT_ACCESS_TOKEN,
  },
  {
    walletAddress: WALLET_ADDRESS,
    quoteId: QUOTE_URL,
  },
);

9. Rotate the Access Token

Rotate the access token obtained from the previous outgoingPayment grant request.

Rotate token

const token = await client.token.rotate({
  url: MANAGE_URL,
  accessToken: ACCESS_TOKEN,
});

10. Create another Outgoing Payment

Create another outgoingPayment on the payer wallet’s RS using the new access token returned by the AS in the previous step.

Add the following properties:

  • walletAddress : the URL of the payer’s wallet address.
  • quoteId : the URL of the quote specifying the payment amount.

Create outgoing payment

const outgoingPayment = await client.outgoingPayment.create(
  {
    url: new URL(WALLET_ADDRESS).origin,
    accessToken: OUTGOING_PAYMENT_ACCESS_TOKEN,
  },
  {
    walletAddress: WALLET_ADDRESS,
    quoteId: QUOTE_URL,
  },
);

11. Make recurring Outgoing Payments

Repeat steps 9 and 10 to facilitate as many payments as needed by your application.