/stripe-guides

How to cancel a subscription with Stripe API?

Learn how to cancel a subscription with the Stripe API, including immediate, scheduled, and prorated cancellations, plus webhook handling and status verification.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free consultation

How to cancel a subscription with Stripe API?

How to Cancel a Subscription with Stripe API

 

Introduction

 

Canceling subscriptions is an essential part of subscription management in any business. Stripe provides a robust API to handle subscription cancellations efficiently. This tutorial will guide you through the process of canceling a subscription using the Stripe API, covering different scenarios and options.

 

Prerequisites

 

  • A Stripe account
  • Stripe API keys (test or live, depending on your environment)
  • Stripe library installed in your project
  • Basic understanding of RESTful APIs
  • A subscription that you want to cancel

 

Step 1: Set Up Your Environment

 

First, you need to set up the Stripe library in your project. Here are examples in different programming languages:

For Node.js:


// Install Stripe via npm
npm install stripe

// Require Stripe in your file
const stripe = require('stripe')('your_stripe_secret\_key');

For Python:


# Install Stripe via pip
pip install stripe

# Import Stripe in your script
import stripe
stripe.api_key = 'your_stripe_secret_key'

For PHP:


// Install Stripe via Composer
composer require stripe/stripe-php

// Require the Stripe library in your file
require\_once 'vendor/autoload.php';
\Stripe\Stripe::setApiKey('your_stripe_secret\_key');

For Ruby:


# Install Stripe via gem
gem install stripe

# Require and configure Stripe
require 'stripe'
Stripe.api_key = 'your_stripe_secret_key'

 

Step 2: Retrieve the Subscription to Cancel

 

Before canceling a subscription, you may want to retrieve it to confirm its details. You need the subscription ID for this.

In Node.js:


async function getSubscription(subscriptionId) {
  try {
    const subscription = await stripe.subscriptions.retrieve(subscriptionId);
    console.log('Subscription details:', subscription);
    return subscription;
  } catch (error) {
    console.error('Error retrieving subscription:', error);
    throw error;
  }
}

In Python:


def get_subscription(subscription_id):
    try:
        subscription = stripe.Subscription.retrieve(subscription\_id)
        print(f"Subscription details: {subscription}")
        return subscription
    except Exception as e:
        print(f"Error retrieving subscription: {e}")
        raise

In PHP:


function getSubscription($subscriptionId) {
    try {
        $subscription = \Stripe\Subscription::retrieve($subscriptionId);
        echo "Subscription details: " . json\_encode($subscription);
        return $subscription;
    } catch (\Exception $e) {
        echo "Error retrieving subscription: " . $e->getMessage();
        throw $e;
    }
}

In Ruby:


def get_subscription(subscription_id)
  begin
    subscription = Stripe::Subscription.retrieve(subscription\_id)
    puts "Subscription details: #{subscription}"
    return subscription
  rescue Stripe::StripeError => e
    puts "Error retrieving subscription: #{e.message}"
    raise e
  end
end

 

Step 3: Cancel the Subscription - Basic Cancellation

 

The simplest way to cancel a subscription is to use the cancel method. By default, this will cancel the subscription immediately.

In Node.js:


async function cancelSubscription(subscriptionId) {
  try {
    const canceledSubscription = await stripe.subscriptions.cancel(subscriptionId);
    console.log('Subscription canceled:', canceledSubscription);
    return canceledSubscription;
  } catch (error) {
    console.error('Error canceling subscription:', error);
    throw error;
  }
}

In Python:


def cancel_subscription(subscription_id):
    try:
        canceled_subscription = stripe.Subscription.cancel(subscription_id)
        print(f"Subscription canceled: {canceled\_subscription}")
        return canceled\_subscription
    except Exception as e:
        print(f"Error canceling subscription: {e}")
        raise

In PHP:


function cancelSubscription($subscriptionId) {
    try {
        $canceledSubscription = \Stripe\Subscription::retrieve($subscriptionId);
        $canceledSubscription->cancel();
        echo "Subscription canceled: " . json\_encode($canceledSubscription);
        return $canceledSubscription;
    } catch (\Exception $e) {
        echo "Error canceling subscription: " . $e->getMessage();
        throw $e;
    }
}

In Ruby:


def cancel_subscription(subscription_id)
  begin
    canceled_subscription = Stripe::Subscription.cancel(subscription_id)
    puts "Subscription canceled: #{canceled\_subscription}"
    return canceled\_subscription
  rescue Stripe::StripeError => e
    puts "Error canceling subscription: #{e.message}"
    raise e
  end
end

 

Step 4: Cancel the Subscription - With Options

 

Stripe allows you to specify options when canceling a subscription, such as canceling at the end of the billing period.

In Node.js:


async function cancelSubscriptionAtPeriodEnd(subscriptionId) {
  try {
    // Instead of canceling immediately, update the subscription to cancel at period end
    const subscription = await stripe.subscriptions.update(subscriptionId, {
      cancel_at_period\_end: true
    });
    console.log('Subscription will be canceled at period end:', subscription);
    return subscription;
  } catch (error) {
    console.error('Error updating subscription:', error);
    throw error;
  }
}

In Python:


def cancel_subscription_at_period_end(subscription\_id):
    try:
        subscription = stripe.Subscription.modify(
            subscription\_id,
            cancel_at_period\_end=True
        )
        print(f"Subscription will be canceled at period end: {subscription}")
        return subscription
    except Exception as e:
        print(f"Error updating subscription: {e}")
        raise

In PHP:


function cancelSubscriptionAtPeriodEnd($subscriptionId) {
    try {
        $subscription = \Stripe\Subscription::update($subscriptionId, [
            'cancel_at_period\_end' => true
        ]);
        echo "Subscription will be canceled at period end: " . json\_encode($subscription);
        return $subscription;
    } catch (\Exception $e) {
        echo "Error updating subscription: " . $e->getMessage();
        throw $e;
    }
}

In Ruby:


def cancel_subscription_at_period_end(subscription\_id)
  begin
    subscription = Stripe::Subscription.update(
      subscription\_id,
      { cancel_at_period\_end: true }
    )
    puts "Subscription will be canceled at period end: #{subscription}"
    return subscription
  rescue Stripe::StripeError => e
    puts "Error updating subscription: #{e.message}"
    raise e
  end
end

 

Step 5: Cancel the Subscription with Proration Options

 

You can also specify proration behavior when canceling a subscription.

In Node.js:


async function cancelSubscriptionWithProration(subscriptionId) {
  try {
    const canceledSubscription = await stripe.subscriptions.cancel(subscriptionId, {
      prorate: true, // Prorates the subscription amount
      invoice\_now: true // Creates a final invoice immediately
    });
    console.log('Subscription canceled with proration:', canceledSubscription);
    return canceledSubscription;
  } catch (error) {
    console.error('Error canceling subscription with proration:', error);
    throw error;
  }
}

In Python:


def cancel_subscription_with_proration(subscription_id):
    try:
        canceled\_subscription = stripe.Subscription.cancel(
            subscription\_id,
            prorate=True,
            invoice\_now=True
        )
        print(f"Subscription canceled with proration: {canceled\_subscription}")
        return canceled\_subscription
    except Exception as e:
        print(f"Error canceling subscription with proration: {e}")
        raise

In PHP:


function cancelSubscriptionWithProration($subscriptionId) {
    try {
        $canceledSubscription = \Stripe\Subscription::retrieve($subscriptionId);
        $canceledSubscription->cancel([
            'prorate' => true,
            'invoice\_now' => true
        ]);
        echo "Subscription canceled with proration: " . json\_encode($canceledSubscription);
        return $canceledSubscription;
    } catch (\Exception $e) {
        echo "Error canceling subscription with proration: " . $e->getMessage();
        throw $e;
    }
}

In Ruby:


