# Recurring payments

Implement recurring payment tokens with Google Pay for subscriptions and recurring billing.

## Overview

Recurring payments enable secure subscriptions and scheduled billing with Google Pay. By configuring the `recurring` object in your transaction data during the initial payment, you can obtain a reusable payment token for future transactions without requiring the customer to re-authenticate each time.

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

Recurring payments are ideal 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


Recurring payments are enabled through the `recurring` configuration in `transactionData`. This is separate from the consent component, which is used to collect customer permission to store payment methods. See [Configuration](/guides/checkout/components/web/google-pay/configuration#consent-component-configuration) for details on the consent component.

## How recurring payments work

The recurring payment flow involves two main phases:

### Phase 1: Initial payment

1. The customer makes their first payment via Google Pay.
2. The SDK processes the payment with `recurring` configuration in `transactionData`.
3. The backend receives and stores the payment token for future use.
4. The customer receives confirmation of their payment and the recurring setup.


### Phase 2: Subsequent payments

1. You initiate a recurring payment using the stored token via backend API.
2. The payment processes without customer interaction.
3. The customer receives a notification of charge. The token remains valid until the `frequencyExpiration` date.


## Implementing recurring payments

To implement recurring payments, you need to:

1. Configure the SDK with `recurring` data in `transactionData`.
2. Optionally, collect customer consent using the `google-pay-consent` component or `onGetConsent` callback (see [Configuration](/guides/checkout/components/web/google-pay/configuration#consent-component-configuration)).
3. Provide a shopper ID via `onGetShopper` for tracking.


### Recurring configuration

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

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


```typescript
const pxpSdk = PxpCheckout.initialize({
  environment: 'test',
  session: sessionData,
  ownerId: 'your-owner-id',
  ownerType: 'MerchantGroup',
  kountDisabled: false, // OPTIONAL: Set to true to disable Kount fraud detection
  transactionData: {
    currency: 'GBP',
    amount: 9.99,
    merchantTransactionId: crypto.randomUUID(),
    merchantTransactionDate: () => new Date().toISOString(),
    entryType: 'Ecom',
    intent: {
      card: 'Authorisation'
    },
    // Configure recurring payment schedule
    recurring: {
      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: async () => {
    return { id: 'customer-123' };
  }
});
```

| Property | Description |
|  --- | --- |
| `frequencyInDays`number | The billing frequency in days (e.g., `30` for monthly, `365` for annual). |
| `frequencyExpiration`string | The date and time when the recurring token should expire, in ISO 8601 format. |


### Creating the Google Pay button

Configure the Google Pay button with your payment parameters:

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.


```typescript
const googlePayButton = pxpSdk.create('google-pay-button', {
  paymentDataRequest: {
    allowedPaymentMethods: [{
      type: 'CARD',
      parameters: {
        allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX'],
        allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS']
      }
    }],
    transactionInfo: {
      currencyCode: 'GBP',
      totalPriceStatus: 'FINAL',
      totalPrice: '9.99'
    }
  },
  
  // Handle payment authorisation
  onPostAuthorisation: async (result, paymentData) => {
    if (result && 'merchantTransactionId' in result) {
      // Success - MerchantSubmitResult
      console.log('Payment authorised');
      console.log('Transaction ID:', result.merchantTransactionId);
      console.log('System Transaction ID:', result.systemTransactionId);
      
      // Store the transaction for subscription tracking
      await storeSubscriptionTransaction({
        customerId: getCurrentCustomerId(),
        transactionId: result.merchantTransactionId,
        systemTransactionId: result.systemTransactionId
      });
      
      console.log('Subscription transaction recorded');
      window.location.href = '/subscription-confirmed';
    }
  }
});

googlePayButton.mount('google-pay-container');
```

### Collecting consent (optional)

To collect explicit customer consent for storing payment information, use the `google-pay-consent` component. See [Configuration](/guides/checkout/components/web/google-pay/configuration#consent-component-configuration) for full documentation.


```typescript
// Create consent component
const consentComponent = pxpSdk.create('google-pay-consent', {
  label: 'I agree to save my payment method for recurring payments',
  checked: false
});

// Link consent to Google Pay button
const googlePayButton = pxpSdk.create('google-pay-button', {
  paymentDataRequest: {
    // ... payment configuration
  },
  googlePayConsentComponent: consentComponent,  // Link the consent
  onPostAuthorisation: async (result, paymentData) => {
    // Handle payment
  }
});

// Mount both components
consentComponent.mount('consent-container');
googlePayButton.mount('google-pay-container');
```

## Complete implementation example

Here's a full example for a subscription service with recurring payments:


```typescript
import { useEffect, useState } from 'react';
import { PxpCheckout } from '@pxpio/web-components-sdk';

function SubscriptionCheckout() {
  const [googlePayButton, setGooglePayButton] = useState(null);
  const [subscriptionPlan, setSubscriptionPlan] = useState('monthly');
  
  // Get subscription details
  const planDetails = {
    monthly: { amount: '9.99', frequency: 'Monthly', frequencyDays: 30 },
    annual: { amount: '99.99', frequency: 'Annually', frequencyDays: 365 }
  };
  
  const plan = planDetails[subscriptionPlan];
  
  useEffect(() => {
    // Initialise PXP SDK with recurring configuration
    const pxpSdk = PxpCheckout.initialize({
      environment: 'test',
      session: sessionData, // Get from your backend
      ownerId: 'your-owner-id',
      ownerType: 'MerchantGroup',
      kountDisabled: false, // OPTIONAL: Set to true to disable Kount fraud detection
      transactionData: {
        currency: 'GBP',
        amount: parseFloat(plan.amount),
        merchantTransactionId: crypto.randomUUID(),
        merchantTransactionDate: () => new Date().toISOString(),
        entryType: 'Ecom',
        intent: {
          card: 'Authorisation'
        },
        // Configure recurring payment schedule
        recurring: {
          frequencyInDays: plan.frequencyDays,
          frequencyExpiration: getSubscriptionExpiry(plan.frequency)
        }
      },
      onGetShopper: async () => {
        // Return customer ID for tracking
        return { id: getCurrentCustomerId() };
      }
    });
    
    // Create Google Pay button
    const button = pxpSdk.create('google-pay-button', {
      paymentDataRequest: {
        allowedPaymentMethods: [{
          type: 'CARD',
          parameters: {
            allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX'],
            allowedAuthMethods: ['CRYPTOGRAM_3DS'] // Prefer 3DS for subscriptions
          }
        }],
        transactionInfo: {
          currencyCode: 'GBP',
          countryCode: 'GB',
          totalPriceStatus: 'FINAL',
          totalPrice: plan.amount,
          totalPriceLabel: `${plan.frequency} Subscription`,
          displayItems: [
            {
              label: `Premium ${plan.frequency} Plan`,
              type: 'LINE_ITEM',
              price: plan.amount,
              status: 'FINAL'
            }
          ]
        }
      },
      
      // Handle initial payment authorisation
      onPostAuthorisation: async (result, paymentData) => {
        console.log('Payment authorisation result:', result);
        
        if (result && 'merchantTransactionId' in result) {
          // Success - MerchantSubmitResult
          console.log('✅ Initial subscription payment authorised');
          console.log('Transaction ID:', result.merchantTransactionId);
          console.log('System Transaction ID:', result.systemTransactionId);
          
          try {
            // Create subscription record on your backend
            await fetch('/api/subscriptions/create', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({
                customerId: getCurrentCustomerId(),
                transactionId: result.merchantTransactionId,
                systemTransactionId: result.systemTransactionId,
                plan: subscriptionPlan,
                amount: plan.amount,
                frequency: plan.frequency,
                nextBillingDate: getNextBillingDate(plan.frequency),
                startDate: new Date().toISOString()
              })
            });
            
            console.log('✅ Subscription created successfully');
            
            // Redirect to success page
            window.location.href = '/subscription/success?plan=' + subscriptionPlan;
          } catch (error) {
            console.error('❌ Error creating subscription:', error);
            showError('An error occurred. Please contact support.');
          }
        } else if (result && 'errorCode' in result) {
          // Failure - FailedSubmitResult
          console.error('❌ Payment declined:', result.errorReason);
          showError('Payment declined. Please try another payment method.');
        }
      },
      
      // Handle errors
      onError: (error) => {
        console.error('Payment error:', error);
        showError('An error occurred during payment. Please try again.');
      },
      
      // Handle cancellation
      onCancel: () => {
        console.log('Payment cancelled by user');
        showMessage('Subscription setup cancelled');
      }
    });
    
    // Mount button
    button.mount('google-pay-button-container');
    setGooglePayButton(button);
    
    // Cleanup on unmount
    return () => {
      if (button) button.unmount();
    };
  }, [subscriptionPlan]);
  
  return (
    <div className="subscription-checkout">
      <h1>Start your subscription</h1>
      
      <div className="plan-selector">
        <label>
          <input
            type="radio"
            name="plan"
            value="monthly"
            checked={subscriptionPlan === 'monthly'}
            onChange={(e) => setSubscriptionPlan(e.target.value)}
          />
          Monthly - £9.99/month
        </label>
        <label>
          <input
            type="radio"
            name="plan"
            value="annual"
            checked={subscriptionPlan === 'annual'}
            onChange={(e) => setSubscriptionPlan(e.target.value)}
          />
          Annual - £99.99/year (Save 17%)
        </label>
      </div>
      
      <div id="google-pay-button-container"></div>
      
      <p className="subscription-terms">
        By subscribing, you authorise us to charge your payment method {
          subscriptionPlan === 'monthly' ? 'monthly' : 'annually'
        } until you cancel. See our <a href="/terms">Terms of Service</a>.
      </p>
    </div>
  );
}

// Helper functions
function getCurrentCustomerId() {
  // Get current customer ID from your auth system
  return localStorage.getItem('customerId') || 'guest-user';
}

function getNextBillingDate(frequency) {
  const nextDate = new Date();
  if (frequency === 'Monthly') {
    nextDate.setMonth(nextDate.getMonth() + 1);
  } else if (frequency === 'Annually') {
    nextDate.setFullYear(nextDate.getFullYear() + 1);
  }
  return nextDate.toISOString();
}

function getSubscriptionExpiry(frequency) {
  // Set token expiry (e.g., 2 years from now)
  const expiry = new Date();
  expiry.setFullYear(expiry.getFullYear() + 2);
  return expiry.toISOString();
}

function showError(message) {
  alert(message); // Replace with your error handling UI
}

function showMessage(message) {
  console.log(message); // Replace with your notification system
}

export default SubscriptionCheckout;
```

For collecting explicit customer consent, add the `google-pay-consent` component. See [Configuration](/guides/checkout/components/web/google-pay/configuration#consent-component-configuration) for implementation details.

## Processing recurring payments

After the initial payment, you must initiate subsequent recurring charges 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.

### Charging subsequent recurring payments

/v1/transactions

Use the following request to charge a customer for a subsequent recurring payment. You'll need the `gatewayTokenId` from the initial transaction stored in your system.


```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": "GBP"
  },
  "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)

### Backend implementation example

Once you have stored the consent token, use it to process recurring payments:


```typescript
// Backend API endpoint for processing recurring payment
app.post('/api/subscriptions/charge', async (req, res) => {
  const { customerId, subscriptionId } = req.body;
  
  try {
    // Retrieve subscription details
    const subscription = await getSubscription(subscriptionId);
    
    // Process recurring payment through backend API
    // Note: Recurring payment implementation depends on your payment processor
    const response = await pxpBackendSDK.payments.create({
      amount: subscription.billingAmount,
      currency: subscription.billingCurrency,
      customerId: customerId,
      subscriptionId: subscriptionId,
      metadata: {
        billingPeriod: getCurrentBillingPeriod(),
        paymentType: 'recurring'
      }
    });
    
    if (response.status === 'Authorised') {
      // Update subscription record
      await updateSubscriptionLastCharge(subscriptionId, {
        transactionId: response.transactionId,
        amount: response.amount,
        date: new Date().toISOString(),
        status: 'success'
      });
      
      // Send receipt to customer
      await sendPaymentReceipt(customerId, response);
      
      res.json({ success: true, transactionId: response.transactionId });
    } else {
      // Handle payment failure
      await handleRecurringPaymentFailure(subscriptionId, response);
      res.status(402).json({ success: false, error: response.errorReason });
    }
    
  } catch (error) {
    console.error('Recurring payment error:', error);
    res.status(500).json({ success: false, error: 'Payment processing failed' });
  }
});
```

### Scheduled billing

Implement automated recurring billing:


```typescript
// Cron job or scheduled task to process recurring payments
async function processScheduledBilling() {
  console.log('🔄 Processing scheduled billing...');
  
  // Get subscriptions due for billing
  const dueSubscriptions = await getSubscriptionsDueForBilling();
  
  console.log(`Found ${dueSubscriptions.length} subscriptions to process`);
  
  for (const subscription of dueSubscriptions) {
    try {
      console.log(`Processing subscription ${subscription.id}`);
      
      // Process payment using stored token
      const result = await processRecurringPayment(subscription);
      
      if (result.success) {
        console.log(`✅ Successfully charged subscription ${subscription.id}`);
        
        // Schedule next billing
        await scheduleNextBilling(subscription);
        
        // Send confirmation email
        await sendBillingConfirmation(subscription.customerId, result);
      } else {
        console.error(`❌ Failed to charge subscription ${subscription.id}`);
        
        // Handle failed payment
        await handleBillingFailure(subscription, result);
      }
    } catch (error) {
      console.error(`Error processing subscription ${subscription.id}:`, error);
      await logBillingError(subscription.id, error);
    }
  }
  
  console.log('✅ Scheduled billing complete');
}

// Run daily at 2 AM
cron.schedule('0 2 * * *', processScheduledBilling);
```

## Managing subscriptions

### Updating payment methods

Allow customers to update their payment method for an existing subscription:


```typescript
import { PxpCheckout } from '@pxpio/web-components-sdk';

function UpdatePaymentMethod({ subscriptionId }) {
  const [googlePayButton, setGooglePayButton] = useState(null);
  
  useEffect(() => {
    const pxpSdk = PxpCheckout.initialize({
      environment: 'test',
      session: sessionData, // Get from your backend
      ownerId: 'your-owner-id',
      ownerType: 'MerchantGroup',
      kountDisabled: false, // OPTIONAL: Set to true to disable Kount fraud detection
      transactionData: {
        currency: 'GBP',
        amount: 0, // No charge for update
        merchantTransactionId: crypto.randomUUID(),
        merchantTransactionDate: () => new Date().toISOString(),
        entryType: 'Ecom',
        intent: {
          card: 'Verification' // Use Verification for payment method updates
        },
        recurring: {
          frequencyInDays: 30, // Maintain existing frequency
          frequencyExpiration: getSubscriptionExpiry()
        }
      },
      onGetShopper: async () => ({ id: getCurrentCustomerId() })
    });
    
    const button = pxpSdk.create('google-pay-button', {
      paymentDataRequest: {
        allowedPaymentMethods: [{
          type: 'CARD',
          parameters: {
            allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX'],
            allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS']
          }
        }],
        transactionInfo: {
          currencyCode: 'GBP',
          totalPriceStatus: 'FINAL',
          totalPrice: '0.00' // No charge for update
        }
      },
      
      onPostAuthorisation: async (result, paymentData) => {
        if (result && 'merchantTransactionId' in result) {
          // Success - Payment method update successful
          await updateSubscriptionPaymentMethod(subscriptionId, {
            transactionId: result.merchantTransactionId,
            systemTransactionId: result.systemTransactionId,
            updatedAt: new Date().toISOString()
          });
          
          alert('Payment method updated successfully');
          window.location.href = '/account/subscription';
        }
      }
    });
    
    button.mount('update-payment-button');
    setGooglePayButton(button);
    
    return () => {
      button.unmount();
    };
  }, [subscriptionId]);
  
  return (
    <div>
      <h2>Update payment method</h2>
      <p>Add a new payment method for your subscription.</p>
      <div id="update-payment-button"></div>
    </div>
  );
}
```

### Cancelling subscriptions

Handle subscription cancellations:


```typescript
async function cancelSubscription(subscriptionId, reason) {
  try {
    // Update subscription status
    await fetch(`/api/subscriptions/${subscriptionId}/cancel`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        cancellationReason: reason,
        cancelledAt: new Date().toISOString()
      })
    });
    
    // Note: Consent token should be deleted from your system
    // but the token itself remains valid with the payment processor
    // until its expiry date. Ensure your backend doesn't use it.
    
    console.log('Subscription cancelled successfully');
    
    // Send cancellation confirmation
    await sendCancellationConfirmation(subscriptionId);
    
    return { success: true };
  } catch (error) {
    console.error('Error cancelling subscription:', error);
    return { success: false, error: error.message };
  }
}
```

## Handling failed recurring payments

Implement retry logic and customer notifications:


```typescript
async function handleRecurringPaymentFailure(subscription, result) {
  console.error('Recurring payment failed:', result.errorReason);
  
  // Track failure
  await recordPaymentFailure(subscription.id, {
    reason: result.errorReason,
    attemptNumber: subscription.failedAttempts + 1,
    date: new Date().toISOString()
  });
  
  const failedAttempts = subscription.failedAttempts + 1;
  
  if (failedAttempts === 1) {
    // First failure: Retry in 3 days
    await scheduleRetry(subscription.id, 3);
    await sendPaymentFailureEmail(subscription.customerId, 'first_failure');
    
  } else if (failedAttempts === 2) {
    // Second failure: Retry in 5 days
    await scheduleRetry(subscription.id, 5);
    await sendPaymentFailureEmail(subscription.customerId, 'second_failure');
    
  } else if (failedAttempts >= 3) {
    // Third failure: Suspend subscription
    await suspendSubscription(subscription.id);
    await sendPaymentFailureEmail(subscription.customerId, 'subscription_suspended');
  }
}
```

## Best practices

### Security


```typescript
// ✅ DO: Store subscription data securely on backend
await secureStorage.store({
  customerId: customer.id,
  transactionId: result.merchantTransactionId,
  systemTransactionId: result.systemTransactionId,
  createdAt: new Date()
});

// ❌ DON'T: Never store sensitive payment data in frontend/localStorage
localStorage.setItem('transactionId', result.merchantTransactionId); // AVOID - use backend storage
```

### Customer communication


```typescript
// Always inform customers before charging
async function notifyUpcomingCharge(subscription) {
  const daysBeforeCharge = 3;
  
  await sendEmail(subscription.customerEmail, {
    subject: 'Upcoming subscription payment',
    body: `Your ${subscription.planName} subscription will renew in ${daysBeforeCharge} days for £${subscription.amount}.`
  });
}

// Send receipt after successful charge
async function sendChargeConfirmation(subscription, transaction) {
  await sendEmail(subscription.customerEmail, {
    subject: 'Payment confirmation',
    body: `We've successfully charged £${transaction.amount} for your ${subscription.planName} subscription.`
  });
}
```

### Compliance


```typescript
// Provide clear subscription terms
function SubscriptionTerms({ amount, frequency }) {
  return (
    <div className="subscription-terms">
      <h3>Subscription agreement</h3>
      <p>
        By completing this purchase, you authorise {merchantName} to:
      </p>
      <ul>
        <li>Charge £{amount} to your payment method {frequency.toLowerCase()}</li>
        <li>Store your payment information securely for recurring billing</li>
        <li>Continue billing until you cancel your subscription</li>
      </ul>
      <p>
        You can cancel at any time from your account settings.
        Cancellations take effect at the end of your current billing period.
      </p>
    </div>
  );
}
```

## Testing recurring payments

### Test scenarios

1. **Successful initial payment**: Verify payment token is created and stored with `recurring` configuration.
2. **Failed initial payment**: Ensure no token is stored
3. **Successful recurring payment**: Process payment using stored token via backend API.
4. **Failed recurring payment**: Test retry logic and notifications.
5. **Token update**: Successfully replace existing payment method.
6. **Subscription cancellation**: Verify token is no longer used.


### Test implementation


```typescript
// Test mode configuration
const testConfig = {
  environment: 'test',
  testMode: true,
  testCards: {
    success: '4111111111111111',
    decline: '4000000000000002',
    insufficientFunds: '4000000000009995'
  }
};

// Verify subscription creation
async function testSubscriptionCreation() {
  const result = await processInitialPayment();
  assert(result.merchantTransactionId, 'Transaction ID should be present');
  assert(result.systemTransactionId, 'System Transaction ID should be present');
  
  const subscription = await getSubscription(result.subscriptionId);
  assert(subscription, 'Subscription should be created');
  assert(subscription.status === 'active', 'Subscription should be active');
  assert(subscription.recurring, 'Recurring configuration should be present');
}
```

Always test the complete subscription lifecycle including initial payment, recurring charges, failed payments, retries, and cancellations before going live.