---
title: Payment processing
description: Handle recurring billing, payment methods, invoicing, and payment failures using the API
---

This guide covers payment processing for a telecom service from end to end. It takes you
through the first collection, the stored payment methods, the billing, and a failed payment.

## Prerequisites

You need all of these before you start:

- **Order management**: Understanding of order creation and pricing flows
- **Customer management**: Active customers with subscription services
- **Payment gateway integration**: Access to payment processors (cards, bank transfers, digital wallets)
- **Billing system**: Understanding of billing cycles and pricing models
- **Compliance**: PCI DSS compliance for handling payment data

## Overview

Payment processing encompasses:

1. Payment session creation for secure payment collection
2. Payment profile management for stored payment methods
3. Payment processing and transaction handling
4. Payment failure management and retry logic
5. Billing and invoice management
6. Promotional pricing and discount handling

## Step-by-step implementation

### Step 1: Create payment sessions for orders

Create secure payment sessions to collect payment for orders:

```bash
# Create payment session for order
curl -X POST "{BASE_URL}/payment-sessions" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "123e4567-e89b-12d3-a456-426614174000",
    "paymentProvider": "STRIPE",
    "hosted": true,
    "returnUrl": "https://yourstore.com/payment/success",
    "cancelUrl": "https://yourstore.com/payment/cancel"
  }'

# Check payment session status
curl -X GET "{BASE_URL}/payment-sessions/{paymentSessionId}" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json"
```

### Step 2: Manage payment profiles

Set up stored payment methods for recurring billing:

```bash
# Create payment profile session
curl -X POST "{BASE_URL}/payment-profiles/sessions" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "returnUrl": "https://yourstore.com/billing/payment-methods",
    "paymentMethods": ["card", "bank_account"]
  }'

# Get payment profiles
curl -X GET "{BASE_URL}/payment-profiles" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json"
```

### Step 3: Process payments

Handle payment processing and transaction management:

```bash
# List payments
curl -X GET "{BASE_URL}/payments?customerId={customerId}&limit=50" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json"

# Get payment details
curl -X GET "{BASE_URL}/payments/{paymentId}" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json"
```

## Handling payment failures and retries

Handle a failed payment with retry logic:
```javascript
// Payment failure handler with exponential backoff
class PaymentRetryHandler {
  constructor(maxRetries = 3) {
    this.maxRetries = maxRetries;
    this.retryDelays = [24, 72, 168]; // Hours: 1 day, 3 days, 1 week
  }

  async handlePaymentFailure(paymentSessionId, failureReason) {
    console.log(`Payment failed for session ${paymentSessionId}: ${failureReason}`);

    // Get payment session details
    const session = await checkPaymentSessionStatus(paymentSessionId);
    const customer = await getOrderCustomer(session.orderId);

    // Categorize failure type
    const failureCategory = this.categorizeFailure(failureReason);

    switch (failureCategory) {
      case 'insufficient_funds':
        await this.scheduleRetry(paymentSessionId, 24); // Retry in 24 hours
        await this.notifyCustomer(customer.customerId, 'insufficient_funds');
        break;

      case 'expired_card':
        await this.requestPaymentMethodUpdate(customer.customerId);
        break;

      case 'fraud_suspected':
        await this.escalateToFraud(session);
        break;

      case 'technical_error':
        await this.scheduleRetry(paymentSessionId, 1); // Retry in 1 hour
        break;

      default:
        await this.escalateToSupport(session);
    }
  }

  categorizeFailure(reason) {
    const failureMap = {
      insufficient_funds: 'insufficient_funds',
      card_declined: 'insufficient_funds',
      expired_card: 'expired_card',
      invalid_cvc: 'expired_card',
      fraud_suspected: 'fraud_suspected',
      processing_error: 'technical_error',
      network_error: 'technical_error',
    };

    return failureMap[reason] || 'unknown';
  }

  async scheduleRetry(paymentSessionId, delayHours) {
    // In production, this would schedule a background job
    setTimeout(
      async () => {
        try {
          // Create new payment session with same order
          const originalSession = await checkPaymentSessionStatus(paymentSessionId);
          const customer = await getOrderCustomer(originalSession.orderId);
          const newSession = await createOrderPaymentSession(originalSession.orderId);

          // Notify customer of retry attempt
          await this.notifyCustomerRetry(customer.customerId, newSession.hostedUrl);
        } catch (error) {
          console.error('Payment retry failed:', error);
        }
      },
      delayHours * 60 * 60 * 1000,
    );
  }

  async notifyCustomer(customerId, failureType) {
    // Implement customer notification logic
    console.log(`Notifying customer ${customerId} about ${failureType}`);
  }

  async requestPaymentMethodUpdate(customerId) {
    // Create payment profile session for updating payment method
    const session = await createPaymentProfileSession(
      `${process.env.BASE_URL}/billing/update-payment`,
      { customerId },
    );

    // Send email with update link
    await this.notifyCustomer(customerId, 'payment_method_update_required');
  }
}
```

