GoCardlessDeveloper Docs
Create a sandbox account

Instalments#

View as Markdown

What are Instalments?#

Spread the cost of a purchase over a defined number of fixed payments. You specify the total amount, number of instalments, and schedule; GoCardless handles the rest.

  • Payment plans for high-ticket purchases
  • Tuition and course fees
  • Professional services (accounting, legal)
  • Healthcare and dental treatment plans

Comparison between other recurring payment options#

Key difference from subscriptions: Instalment schedules have a defined end. Subscriptions continue indefinitely until cancelled.

SubscriptionsInstalmentsVariable Recurring PaymentsInstant Bank Pay + Direct Debit
Mandate requiredYesYesYes (consent)Yes
Customer authorisationOnceOnceOnce (with agreed limits and frequency)Once
Payment amountsFixed (per subscription)Fixed per instalment, can vary across scheduleVariable within agreed constraintsFirst payment fixed; subsequent payments flexible
Payment scheduleMerchant-defined recurring (weekly, monthly, yearly)Merchant-defined schedule with a defined endFlexible - merchant triggers payments as neededFlexible after first payment
Confirmation speed2-x business days2-x business daysSecondsFirst payment: minutes; subsequent: 2-x business days
Chargeback riskYesYesNoneFirst payment: none; subsequent: yes
Missed payment retriesYes (with Success+)Yes (with Success+)YesSubsequent payments: yes
Best forSaaS, memberships, gym fees, insurancePayment plans, tuition, professional servicesUsage-based regulated billing and utilities, financial services, government servicesSubscription services needing immediate, initial payment
AvailabilityAll schemesAll schemesGBP onlyGBP and EUR

How it works#

  1. The customer sets up a mandate
  2. You create an instalment schedule specifying the total amount, the number of payments, and the frequency
  3. GoCardless automatically creates and collects each instalment
  4. The schedule completes after the final instalment

Frequency options: Weekly, monthly, yearly, or custom intervals.

Step-by-step guide#

Create a billing request#

curl -X POST https://api.gocardless.com/billing_requests \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "billing_requests": {
      "mandate_request": {
        "scheme": "bacs"
      }
    }
  }'
<?php
require 'vendor/autoload.php';
 
$client = new \GoCardlessPro\Client([
    'access_token' => getenv('GC_ACCESS_TOKEN'),
    'environment' => \GoCardlessPro\Environment::SANDBOX
]);
 
$response = $client->billingRequests()->create([
    'params' => [
        'mandate_request' => [
            'scheme' => 'bacs',
        ],
    ],
]);
import os
import gocardless_pro
 
client = gocardless_pro.Client(
    access_token=os.environ['GC_ACCESS_TOKEN'],
    environment='sandbox'
)
 
response = client.billing_requests.create(
    params={
        'mandate_request': {
            'scheme': 'bacs',
        }
    }
)
require 'gocardless_pro'
 
client = GoCardlessPro::Client.new(
    access_token: ENV['GC_ACCESS_TOKEN'],
    environment: :sandbox
)
 
response = client.billing_requests.create(
  params: {
    mandate_request: {
      scheme: 'bacs'
    }
  }
)
import com.gocardless.GoCardlessClient;
 
GoCardlessClient client = GoCardlessClient
    .newBuilder(System.getenv("GC_ACCESS_TOKEN"))
    .withEnvironment(GoCardlessClient.Environment.SANDBOX)
    .build();
 
BillingRequestCreateResponse response = client.billingRequests()
    .create()
    .withMandateRequestScheme("bacs")
    .execute();
const gocardless = require("gocardless-nodejs");
const constants = require("gocardless-nodejs/constants");
 
const client = gocardless(process.env.GC_ACCESS_TOKEN, constants.Environments.Sandbox);
 
const response = await client.billingRequests.create({
  mandate_request: {
    scheme: "bacs",
  },
});
using GoCardless;
 
var client = GoCardlessClient.Create(
    Environment.GetEnvironmentVariable("GC_ACCESS_TOKEN"),
    GoCardlessClient.Environment.SANDBOX
);
 
var response = await client.BillingRequests.CreateAsync(
    new BillingRequestCreateRequest
    {
        MandateRequest = new BillingRequestCreateRequest.BillingRequestMandateRequest
        {
            Scheme = "bacs"
        }
    }
);
package main
 
import (
    "context"
    "fmt"
    "os"
 
    gocardless "github.com/gocardless/gocardless-pro-go/v6"
)
 