def cancel_subscription_with_proration(subscription_id)
  begin
    canceled\_subscription = Stripe::Subscription.cancel(
      subscription\_id,
      { prorate: true, invoice\_now: true }
    )
    puts "Subscription canceled with proration: #{canceled\_subscription}"
    return canceled\_subscription
  rescue Stripe::StripeError => e
    puts "Error canceling subscription with proration: #{e.message}"
    raise e
  end
end

 

Step 6: Cancel the Subscription at a Specific Date

 

You might want to schedule a subscription to be canceled at a specific date in the future.

In Node.js:


async function cancelSubscriptionAtDate(subscriptionId, cancelDate) {
  try {
    // cancelDate should be a Unix timestamp (seconds since the epoch)
    const subscription = await stripe.subscriptions.update(subscriptionId, {
      cancel\_at: cancelDate
    });
    console.log(`Subscription will be canceled at ${new Date(cancelDate * 1000)}:`, subscription);
    return subscription;
  } catch (error) {
    console.error('Error scheduling subscription cancellation:', error);
    throw error;
  }
}

// Example usage:
// const oneMonthFromNow = Math.floor(Date.now() / 1000) + (30 _ 24 _ 60 \* 60);
// cancelSubscriptionAtDate('sub\_12345', oneMonthFromNow);

In Python:


import time

def cancel_subscription_at_date(subscription_id, cancel\_date):
    try:
        # cancel\_date should be a Unix timestamp (seconds since the epoch)
        subscription = stripe.Subscription.modify(
            subscription\_id,
            cancel_at=cancel_date
        )
        print(f"Subscription will be canceled at {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(cancel\_date))}: {subscription}")
        return subscription
    except Exception as e:
        print(f"Error scheduling subscription cancellation: {e}")
        raise

# Example usage:
# import time
# one_month_from\_now = int(time.time()) + (30 _ 24 _ 60 \* 60)
# cancel_subscription_at_date('sub_12345', one_month_from\_now)

In PHP:


function cancelSubscriptionAtDate($subscriptionId, $cancelDate) {
    try {
        // $cancelDate should be a Unix timestamp (seconds since the epoch)
        $subscription = \Stripe\Subscription::update($subscriptionId, [
            'cancel\_at' => $cancelDate
        ]);
        echo "Subscription will be canceled at " . date('Y-m-d H:i:s', $cancelDate) . ": " . json\_encode($subscription);
        return $subscription;
    } catch (\Exception $e) {
        echo "Error scheduling subscription cancellation: " . $e->getMessage();
        throw $e;
    }
}

// Example usage:
// $oneMonthFromNow = time() + (30 _ 24 _ 60 \* 60);
// cancelSubscriptionAtDate('sub\_12345', $oneMonthFromNow);

In Ruby:


require 'time'

def cancel_subscription_at_date(subscription_id, cancel\_date)
  begin
    # cancel\_date should be a Unix timestamp (seconds since the epoch)
    subscription = Stripe::Subscription.update(
      subscription\_id,
      { cancel_at: cancel_date }
    )
    puts "Subscription will be canceled at #{Time.at(cancel\_date).strftime('%Y-%m-%d %H:%M:%S')}: #{subscription}"
    return subscription
  rescue Stripe::StripeError => e
    puts "Error scheduling subscription cancellation: #{e.message}"
    raise e
  end
end

# Example usage:
# one_month_from_now = Time.now.to_i + (30 _ 24 _ 60 \* 60)
# cancel_subscription_at_date('sub_12345', one_month_from\_now)

 

Step 7: Verify the Subscription Status

 

After canceling a subscription, you should verify its status to confirm the cancellation.

In Node.js:


async function verifySubscriptionStatus(subscriptionId) {
  try {
    const subscription = await stripe.subscriptions.retrieve(subscriptionId);
    console.log('Subscription status:', subscription.status);
    console.log('Cancel at period end:', subscription.cancel_at_period\_end);
    console.log('Cancel at:', subscription.cancel_at ? new Date(subscription.cancel_at \* 1000) : 'Not set');
    return subscription;
  } catch (error) {
    console.error('Error verifying subscription status:', error);
    throw error;
  }
}

