GoCardlessDeveloper Docs
Create a sandbox account

GoCardless Remember Me Components#

View as Markdown

GoCardless Components is a JavaScript component library aimed at providing GoCardless integrators with customisable functionality for creating checkout flows without the need to build their own from scratch using the API.

This document explains how to integrate the Remember Me components into your checkout flow.

1. Prerequisites#

1.1. Billing Request Setup#

Remember Me components require a valid Billing Request ID to function. Ensure you can:

  1. Create billing requests via the GoCardless API
  2. Pass billing request IDs to your frontend
  3. Fulfil billing requests from your backend

If you haven't created a billing request yet, see the GoCardless API Documentation.

1.2. Requirements#

Before implementing Remember Me components, ensure you have:

  1. Active GoCardless Account with the GoCardless Components upgrade activated

    • Contact your implementation manager to enable this
  2. Public Token with appropriate scopes

    To generate a public token via the dashboard, go to https://manage-sandbox.gocardless.com/developers/public-tokens and do the following:

    1. Select Create public token
    2. Enter a Name to identify this token
    3. Provide the Domain where you will be using the GoCardless Component
    4. Tick the ui_components_remember_me scope
    5. Click Create
  3. Billing Request implemented

    • Must have an existing billing request created

2. Components#

2.1. Component Types#

GoCardless provides two Remember Me components:

ReturnFlow Component#

Used at the start of checkout to recognise and verify returning customers who have previously saved their details with GoCardless.

  • Use when: Customer enters checkout flow
  • Purpose: Recognise customers via email and complete verification to pre-fill payment details
  • Recommended location: Top of checkout page, before any form fields

ReturnFlow Component

Signup Component#

Used during checkout to allow new customers to opt-in to saving their details for future use.

  • Use when: New customer completes payment or at any point in the checkout flow as long as the billing request has been created or started.
  • Purpose: Saves customer details for a faster checkout experience in the future
  • Recommended location: Payment confirmation page, below success message or inline during checkout as opt-in checkbox

Signup Component

3. Installation#

3.1. Add GoCardless Components Library#

React Dependency#

The GoCardless Components library requires React as a global variable:

<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>

GoCardless Components#

Add the latest version of the GoCardless Components library:

<script src="https://storage.googleapis.com/gc-prd-ui-components-production/latest/index.js"></script>

To patch the styling of the Signup component of the GoCardless Components library, link the compiled CSS globally in the build:

<link
  rel="stylesheet"
  href="https://storage.googleapis.com/gc-prd-ui-components-production/latest/ui-components.css"
/>

Using the latest version of the GoCardless Components library is advised during the development and testing phases of the integration.

However, for go-live, it is recommended to use a specific version of the GoCardless Components library:

<link
  rel="stylesheet"
  href="https://storage.googleapis.com/gc-prd-ui-components-production/1.4.0/ui-components.css"
/>
<script src="https://storage.googleapis.com/gc-prd-ui-components-production/1.4.0/index.js"></script>

3.2. TypeScript Configuration#

If you're using TypeScript, add global type definitions to enable proper type checking for the GoCardless Components library.

Create Global Type Definitions#

Create a file at src/types/global.d.ts with the following content:

import { BillingRequestResource } from "@gocardless/api/dashboard/types";
 
interface RememberMeReturnConfigurationParams {
  publicToken: string;
  environment: string;
  scheme: string;
  enableAnalytics?: boolean;
  appearance?: {
    variables?: Record<string, string>;
  };
}
 
interface MountableComponent {
  mount: () => void;
  unmount: () => void;
}
 
interface LoadConfigResponse {
  success: boolean;
  error?: Error;
}
 
enum Environment {
  Sandbox = "sandbox",
  Live = "live",
}
 
enum Scheme {
  Bacs = "bacs",
}
 
interface RememberMeSignupProps {
  billingRequestId: string;
  email: string;
  onError?: (error: Error) => void;
  onSuccess?: (result: { email: string; phoneNumber?: null | string }) => void;
}
 
interface RecognisedCustomerData {
  email: string;
  phoneNumber?: string;
  customerId: string;
  bankAccountId: string;
}
 
interface CustomerRecognitionResult {
  recognised: boolean;
  customerData?: RecognisedCustomerData;
}
 
interface VerificationResult {
  success: boolean;
  customerId?: string;
  billingRequest?: BillingRequestResource;
  error?: Error;
}
 
