Saltearse al contenido
GitHub

Set up recurring payments with a fixed incoming amount

Esta página aún no está disponible en tu idioma.

A subscription payment is a recurring transfer of money where a customer pays a fixed fee at regular intervals to access a service or product. In this guide, you will learn how to implement a recurring subscription payment feature where the service provider receives the same amount each billing period.

This approach is particularly useful for subscription service scenarios where:

  • The service provider charges a fixed monthly subscription fee
  • The customer authorizes recurring payments at a set interval
  • The customer wants to avoid manually approving each monthly payment

Imagine a customer subscribing to a streaming service. They want to authorize monthly payments of exactly $15 USD for 12 months, and the service provider must receive the full $15 USD each month to maintain the subscription.

For this guide, you’ll assume the role of a developer working for the service provider. The guide explains how to set up a $15 USD monthly subscription payment that recurs for 12 months, where the service provider receives exactly $15 USD each billing period.

Example transaction details:

  • Service provider receives: $15.00 USD (exact amount each month)
  • Payment frequency: Monthly for 12 months
  • Customer pays: $15.00 USD each month

The three parties involved in this scenario are:

  • Developer: you, working for the service provider
  • Customer: the person subscribing to and paying for the service
  • Service provider: the service provider, receiving the subscription payments

When the customer initiates a subscription, you need to get wallet address information for both the customer and the service provider.

Let’s assume the customer has already provided their wallet address when they signed up for your service. Let’s also assume you already have the service provider’s wallet address configured in your system.

Call the GET Get Wallet Address API for each address.

const customerWalletAddress = await client.walletAddress.get({
url: 'https://cloudninebank.example.com/customer'
})
const serviceProviderWalletAddress = await client.walletAddress.get({
url: 'https://happylifebank.example.com/service-provider'
})

Example responses The following example shows a response from the customer’s wallet provider.

{
"id": "https://cloudninebank.example.com/customer",
"assetCode": "USD",
"assetScale": 2,
"authServer": "https://auth.cloudninebank.example.com/",
"resourceServer": "https://cloudninebank.example.com/op"
}

The following example shows a response from the service provider’s wallet provider.

{
"id": "https://happylifebank.example.com/service-provider",
"assetCode": "USD",
"assetScale": 2,
"authServer": "https://auth.happylifebank.example.com/",
"resourceServer": "https://happylifebank.example.com/op"
}

Use the service provider’s authServer details, received in the previous step, to call the POST Grant Request API.

This call obtains an access token that allows you to request that an incoming payment resource be created on the service provider’s wallet account.

const serviceProviderIncomingPaymentGrant = await client.grant.request(
{
url: serviceProviderWalletAddress.authServer
},
{
access_token: {
access: [
{
type: "incoming-payment",
actions: ["create"],
},
],
},
},
);

Example response The following shows an example response from the service provider’s wallet provider.

{
"access_token": {
"value": "...", // access token value for incoming payment grant
"manage": "https://happylifebank.example.com/token/{...}", // management uri for access token
"access": [
{
"type": "incoming-payment",
"actions": ["create"]
}
]
},
"continue": {
"access_token": {
"value": "..." // access token for continuing the request
},
"uri": "https://happylifebank.example.com/continue/{...}" // continuation request uri
}
}

3. Request the creation of an incoming payment resource

Section titled “3. Request the creation of an incoming payment resource”

Use the access token returned in the previous response to call the POST Create Incoming Payment API.

This call requests an incoming payment resource be created on the service provider’s wallet account.

const serviceProviderIncomingPayment = await client.incomingPayment.create(
{
url: serviceProviderWalletAddress.resourceServer,
accessToken: serviceProviderIncomingPaymentGrant.access_token.value
},
{
walletAddress: serviceProviderWalletAddress.id,
incomingAmount: {
value: '1500', // The amount the service provider expects to receive in the first payment
assetCode: 'USD',
assetScale: 2
},
},
)

Example response The following shows an example response from the service provider’s wallet provider.

{
"id": "https://happylifebank.example.com/incoming-payments/{...}",
"walletAddress": "https://happylifebank.example.com/service-provider",
"incomingAmount": {
"value": "1500",
"assetCode": "USD",
"assetScale": 2
},
"receivedAmount": {
"value": "0",
"assetCode": "USD",
"assetScale": 2
},
"completed": false,
"createdAt": "2025-10-14T00:00:50.52Z",
"methods": [
{
"type": "ilp",
"ilpAddress": "...",
"sharedSecret": "..."
}
]
}