In Python:


import time

def verify_subscription_status(subscription\_id):
    try:
        subscription = stripe.Subscription.retrieve(subscription\_id)
        print(f"Subscription status: {subscription.status}")
        print(f"Cancel at period end: {subscription.cancel_at_period\_end}")
        if subscription.get('cancel\_at'):
            cancel_date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(subscription.cancel_at))
            print(f"Cancel at: {cancel\_date}")
        else:
            print("Cancel at: Not set")
        return subscription
    except Exception as e:
        print(f"Error verifying subscription status: {e}")
        raise

In PHP:


function verifySubscriptionStatus($subscriptionId) {
    try {
        $subscription = \Stripe\Subscription::retrieve($subscriptionId);
        echo "Subscription status: " . $subscription->status . "\n";
        echo "Cancel at period end: " . ($subscription->cancel_at_period\_end ? 'Yes' : 'No') . "\n";
        if ($subscription->cancel\_at) {
            echo "Cancel at: " . date('Y-m-d H:i:s', $subscription->cancel\_at) . "\n";
        } else {
            echo "Cancel at: Not set\n";
        }
        return $subscription;
    } catch (\Exception $e) {
        echo "Error verifying subscription status: " . $e->getMessage();
        throw $e;
    }
}

In Ruby:


def verify_subscription_status(subscription\_id)
  begin
    subscription = Stripe::Subscription.retrieve(subscription\_id)
    puts "Subscription status: #{subscription.status}"
    puts "Cancel at period end: #{subscription.cancel_at_period\_end}"
    if subscription.cancel\_at
      puts "Cancel at: #{Time.at(subscription.cancel\_at).strftime('%Y-%m-%d %H:%M:%S')}"
    else
      puts "Cancel at: Not set"
    end
    return subscription
  rescue Stripe::StripeError => e
    puts "Error verifying subscription status: #{e.message}"
    raise e
  end
end

 

Step 8: Handle Webhooks for Subscription Cancellation Events

 

To properly track subscription cancellations in your system, you should set up webhook handlers for relevant events.

In Node.js (using Express):


const express = require('express');
const app = express();

// This is your Stripe CLI webhook secret for testing
const endpointSecret = 'whsec\_...';

app.post('/webhook', express.raw({type: 'application/json'}), (request, response) => {
  const sig = request.headers['stripe-signature'];

  let event;

  try {
    event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);
  } catch (err) {
    response.status(400).send(`Webhook Error: ${err.message}`);
    return;
  }

  // Handle the event
  switch (event.type) {
    case 'customer.subscription.deleted':
      const subscription = event.data.object;
      console.log('Subscription canceled:', subscription.id);
      // Perform actions like updating your database
      // handleSubscriptionCanceled(subscription);
      break;
    case 'customer.subscription.updated':
      const updatedSubscription = event.data.object;
      // Check if the subscription is marked to be canceled at period end
      if (updatedSubscription.cancel_at_period\_end) {
        console.log('Subscription marked for cancellation at period end:', updatedSubscription.id);
        // handleSubscriptionMarkedForCancellation(updatedSubscription);
      }
      break;
    default:
      console.log(`Unhandled event type ${event.type}`);
  }

  // Return a 200 response to acknowledge receipt of the event
  response.send();
});

app.listen(3000, () => console.log('Running on port 3000'));

In Python (using Flask):


from flask import Flask, request, jsonify
import stripe

app = Flask(**name**)

# This is your Stripe CLI webhook secret for testing
endpoint_secret = 'whsec_...'