enum VerificationExitPoint {
  UserClosed = "user_closed",
  VerificationFailed = "verification_failed",
  VerificationSuccess = "verification_success",
}
 
export interface RememberMeReturnFlowProps {
  billingRequestId: string;
  defaultEmail?: string;
  onChange?: (email: string) => void;
  onCustomerRecognitionCheck?: (result: CustomerRecognitionResult) => void;
  onError?: (error: Error) => void;
  onVerificationAttempt?: (result: VerificationResult) => void;
  onVerificationExit?: (exitPoint: VerificationExitPoint) => void;
}
 
declare global {
  interface Window {
    GcComponents: {
      createRememberMeReturnFlowComponent: (
        selector: string,
        options: RememberMeReturnFlowProps,
      ) => MountableComponent;
      createRememberMeSignupComponent: (
        selector: string,
        options: RememberMeSignupProps,
      ) => MountableComponent;
      createErrorComponent: (selector: string) => MountableComponent;
      loadConfig: (config: RememberMeReturnConfigurationParams) => Promise<LoadConfigResponse>;
      Environment: typeof Environment;
      Scheme: typeof Scheme;
    };
  }
}
 
export {};

This provides type safety for the window.GcComponents global object and all callback interfaces.

Update tsconfig.json#

Ensure your tsconfig.json includes the types directory:

{
  "compilerOptions": {
    "typeRoots": ["./node_modules/@types", "./src/types"]
  },
  "include": ["src"]
}

3.3. Using the useGoCardlessComponents Hook (React)#

For React applications, create a custom hook to detect when the GoCardless Components library has loaded:

Create the Hook#

Create a file at src/hooks/useGoCardlessComponents.ts:

import { useState, useEffect } from "react";
 
export function useGoCardlessComponents() {
  const [isLoaded, setIsLoaded] = useState(false);
  const [error, setError] = useState<Error | null>(null);
 
  useEffect(() => {
    let attempts = 0;
    const maxAttempts = 10;
 
    const checkComponentsLoaded = () => {
      attempts++;
 
      // Check if the global GcComponents library is available
      if (window.GcComponents) {
        setIsLoaded(true);
        return;
      }
 
      if (attempts >= maxAttempts) {
        const err = new Error(
          "GoCardless Components failed to load. Please check your internet connection.",
        );
        setError(err);
        return;
      }
 
      // Retry after 100ms
      setTimeout(checkComponentsLoaded, 100);
    };
 
    checkComponentsLoaded();
  }, []);
 
  return { isLoaded, error };
}

Using the Hook#

import { useGoCardlessComponents } from '../hooks/useGoCardlessComponents';
 
const CheckoutPage = () => {
  const { isLoaded, error } = useGoCardlessComponents();
 
  if (error) {
    return <div>Error: {error.message}</div>;
  }
 
  if (!isLoaded) {
    return <div>Loading GoCardless Components...</div>;
  }
 
  // Proceed with rendering components
  return <div>{/* Your checkout form */}</div>;
};

3.4. Configure Components#

Load your configuration before creating components:

const config = {
  publicToken: YOUR_PUBLIC_TOKEN,
  environment: GcComponents.Environment.Sandbox, // or Environment.Live
  scheme: GcComponents.Scheme.Bacs,
  enableAnalytics: true, // Optional: defaults to true
};
 
GcComponents.loadConfig(config);

Configuration Options#

PropertyDescriptionRequired
publicTokenYour public token with the ui_components_remember_me scopeYes
environmentSandbox or LiveYes
schemePayment scheme (currently only Bacs supported)Yes
enableAnalyticsEnable usage analytics (default: true)No

3.5. Select where to mount GoCardless Components#

When creating a GC Component, you need to specify where on your webpage it will be mounted. Create a <div> in your app in the place you want the GC Component to be displayed and give it an intuitive identifier (i.e. gc-billing-return-flow), to select when creating a component.

Example:

<div id="gc-billing-return-flow"></div>
 
<div id="gc-billing-signup"></div>

4. Frontend Implementation#

4.1. Implementing ReturnFlow for Returning Customers#

The ReturnFlow component should be placed at the beginning of your checkout flow to recognise and verify returning customers.

Basic Implementation#

