Setting Up Recurring Payments with Cryptocurrency: A Complete Guide

Recurring payments are essential for SaaS businesses, subscription services, and membership platforms. While traditional payment processors handle subscriptions well, cryptocurrency offers unique advantages for recurring billing. This guide shows you how to implement recurring crypto payments.

Why Recurring Crypto Payments?

Benefits for Businesses

  • Lower Fees: Save on recurring transaction costs
  • Global Subscriptions: Accept payments from anywhere without currency conversion
  • No Chargebacks: Eliminate subscription chargeback risks
  • Faster Settlement: Receive funds immediately after payment
  • Automated Billing: Set up once, collect automatically

Benefits for Customers

  • Privacy: No need to share credit card information
  • Control: Customers control when to pay
  • Lower Costs: Reduced fees passed to customers
  • Global Access: Pay from any country

Implementation Approaches

Approach 1: Invoice-Based Recurring Payments

Create invoices automatically on a schedule:

// Schedule recurring invoice creation
const scheduleRecurringInvoice = async (customerId, amount, interval) => {
  const cron = require('node-cron');
  
  // Create invoice every month
  cron.schedule('0 0 1 * *', async () => {
    const invoice = await createInvoice({
      amount: amount,
      currency: 'USDT',
      description: `Monthly subscription - ${customerId}`,
      metadata: {
        customerId: customerId,
        subscriptionId: subscriptionId,
        billingCycle: 'monthly'
      }
    });
    
    // Send invoice to customer
    await sendInvoiceEmail(customerId, invoice);
  });
};

Approach 2: Pre-Authorized Payments

Store customer wallet addresses and create invoices automatically:

// Store customer payment preferences
const customer = {
  id: 'cust_123',
  walletAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
  subscriptionPlan: 'pro',
  billingAmount: '99.00',
  billingInterval: 'monthly',
  nextBillingDate: '2025-02-01'
};

// Automated billing function
async function processRecurringBilling() {
  const dueSubscriptions = await getDueSubscriptions();
  
  for (const subscription of dueSubscriptions) {
    // Create invoice
    const invoice = await createInvoice({
      amount: subscription.amount,
      currency: 'USDT',
      description: `${subscription.planName} - ${subscription.billingCycle}`,
      metadata: {
        subscriptionId: subscription.id,
        customerId: subscription.customerId
      }
    });
    
    // Notify customer
    await notifyCustomer(subscription.customerId, invoice);
    
    // Update next billing date
    await updateNextBillingDate(subscription.id);
  }
}

Best Practices

1. Clear Communication

Always notify customers before billing:

// Send reminder 3 days before billing
async function sendBillingReminder(subscription) {
  await sendEmail({
    to: subscription.customerEmail,
    subject: 'Upcoming Payment: ${subscription.planName}',
    body: `Your ${subscription.planName} subscription will renew on ${subscription.nextBillingDate} for ${subscription.amount} USDT.`
  });
}

2. Grace Periods

Give customers time to update payment methods:

const GRACE_PERIOD_DAYS = 7;

async function handleFailedPayment(subscription) {
  // Mark subscription as past due
  await updateSubscriptionStatus(subscription.id, 'past_due');
  
  // Give grace period
  const gracePeriodEnd = new Date();
  gracePeriodEnd.setDate(gracePeriodEnd.getDate() + GRACE_PERIOD_DAYS);
  
  await updateGracePeriodEnd(subscription.id, gracePeriodEnd);
  
  // Notify customer
  await sendPastDueNotification(subscription);
}

3. Automatic Retry

Retry failed payments automatically:

async function retryFailedPayment(subscription, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    const invoice = await createInvoice({
      amount: subscription.amount,
      currency: 'USDT',
      description: `Retry payment - ${subscription.planName}`
    });
    
    // Wait for payment
    const paid = await waitForPayment(invoice.id, 24 * 60 * 60 * 1000); // 24 hours
    
    if (paid) {
      await updateSubscriptionStatus(subscription.id, 'active');
      return true;
    }
    
    // Wait before next retry
    await delay(attempt * 24 * 60 * 60 * 1000);
  }
  
  // Cancel subscription after max retries
  await cancelSubscription(subscription.id);
  return false;
}

Subscription Management

Creating Subscriptions

