# Recurring payments

Set up recurring payments using card components.

## 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 using your own scheduling system.
* **Card-on-file transactions with shopper consent:** For storing payment methods with customer consent for future use, allowing faster checkout for returning customers.


PXP doesn't provide an automatic payment scheduler. You must implement your own scheduling system to initiate subsequent recurring charges via the Transactions API.

## Merchant-initiated recurring payments

Merchant-initiated recurring payments require two steps: an initial setup transaction using the SDK, followed by subsequent charges initiated from your backend using the Transactions API.

### Step 1: Set up the initial recurring payment

Use the card components to collect the customer's card details and set up the recurring payment. Include the `recurring` object in your transaction data to specify the payment frequency.

When you include the `recurring` object in your transaction data, the SDK automatically sets the `processingModel` to `MerchantInitiatedInitialRecurring` when creating the transaction request.


```typescript
// Configure initial subscription transaction
const transactionData = {
  amount: 9.99,
  currency: 'USD',
  entryType: 'Ecom',
  intent: {
    card: 'Authorisation'
  },
  merchantTransactionId: 'sub-setup-123',
  merchantTransactionDate: () => new Date().toISOString(),
  recurring: {
    frequencyInDays: 30,
    frequencyExpiration: '2025-12-31'
  }
};

// Create SDK configuration
const sdkConfig = {
  environment: 'test',
  session: {
    sessionId: 'your-session-id'
  },
  transactionData,
  ownerId: 'your-owner-id'
  // ... other SDK settings
};

// Create card components
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: async (data) => {
    console.log('Subscription setup completed');
    
    // Get full result from backend
    const result = await getAuthorisationResultFromGateway(
      data.merchantTransactionId, 
      data.systemTransactionId
    );
    
    if (result.state === 'Authorised' || result.state === 'Captured') {
      console.log('Subscription started!');
      
      // Store the gatewayTokenId and subscription details in your system
      // You'll need these for subsequent charges
      await saveSubscription({
        gatewayTokenId: result.fundingData.gatewayTokenId,
        customerId: 'customer-123',
        amount: 9.99,
        currency: 'USD',
        frequency: 30,
        nextChargeDate: calculateNextChargeDate(30)
      });
    }
  }
});

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

### Step 2: Charge subsequent recurring payments

For subsequent recurring charges, you must initiate transactions from your backend using the Transactions API. PXP doesn't automatically charge customers based on the `frequencyInDays`, so you need to implement your own scheduler to trigger these charges.

/v1/transactions

Use the following request to charge a customer for a subsequent recurring payment. Replace the full card details with the `gatewayTokenId` you received from the initial transaction.


```json
{
  "merchant": "MERCHANT-1",
  "site": "SITE-1",
  "merchantTransactionId": "sub-charge-456",
  "merchantTransactionDate": "2025-02-27T08:51:02.826Z",
  "transactionMethod": {
    "intent": "Purchase",
    "entryType": "Ecom",
    "fundingType": "Card"
  },
  "fundingData": {
    "card": {
      "gatewayTokenId": "5fbd77ce-02c1-40ed-94bc-1016660b7512"
    }
  },
  "amounts": {
    "transaction": 9.99,
    "currencyCode": "USD"
  },
  "recurring": {
    "processingModel": "MerchantInitiatedSubsequentRecurring"
  }
}
```

| Parameter | Description |
|  --- | --- |
| `merchant`string (≤ 20 characters) | Your unique merchant identifier, as assigned by PXP. |
| `site`string (≤ 20 characters) | Your unique site identifier, as assigned by PXP. |
| `merchantTransactionId`string (≤ 50 characters) | A unique identifier for this transaction. |
| `merchantTransactionDate`date-time | The date and time when the transaction happened, in ISO 8601 format. |
| `transactionMethod`object | Details about the transaction method. |
| `transactionMethod.intent`string (enum) | The payment intent. For recurring charges, use `Purchase` or `Authorisation`.Possible values:`Authorisation``Purchase` |
| `transactionMethod.entryType`string | The entry type. For e-commerce transactions, this is always `Ecom`. |
| `transactionMethod.fundingType`string | The funding type. For card transactions, this is always `Card`. |
| `fundingData`object | Details about the payment method used for the transaction. |
| `fundingData.card`object | Details about the card. |
| `fundingData.card.gatewayTokenId`string (UUID) | The gateway token ID from the initial transaction. This replaces the need to provide full card details. |
| `amounts`object | Details about the transaction amount. |
| `amounts.transaction`number | The transaction amount. |
| `amounts.currencyCode`string (3 characters) | The currency code in ISO 4217 format. |
| `recurring`object | Details about the recurring payment. |
| `recurring.processingModel`string (enum) | The processing model for the recurring payment. Use `MerchantInitiatedSubsequentRecurring` for standard subscription charges.Possible values:`MerchantInitiatedSubsequentRecurring``MerchantInitiatedReAuthorisation``MerchantInitiatedResubmission``MerchantInitiatedDelayedCharge``MerchantInitiatedNoShow` |


#### Response example

If your request is successful, you'll receive a `200` response with the transaction state. You'll also receive a webhook notification.


```json
{
  "state": "Captured",
  "stateData": {},
  "approvalCode": "210693",
  "merchantTransactionId": "sub-charge-456",
  "systemTransactionId": "635b1b51-4a27-4d23-8c9f-8150ff7eb9dd",
  "merchantTransactionDate": "2025-02-27T08:51:02.826Z",
  "fundingData": {
    "cardScheme": "Visa",
    "maskedPrimaryAccountNumber": "411111******1111",
    "expiryMonth": "09",
    "expiryYear": "2025",
    "gatewayTokenId": "5fbd77ce-02c1-40ed-94bc-1016660b7512",
    "providerResponse": {
      "provider": "pxpfinancial",
      "code": "00",
      "paymentAccountReference": "637607302178175469",
      "authorisedAmount": 9.99
    }
  }
}
```

[Learn more about initiating transactions via API](/guides/transactions/initiate-transactions)

## Card-on-file recurring payments

### 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.


```typescript
const transactionData = {
  amount: 99.99,
  currency: 'USD',
  entryType: 'Ecom',
  intent: {
    card: 'Authorisation'
  },
  merchantTransactionId: 'order-12345',
  merchantTransactionDate: () => new Date().toISOString()
};

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: async (data) => {
    console.log('Card tokenised. Gateway Token ID:', data.gatewayTokenId);
    
    // Get full token details from backend if needed for validation
    const tokenDetails = await getTokenDetailsFromBackend(data.gatewayTokenId);
    console.log('Card saved for future use');
    
    // Store the token reference in your system
  },
  onPostAuthorisation: async (data) => {
    console.log('Payment completed');
    
    // Get full result from backend
    const result = await getAuthorisationResultFromGateway(data.merchantTransactionId, data.systemTransactionId);
    
    if (result.state === 'Authorised' || result.state === 'Captured') {
      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 with saved cards, you can 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.


When using card-on-file or click-once components, the SDK automatically sets the `processingModel` to `CardOnFileShopperInitiated` for subsequent transactions.


```typescript Card-on-file
const cardOnFileComponent = new CardOnFileComponent(sdkConfig, {
  limitTokens: 10,
  orderBy: {
    lastUsageDate: { direction: 'desc', priority: 1 }
  },
  deleteCardButtonAriaLabel: 'Delete this card',
  editCardInformationAriaLabel: 'Edit card details'
});
```


```typescript Click-once standalone
const clickOnceComponent = new ClickOnceStandaloneComponent(sdkConfig, {
  limitTokens: 1,
  isCvcRequired: false, 
  submitText: 'Complete purchase',
  disableCardSelection: true
});
```


```typescript Hybrid
// 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');
};
```

### Using the API for backend-initiated card-on-file transactions

While the components above handle shopper-initiated transactions (where the customer is present and actively making a payment), you can also use the Transactions API to charge a saved card from your backend without the customer being present.

This is useful for scenarios like:

- Auto-renewing a service when the customer returns to your platform.
- Charging for usage-based billing at the end of a billing period.
- Processing payments for bookings or reservations.


To charge a saved card via API, use the `CardOnFileShopperInitiated` processing model with the `gatewayTokenId`:


```json
{
  "merchant": "MERCHANT-1",
  "site": "SITE-1",
  "merchantTransactionId": "charge-001",
  "merchantTransactionDate": "2025-02-27T08:51:02.826Z",
  "transactionMethod": {
    "intent": "Purchase",
    "entryType": "Ecom",
    "fundingType": "Card"
  },
  "fundingData": {
    "card": {
      "gatewayTokenId": "5fbd77ce-02c1-40ed-94bc-1016660b7512"
    }
  },
  "amounts": {
    "transaction": 49.99,
    "currencyCode": "USD"
  },
  "recurring": {
    "processingModel": "CardOnFileShopperInitiated"
  }
}
```

[Learn more about card-on-file payments via API](/guides/transactions/e-commerce/recurring-payments#set-up-a-card-on-file-recurring-payment).