@app.route('/webhook', methods=['POST'])
def webhook():
    event = None
    payload = request.data
    sig\_header = request.headers.get('Stripe-Signature')

    try:
        event = stripe.Webhook.construct\_event(
            payload, sig_header, endpoint_secret
        )
    except ValueError as e:
        # Invalid payload
        return jsonify(success=False), 400
    except stripe.error.SignatureVerificationError as e:
        # Invalid signature
        return jsonify(success=False), 400

    # Handle the event
    if event['type'] == 'customer.subscription.deleted':
        subscription = event\['data']\['object']
        print(f"Subscription canceled: {subscription['id']}")
        # Perform actions like updating your database
        # handle_subscription_canceled(subscription)
    elif event['type'] == 'customer.subscription.updated':
        subscription = event\['data']\['object']
        # Check if the subscription is marked to be canceled at period end
        if subscription.get('cancel_at_period\_end'):
            print(f"Subscription marked for cancellation at period end: {subscription['id']}")
            # handle_subscription_marked_for_cancellation(subscription)
    else:
        print(f"Unhandled event type {event['type']}")

    return jsonify(success=True)

if **name** == '**main**':
    app.run(port=3000)

In PHP:


type) {
    case 'customer.subscription.deleted':
        $subscription = $event->data->object;
        echo "Subscription canceled: " . $subscription->id;
        // Perform actions like updating your database
        // handleSubscriptionCanceled($subscription);
        break;
    case 'customer.subscription.updated':
        $subscription = $event->data->object;
        // Check if the subscription is marked to be canceled at period end
        if ($subscription->cancel_at_period\_end) {
            echo "Subscription marked for cancellation at period end: " . $subscription->id;
            // handleSubscriptionMarkedForCancellation($subscription);
        }
        break;
    default:
        echo "Unhandled event type " . $event->type;
}

http_response_code(200);
?>

In Ruby (using Sinatra):


require 'sinatra'
require 'stripe'
require 'json'

Stripe.api_key = 'your_stripe_secret_key'

endpoint_secret = 'whsec_...'

post '/webhook' do
  payload = request.body.read
  sig_header = request.env['HTTP_STRIPE\_SIGNATURE']
  event = nil

  begin
    event = Stripe::Webhook.construct\_event(
      payload, sig_header, endpoint_secret
    )
  rescue JSON::ParserError => e
    # Invalid payload
    status 400
    return
  rescue Stripe::SignatureVerificationError => e
    # Invalid signature
    status 400
    return
  end

  # Handle the event
  case event.type
  when 'customer.subscription.deleted'
    subscription = event.data.object
    puts "Subscription canceled: #{subscription.id}"
    # Perform actions like updating your database
    # handle_subscription_canceled(subscription)
  when 'customer.subscription.updated'
    subscription = event.data.object
    # Check if the subscription is marked to be canceled at period end
    if subscription.cancel_at_period\_end
      puts "Subscription marked for cancellation at period end: #{subscription.id}"
      # handle_subscription_marked_for_cancellation(subscription)
    end
  else
    puts "Unhandled event type: #{event.type}"
  end

  status 200
end

 

Step 9: Implement a Complete Subscription Cancellation Workflow

 

Now, let's put everything together into a complete workflow for subscription cancellation:

In Node.js:


async function handleSubscriptionCancellation(subscriptionId, cancellationType, options = {}) {
  try {
    // First, retrieve the subscription to check its current status
    const subscription = await stripe.subscriptions.retrieve(subscriptionId);
    console.log(`Current subscription status: ${subscription.status}`);
    
    let result;
    
    switch (cancellationType) {
      case 'immediate':
        // Cancel immediately
        result = await stripe.subscriptions.cancel(subscriptionId);
        console.log('Subscription canceled immediately');
        break;
      
      case 'period\_end':
        // Cancel at period end
        result = await stripe.subscriptions.update(subscriptionId, {
          cancel_at_period\_end: true
        });
        console.log('Subscription will be canceled at period end');
        break;
      
      case 'specific\_date':
        // Cancel at a specific date
        if (!options.cancelDate) {
          throw new Error('Cancel date is required for specific\_date cancellation type');
        }
        result = await stripe.subscriptions.update(subscriptionId, {
          cancel\_at: options.cancelDate
        });
        console.log(`Subscription will be canceled at ${new Date(options.cancelDate * 1000)}`);
        break;
      
      case 'prorate':
        // Cancel with proration
        result = await stripe.subscriptions.cancel(subscriptionId, {
          prorate: true,
          invoice\_now: options.invoiceNow || false
        });
        console.log('Subscription canceled with proration');
        break;
      
      default:
        throw new Error(`Unknown cancellation type: ${cancellationType}`);
    }
    
    // Verify the result
    await verifySubscriptionStatus(subscriptionId);
    
    return result;
  } catch (error) {
    console.error('Error during subscription cancellation workflow:', error);
    throw error;
  }
}