const CheckoutPage = () => {
  const [billingRequestId] = useState('BR123456'); // From your backend
  const [isVerified, setIsVerified] = useState(false);
 
  const handleCustomerRecognised = (result) => {
    if (result.recognised) {
      console.log('Customer recognised', result.customerData);
      // Customer found - they will be prompted for verification
    } else {
      console.log('Customer not recognised - show standard checkout');
      // Show your normal checkout form
    }
  };
 
  const handleVerificationSuccess = (result) => {
    if (result.success) {
      console.log('Verification complete');
      setIsVerified(true);
      // Pre-fill customer details from result.billingRequest
      // Navigate to confirmation page
    }
  };
 
  const handleVerificationExit = (exitPoint) => {
    if (exitPoint === 'user_closed') {
      // User cancelled - show manual entry option
      console.log('User cancelled verification');
    } else if (exitPoint === 'verification_failed') {
      // Verification failed - offer retry or manual entry
      console.log('Verification failed');
    }
  };
 
  const handleError = (error) => {
    console.error('Remember Me error:', error);
    // Fallback to standard checkout
  };
 
  return (
    <div>
      <h1>Checkout</h1>
 
      <RememberMeReturnFlow
        billingRequestId={billingRequestId}
        defaultEmail="" // Optional: pre-fill email if available
        onCustomerRecognitionCheck={handleCustomerRecognised}
        onVerificationAttempt={handleVerificationSuccess}
        onVerificationExit={handleVerificationExit}
        onError={handleError}
      />
 
      {/* Show manual checkout form if needed */}
      {!isVerified && <StandardCheckoutForm />}
    </div>
  );
};

ReturnFlow Props#

PropTypeRequiredDescription
billingRequestIdstringYesUnique billing request ID for this transaction
defaultEmailstringNoPre-populate email field (e.g., from logged-in session)
onCustomerRecognitionCheckfunctionNoCalled when customer recognition check completes
onVerificationAttemptfunctionNoCalled when verification is attempted
onVerificationExitfunctionNoCalled when user exits verification flow
onErrorfunctionNoGlobal error handler
onChangefunctionNoHandles the input change when the payer enters their email address

Callback Data Structures#

onCustomerRecognitionCheck:

{
  recognised: boolean,
  customerData?: {
    customerId: string,
    bankAccountId: string,
    email: string,
    phoneNumber?: string
  }
}

onVerificationAttempt:

{
  success: boolean,
  customerId?: string,
  billingRequest?: BillingRequestResource,
  error?: Error
}

onVerificationExit:

// exitPoint values:
"user_closed" | "verification_failed" | "verification_success";

4.2. Using ReturnFlow Inside a Form#

The ReturnFlow component can be embedded directly inside your checkout form to provide a seamless experience. The component replaces the email input field and handles customer recognition and verification.

Implementation Example#

import React, { useState, useEffect, useRef } from 'react';
import { useGoCardlessComponents } from '../hooks/useGoCardlessComponents';
 
const CheckoutForm = ({
  billingRequestId,
  onCustomerRecognitionCheck,
  onVerificationAttempt,
  onVerificationExit,
  onError,
}) => {
  const { isLoaded } = useGoCardlessComponents();
  const componentInstance = useRef(null);
  const [formData, setFormData] = useState({
    email: '',
    firstName: '',
    lastName: '',
    // ... other fields
  });
 
  // Mount the RememberMeReturnFlow component
  useEffect(() => {
    if (!isLoaded || !billingRequestId || !window.GcComponents) return;
 
    const config = {
      publicToken: YOUR_PUBLIC_TOKEN,
      environment: GcComponents.Environment.Sandbox,
      scheme: GcComponents.Scheme.Bacs,
    };
 
    const mountComponent = async () => {
      const { success } = await window.GcComponents.loadConfig(config);
 
      if (success) {
        const options = {
          billingRequestId,
          defaultEmail: formData.email,
          // Sync email changes to form state
          onChange: (email) => {
            setFormData((prev) => ({ ...prev, email }));
          },
          onCustomerRecognitionCheck,
          onVerificationAttempt,
          onVerificationExit,
          onError,
        };
 
        componentInstance.current =
          window.GcComponents.createRememberMeReturnFlowComponent(
            '#gc-remember-me-return-flow',
            options
          );
        componentInstance.current.mount();
      }
    };
 
    mountComponent();
 
    // Cleanup on unmount
    return () => {
      if (componentInstance.current) {
        componentInstance.current.unmount();
      }
    };
  }, [isLoaded, billingRequestId]);
 
  const handleSubmit = (e) => {
    e.preventDefault();
    // Submit form data
  };
 
  return (
    <form onSubmit={handleSubmit}>
      <h2>Checkout</h2>
 
      {/* ReturnFlow component mounts here and replaces email input */}
      <div className="form-group">
        <div id="gc-remember-me-return-flow"></div>
      </div>
 
      <div className="form-row">
        <div className="form-group">
          <label>First Name *</label>
          <input
            type="text"
            name="firstName"
            value={formData.firstName}
            onChange={(e) => setFormData({ ...formData, firstName: e.target.value })}
          />
        </div>
 
        <div className="form-group">
          <label>Last Name *</label>
          <input
            type="text"
            name="lastName"
            value={formData.lastName}
            onChange={(e) => setFormData({ ...formData, lastName: e.target.value })}
          />
        </div>
      </div>
 
      {/* Additional form fields */}
 
      <button type="submit">Complete Checkout</button>
    </form>
  );
};