## Promotional pricing and discounts

Handle promotional codes and discount applications:
```javascript
// Get promotion by promo code
const getPromotionByCode = async (promoCode) => {
  const response = await fetch(`{BASE_URL}/discounts/promotions/promo-code/${promoCode}`, {
    method: 'GET',
    headers: {
      Authorization: 'Bearer YOUR_ACCESS_TOKEN',
      'X-API-Key': 'YOUR_API_KEY',
      'Content-Type': 'application/json',
    },
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Invalid promo code: ${error.message}`);
  }

  return await response.json();
};

// Apply promo code to an order and re-price it
const applyPromoCodeToOrder = async (orderId, promoCode) => {
  try {
    // Validate promo code first
    const promotion = await getPromotionByCode(promoCode);

    // The promo code lives on the order itself
    const updateResponse = await fetch(`{BASE_URL}/orders/${orderId}`, {
      method: 'PUT',
      headers: {
        Authorization: 'Bearer YOUR_ACCESS_TOKEN',
        'X-API-Key': 'YOUR_API_KEY',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ promoCode }),
    });

    if (!updateResponse.ok) {
      const error = await updateResponse.json();
      throw new Error(`Failed to apply promo code: ${error.message}`);
    }

    // Read the order back to see the promotion applied
    const pricingResponse = await fetch(`{BASE_URL}/orders/${orderId}`, {
      headers: {
        Authorization: 'Bearer YOUR_ACCESS_TOKEN',
        'X-API-Key': 'YOUR_API_KEY',
      },
    });

    if (!pricingResponse.ok) {
      const error = await pricingResponse.json();
      throw new Error(`Reading the order failed: ${error.message}`);
    }

    const { pricing } = await pricingResponse.json();
    const discountsMinor = (pricing.lineItems ?? []).reduce(
      (sum, item) => sum + (item.totalDiscountsMinor ?? 0),
      0,
    );

    // Amounts are integers in the minor units of pricing.currency: 2749 is $27.49
    return {
      subtotalMinor: pricing.subtotalMinor,
      discountsMinor,
      totalMinor: pricing.totalMinor,
      currency: pricing.currency,
      promotion,
    };
  } catch (error) {
    console.error('Failed to apply promo code:', error);
    throw error;
  }
};

// Promo code component
const PromoCodeInput = ({ orderId, onApplied, onError }) => {
  const [promoCode, setPromoCode] = useState('');
  const [loading, setLoading] = useState(false);
  const [applied, setApplied] = useState(null);

  const handleApply = async () => {
    if (!promoCode.trim()) return;

    setLoading(true);
    try {
      const result = await applyPromoCodeToOrder(orderId, promoCode);
      setApplied(result);
      onApplied(result);
    } catch (error) {
      onError(error.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="promo-code-input">
      <input
        type="text"
        placeholder="Enter promo code"
        value={promoCode}
        onChange={(e) => setPromoCode(e.target.value.toUpperCase())}
        disabled={loading || applied}
      />
      <button onClick={handleApply} disabled={loading || applied}>
        {loading ? 'Applying...' : 'Apply'}
      </button>

      {applied && (
        <div className="promo-applied">
          <p>✓ {applied.promotion.discount.description} applied</p>
          <p>
            Discount: -
            {new Intl.NumberFormat('en-US', {
              style: 'currency',
              currency: applied.currency,
            }).format(applied.discountsMinor / 100)}
          </p>
        </div>
      )}
    </div>
  );
};
```

## Next steps

After implementing payment processing:

- [Order fulfillment](/developer-guide/use-cases/order-fulfillment.md) — Handle service provisioning after successful payment
- [Customer self-service](/developer-guide/use-cases/self-management.md) — A customer manages their own payment methods

## Best practices

### Security

- Never store raw payment card data - use tokenized payment profiles
- Implement PCI DSS compliance for card processing
- Use HTTPS for all payment-related communications
- Validate all payment webhooks and callbacks

### User experience

- Provide clear payment status updates to customers
- Implement user-friendly error messages for payment failures
- Offer multiple payment methods when possible
- Save successful payment methods for future use

### Reliability

- Retry a failed payment on a schedule that you control.
- Handle payment processor downtime gracefully
- Monitor payment success rates and failure patterns
- Set up alerts for payment processing issues

## Common questions

**Q: How do I handle different currencies?**
A: The API supports more than one currency. Name the currency in the payment session. Your payment processor must support that currency.

**Q: Can I process refunds through the API?**
A: A refund normally goes through your payment processor's dashboard or API, then reflected in the API payment records.

**Q: How do I implement recurring billing?**
A: Use stored payment profiles with scheduled payment sessions. The billing system can automatically create payment sessions for recurring charges.
