Deleting Subscription Addons Automatically in ReCharge

Deleting Subscription Addons Automatically in ReCharge

ReCharge is an amazing app for handling subscriptions in an ecommerce store. However, I recently discovered a missing functionality that has been causing frustration with both our customers and my fellow employees.

Warning. This fix is pretty extensive, utilizing webhooks and a cloud functions as a temporary fix. ReCharge team, if you are reading this, hire me and I can add this endpoint for you! (Please, I'm broke).

The Problem

When you cancel a subscription in ReCharge, it only cancels the primary item and not the addons. This means that an employee must go in and manually cancel these items after a cancelation, or the customer will be charged for something they believed they canceled.

I've devised a way to utilize the ReCharge API and webhooks to automatically delete addons upon subscription cancellation.

Solution Summary

Our solution will have 4 steps:

  1. Writing the Node.js code

  2. Creating a cloud function and API endpoint to get called upon subscription cancellation webhook

  3. Create the webhook via Recharge API

  4. Testing Endpoint

Pre-Requisites

Node.js Code

This is the code that will handle our addon cancellation.

This function receives the subscription object from the webhook, takes the address_id and finds all of the addons that share this address_id. Then the program loops through them, and cancels them one by one.

Notice that my store's addons are all onetime purchases. If you have subscription addons, you can use /subscriptions/{id}/cancel (documentation here). Don't forget you'll also need to get all of the subscriptions with the same address_id if you have reoccurring addons.

const axios = require('axios');

// Function to get all one-time items for a given address ID
async function getOneTimeItems(addressId) {
    const oneTimesUrl = `https://api.rechargeapps.com/onetimes?address_id=${addressId}`;
    try {
        const response = await axios.get(oneTimesUrl, {
            headers: {
                'Content-Type': 'application/json',
                'X-Recharge-Access-Token': process.env.RECHARGE_API_TOKEN
            }
        });

        const oneTimes = response.data.onetimes;
        if (oneTimes && oneTimes.length > 0) {
            return oneTimes;
        } else {
            throw new Error('No one-time items found for the address');
        }
    } catch (error) {
        throw new Error(`Error fetching one-time items: ${error.response ? error.response.data : error.message}`);
    }
}

// Function to delete all add-ons for a given subscription address ID
async function deleteAllAddons(addressId) {
    let addOns = [];
    let errorMessages = [];

    try {
        const oneTimes = await getOneTimeItems(addressId);

        if (oneTimes && oneTimes.length > 0) {
            addOns = oneTimes;
        }

        if (addOns.length === 0) {
            return { success: false, message: "No addons detected" };
        }

        for (const addOn of addOns) {
            const oneTimeItemId = addOn.id;
            const actionUrl = `https://api.rechargeapps.com/onetimes/${oneTimeItemId}`;
            try {
                const response = await axios.delete(actionUrl, {
                    headers: {
                        'Content-Type': 'application/json',
                        'X-Recharge-Access-Token': process.env.RECHARGE_API_TOKEN
                    }
                });
                console.log('One-time item deleted successfully:', response.data);
            } catch (error) {
                console.error(`Error deleting one-time item with ID ${oneTimeItemId}:`, error.response ? error.response.data : error.message);
                errorMessages.push({
                    itemId: oneTimeItemId,
                    error: error.response ? error.response.data : error.message
                });
            }
        }

        if (errorMessages.length > 0) {
            return { success: false, message: "Some addons could not be deleted", errors: errorMessages };
        }

        return { success: true, message: "Addons deleted successfully" };

    } catch (error) {
        return { success: false, message: `Failed to fetch one-time items or delete addons: ${error.message}` };
    }
}

// Main Lambda handler function
exports.handler = async (event) => {
    const subscription = JSON.parse(event.body).subscription;
    const addressId = subscription.address_id;
    const result = await deleteAllAddons(addressId);
    return {
        statusCode: result.success ? 200 : 500,
        body: JSON.stringify(result),
    };
};

Hosting a Cloud Function

I won't get in too many specifics, since this is not a cloud computing tutorial. The main idea here is that your code needs to run on command whenever this webhook is sent out.

  1. Take the above code and place it into an cloud function from your preferred cloud provider (AWS Lambda, Google Cloud Functions, Azure Functions, etc.).

  2. Now attach this function you just made to an API endpoint so that it accepts POST requests. I used AWS API Gateway (your cloud service provider should provide an easy way to host your functions on an endpoint).

Testing your Endpoint

It is recommended to test the endpoint in Postman with a subscription object before moving on. And for testing, remember that the subscription item will be nested inside the parent JSON object like so:

{
    "subscription": "subscription fields here"
}

To get a subscription object, use ReCharge's GET /subscriptions/{id} endpoint, then copy the response into your POST request to test the cloud endpoint.

Now you should have a working API endpoint!

Creating the Cancel Subscription Webhook

ReCharge has multiple webhook events you can attach to, allowing our cloud function to run every time a subscription is canceled.

Currently, ReCharge only allows creation of webhooks through their API, like so:

curl -X POST https://api.rechargeapps.com/webhooks \
-H "Content-Type: application/json" \
-H "X-Recharge-Access-Token: your_api_token" \
-d '{
  "address": "https://<your_api_id>.execute-api.<region>.amazonaws.com/<endpoint_name>",
  "topic": "subscription/cancelled"
}'

Make sure that...

  • The address field correctly matches the API endpoint we created in the previous step

  • Your API token is entered properly.

Conclusion

If configured properly, your addons will now delete upon subscription cancelation. Make sure to test everything a few times before deploying this to your live site. Hopefully this helps any issues you may have with automatically deleting addons upon subscription cancellation in ReCharge.