func main() {
    opts := gocardless.WithEndpoint(gocardless.SandboxEndpoint)
    client, err := gocardless.New(os.Getenv("GC_ACCESS_TOKEN"), opts)
    if err != nil {
        panic(err)
    }
 
    params := gocardless.BillingRequestCreateParams{
        MandateRequest: &gocardless.BillingRequestCreateParamsMandateRequest{
            Scheme: "bacs",
        },
    }
 
    result, err := client.BillingRequests.Create(context.TODO(), params)
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}

Response:

{
  "billing_requests": {
    "id": "BRQ000123",
    "status": "pending",
    "mandate_request": {
      "currency": "GBP",
      "scheme": "bacs"
    }
  }
}

Confirm the mandate#

After creating the Billing Request, direct the customer to authorise the mandate. How you do this depends on your integration type:

  • Hosted Pages - Create a Billing Request Flow and redirect the customer to the authorisation_url. See Hosted Payment Pages.
  • Custom API - Collect customer details and bank account via Billing Request actions, then confirm. See Custom Payment Pages.

Handle the outcome#

Once fulfilled, retrieve both mandate ID and payment ID from the billing request:

curl https://api.gocardless.com/billing_requests/BRQ000123 \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
<?php
require 'vendor/autoload.php';
 
$client = new \GoCardlessPro\Client([
    'access_token' => getenv('GC_ACCESS_TOKEN'),
    'environment' => \GoCardlessPro\Environment::SANDBOX
]);
 
$response = $client->billingRequests()->get('BRQ000123');
 
$mandateId = $response->mandate_request->links->mandate;
import os
import gocardless_pro
 
client = gocardless_pro.Client(
    access_token=os.environ['GC_ACCESS_TOKEN'],
    environment='sandbox'
)
 
response = client.billing_requests.get('BRQ000123')
 
mandate_id = response.mandate_request.links.mandate
require 'gocardless_pro'
 
client = GoCardlessPro::Client.new(
    access_token: ENV['GC_ACCESS_TOKEN'],
    environment: :sandbox
)
 
response = client.billing_requests.get('BRQ000123')
 
mandate_id = response.mandate_request.links.mandate
import com.gocardless.GoCardlessClient;
 
GoCardlessClient client = GoCardlessClient
    .newBuilder(System.getenv("GC_ACCESS_TOKEN"))
    .withEnvironment(GoCardlessClient.Environment.SANDBOX)
    .build();
 
BillingRequest billingRequest = client.billingRequests()
    .get("BRQ000123")
    .execute();
 
String mandateId = billingRequest.getMandateRequest().getLinks().getMandate();
const gocardless = require("gocardless-nodejs");
const constants = require("gocardless-nodejs/constants");
 
const client = gocardless(process.env.GC_ACCESS_TOKEN, constants.Environments.Sandbox);
 
const billingRequest = await client.billingRequests.find("BRQ000123");
 
const mandateId = billingRequest.mandate_request.links.mandate;
using GoCardless;
 
var client = GoCardlessClient.Create(
    Environment.GetEnvironmentVariable("GC_ACCESS_TOKEN"),
    GoCardlessClient.Environment.SANDBOX
);
 
var billingRequest = await client.BillingRequests.GetAsync("BRQ000123");
 
var mandateId = billingRequest.MandateRequest.Links.Mandate;
package main
 
import (
    "context"
    "fmt"
    "os"
 
    gocardless "github.com/gocardless/gocardless-pro-go/v6"
)
 
func main() {
    opts := gocardless.WithEndpoint(gocardless.SandboxEndpoint)
    client, err := gocardless.New(os.Getenv("GC_ACCESS_TOKEN"), opts)
    if err != nil {
        panic(err)
    }
 
    billingRequest, err := client.BillingRequests.Get(context.TODO(), "BRQ000123")
    if err != nil {
        panic(err)
    }
 
    mandateId := billingRequest.MandateRequest.Links.Mandate
    fmt.Println(mandateId)
}

Response:

{
  "billing_requests": {
    "id": "BRQ000123",
    "status": "fulfilled",
    "mandate_request": {
      "scheme": "bacs",
      "links": {
        "mandate": "MD123"
      }
    }
  }
}

Listen for webhooks to confirm the payment status:

EventMeaning
billing_request_fulfilledCustomer authorised the mandate
mandates_createdMandate created and ready to use
mandates_activeMandate active