Use the customer’s authServer details, received in Step 1, to call the POST Grant Request API.

This call obtains an access token that allows you to request that a quote resource be created on the customer’s wallet account.

const customerQuoteGrant = await client.grant.request(
{
url: customerWalletAddress.authServer
},
{
access_token: {
access: [
{
type: 'quote',
actions: ['create']
}
]
}
}
)

Example response The following shows an example response from the customer’s wallet provider.

{
"access_token": {
"value": "...", // access token value for quote grant
"manage": "https://auth.cloudninebank.example.com/token/{...}", // management uri for access token
"access": [
{
"type": "quote",
"actions": ["create"]
}
]
},
"continue": {
"access_token": {
"value": "..." // access token for continuing the request
},
"uri": "https://auth.cloudninebank.example.com/continue/{...}" // continuation request uri
}
}

5. Request the creation of a quote resource

Section titled “5. Request the creation of a quote resource”

Use the access token received in the previous step to call the POST Create Quote API.

This call requests that a quote resource be created on the customer’s wallet account.

The request must contain the receiver, which is the id of the service provider’s incoming payment. The id was returned in the Create an Incoming Payment API response in Step 3.

const customerQuote = await client.quote.create(
{
url: customerWalletAddress.resourceServer,
accessToken: customerQuoteGrant.access_token.value
},
{
method: 'ilp',
walletAddress: customerWalletAddress.id,
receiver: serviceProviderIncomingPayment.id,
}
)

The response returns a debitAmount, a receiveAmount, and other required information.

  • debitAmount - The amount that will be charged to the customer.
  • receiveAmount - The incomingAmount value from the incoming payment resource

Example response The following shows an example response from the customer’s wallet provider.

{
"id": "https://cloudninebank.example.com/quotes/{...}", // url identifying the quote
"walletAddress": "https://cloudninebank.example.com/customer",
"receiver": "https://happylifebank.example.com/incoming-payments/{...}", // url of the incoming payment the quote is created for
"debitAmount": {
"value": "1500",
"assetCode": "USD",
"assetScale": 2
},
"receiveAmount": {
"value": "1500",
"assetCode": "USD",
"assetScale": 2
},
"method": "ilp",
"createdAt": "2025-10-14T00:00:51.50Z",
"expiresAt": "2025-10-14T00:02:51.50Z"
}

6. Request an interactive outgoing payment grant

Section titled “6. Request an interactive outgoing payment grant”

Use the customer’s authServer information received in Step 1 to call the POST Grant Request API.

This call obtains an access token that allows you to request that an outgoing payment resource be created on the customer’s wallet account.

For recurring payments, include the interval property to specify how often the payment should occur. Remember that the customer wants to pay $15 USD a month for 12 months.

const pendingCustomerOutgoingPaymentGrant = await client.grant.request(
{
url: customerWalletAddress.authServer
},
{
access_token: {
access: [
{
identifier: customerWalletAddress.id,
type: 'outgoing-payment',
actions: ['create', 'read'],
limits: {
debitAmount: {
assetCode: 'USD',
assetScale: 2,
value: '1500',
},
interval: 'R12/2025-10-14T00:03:00Z/P1M'
}
}
]
},
interact: {
start: ['redirect'],
finish: {
method: 'redirect',
uri: 'https://myapp.example.com/finish/{...}', // where to redirect the customer after they've completed interaction
nonce: NONCE
}
}
}
)

Example response The following shows an example response from the customer’s wallet provider.

{
"interact": {
"redirect": "https://auth.cloudninebank.example.com/{...}", // uri to redirect the customer to, to begin interaction
"finish": "..." // unique key to secure the callback
},
"continue": {
"access_token": {
"value": "..." // access token for continuing the outgoing payment grant request
},
"uri": "https://auth.cloudninebank.example.com/continue/{...}", // uri for continuing the outgoing payment grant request
"wait": 30
}
}

