# Recurring payments

Learn how to implement and manage recurring payments with PXP Android Components.

## Overview

Recurring payments allow merchants to automatically charge customers on a regular basis for subscriptions, memberships, or ongoing services. The PXP Android SDK supports recurring payment implementation through card tokenisation and proper transaction configuration.

By implementing recurring payments, you can:

* Provide a seamless subscription experience for your customers.
* Reduce customer churn through automated billing.
* Ensure compliance with regional payment regulations.
* Improve cash flow predictability for your business.


## How recurring payments work

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

### Card tokenisation approach

The Android SDK implements recurring payments through a card tokenisation approach:

1. **Initial payment setup:** The customer provides their card details and consents to recurring billing through the SDK.
2. **Card tokenisation:** Card details are securely tokenised and a `gatewayTokenId` is returned.
3. **Subsequent payments:** Use the stored `gatewayTokenId` in backend API calls to charge customers without requiring their interaction.


### Recurring transaction data

Configure recurring payment information using the `RecurringData` class in your transaction configuration:


```kotlin
data class RecurringData(
    val frequencyInDays: Int? = null,
    val frequencyExpiration: String? = null
)
```

## Setting up recurring payments

### Initial subscription setup

Configure your SDK with recurring transaction data to establish the subscription:


```kotlin
val transactionData = TransactionData(
    amount = 9.99,
    currency = "USD",
    entryType = EntryType.Ecom,
    intent = TransactionIntentData(
        card = IntentType.Authorisation
    ),
    merchant = "Your Merchant Name",
    merchantTransactionId = "subscription-setup-${UUID.randomUUID()}",
    merchantTransactionDate = { Instant.now().toString() },
    recurring = RecurringData(
        frequencyInDays = 30, // Monthly billing
        frequencyExpiration = LocalDateTime.now().plusYears(1).toString()
    ),
    shopper = Shopper(
        email = "customer@example.com",
        firstName = "John",
        lastName = "Doe"
    )
)

When you include the `recurring` object in your transaction data, the SDK automatically sets the `processingModel` to `MerchantInitiatedInitialRecurring` when creating the transaction request. You don't need to set this manually.

val sdkConfig = PxpSdkConfig(
    environment = Environment.TEST,
    session = sessionConfig,
    transactionData = transactionData,
    clientId = "your-client-id",
    ownerType = "MerchantGroup",
    ownerId = "UnityGroup"
)
```

### Card-on-file for recurring payments

Use the card-on-file component to manage stored payment methods for recurring billing:


```kotlin
val cardOnFileConfig = CardOnFileComponentConfig().apply {
    // Configure for recurring payment selection
    onTokenSelected = { token ->
        Log.d("Recurring", "Selected token for recurring payment: ${token.id}")
        // Store token reference for future billing cycles
        storeRecurringPaymentToken(token.id)
    }
    
    onTokensLoaded = { tokens ->
        Log.d("Recurring", "Available payment methods: ${tokens.size}")
        // Display available payment methods
        displayRecurringPaymentOptions(tokens)
    }
}
```

When using saved cards from the card-on-file component, the SDK automatically sets the `processingModel` to `CardOnFileShopperInitiated` for shopper-present transactions. For backend-initiated charges with saved cards, use the Transactions API with the stored `gatewayTokenId`.

### New card setup for recurring

When customers add a new payment method for recurring billing:


```kotlin
val newCardConfig = NewCardComponentConfig().apply {
    // Configure card consent for recurring payments
    fields.cardConsent = CardConsentConfig(
        label = "I authorise recurring charges to this payment method",
        isRequired = true,
        onToggleChanged = { isConsented ->
            if (isConsented) {
                Log.d("Recurring", "Customer consented to recurring billing")
                enableRecurringPaymentSetup()
            }
        }
    )
}
```

## Managing recurring payments

### Charging subsequent recurring payments

After the initial setup, you initiate subsequent recurring charges from your backend using the PXP Transactions API with the stored `gatewayTokenId`.

/v1/transactions

**Example API request:**