Setup the instalments#

Once the mandate is active, set up recurring payments against it.

Option A - Create the instalment plan with fixed dates (you specify each exact charge date):

curl -X POST https://api.gocardless.com/instalment_schedules \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "instalment_schedules": {
      "name": "Invoice #1234",
      "currency": "GBP",
      "total_amount": 2500,
      "instalments": [
        { "amount": 1500, "charge_date": "2024-11-01" },
        { "amount": 1000, "charge_date": "2025-02-01" }
      ],
      "links": {
        "mandate": "MD123"
      }
    }
  }'
<?php
require 'vendor/autoload.php';
 
$client = new \GoCardlessPro\Client([
    'access_token' => getenv('GC_ACCESS_TOKEN'),
    'environment' => \GoCardlessPro\Environment::SANDBOX
]);
 
$response = $client->instalmentSchedules()->createWithDates([
    'params' => [
        'name'         => 'Invoice #1234',
        'currency'     => 'GBP',
        'total_amount' => 2500,
        'instalments'  => [
            ['amount' => 1500, 'charge_date' => '2024-11-01'],
            ['amount' => 1000, 'charge_date' => '2025-02-01'],
        ],
        'links' => [
            'mandate' => 'MD123',
        ],
    ],
]);
import os
import gocardless_pro
 
client = gocardless_pro.Client(
    access_token=os.environ['GC_ACCESS_TOKEN'],
    environment='sandbox'
)
 
response = client.instalment_schedules.create_with_dates(
    params={
        'name':         'Invoice #1234',
        'currency':     'GBP',
        'total_amount': 2500,
        'instalments': [
            {'amount': 1500, 'charge_date': '2024-11-01'},
            {'amount': 1000, 'charge_date': '2025-02-01'},
        ],
        'links': {
            'mandate': 'MD123',
        },
    }
)
require 'gocardless_pro'
 
client = GoCardlessPro::Client.new(
    access_token: ENV['GC_ACCESS_TOKEN'],
    environment: :sandbox
)
 
response = client.instalment_schedules.create_with_dates(
  params: {
    name:         'Invoice #1234',
    currency:     'GBP',
    total_amount: 2500,
    instalments: [
      { amount: 1500, charge_date: '2024-11-01' },
      { amount: 1000, charge_date: '2025-02-01' },
    ],
    links: {
      mandate: 'MD123'
    }
  }
)
import com.gocardless.GoCardlessClient;
 
GoCardlessClient client = GoCardlessClient
    .newBuilder(System.getenv("GC_ACCESS_TOKEN"))
    .withEnvironment(GoCardlessClient.Environment.SANDBOX)
    .build();
 
InstalmentSchedule instalmentSchedule = client.instalmentSchedules()
    .createWithDates()
    .withName("Invoice #1234")
    .withCurrency("GBP")
    .withTotalAmount(2500)
    .withInstalment(1500, "2024-11-01")
    .withInstalment(1000, "2025-02-01")
    .withLinksMandate("MD123")
    .execute();
const gocardless = require("gocardless-nodejs");
const constants = require("gocardless-nodejs/constants");
 
const client = gocardless(process.env.GC_ACCESS_TOKEN, constants.Environments.Sandbox);
 
const instalmentSchedule = await client.instalmentSchedules.createWithDates({
  name: "Invoice #1234",
  currency: "GBP",
  total_amount: 2500,
  instalments: [
    { amount: 1500, charge_date: "2024-11-01" },
    { amount: 1000, charge_date: "2025-02-01" },
  ],
  links: {
    mandate: "MD123",
  },
});
using GoCardless;
 
var client = GoCardlessClient.Create(
    Environment.GetEnvironmentVariable("GC_ACCESS_TOKEN"),
    GoCardlessClient.Environment.SANDBOX
);
 
var instalmentSchedule = await client.InstalmentSchedules.CreateWithDatesAsync(
    new InstalmentScheduleCreateWithDatesRequest
    {
        Name        = "Invoice #1234",
        Currency    = "GBP",
        TotalAmount = 2500,
        Instalments = new List<InstalmentScheduleCreateWithDatesRequest.InstalmentScheduleInstalments>
        {
            new InstalmentScheduleCreateWithDatesRequest.InstalmentScheduleInstalments
            {
                Amount     = 1500,
                ChargeDate = "2024-11-01"
            },
            new InstalmentScheduleCreateWithDatesRequest.InstalmentScheduleInstalments
            {
                Amount     = 1000,
                ChargeDate = "2025-02-01"
            }
        },
        Links = new InstalmentScheduleCreateWithDatesRequest.InstalmentScheduleLinks
        {
            Mandate = "MD123"
        }
    }
);
package main
 