The interval used in this guide is R12/2025-10-14T00:03:00Z/P1M. Remember that the customer wants to pay $15 USD a month for 12 months. The interval breaks down like this:

  • R12/ is the number of repetitions - twelve
  • 2025-10-14 is the start date of the repeating interval - 14 October 2025
  • T00:03:00Z/ is the start time of the repeating interval - 12:03 AM UTC
  • P1M is the period between each interval - one month. Used with R12, you have a grant that’s valid once a month for 12 months.

Altogether, this grant will allow the customer to pay $15 USD twelve times.

Once the client receives the authorization server’s response, it must send the user to the interact.redirect URI contained in the response. This starts the interaction flow.

The response also includes a continue object, which is essential for managing the interaction and obtaining explicit user consent for outgoing payment grants. The continue object contains an access token and a URI that the client will use to finalize the grant request after the user has completed their interaction with the identity provider (IdP). This ensures that the client can securely obtain the necessary permissions to proceed with the payment process.

The user interacts with the authorization server through the server’s interface and approves or denies the grant.

Provided the user approves the grant, the authorization server:

  • Sends the user to the finish.uri provided in the interactive outgoing payment grant request. The means by which the server sends the user to the URI is out of scope, but common options include redirecting the user from a web page and launching the system browser with the target URI.
  • Secures the redirect by adding a unique hash, allowing your client to validate the finish call, and an interaction reference as query parameters to the URI.

In our example, we’re assuming the IdP the customer interacted with has a user interface. When the interaction completes, the customer returns to your platform. Now your platform can make a continuation request for the outgoing payment grant.

Call the POST Grant Continuation Request API. This call requests an access token that allows you to request that an outgoing payment resource be created on the customer’s wallet account.

Issue the request to the continue.uri provided in the initial outgoing payment grant response in Step 6.

Include the interact_ref returned in the redirect URI’s query parameters.

const customerOutgoingPaymentGrant = await client.grant.continue(
{
url: customerOutgoingPaymentGrant.continue.uri,
accessToken: customerOutgoingPaymentGrant.continue.access_token.value
},
{
interact_ref: interactRef
}
)

Example response The following shows an example response from the customer’s wallet provider.

{
"access_token": {
"value": "...", // final access token required before creating outgoing payments
"manage": "https://auth.cloudninebank.example.com/token/{...}", // management uri for access token
"access": [
{
"type": "outgoing-payment",
"actions": ["create", "read"],
"identifier": "https://cloudninebank.example.com/customer",
"limits": {
"debitAmount": {
"assetCode": "USD",
"assetScale": 2,
"value": "1500"
},
"interval": "R12/2025-10-14T00:03:00Z/P1M"
}
}
]
},
"continue": {
"access_token": {
"value": "..." // access token for continuing the request
},
"uri": "https://auth.cloudninebank.example.com/continue/{...}" // continuation request uri
}
}

10. Request the creation of an outgoing payment resource

Section titled “10. Request the creation of an outgoing payment resource”

Use the access token returned in Step 9 to call the POST Create Outgoing Payment API.

Include the quoteId from the quote created in Step 5.

const customerOutgoingPayment = await client.outgoingPayment.create(
{
url: customerWalletAddress.resourceServer,
accessToken: customerOutgoingPaymentGrant.access_token.value
},
{
walletAddress: customerWalletAddress.id,
quoteId: customerQuote.id
}
)

Example response The following shows an example response from the customer’s wallet provider.

{
"id": "https://cloudninebank.example.com/outgoing-payments/{...}", // url identifying the outgoing payment
"walletAddress": "https://cloudninebank.example.com/customer",
"receiver": "https://happylifebank.example.com/incoming-payments/{...}", // url of the incoming payment being paid
"debitAmount": {
"value": "1500",
"assetCode": "USD",
"assetScale": 2
},
"receiveAmount": {
"value": "1500",
"assetCode": "USD",
"assetScale": 2
},
"sentAmount": {
"value": "0",
"assetCode": "USD",
"assetScale": 2
},
"createdAt": "2025-10-14T05:00:54.52Z"
}

The first of the 12 recurring subscription payments is now set up. At the next interval (one month from now), repeat the following steps to request the creation of:

  1. An incoming payment resource (step 3)
  2. A quote resource (step 5)
  3. An outgoing payment resource (step 10)

You don’t need to request new grants because the original grants should be valid for the remaining billing periods.