# Recurring payments

Learn how to implement recurring payment tokens with Google Pay for subscriptions and recurring billing.

## Overview

Consent payment tokens enable secure recurring payments with Google Pay. By obtaining customer consent during the initial payment, you can store a reusable payment token for future transactions without requiring the customer to re-authenticate each time.

Consent payment tokens are essential for:

* Monthly or annual billing, such as subscription services
* Regular scheduled charges
* Automatic account top-ups
* Instalment plans
* Pay-as-you-go services
* Membership renewals


Consent payment tokens provide a frictionless payment experience for returning customers whilst maintaining security through tokenisation and customer consent requirements.

## How consent tokens 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.

The consent token flow involves two main phases:

### Phase 1: Initial payment with consent

1. The customer makes their first payment via Google Pay.
2. During payment, the customer grants consent for future charges.
3. The SDK creates and authorises the initial payment.
4. The backend stores encrypted payment token for future use.
5. The customer receives confirmation of payment and consent.


### Phase 2: Subsequent payments

1. Your backend initiates recurring payments using the stored `gatewayTokenId` via the PXP Transactions API.
2. The payment processes without customer interaction.
3. The customer receives notification of the charge.
4. The token remains valid for future transactions until expiration.


## Implementing consent payments

To implement recurring payments, you need to:

1. Configure the SDK with `recurring` data in `transactionData`.
2. Use either the `google-pay-consent` component or the `onGetConsent` callback.
3. Provide a shopper ID via `onGetShopper` for consent tracking.


### Recurring configuration

When initialising the SDK, include the `recurring` configuration in `transactionData`:


```kotlin
import com.pxp.PxpCheckout
import com.pxp.checkout.models.*
import com.pxp.checkout.services.models.transaction.Shopper
import java.text.SimpleDateFormat
import java.util.*

val pxpCheckout = PxpCheckout.builder()
    .withConfig(
        PxpSdkConfig(
            environment = Environment.TEST,
            session = sessionData,
            ownerId = "your-owner-id",
            ownerType = "MerchantGroup",
            transactionData = TransactionData(
                currency = "GBP",
                amount = 9.99,
                merchantTransactionId = UUID.randomUUID().toString(),
                merchantTransactionDate = { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(Date()) },
                entryType = EntryType.Ecom,
                intent = TransactionIntentData(
                    card = IntentType.Authorisation
                ),
                merchant = "Merchant Name",
                // Configure recurring payment schedule
                recurring = RecurringData(
                    frequencyInDays = 30,  // How often the customer will be charged
                    frequencyExpiration = "2027-12-31T00:00:00Z"  // When the recurring token expires
                )
            ),
            // Required: Provide customer ID for consent tracking
            onGetShopper = {
                Shopper(id = "customer-123")
            }
        )
    )
    .withContext(context)
    .build()
```

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.

| Property  | Description |
|  --- | --- |
| `frequencyInDays`Int | The billing frequency in days (e.g., `30` for monthly, `365` for annual). |
| `frequencyExpiration`String? | When the recurring token should expire (ISO 8601 date string, e.g., `"2027-12-31T00:00:00Z"`). |


### Step 1: Configure for consent

Configure the Google Pay button to request consent:

The SDK automatically configures the `tokenizationSpecification` with the correct gateway and merchant ID from your session. You only need to provide `allowedPaymentMethods` with the card parameters.


```kotlin
import com.pxp.checkout.components.googlepay.*
import com.pxp.checkout.components.googlepay.types.*

val googlePayConfig = GooglePayButtonComponentConfig().apply {
    paymentDataRequest = PaymentDataRequest(
        allowedPaymentMethods = listOf(
            PaymentMethodSpecification(
                parameters = PaymentMethodParameters(
                    allowedCardNetworks = listOf(
                        CardNetwork.VISA,
                        CardNetwork.MASTERCARD
                    ),
                    allowedAuthMethods = listOf(
                        CardAuthMethod.PAN_ONLY,
                        CardAuthMethod.CRYPTOGRAM_3DS
                    )
                )
            )
        ),
        transactionInfo = TransactionInfo(
            currencyCode = "GBP",
            totalPriceStatus = TotalPriceStatus.FINAL,
            totalPrice = "9.99"
        )
    )
    
    // Option 1: Use the consent component (recommended)
    googlePayConsentComponent = consentComponent
    
    // Option 2: Use a simple boolean consent callback
    onGetConsent = {
        true // Return true if customer has given consent
    }
}
```