import (
    "context"
    "fmt"
    "os"
 
    gocardless "github.com/gocardless/gocardless-pro-go/v6"
)
 
func main() {
    opts := gocardless.WithEndpoint(gocardless.SandboxEndpoint)
    client, err := gocardless.New(os.Getenv("GC_ACCESS_TOKEN"), opts)
    if err != nil {
        panic(err)
    }
 
    params := gocardless.InstalmentScheduleCreateWithDatesParams{
        Name:        "Invoice #1234",
        Currency:    "GBP",
        TotalAmount: 2500,
        Instalments: []gocardless.InstalmentScheduleCreateWithDatesParamsInstalments{
            {Amount: 1500, ChargeDate: "2024-11-01"},
            {Amount: 1000, ChargeDate: "2025-02-01"},
        },
        Links: gocardless.InstalmentScheduleCreateWithDatesParamsLinks{
            Mandate: "MD123",
        },
    }
 
    instalmentSchedule, err := client.InstalmentSchedules.CreateWithDates(context.TODO(), params)
    if err != nil {
        panic(err)
    }
    fmt.Println(instalmentSchedule)
}

Option B - Create the instalment plan with regular intervals (GoCardless calculates the dates):

curl -X POST https://api.gocardless.com/instalment_schedules \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "instalment_schedules": {
      "name": "Invoice #1234",
      "currency": "GBP",
      "total_amount": 2500,
      "instalments": {
        "start_date": "2024-11-01",
        "interval_unit": "monthly",
        "interval": 1,
        "amounts": [1500, 1000]
      },
      "links": {
        "mandate": "MD123"
      }
    }
  }'
<?php
require 'vendor/autoload.php';
 
$client = new \GoCardlessPro\Client([
    'access_token' => getenv('GC_ACCESS_TOKEN'),
    'environment' => \GoCardlessPro\Environment::SANDBOX
]);
 
$response = $client->instalmentSchedules()->createWithSchedule([
    'params' => [
        'name'         => 'Invoice #1234',
        'currency'     => 'GBP',
        'total_amount' => 2500,
        'instalments'  => [
            'start_date'    => '2024-11-01',
            'interval_unit' => 'monthly',
            'interval'      => 1,
            'amounts'       => [1500, 1000],
        ],
        'links' => [
            'mandate' => 'MD123',
        ],
    ],
]);
import os
import gocardless_pro
 
client = gocardless_pro.Client(
    access_token=os.environ['GC_ACCESS_TOKEN'],
    environment='sandbox'
)
 
response = client.instalment_schedules.create_with_schedule(
    params={
        'name':         'Invoice #1234',
        'currency':     'GBP',
        'total_amount': 2500,
        'instalments': {
            'start_date':    '2024-11-01',
            'interval_unit': 'monthly',
            'interval':      1,
            'amounts':       [1500, 1000],
        },
        'links': {
            'mandate': 'MD123',
        },
    }
)
require 'gocardless_pro'
 
client = GoCardlessPro::Client.new(
    access_token: ENV['GC_ACCESS_TOKEN'],
    environment: :sandbox
)
 
response = client.instalment_schedules.create_with_schedule(
  params: {
    name:         'Invoice #1234',
    currency:     'GBP',
    total_amount: 2500,
    instalments: {
      start_date:    '2024-11-01',
      interval_unit: 'monthly',
      interval:      1,
      amounts:       [1500, 1000]
    },
    links: {
      mandate: 'MD123'
    }
  }
)
import com.gocardless.GoCardlessClient;
 
GoCardlessClient client = GoCardlessClient
    .newBuilder(System.getenv("GC_ACCESS_TOKEN"))
    .withEnvironment(GoCardlessClient.Environment.SANDBOX)
    .build();
 
InstalmentSchedule instalmentSchedule = client.instalmentSchedules()
    .createWithSchedule()
    .withName("Invoice #1234")
    .withCurrency("GBP")
    .withTotalAmount(2500)
    .withInstalmentsStartDate("2024-11-01")
    .withInstalmentsIntervalUnit("monthly")
    .withInstalmentsInterval(1)
    .withInstalmentsAmounts(Arrays.asList(1500, 1000))
    .withLinksMandate("MD123")
    .execute();
