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:
Writing the Node.js code
Creating a cloud function and API endpoint to get called upon subscription cancellation webhook
Create the webhook via Recharge API
Testing Endpoint
Pre-Requisites
Knowledge of JavaScript and ReCharge
Knowledge of Webhooks
Knowledge of Cloud Functions and REST APIs
ReCharge Documentation: https://developer.rechargepayments.com/2021-01/subscriptions/subscriptions_delete
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.
Take the above code and place it into an cloud function from your preferred cloud provider (AWS Lambda, Google Cloud Functions, Azure Functions, etc.).
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 stepYour 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.