### Step 2: Using the consent component

For recurring payments, use the `google-pay-consent` component to collect customer consent:


```kotlin
import com.pxp.checkout.components.googlepayconsent.*
import com.pxp.checkout.types.ComponentType

// Create consent component
val consentComponent = pxpCheckout.createComponent<
    GooglePayConsentComponent,
    GooglePayConsentConfig
>(
    type = ComponentType.GOOGLE_PAY_CONSENT,
    config = GooglePayConsentConfig(
        label = "I agree to save my payment method for future purchases",
        initialChecked = false
    )
)

// Link consent component to Google Pay button
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    googlePayConsentComponent = consentComponent
    // ... rest of configuration
}
```

### Consent component configuration

The `google-pay-consent` component provides customisation options:

| Property  | Description |
|  --- | --- |
| `label`String | The consent text displayed to the customer. |
| `initialChecked`Boolean | Whether the checkbox is initially checked. Default: `false`. |
| `style`CheckboxStyle? | Custom styling for the checkbox appearance (colors, text styles, etc.). |


### Consent component example


```kotlin
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import com.pxp.checkout.styles.CheckboxStyle

@Composable
fun SubscriptionCheckout(pxpCheckout: PxpCheckout) {
    // Create consent component
    val consentComponent = remember {
        pxpCheckout.createComponent<GooglePayConsentComponent, GooglePayConsentConfig>(
            type = ComponentType.GOOGLE_PAY_CONSENT,
            config = GooglePayConsentConfig(
                label = "Save this payment method for my monthly subscription",
                initialChecked = false,
                style = CheckboxStyle(
                    checkedColor = Color(0xFF4CAF50),
                    checkedLabelStyle = TextStyle(
                        fontWeight = FontWeight.Bold,
                        color = Color(0xFF4CAF50)
                    )
                )
            )
        )
    }
    
    // Create Google Pay button with consent
    val googlePayComponent = remember {
        pxpCheckout.createComponent<GooglePayButtonComponent, GooglePayButtonComponentConfig>(
            type = ComponentType.GOOGLE_PAY_BUTTON,
            config = GooglePayButtonComponentConfig().apply {
                googlePayConsentComponent = consentComponent
                // ... payment configuration
            }
        )
    }
    
    // Render components
    Column {
        googlePayComponent.Content()
        Spacer(modifier = Modifier.height(16.dp))
        consentComponent.Content()
    }
}
```

## Charging subsequent recurring payments

After the initial setup with Google Pay, 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-456",
  "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": "GBP"
  },
  "recurring": {
    "processingModel": "MerchantInitiatedSubsequentRecurring"
  }
}
```

| Parameter  | Description |
|  --- | --- |
| `fundingData.card.gatewayTokenId`String | The token ID obtained from the initial Google Pay payment (stored on your backend after the initial authorisation). |
| `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 SDK automatically sets `processingModel` to `"MerchantInitiatedInitialRecurring"` for the initial Google Pay transaction. For subsequent charges, you must use the Transactions API from your backend with `"MerchantInitiatedSubsequentRecurring"`.

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

## Complete implementation example

Here's a complete example of implementing recurring payments with Google Pay:


```kotlin
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.pxp.PxpCheckout
import com.pxp.checkout.components.googlepay.*
import com.pxp.checkout.components.googlepay.types.*
import com.pxp.checkout.components.googlepayconsent.*
import com.pxp.checkout.models.*
import com.pxp.checkout.services.models.transaction.Shopper
import com.pxp.checkout.types.ComponentType
import java.text.SimpleDateFormat
import java.util.*

class SubscriptionActivity : ComponentActivity() {
    
    private lateinit var pxpCheckout: PxpCheckout
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // Initialise SDK
        pxpCheckout = PxpCheckout.builder()
            .withConfig(
                PxpSdkConfig(
                    environment = Environment.TEST,
                    session = sessionData,
                    ownerId = "your-owner-id",
                    ownerType = "MerchantGroup",
                    transactionData = TransactionData(
                        currency = "GBP",
                        amount = 9.99,
                        merchantTransactionId = UUID.randomUUID().toString(),
                        merchantTransactionDate = { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(Date()) },
                        entryType = EntryType.Ecom,
                        intent = TransactionIntentData(card = IntentType.Authorisation),
                        merchant = "Merchant Name",
                        recurring = RecurringData(
                            frequencyInDays = 30,
                            frequencyExpiration = "2027-12-31T00:00:00Z"
                        )
                ),
                onGetShopper = {
                    Shopper(id = "customer-123")
                }
                )
            )
            .withContext(this)
            .build()
        
        setContent {
            SubscriptionScreen(pxpCheckout)
        }
    }
}

@Composable
fun SubscriptionScreen(pxpCheckout: PxpCheckout) {
    // Create consent component
    val consentComponent = remember {
        pxpCheckout.createComponent<GooglePayConsentComponent, GooglePayConsentConfig>(
            type = ComponentType.GOOGLE_PAY_CONSENT,
            config = GooglePayConsentConfig(
                label = "Save my payment method for monthly subscription payments"
            )
        )
    }
    
    // Create Google Pay button
    val googlePayComponent = remember {
        pxpCheckout.createComponent<GooglePayButtonComponent, GooglePayButtonComponentConfig>(
            type = ComponentType.GOOGLE_PAY_BUTTON,
            config = GooglePayButtonComponentConfig().apply {
                paymentDataRequest = PaymentDataRequest(
                    allowedPaymentMethods = listOf(
                        PaymentMethodSpecification(
                            parameters = PaymentMethodParameters(
                                allowedCardNetworks = listOf(
                                    CardNetwork.VISA,
                                    CardNetwork.MASTERCARD
                                ),
                                allowedAuthMethods = listOf(
                                    CardAuthMethod.PAN_ONLY,
                                    CardAuthMethod.CRYPTOGRAM_3DS
                                )
                            )
                        )
                    ),
                    transactionInfo = TransactionInfo(
                        currencyCode = "GBP",
                        totalPriceStatus = TotalPriceStatus.FINAL,
                        totalPrice = "9.99"
                    )
                )
                
                googlePayConsentComponent = consentComponent
                
                onPostAuthorisation = { result, paymentData ->
                    when (result) {
                        is AuthorizedSubmitResult -> {
                            Log.d("Subscription", "Subscription authorized: ${result.providerResponse.code}")
                            // Store subscription details
                        }
                        is CapturedSubmitResult -> {
                            Log.d("Subscription", "Subscription captured: ${result.providerResponse.code}")
                            // Store subscription details
                        }
                        is MerchantSubmitResult -> {
                            Log.d("Subscription", "Subscription created: ${result.merchantTransactionId}")
                        }
                        is FailedSubmitResult -> {
                            Log.e("Subscription", "Payment failed: ${result.errorReason}")
                        }
                    }
                }
            }
        )
    }
    
    // Render UI
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        Text("Premium Subscription", style = MaterialTheme.typography.headlineMedium)
        Text("£9.99/month", style = MaterialTheme.typography.titleLarge)
        Spacer(modifier = Modifier.height(24.dp))
        googlePayComponent.Content()
        Spacer(modifier = Modifier.height(16.dp))
        consentComponent.Content()
    }
}
```

## Processing recurring payments

### Backend implementation

Your backend must handle the initial token creation and subsequent recurring charges. Here's the flow:

#### Step 1: Initial payment with consent

When the first payment is completed, your webhook receives:


```json
{
  "transactionId": "txn_123",
  "customerId": "customer-123",
  "amount": 9.99,
  "currency": "GBP",
  "consentToken": "tok_abc123",
  "frequencyInDays": 30,
  "frequencyExpiration": "2027-12-31T00:00:00Z"
}
```

Store the `consentToken` securely in your database for future use.

#### Step 2: Recurring charge