Key Points#

  • The ReturnFlow component replaces the email input field in your form
  • Use the onChange callback to sync the email value to your form state
  • Mount the component in a dedicated <div> with a unique ID
  • The component handles email recognition and verification automatically
  • After verification, the customer's email is available in the onVerificationAttempt callback

4.3. Auto-filling Form Inputs After Verification#

When a returning customer successfully verifies their identity, you should auto-fill the form fields with their saved information from the billing request.

Extracting Customer Data#

The onVerificationAttempt callback receives a VerificationResult object containing the billing request with customer data:

const handleVerificationSuccess = (result: VerificationResult) => {
  if (result.success && result.billingRequest) {
    const { resources } = result.billingRequest;
    const customer = resources?.customer;
    const billingDetail = resources?.customer_billing_detail;
    const bankAccount = resources?.customer_bank_account;
 
    // Extract and parse customer data
    if (customer && billingDetail) {
      // Parse address line 1 to extract house number and street
      const addressLine1 = billingDetail.address_line1 || "";
      const addressParts = addressLine1.match(/^(\S+)\s+(.+)$/);
 
      const customerData = {
        email: customer.email,
        firstName: customer.given_name || "",
        lastName: customer.family_name || "",
        houseNumber: addressParts?.[1] || "",
        flatNumber: billingDetail.address_line2 || addressLine1,
        streetName: addressParts?.[2] || addressLine1,
        townCity: billingDetail.city || "",
        postcode: billingDetail.postal_code || "",
        county: billingDetail.region || "",
      };
 
      // Update form state with customer data
      setFormData(customerData);
    }
 
    // Store masked bank account for display
    if (bankAccount) {
      setMaskedBankAccount(bankAccount);
    }
 
    // Mark customer as verified
    setIsVerified(true);
  }
};

Updating Form Inputs#

Make sure your form inputs are controlled components that respond to state changes:

// Use useEffect to update form data when verification completes
useEffect(() => {
  if (verifiedCustomerData) {
    setFormData((prev) => ({
      ...prev,
      ...verifiedCustomerData,
    }));
  }
}, [verifiedCustomerData]);
 
// Form inputs automatically reflect the updated state
<input
  type="text"
  name="firstName"
  value={formData.firstName}  // This updates automatically
  onChange={handleChange}
/>

Displaying Saved Bank Account#

For verified customers with saved bank accounts, display the masked account details:

{maskedBankAccount ? (
  <div className="masked-account-section">
    <div className="masked-account-info">
      <p><strong>Account Holder:</strong> {maskedBankAccount.account_holder_name}</p>
      <p><strong>Account Number:</strong> ****{maskedBankAccount.account_number_ending}</p>
      {maskedBankAccount.bank_name && (
        <p><strong>Bank:</strong> {maskedBankAccount.bank_name}</p>
      )}
    </div>
    <button type="button" onClick={() => setShowBankAccountEdit(true)}>
      Edit Bank Details
    </button>
  </div>
) : (
  // Show manual bank account input fields
  <div className="bank-account-fields">
    {/* Account number, sort code, etc. */}
  </div>
)}

4.4. Implementing Signup for New Customers#

The Signup component should be shown to new customers during the flow or after successful payment to allow them to opt-in to Remember Me.

Basic Implementation#

import { RememberMeSignup } from '@gocardless/ui-components';
 
