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
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,67 @@ const gocardless = require('gocardless-nodejs');
const { Environments } = require('gocardless-nodejs');
```

### Handling webhooks

GoCardless supports webhooks, allowing you to receive real-time notifications when things happen in your account, so you can take automatic actions in response, for example:

* When a customer cancels their mandate with the bank, suspend their club membership
* When a payment fails due to lack of funds, mark their invoice as unpaid
* When a customer's subscription generates a new payment, log it in their "past payments" list

The client allows you to validate that a webhook you receive is genuinely from GoCardless, and to parse it into `Event` objects which are easy to work with:

```typescript
import { webhooks } from 'gocardless-nodejs';

// When you create a webhook endpoint, you can specify a secret. When GoCardless sends
// you a webhook, it will sign the body using that secret. Since only you and GoCardless
// know the secret, you can check the signature and ensure that the webhook is truly
// from GoCardless.
const webhookEndpointSecret = process.env.GOCARDLESS_WEBHOOK_ENDPOINT_SECRET;

// In your webhook handler (e.g. Express route)
app.post('/webhooks', (req, res) => {
try {
const events = webhooks.parse(
req.body,
webhookEndpointSecret,
req.headers['webhook-signature']
);

events.forEach((event) => {
console.log(event.id);
});

res.status(200).end();
} catch (e) {
if (e.name === 'InvalidSignatureError') {
// The webhook doesn't appear to be genuinely from GoCardless
res.status(498).end();
}
throw e;
}
});
```

#### Accessing the webhook ID

If you need to access the webhook ID for debugging purposes, you can use `parseWithMeta` instead:

```typescript
const result = webhooks.parseWithMeta(
req.body,
webhookEndpointSecret,
req.headers['webhook-signature']
);
const events = result.events;
const webhookId = result.webhookId; // e.g. "WB123" - useful for debugging
```

Note: The webhook ID is intended for debugging and logging purposes only. It should not be used for deduplication - instead, use the event IDs to deduplicate, as each event has a unique ID that remains consistent if the same event is sent multiple times.

For more details on working with webhooks, see our ["Getting started" guide](https://developer.gocardless.com/getting-started/api/introduction/?lang=node).

## Upgrading from older versions

If you're upgrading from v7 or earlier to v8 or later, see: MIGRATION_V8.md
268 changes: 162 additions & 106 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
"scripts": {
"test": "jest",
"lint": "eslint src/**/*.ts",
"format": "prettier src --check --write && eslint src/**/*.ts --fix",
"format": "prettier src --write && eslint src/**/*.ts --fix",
"build": "npm run build:esm && npm run build:cjs && npm run build:package-markers",
"build:esm": "tsc -p tsconfig.json",
"build:cjs": "tsc -p tsconfig.cjs.json",
Expand Down
61 changes: 61 additions & 0 deletions src/codeSamples/balancesCodeSamples.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// WARNING: Do not edit by hand, this file was generated by Crank:
//
// https://github.com/gocardless/crank
//

// Code Sample Tests
// These tests verify that the documentation code samples are syntactically valid
// and can execute against a mocked API without errors.
//
// IMPORTANT: These tests do NOT verify business logic - they only verify that
// the code samples compile and execute without syntax errors.

import * as nock from 'nock';
import { GoCardlessClient } from '../client';
import { Environments } from '../constants';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import * as Types from '../types/Types';

describe('Balances Code Samples', () => {
const token = 'SECRET_TOKEN';
let client: GoCardlessClient;

beforeAll(() => {
nock.disableNetConnect();
});

beforeEach(() => {
client = new GoCardlessClient(token, Environments.Live, {});
});

afterEach(() => {
nock.cleanAll();
});

test('list code sample executes without error', async () => {
// Convert :param placeholders to regex wildcards for flexible matching
const stubUrl = '/balances';
const pathPattern = new RegExp('^' + stubUrl.replace(/:[\w]+/g, '[^/?]+') + '(?:\\?.*)?$');

// Mock response - repeat multiple times to handle code samples with multiple API calls
const responseBody = { balances: [{}], meta: { cursors: {}, limit: 50 } };
for (let i = 0; i < 5; i++) {
nock('https://api.gocardless.com').get(pathPattern).query(true).reply(200, responseBody);
}

// Suppress console.log from code samples
const originalLog = console.log;
// eslint-disable-next-line @typescript-eslint/no-empty-function
console.log = () => {};

try {
/* eslint-disable @typescript-eslint/no-unused-vars */
await client.balances.list({
creditor: 'CR123',
});
/* eslint-enable @typescript-eslint/no-unused-vars */
} finally {
console.log = originalLog;
}
});
});
61 changes: 61 additions & 0 deletions src/codeSamples/bank_account_detailsCodeSamples.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// WARNING: Do not edit by hand, this file was generated by Crank:
//
// https://github.com/gocardless/crank
//

// Code Sample Tests
// These tests verify that the documentation code samples are syntactically valid
// and can execute against a mocked API without errors.
//
// IMPORTANT: These tests do NOT verify business logic - they only verify that
// the code samples compile and execute without syntax errors.

import * as nock from 'nock';
import { GoCardlessClient } from '../client';
import { Environments } from '../constants';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import * as Types from '../types/Types';

describe('BankAccountDetails Code Samples', () => {
const token = 'SECRET_TOKEN';
let client: GoCardlessClient;

beforeAll(() => {
nock.disableNetConnect();
});

beforeEach(() => {
client = new GoCardlessClient(token, Environments.Live, {});
});

afterEach(() => {
nock.cleanAll();
});

test('get code sample executes without error', async () => {
// Convert :param placeholders to regex wildcards for flexible matching
const stubUrl = '/bank_account_details/:identity';
const pathPattern = new RegExp('^' + stubUrl.replace(/:[\w]+/g, '[^/?]+') + '(?:\\?.*)?$');

// Mock response - repeat multiple times to handle code samples with multiple API calls
const responseBody = { bank_account_details: {} };
for (let i = 0; i < 5; i++) {
nock('https://api.gocardless.com').get(pathPattern).query(true).reply(200, responseBody);
}

// Suppress console.log from code samples
const originalLog = console.log;
// eslint-disable-next-line @typescript-eslint/no-empty-function
console.log = () => {};

try {
/* eslint-disable @typescript-eslint/no-unused-vars */
const resp = await client.bankAccountDetails.find('BA123', {
'Gc-Key-Id': 'PK123',
});
/* eslint-enable @typescript-eslint/no-unused-vars */
} finally {
console.log = originalLog;
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// WARNING: Do not edit by hand, this file was generated by Crank:
//
// https://github.com/gocardless/crank
//

// Code Sample Tests
// These tests verify that the documentation code samples are syntactically valid
// and can execute against a mocked API without errors.
//
// IMPORTANT: These tests do NOT verify business logic - they only verify that
// the code samples compile and execute without syntax errors.

import * as nock from 'nock';
import { GoCardlessClient } from '../client';
import { Environments } from '../constants';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import * as Types from '../types/Types';

describe('BankAccountHolderVerifications Code Samples', () => {
const token = 'SECRET_TOKEN';
let client: GoCardlessClient;

beforeAll(() => {
nock.disableNetConnect();
});

beforeEach(() => {
client = new GoCardlessClient(token, Environments.Live, {});
});

afterEach(() => {
nock.cleanAll();
});

test('create code sample executes without error', async () => {
// Convert :param placeholders to regex wildcards for flexible matching
const stubUrl = '/bank_account_holder_verifications';
const pathPattern = new RegExp('^' + stubUrl.replace(/:[\w]+/g, '[^/?]+') + '(?:\\?.*)?$');

// Mock response - repeat multiple times to handle code samples with multiple API calls
const responseBody = { bank_account_holder_verifications: {} };
for (let i = 0; i < 5; i++) {
nock('https://api.gocardless.com').post(pathPattern).query(true).reply(200, responseBody);
}

// Suppress console.log from code samples
const originalLog = console.log;
// eslint-disable-next-line @typescript-eslint/no-empty-function
console.log = () => {};

try {
/* eslint-disable @typescript-eslint/no-unused-vars */
const verification = await client.bankAccountHolderVerifications.create({
type: 'confirmation_of_payee',
links: {
bank_account: 'BA123',
},
});
/* eslint-enable @typescript-eslint/no-unused-vars */
} finally {
console.log = originalLog;
}
});

test('get code sample executes without error', async () => {
// Convert :param placeholders to regex wildcards for flexible matching
const stubUrl = '/bank_account_holder_verifications/:identity';
const pathPattern = new RegExp('^' + stubUrl.replace(/:[\w]+/g, '[^/?]+') + '(?:\\?.*)?$');

// Mock response - repeat multiple times to handle code samples with multiple API calls
const responseBody = { bank_account_holder_verifications: {} };
for (let i = 0; i < 5; i++) {
nock('https://api.gocardless.com').get(pathPattern).query(true).reply(200, responseBody);
}

// Suppress console.log from code samples
const originalLog = console.log;
// eslint-disable-next-line @typescript-eslint/no-empty-function
console.log = () => {};

try {
/* eslint-disable @typescript-eslint/no-unused-vars */
const resp = await client.bankAccountHolderVerifications.find('BAHV123');
/* eslint-enable @typescript-eslint/no-unused-vars */
} finally {
console.log = originalLog;
}
});
});
89 changes: 89 additions & 0 deletions src/codeSamples/bank_authorisationsCodeSamples.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// WARNING: Do not edit by hand, this file was generated by Crank:
//
// https://github.com/gocardless/crank
//

// Code Sample Tests
// These tests verify that the documentation code samples are syntactically valid
// and can execute against a mocked API without errors.
//
// IMPORTANT: These tests do NOT verify business logic - they only verify that
// the code samples compile and execute without syntax errors.

import * as nock from 'nock';
import { GoCardlessClient } from '../client';
import { Environments } from '../constants';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import * as Types from '../types/Types';

describe('BankAuthorisations Code Samples', () => {
const token = 'SECRET_TOKEN';
let client: GoCardlessClient;

beforeAll(() => {
nock.disableNetConnect();
});

beforeEach(() => {
client = new GoCardlessClient(token, Environments.Live, {});
});

afterEach(() => {
nock.cleanAll();
});

test('create code sample executes without error', async () => {
// Convert :param placeholders to regex wildcards for flexible matching
const stubUrl = '/bank_authorisations';
const pathPattern = new RegExp('^' + stubUrl.replace(/:[\w]+/g, '[^/?]+') + '(?:\\?.*)?$');

// Mock response - repeat multiple times to handle code samples with multiple API calls
const responseBody = { bank_authorisations: {} };
for (let i = 0; i < 5; i++) {
nock('https://api.gocardless.com').post(pathPattern).query(true).reply(200, responseBody);
}

// Suppress console.log from code samples
const originalLog = console.log;
// eslint-disable-next-line @typescript-eslint/no-empty-function
console.log = () => {};

try {
/* eslint-disable @typescript-eslint/no-unused-vars */
const bankAuthorisation = await client.bankAuthorisations.create({
redirect_uri: 'https://my-company.com/landing',
links: {
billing_request: 'BRQ123',
},
});
/* eslint-enable @typescript-eslint/no-unused-vars */
} finally {
console.log = originalLog;
}
});

test('get code sample executes without error', async () => {
// Convert :param placeholders to regex wildcards for flexible matching
const stubUrl = '/bank_authorisations/:identity';
const pathPattern = new RegExp('^' + stubUrl.replace(/:[\w]+/g, '[^/?]+') + '(?:\\?.*)?$');

// Mock response - repeat multiple times to handle code samples with multiple API calls
const responseBody = { bank_authorisations: {} };
for (let i = 0; i < 5; i++) {
nock('https://api.gocardless.com').get(pathPattern).query(true).reply(200, responseBody);
}

// Suppress console.log from code samples
const originalLog = console.log;
// eslint-disable-next-line @typescript-eslint/no-empty-function
console.log = () => {};

try {
/* eslint-disable @typescript-eslint/no-unused-vars */
const resp = await client.bankAuthorisations.find('BAU123');
/* eslint-enable @typescript-eslint/no-unused-vars */
} finally {
console.log = originalLog;
}
});
});
Loading
Loading