const gocardless = require("gocardless-nodejs");
const constants = require("gocardless-nodejs/constants");
 
const client = gocardless(process.env.GC_ACCESS_TOKEN, constants.Environments.Sandbox);
 
const instalmentSchedule = await client.instalmentSchedules.createWithSchedule({
  name: "Invoice #1234",
  currency: "GBP",
  total_amount: 2500,
  instalments: {
    start_date: "2024-11-01",
    interval_unit: "monthly",
    interval: 1,
    amounts: [1500, 1000],
  },
  links: {
    mandate: "MD123",
  },
});
using GoCardless;
 
var client = GoCardlessClient.Create(
    Environment.GetEnvironmentVariable("GC_ACCESS_TOKEN"),
    GoCardlessClient.Environment.SANDBOX
);
 
var instalmentSchedule = await client.InstalmentSchedules.CreateWithScheduleAsync(
    new InstalmentScheduleCreateWithScheduleRequest
    {
        Name        = "Invoice #1234",
        Currency    = "GBP",
        TotalAmount = 2500,
        Instalments = new InstalmentScheduleCreateWithScheduleRequest.InstalmentScheduleInstalments
        {
            StartDate    = "2024-11-01",
            IntervalUnit = InstalmentScheduleCreateWithScheduleRequest.InstalmentScheduleInstalments.InstalmentScheduleInstalmentsIntervalUnit.Monthly,
            Interval     = 1,
            Amounts      = new List<int> { 1500, 1000 }
        },
        Links = new InstalmentScheduleCreateWithScheduleRequest.InstalmentScheduleLinks
        {
            Mandate = "MD123"
        }
    }
);
package main
 
import (
    "context"
    "fmt"
    "os"
 
    gocardless "github.com/gocardless/gocardless-pro-go/v6"
)
 
func main() {
    opts := gocardless.WithEndpoint(gocardless.SandboxEndpoint)
    client, err := gocardless.New(os.Getenv("GC_ACCESS_TOKEN"), opts)
    if err != nil {
        panic(err)
    }
 
    params := gocardless.InstalmentScheduleCreateWithScheduleParams{
        Name:        "Invoice #1234",
        Currency:    "GBP",
        TotalAmount: 2500,
        Instalments: gocardless.InstalmentScheduleCreateWithScheduleParamsInstalments{
            StartDate:    "2024-11-01",
            IntervalUnit: "monthly",
            Interval:     1,
            Amounts:      []int{1500, 1000},
        },
        Links: gocardless.InstalmentScheduleCreateWithScheduleParamsLinks{
            Mandate: "MD123",
        },
    }
 
    instalmentSchedule, err := client.InstalmentSchedules.CreateWithSchedule(context.TODO(), params)
    if err != nil {
        panic(err)
    }
    fmt.Println(instalmentSchedule)
}

Response:

{
  "instalment_schedules": {
    "id": "IS123",
    "status": "active",
    "name": "Invoice #1234",
    "currency": "GBP",
    "total_amount": 2500,
    "links": {
      "mandate": "MD123",
      "payments": ["PM001", "PM002"]
    }
  }
}

Manage the instalment plan#

Listen for webhooks to confirm the mandate status and notifications about future instalments:

EventMeaning
instalment_schedules_createdSchedule created, all payments queued
payments_confirmedIndividual payment collected successfully
payments_failedIndividual payment failed
instalment_schedules_completedAll payments in the schedule collected
instalment_schedules_erroredOne or more payments failed

You can cancel the instalment plan:

curl -X POST https://api.gocardless.com/instalment_schedules/IS123/actions/cancel \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
$response = $client->instalmentSchedules()->cancel('IS123');
response = client.instalment_schedules.cancel('IS123')
response = client.instalment_schedules.cancel('IS123')
InstalmentSchedule instalmentSchedule = client.instalmentSchedules()
    .cancel("IS123")
    .execute();
 
const instalmentSchedule = await client.instalmentSchedules.cancel("IS123");
var instalmentSchedule = await client.InstalmentSchedules.CancelAsync("IS123");
instalmentSchedule, err := client.InstalmentSchedules.Cancel(context.TODO(), "IS123", gocardless.InstalmentScheduleCancelParams{})

What's next?#