// Example usage:
// handleSubscriptionCancellation('sub\_12345', 'immediate');
// handleSubscriptionCancellation('sub_12345', 'period_end');
// handleSubscriptionCancellation('sub_12345', 'specific_date', { cancelDate: Math.floor(Date.now() / 1000) + (7 _ 24 _ 60 \* 60) });
// handleSubscriptionCancellation('sub\_12345', 'prorate', { invoiceNow: true });

In Python:


import time

def handle_subscription_cancellation(subscription_id, cancellation_type, options=None):
    if options is None:
        options = {}
    
    try:
        # First, retrieve the subscription to check its current status
        subscription = stripe.Subscription.retrieve(subscription\_id)
        print(f"Current subscription status: {subscription.status}")
        
        if cancellation\_type == 'immediate':
            # Cancel immediately
            result = stripe.Subscription.cancel(subscription\_id)
            print("Subscription canceled immediately")
        
        elif cancellation_type == 'period_end':
            # Cancel at period end
            result = stripe.Subscription.modify(
                subscription\_id,
                cancel_at_period\_end=True
            )
            print("Subscription will be canceled at period end")
        
        elif cancellation_type == 'specific_date':
            # Cancel at a specific date
            if 'cancel\_date' not in options:
                raise ValueError("Cancel date is required for specific\_date cancellation type")
            
            result = stripe.Subscription.modify(
                subscription\_id,
                cancel_at=options['cancel_date']
            )
            cancel_date_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(options['cancel\_date']))
            print(f"Subscription will be canceled at {cancel_date_str}")
        
        elif cancellation\_type == 'prorate':
            # Cancel with proration
            result = stripe.Subscription.cancel(
                subscription\_id,
                prorate=True,
                invoice_now=options.get('invoice_now', False)
            )
            print("Subscription canceled with proration")
        
        else:
            raise ValueError(f"Unknown cancellation type: {cancellation\_type}")
        
        # Verify the result
        verify_subscription_status(subscription\_id)
        
        return result
    
    except Exception as e:
        print(f"Error during subscription cancellation workflow: {e}")
        raise

# Example usage:
# handle_subscription_cancellation('sub\_12345', 'immediate')
# handle_subscription_cancellation('sub_12345', 'period_end')
# handle_subscription_cancellation('sub_12345', 'specific_date', {'cancel\_date': int(time.time()) + (7 _ 24 _ 60 \* 60)})
# handle_subscription_cancellation('sub_12345', 'prorate', {'invoice_now': True})

In PHP:


function handleSubscriptionCancellation($subscriptionId, $cancellationType, $options = []) {
    try {
        // First, retrieve the subscription to check its current status
        $subscription = \Stripe\Subscription::retrieve($subscriptionId);
        echo "Current subscription status: " . $subscription->status . "\n";
        
        $result = null;
        
        switch ($cancellationType) {
            case 'immediate':
                // Cancel immediately
                $result = \Stripe\Subscription::retrieve($subscriptionId);
                $result->cancel();
                echo "Subscription canceled immediately\n";
                break;
            
            case 'period\_end':
                // Cancel at period end
                $result = \Stripe\Subscription::update($subscriptionId, [
                    'cancel_at_period\_end' => true
                ]);
                echo "Subscription will be canceled at period end\n";
                break;
            
            case 'specific\_date':
                // Cancel at a specific date
                if (!isset($options['cancelDate'])) {
                    throw new \Exception('Cancel date is required for specific\_date cancellation type');
                }
                $result = \Stripe\Subscription::update($subscriptionId, [
                    'cancel\_at' => $options['cancelDate']
                ]);
                echo "Subscription will be canceled at " . date('Y-m-d H:i:s', $options['cancelDate']) . "\n";
                break;
            
            case 'prorate':
                // Cancel with proration
                $result = \Stripe\Subscription::retrieve($subscriptionId);
                $result->cancel([
                    'prorate' => true,
                    'invoice\_now' => isset($options['invoiceNow']) ? $options['invoiceNow'] : false
                ]);
                echo "Subscription canceled with proration\n";
                break;
            
            default:
                throw new \Exception("Unknown cancellation type: $cancellationType");
        }
        
        // Verify the result
        verifySubscriptionStatus($subscriptionId);
        
        return $result;
    } catch (\Exception $e) {
        echo "Error during subscription cancellation workflow: " . $e->getMessage() . "\n";
        throw $e;
    }
}

// Example usage:
// handleSubscriptionCancellation('sub\_12345', 'immediate');
// handleSubscriptionCancellation('sub_12345', 'period_end');
// handleSubscriptionCancellation('sub_12345', 'specific_date', ['cancelDate' => time() + (7 _ 24 _ 60 \* 60)]);
// handleSubscriptionCancellation('sub\_12345', 'prorate', ['invoiceNow' => true]);

In Ruby:


def handle_subscription_cancellation(subscription_id, cancellation_type, options = {})
  begin
    # First, retrieve the subscription to check its current status
    subscription = Stripe::Subscription.retrieve(subscription\_id)
    puts "Current subscription status: #{subscription.status}"
    
    case cancellation\_type
    when 'immediate'
      # Cancel immediately
      result = Stripe::Subscription.cancel(subscription\_id)
      puts "Subscription canceled immediately"
    
    when 'period\_end'
      # Cancel at period end
      result = Stripe::Subscription.update(
        subscription\_id,
        { cancel_at_period\_end: true }
      )
      puts "Subscription will be canceled at period end"
    
    when 'specific\_date'
      # Cancel at a specific date
      unless options[:cancel\_date]
        raise "Cancel date is required for specific\_date cancellation type"
      end
      
      result = Stripe::Subscription.update(
        subscription\_id,
        { cancel_at: options[:cancel_date] }
      )
      cancel_date_str = Time.at(options[:cancel\_date]).strftime('%Y-%m-%d %H:%M:%S')
      puts "Subscription will be canceled at #{cancel_date_str}"
    
    when 'prorate'
      # Cancel with proration
      result = Stripe::Subscription.cancel(
        subscription\_id,
        { 
          prorate: true,
          invoice_now: options[:invoice_now] || false
        }
      )
      puts "Subscription canceled with proration"
    
    else
      raise "Unknown cancellation type: #{cancellation\_type}"
    end
    
    # Verify the result
    verify_subscription_status(subscription\_id)
    
    return result
  
  rescue => e
    puts "Error during subscription cancellation workflow: #{e.message}"
    raise e
  end
end

# Example usage:
# handle_subscription_cancellation('sub\_12345', 'immediate')
# handle_subscription_cancellation('sub_12345', 'period_end')
# handle_subscription_cancellation('sub_12345', 'specific_date', { cancel_date: Time.now.to_i + (7 _ 24 _ 60 \* 60) })
# handle_subscription_cancellation('sub_12345', 'prorate', { invoice_now: true })

 

Conclusion

 

You've now learned how to cancel subscriptions using the Stripe API in different scenarios. The key points to remember are:

  • You can cancel subscriptions immediately or at the end of the billing period
  • You can schedule cancellations for a specific date
  • You can configure proration behavior when canceling
  • Always verify the subscription status after cancellation
  • Set up webhooks to be notified about subscription changes

Proper subscription cancellation handling is crucial for maintaining good customer relationships and ensuring accurate billing. By following this guide, you should be able to implement a robust subscription cancellation system using Stripe's API.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022