To process subsequent payments, make a server-to-server API call to the PXP API using the stored consent token:


```kotlin
// Server-side code (not SDK)
POST /api/v1/transactions
Authorization: Bearer your-api-key

{
  "amount": 9.99,
  "currency": "GBP",
  "customerId": "customer-123",
  "consentToken": "tok_abc123",
  "merchantTransactionId": "recurring_456",
  "description": "Monthly subscription - February 2024"
}
```

### Scheduled billing

Implement a scheduled job to process recurring payments:


```kotlin
// Example server-side scheduler
fun scheduleRecurringPayments() {
    val subscriptions = getActiveSubscriptions()
    
    subscriptions.forEach { subscription ->
        if (subscription.isPaymentDue()) {
            processRecurringPayment(
                customerId = subscription.customerId,
                consentToken = subscription.consentToken,
                amount = subscription.amount,
                currency = subscription.currency
            )
        }
    }
}
```

## Managing subscriptions

### Updating payment methods

Customers can update their payment method by making a new payment with consent:


```kotlin
val updatePaymentConfig = GooglePayButtonComponentConfig().apply {
    googlePayConsentComponent = consentComponent
    
    onPostAuthorisation = { result, paymentData ->
        when (result) {
            is AuthorizedSubmitResult -> {
                // Retrieve and update stored consent token from backend
                val transactionDetails = fetchTransactionDetails(result.providerResponse.code)
                
                updateSubscriptionPaymentMethod(
                    customerId = "customer-123",
                    newConsentToken = transactionDetails.consentToken
                )
            }
            is CapturedSubmitResult -> {
                // Retrieve and update stored consent token from backend
                val transactionDetails = fetchTransactionDetails(result.providerResponse.code)
                
                updateSubscriptionPaymentMethod(
                    customerId = "customer-123",
                    newConsentToken = transactionDetails.consentToken
                )
            }
            is MerchantSubmitResult -> {
                // Retrieve and update stored consent token from backend
                val transactionDetails = fetchTransactionDetails(result.systemTransactionId)
                
                updateSubscriptionPaymentMethod(
                    customerId = "customer-123",
                    newConsentToken = transactionDetails.consentToken
                )
            }
            is FailedSubmitResult -> {
                showError("Failed to update payment method")
            }
        }
    }
}
```

### Cancelling subscriptions

Allow customers to cancel their subscriptions:


```kotlin
fun cancelSubscription(customerId: String) {
    // Delete or deactivate the consent token
    deleteConsentToken(customerId)
    
    // Update subscription status
    updateSubscriptionStatus(customerId, SubscriptionStatus.CANCELLED)
}
```

## Handling failed recurring payments

Implement retry logic for failed recurring payments:


```kotlin
fun handleFailedRecurringPayment(
    subscription: Subscription,
    failureReason: String
) {
    when {
        subscription.retryCount < 3 -> {
            // Retry with exponential backoff
            scheduleRetry(
                subscription = subscription,
                delayDays = 2.pow(subscription.retryCount)
            )
        }
        else -> {
            // Suspend subscription after 3 failed attempts
            suspendSubscription(subscription)
            notifyCustomer(subscription, FailureReason.PAYMENT_FAILURE)
        }
    }
}
```

## Testing consent payments

### Test scenarios

Test these key scenarios:

1. **Initial payment with consent:** Verify consent token is created and stored.
2. **Recurring payment:** Test automated recurring charges using stored token.
3. **Failed recurring payment:** Test retry logic and customer notifications.
4. **Update payment method:** Verify customers can update their payment details.
5. **Cancel subscription:** Test subscription cancellation flow.


### Test implementation


```kotlin
@Test
fun testRecurringPaymentFlow() {
    // Step 1: Initial payment with consent
    val initialResult = processInitialPayment(
        amount = 9.99,
        withConsent = true
    )
    
    assert(initialResult.consentToken != null)
    
    // Step 2: Process recurring payment
    val recurringResult = processRecurringPayment(
        consentToken = initialResult.consentToken,
        amount = 9.99
    )
    
    assert(recurringResult.isSuccess)
}
```