const PaymentConfirmationPage = () => {
  const [billingRequestId] = useState('BR123456');
  const [customerEmail] = useState('customer@example.com');
  const [signupComplete, setSignupComplete] = useState(false);
 
  const handleSignupSuccess = (result) => {
    console.log('Customer enrolled in Remember Me:', result);
    setSignupComplete(true);
    // Optional: Track analytics
    // analytics.track('remember_me_enrolled', { customerId: result.id });
  };
 
  const handleSignupError = (error) => {
    console.error('Signup failed:', error);
    // Optional: Show user-friendly message
    // Don't block user from proceeding if signup fails
  };
 
  return (
    <div>
      <h1>Payment Successful!</h1>
 
      <p>Thank you for your payment.</p>
 
      {!signupComplete && (
        <RememberMeSignup
          billingRequestId={billingRequestId}
          email={customerEmail}
          onSuccess={handleSignupSuccess}
          onError={handleSignupError}
        />
      )}
 
      {signupComplete && (
        <p>Your details have been saved for faster checkout next time</p>
      )}
    </div>
  );
};

Signup Props#

PropTypeRequiredDescription
billingRequestIdstringYesBilling request ID for this transaction
emailstringYesCustomer's email address (already validated)
onSuccessfunctionNoCalled when customer details are successfully saved
onErrorfunctionNoCalled if enrollment fails

Callback Data Structures#

onSuccess:

{
  email: string;
  phoneNumber: string;
}

5. Customisation#

5.1. Theming#

Both components use the GoCardless theme by default. The theme automatically adapts to create a cohesive experience.

The GC Component can be customised to fit the UI of your page. This is done through the optional appearance parameter provided in the configuration step.

Example:

const appearanceVariables = {
  variables: {
    backgroundColor: "#bfe6f2",
    inputBorderRadius: "0px",
    wrapperBorderRadius: "0",
    textFontFamily: "Georgia, serif",
  },
};
 
const config = {
  publicToken: YOUR_PUBLIC_TOKEN,
  environment: Environment.Sandbox,
  scheme: GcComponents.Scheme.Bacs,
  appearance: appearanceVariables,
};
 
GcComponents.loadConfig(config);

You can modify colours, fonts, borders, padding etc. for a variety of different on-screen elements. We recommend playing around with backgroundColor, textFontSize, textFontFamily, buttonColor and buttonBorderRadius to get started.

You may notice that GoCardless Components come with some default styling even when no appearance variables are provided. Any appearance variables you add will be applied on top of the default styles.

Full list of appearance variables and their default values:

export const defaultTheme: AppearanceVariables = {
  backgroundColor: "#F9F9F9",
 
  buttonBorderRadius: "32px",
  buttonColor: "#1C1B18",
  buttonHoverBackgroundColor: "#545048",
  buttonHoverColor: "#FAF9F7",
  buttonSubmitTextContent: "Set up this Direct Debit",
  buttonTextColor: "#FAF9F7",
  buttonTextFontSize: "12px",
  buttonTextFontWeight: FontWeight.medium,
 
  checkboxBackgroundColor: "#DFDFDF",
  checkboxSize: "16px",
  checkboxTextColor: "#1C1B18",
  checkboxTextFontSize: "14px",
  checkboxTextFontWeight: FontWeight.normal,
 
  dropdownFooterBackgroundColor: "#FaF9F7",
  dropdownFooterBorderColor: "#dFddda",
  dropdownHoverBackgroundColor: "#1C1B18",
  dropdownHoverTextColor: "#FFFFFF",
  dropdownPadding: "8px",
 
  formValidationErrorColor: "#C52F2F",
  formVerticalSpacing: "12px",
 
  hintTextFontSize: "12px",
  hintTextFontWeight: "600",
 
  inputBackgroundColor: "#FFFFFF",
  inputBorderColor: "#8C8579",
  inputBorderRadius: "4px",
  inputPadding: "12px",
  inputPlaceholderColor: "#6E685E",
  inputTextColor: "#1C1B18",
  inputTextFontWeight: FontWeight.normal,
 
  labelTextColor: "#545048",
  labelTextFontSize: "14px",
  labelTextFontWeight: FontWeight.normal,
 
  linkHoverTextColor: "#1E1751",
  linkTextColor: "#5949CB",
  linkTextFontSize: "12px",
  linkTextFontWeight: FontWeight.normal,
 
  loadingSpinnerColor: "#1e1a14",
 
  textColor: "#000000",
  textFontFamily: 'Inter, "Helvetica Neue", Helvetica, Arial, sans-serif',
  textFontSize: "14px",
  textFontWeight: FontWeight.normal,
 
  wrapperBorderRadius: "8px",
  wrapperMargin: "16px",
};

What's next?#