Recurring payments

Perform merchant-initiated or card-on-file transactions.

Overview

Card components support two types of recurring payments:

  • Merchant-initiated transactions (MITs): For subscription-based payments where the customer signs up and pays initially, and you then automatically charge them at a specified interval. This means there's no customer action needed for renewals.
  • Card-on-file transactions with shopper consent: For storing payment methods with customer consent for future use. This allows you to provide a faster checkout experience for returning customers.

Merchant-initiated

Step 1: Set up the SDK config

To set up a recurring payment, you need to include the recurring object in your transaction data of your sdkConfig. You'll need to specify the frequency of this recurring payment (in days), but the expiration data is optional.

// Configure subscription transaction data
const transactionData = {
  amount: 9.99,
  currency: 'USD',
  entryType: 'Ecom',
  intent: 'Authorisation',
  merchantTransactionId: 'sub-setup-123',
  merchantTransactionDate: () => new Date().toISOString(),
  shopper: {
    email: '[email protected]',
    firstName: 'John',
    lastName: 'Doe'
  },
  recurring: {
    frequencyInDays: 30,
    frequencyExpiration: '2025-12-31'
  }
};

// Create complete SDK configuration
const sdkConfig = {
  transactionData,           // ← This connects Step 1 to Step 2
  apiKey: 'your-api-key',
  environment: 'sandbox',
  // ... other SDK settings
};

Step 2: Create your components

Next, you're going to use your sdkConfig to create the relevant components. When the customer clicks submit, the recurring payments will start.

const cardNumberComponent = new CardNumberComponent(sdkConfig);
const cardExpiryComponent = new CardExpiryDateComponent(sdkConfig);
const cardCvcComponent = new CardCvcComponent(sdkConfig);

const cardSubmitComponent = new CardSubmitComponent(sdkConfig, {
  cardNumberComponent,
  cardExpiryDateComponent: cardExpiryComponent,
  cardCvcComponent,
  useCardOnFile: false,
  submitText: 'Start subscription',
  onPostAuthorisation: (result) => {
    if (result.success) {
      console.log('Subscription started!');
      // The recurring data from step 1 was automatically used
    }
  }
});

// Mount components
cardNumberComponent.mount('card-number');
cardExpiryComponent.mount('card-expiry');
cardCvcComponent.mount('card-cvc');
cardSubmitComponent.mount('submit-button');

Card-on-file

Step 1: Set up the initial transaction

To save a card, you'll first need to ask for the customer's consent during the initial transaction. If the customer ticks the checkbox, their card details will be saved as a token for future reuse.

const transactionData = {
  amount: 99.99,
  currency: 'USD',
  entryType: 'Ecom',
  intent: 'Authorisation',
  merchantTransactionId: 'order-12345',
  merchantTransactionDate: () => new Date().toISOString(),
  shopper: {
    email: '[email protected]',
    firstName: 'John',
    lastName: 'Doe'
  }
};

const sdkConfig = {
  transactionData,
  // ... other SDK configuration
};

// Create the card consent component
const cardConsentComponent = new CardConsentComponent(sdkConfig, {
  label: 'Save this card for faster checkout in the future',
  class: 'consent-checkbox'
});

// Create the card input components
const cardNumberComponent = new CardNumberComponent(sdkConfig);
const cardExpiryComponent = new CardExpiryDateComponent(sdkConfig);
const cardCvcComponent = new CardCvcComponent(sdkConfig);

// Create the card submit component
const cardSubmitComponent = new CardSubmitComponent(sdkConfig, {
  cardNumberComponent,
  cardExpiryDateComponent: cardExpiryComponent,
  cardCvcComponent,
  cardConsentComponent, // Include consent component
  useCardOnFile: false, // Use a new card, not a saved card
  submitText: 'Pay & save card',
  onPostTokenisation: (tokenResult) => {
    console.log('Card tokenised and saved:', tokenResult);
    // Store the token reference in your system
  },
  onPostAuthorisation: (submitResult) => {
    if (submitResult.success) {
      console.log('Payment successful, card saved for future use');
    }
  }
});

// Mount components
cardNumberComponent.mount('card-number');
cardExpiryComponent.mount('card-expiry');
cardCvcComponent.mount('card-cvc');
cardConsentComponent.mount('card-consent');
cardSubmitComponent.mount('submit-button');

Step 2: Set up subsequent transactions

For subsequent transactions, you can use choose from:

  • A traditional card selection: This is best for customers with multiple saved cards and provides full card management (edit, delete, update) but requires more interaction from the customer. It uses the card-on-file component.
  • A streamlined click-once checkout: This is ideal for customers with one primary payment method and offers a faster checkout experience, but less flexibility. It uses the standalone click-once component.
  • A hybrid approach: This combines the two components. It defaults to displaying a primary card, but allows customers to choose a different one if they want to.
const cardOnFileComponent = new CardOnFileComponent(sdkConfig, {
  limitTokens: 10,
  orderBy: {
    lastUsageDate: { direction: 'desc', priority: 1 }
  },
  deleteCardButtonAriaLabel: 'Delete this card',
  editCardInformationAriaLabel: 'Edit card details'
});
const clickOnceComponent = new ClickOnceStandaloneComponent(sdkConfig, {
  limitTokens: 1,
  isCvcRequired: false, 
  submitText: 'Complete purchase',
  disableCardSelection: true
});
// Primary checkout with click-once
const quickCheckout = new ClickOnceStandaloneComponent(sdkConfig, {
  limitTokens: 1,
  submitText: 'Pay with Primary Card',
  class: 'primary-checkout'
});

// Alternative with full card selection
const alternativeCheckout = new CardOnFileComponent(sdkConfig, {
  limitTokens: 5,
  class: 'alternative-checkout hidden' // Initially hidden
});

// Toggle between interfaces
const showAlternativeButton = document.createElement('button');
showAlternativeButton.textContent = 'Use different card';
showAlternativeButton.onclick = () => {
  document.querySelector('.primary-checkout').classList.add('hidden');
  document.querySelector('.alternative-checkout').classList.remove('hidden');
};