async function createSubscription(customerId, planId) {
  const plan = await getPlan(planId);
  
  const subscription = {
    customerId: customerId,
    planId: planId,
    amount: plan.amount,
    interval: plan.interval, // 'monthly', 'yearly', etc.
    status: 'active',
    startDate: new Date(),
    nextBillingDate: calculateNextBillingDate(plan.interval),
    createdAt: new Date()
  };
  
  await saveSubscription(subscription);
  
  // Create first invoice
  const invoice = await createInvoice({
    amount: plan.amount,
    currency: 'USDT',
    description: `${plan.name} subscription`,
    metadata: {
      subscriptionId: subscription.id,
      billingCycle: 'initial'
    }
  });
  
  return { subscription, invoice };
}

Updating Subscriptions

async function updateSubscription(subscriptionId, updates) {
  const subscription = await getSubscription(subscriptionId);
  
  // Handle plan changes
  if (updates.planId && updates.planId !== subscription.planId) {
    const newPlan = await getPlan(updates.planId);
    
    // Prorate if mid-cycle
    if (shouldProrate(subscription)) {
      const proratedAmount = calculateProration(
        subscription,
        newPlan,
        subscription.nextBillingDate
      );
      
      // Create prorated invoice
      await createInvoice({
        amount: proratedAmount,
        currency: 'USDT',
        description: 'Plan upgrade proration'
      });
    }
    
    subscription.planId = newPlan.id;
    subscription.amount = newPlan.amount;
  }
  
  await saveSubscription(subscription);
}

Canceling Subscriptions

async function cancelSubscription(subscriptionId, immediate = false) {
  const subscription = await getSubscription(subscriptionId);
  
  if (immediate) {
    // Cancel immediately, no refund
    subscription.status = 'canceled';
    subscription.canceledAt = new Date();
  } else {
    // Cancel at end of billing period
    subscription.status = 'canceled_at_period_end';
    subscription.cancelAt = subscription.nextBillingDate;
  }
  
  await saveSubscription(subscription);
  await notifyCustomer(subscription.customerId, {
    type: 'subscription_canceled',
    subscription: subscription
  });
}

Webhook Integration

Set up webhooks to handle subscription events:

app.post('/webhooks/subscriptions', async (req, res) => {
  const { event, data } = req.body;
  
  switch (event) {
    case 'invoice.confirmed':
      // Payment received, activate subscription
      await activateSubscription(data.metadata.subscriptionId);
      break;
      
    case 'invoice.expired':
      // Payment failed, mark as past due
      await markSubscriptionPastDue(data.metadata.subscriptionId);
      break;
      
    case 'invoice.overpaid':
      // Handle overpayment (credit to account)
      await creditAccount(data.metadata.subscriptionId, data.excessAmount);
      break;
  }
  
  res.status(200).json({ received: true });
});

Testing Recurring Payments

Test Scenarios

  1. Successful Payment: Verify subscription continues
  2. Failed Payment: Test grace period and retry logic
  3. Plan Upgrade: Test proration calculations
  4. Plan Downgrade: Test effective date handling
  5. Cancellation: Test immediate vs. end-of-period
  6. Renewal: Test automatic invoice creation

Test Implementation

describe('Recurring Payments', () => {
  it('should create invoice on billing date', async () => {
    const subscription = await createTestSubscription();
    const nextBilling = subscription.nextBillingDate;
    
    // Simulate billing date
    await processRecurringBilling();
    
    const invoices = await getInvoicesForSubscription(subscription.id);
    expect(invoices).toHaveLength(2); // Initial + recurring
  });
  
  it('should handle failed payment with grace period', async () => {
    const subscription = await createTestSubscription();
    
    // Simulate failed payment
    await simulateFailedPayment(subscription.id);
    
    const updated = await getSubscription(subscription.id);
    expect(updated.status).toBe('past_due');
    expect(updated.gracePeriodEnd).toBeDefined();
  });
});

Common Challenges and Solutions

Challenge 1: Customer Forgets to Pay

Solution: Implement automated reminders and grace periods

Challenge 2: Price Changes

Solution: Notify customers in advance and offer grandfathering options

Challenge 3: Proration Complexity

Solution: Use standardized proration formulas and clearly communicate to customers

Challenge 4: Time Zone Issues

Solution: Store all dates in UTC and convert for display

Conclusion

Recurring crypto payments offer significant advantages for subscription businesses. With proper implementation, you can automate billing while providing customers with a seamless payment experience.

Start implementing recurring payments with FromChain and automate your subscription billing today!