```json
{
  "merchant": "MERCHANT-1",
  "site": "SITE-1",
  "merchantTransactionId": "recurring-charge-789",
  "merchantTransactionDate": "2025-03-15T10:30:00.000Z",
  "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 |
|  --- | --- |
| `fundingData.card.gatewayTokenId`String | The token ID obtained from the initial payment setup (returned in the `onPostTokenisation` callback). |
| `recurring.processingModel`String | Must be `"MerchantInitiatedSubsequentRecurring"` for recurring charges after the initial setup. |
| `transactionMethod.intent`String | Use `"Purchase"` for immediate charge or `"Authorisation"` for pre-authorisation. |


**Example API response:**


```json
{
  "state": "Captured",
  "provider": {
    "code": "00",
    "message": "Successful",
    "transactionId": "TXN-987654321"
  },
  "fundingData": {
    "card": {
      "cardScheme": "VISA",
      "expiryMonth": "12",
      "expiryYear": "2026",
      "lastFour": "4242",
      "gatewayTokenId": "5fbd77ce-02c1-40ed-94bc-1016660b7512"
    }
  }
}
```

The `gatewayTokenId` is stored securely by PXP and can be reused for future recurring charges. Implement your own scheduling system to call this API at the appropriate billing intervals.

For more details about the Transactions API, see [Initiate transactions](/guides/transactions/initiate-transactions).

### Card submit for recurring transactions

Configure the card submit component to handle recurring payment processing:


```kotlin
val cardSubmitConfig = CardSubmitComponentConfig().apply {
    // Set component references
    newCardComponent = newCardComponent
    
    // Configure recurring payment callbacks
    onPreTokenisation = {
        Log.d("Recurring", "Starting tokenisation for recurring payment")
        true // Proceed with tokenisation
    }
    
    onPostTokenisation = { result ->
        when (result) {
            is CardTokenisationResult.Success -> {
                Log.d("Recurring", "Token created for recurring payment: ${result.gatewayTokenId}")
                // Store token for future recurring charges
                saveRecurringPaymentToken(result.gatewayTokenId)
            }
            is CardTokenisationResult.Failure -> {
                Log.e("Recurring", "Tokenisation failed: ${result.errorReason}")
                handleRecurringSetupFailure(result.errorReason)
            }
        }
    }
    
    onPostAuthorisation = { result ->
        when (result) {
            is AuthorizedSubmitResult -> {
                Log.d("Recurring", "Recurring payment setup successful")
                Log.d("Recurring", "Provider code: ${result.providerResponse.code}")
                confirmRecurringPaymentSetup(result)
            }
            is CapturedSubmitResult -> {
                Log.d("Recurring", "Recurring payment captured successfully")
                confirmRecurringPaymentSetup(result)
            }
            is RefusedSubmitResult -> {
                Log.e("Recurring", "Recurring payment refused: ${result.stateData.message}")
                handleRecurringSetupFailure(result.stateData.message)
            }
            is FailedSubmitResult -> {
                Log.e("Recurring", "Recurring payment setup failed: ${result.errorReason}")
                handleRecurringSetupFailure(result.errorReason)
            }
        }
    }
}
```

## Frequency configuration

### Common billing frequencies

Configure recurring payment frequency using `frequencyInDays`:


```kotlin
// Different billing cycles
val dailyBilling = RecurringData(frequencyInDays = 1)
val weeklyBilling = RecurringData(frequencyInDays = 7)
val monthlyBilling = RecurringData(frequencyInDays = 30)
val quarterlyBilling = RecurringData(frequencyInDays = 90)
val yearlyBilling = RecurringData(frequencyInDays = 365)

// Custom frequencies
val biWeeklyBilling = RecurringData(frequencyInDays = 14)
val semiMonthlyBilling = RecurringData(frequencyInDays = 15)
```

### Expiration handling

Set expiration dates for recurring billing agreements:


```kotlin
val recurringData = RecurringData(
    frequencyInDays = 30,
    frequencyExpiration = LocalDateTime.now()
        .plusYears(2) // Subscription expires in 2 years
        .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
)
```

## 3DS and recurring payments

### Initial setup with 3DS

The first payment in a recurring series typically requires 3DS authentication:


```kotlin
val initialRecurringConfig = CardSubmitComponentConfig().apply {
    // Enable 3DS for initial setup
    onPreInitiateAuthentication = {
        Log.d("Recurring", "Starting 3DS for recurring payment setup")
        PreInitiateIntegratedAuthenticationData(
            threeDSRequestorAuthenticationIndicator = "02" // Recurring transaction
        )
    }
    
    onPostAuthentication = { result, authData ->
        Log.d("Recurring", "3DS completed for recurring setup")
        // Process successful authentication for recurring setup
        processRecurringAuthentication(result, authData)
    }
}
```

### Subsequent payments (MIT)

Merchant-initiated transactions (MIT) for recurring payments are processed via the backend API and typically bypass 3DS authentication. The SDK is only used for the initial setup where the customer consents and provides their payment details.

The `recurring.processingModel` field in the API request indicates the transaction type:

- `"MerchantInitiatedInitialRecurring"`: First payment in a recurring series (set automatically by SDK)
- `"MerchantInitiatedSubsequentRecurring"`: Subsequent recurring charges (set in your backend API calls)


## Error handling

### Common recurring payment scenarios

Handle various error conditions in recurring payment flows:


```kotlin
fun handleRecurringPaymentError(error: BaseSdkException) {
    when (error.errorCode) {
        "CARD_EXPIRED" -> {
            Log.w("Recurring", "Card expired, request updated payment method")
            requestPaymentMethodUpdate()
        }
        "INSUFFICIENT_FUNDS" -> {
            Log.w("Recurring", "Payment failed due to insufficient funds")
            scheduleRetryAttempt()
        }
        "CARD_DECLINED" -> {
            Log.w("Recurring", "Card declined, notify customer")
            notifyCustomerOfDeclinedPayment()
        }
        else -> {
            Log.e("Recurring", "Unexpected error in recurring payment: ${error.message}")
            handleGeneralRecurringError(error)
        }
    }
}
```

### Retry logic

Implement retry logic for failed recurring payments:


```kotlin
class RecurringPaymentManager {
    private val maxRetryAttempts = 3
    private var retryCount = 0
    
    fun processRecurringPayment(tokenId: String, amount: Double) {
        if (retryCount >= maxRetryAttempts) {
            handleMaxRetriesExceeded()
            return
        }
        
        // Configure transaction for retry
        val retryTransactionData = TransactionData(
            amount = amount,
            currency = CurrencyType.USD,
            merchantTransactionId = "retry-${retryCount}-${UUID.randomUUID()}",
            merchantTransactionDate = { Instant.now().toString() },
            recurring = RecurringData(
                frequencyInDays = 30,
                frequencyExpiration = getSubscriptionExpiration()
            )
        )
        
        // Process with stored token
        processPaymentWithToken(tokenId, retryTransactionData)
    }
    
    private fun handlePaymentFailure(error: BaseSdkException) {
        retryCount++
        
        if (shouldRetry(error.errorCode)) {
            scheduleRetry()
        } else {
            handlePermanentFailure(error)
        }
    }
}
```