Auto-Sync Discounts between Shopify and ReCharge

David WillifordDavid Williford
6 min read

For E-Commerce stores that offer subscription-based products, ReCharge is a powerful application for handling recurring payments and orders. However, ReCharge does not automatically sync discount codes from your Shopify admin, meaning discount codes from your Shopify admin will not automatically register on your ReCharge theme pages.

ReCharge offers a manual way to sync discounts through their web UI, but this can be time consuming for an advanced discount code system. For example, say your site auto-generates discounts for customers with a certain level of loyalty points, and you want these discounts to be available on subscriptions in real-time.


My Solution:

  1. Understanding Shopify discounts/create Webhook

  2. Understanding Shopify and ReCharge API Endpoints

  3. Set Up AWS Lambda and API Gateway

  4. Write Lambda Function Code

  5. Connect your Lambda Function to discounts/create


The Discounts/Create Webhook

Shopify has a plethora of webhooks, one of them being for the event disocunts/create. This webhook will send a JSON payload to a specified URL. See more Shopify webhooks here

In this tutorial, we will utilize this webhook payload to trigger a AWS lambda function (or any hosted function provider of your choice), that will then make an API call to add this discount to ReCharge.

To clarify before we begin, the discounts that I will be syncing will only be for x% off or $x off. You can easily apply the same concept to specific item discounts, read here to see more information on ReCharge's discount object.

Shopify and ReCharge Endpoints

Now let's go over the endpoints you will need, and what they accomplish.

EndpointFunction
/admin/api/2024-01/graphql.jsonGeneral-purpose endpoint for Shopify Admin GraphQL requests.
api.rechargeapps.com/discountsReCharge endpoint that we will use to create discounts

Now we can derive a gameplan using the webhook and these API endpoints, on how we can sync these discounts.

  1. Set up discounts/create webhook to send payload to our cloud function (AWS Lambda, in my case)

  2. Within cloud function, make a GraphQL request to get all of the necessary information regarding this newly created discount

  3. Then, make a POST request to ReCharge API to add this discount to ReCharge.

Set Up AWS Lambda and API Gateway

If you are not using AWS for your cloud function and API gateway, feel free to skip this step. Just make sure that your cloud function can be triggered by a POST request to an endpoint. This function will be triggered by the Shopify discounts/create webhook.

In AWS, you want to connect your Lambda function to an API endpoint. I suggest you use API Gateway, a native AWS service. This will allow you to trigger this Lambda function via POST request, and the setup is rather easy.

Here is the official AWS tutorial for accomplishing this. If you have some experience with AWS already, this should be trivial.

Connect your Lambda Function to discounts/create

Now we need to let Shopify know where to send the discount payload when a new discount is created.

  1. Go to your Shopify Admin page

  2. Go to Settings > Notifications > Webhooks

  3. Select 'Create Webhook'

  4. Select 'Discount Creation' for the Event field

  5. Enter the API endpoint to your cloud function in the 'URL' field

Here is an example payload your function will receive:

{
  "admin_graphql_api_id": "gid://shopify/DiscountAutomaticNode/1",
  "title": "Automatic free shipping",
  "status": "ACTIVE",
  "created_at": "2016-08-29T08:00:00-04:00",
  "updated_at": "2016-08-29T08:00:00-04:00"
}

Write Lambda Function Code

Now that we have the webhook sending the above payload to your cloud function, we need to write the cloud function. I will be using Python, since I found it the easiest for this application. Node.js or any other scripting language available should be fine.

import json
import os
import re
import urllib.request
import urllib.parse
from datetime import datetime, timedelta

def lambda_handler(event, context):

    shopify_graphql_url = "https://sizzlefish.myshopify.com/admin/api/2024-01/graphql.json"
    shopify_access_token = os.environ['SHOPIFY_ACCESS_TOKEN']  

    recharge_api_url = "https://api.rechargeapps.com/discounts"
    recharge_access_token = os.environ['RECHARGE_ACCESS_TOKEN']

    # Extract data from the incoming event
    body = event.get("body")

    if isinstance(body, str):
        data = json.loads(body)
    else:
        data = body  # Assume it's already a dict if not a string

    discount_code = data.get("title")

    if not discount_code:
        return {
            "statusCode": 400,
            "body": json.dumps({"message": "Discount code (title) not found in the event body"})
        }

    # Step 1: Query Shopify GraphQL API to get discount details
    query = f"""
    query {{
      codeDiscountNodeByCode(code: "{discount_code}") {{
        id
        codeDiscount {{
            __typename
            ... on DiscountCodeBasic {{
                title
                status
                createdAt
                codeCount 
                shortSummary
                discountClass
                endsAt
            }}
        }}
      }}
    }}
    """

    headers = {
        "X-Shopify-Access-Token": shopify_access_token,
        "Content-Type": "application/json"
    }

    # Create the request for Shopify API
    shopify_request = urllib.request.Request(
        shopify_graphql_url,
        data=json.dumps({"query": query}).encode('utf-8'),
        headers=headers
    )

    try:
        with urllib.request.urlopen(shopify_request) as response:
            response_body = response.read().decode('utf-8')
            shopify_data = json.loads(response_body)
    except urllib.error.HTTPError as e:
        return {
            "statusCode": e.code,
            "body": json.dumps({"message": "Error querying Shopify API", "details": e.read().decode('utf-8')})
        }

    print(shopify_data)
    code_discount = shopify_data.get("data", {}).get("codeDiscountNodeByCode", {}).get("codeDiscount", {})
    short_summary = code_discount.get("shortSummary", "")

    if "0.01" in short_summary:
        return {
            "statusCode": 200,
            "body": json.dumps({"message": "Discount not created due to shortSummary containing '0.01'"})
        }

    # Step 2: Extract value and value_type from shortSummary
    value = None
    value_type = None

    # Match percentage-based discount (e.g., "20% off")
    percentage_match = re.search(r'(\d+)% off', short_summary)
    if percentage_match:
        value = percentage_match.group(1)
        value_type = "percentage"

    # Match amount-based discount (e.g., "$10 off")
    amount_match = re.search(r'\$(\d+\.?\d*) off', short_summary)
    if amount_match:
        value = amount_match.group(1)
        value_type = "fixed_amount"

    # If value and value_type could not be determined, return an error
    if not value or not value_type:
        return {
            "statusCode": 400,
            "body": json.dumps({"message": "Could not parse value and value_type from shortSummary"})
        }

    # Calculate the ends_at date (e.g., 30 days from now)
    current_time = datetime.utcnow()
    ends_at = current_time + timedelta(days=30)
    ends_at_str = ends_at.strftime('%Y-%m-%dT%H:%M:%SZ')

    # Step 3: Create the discount in Recharge
    recharge_payload = {
        "applies_to": {
            "purchase_item_type": "ALL"
        },
        "channel_settings": {
            "api": {
                "can_apply": True
            },
            "checkout_page": {
                "can_apply": True
            },
            "customer_portal": {
                "can_apply": True
            },
            "merchant_portal": {
                "can_apply": True
            }
        },
        "code": discount_code,
        "status": "enabled",
        "usage_limits": {
            "first_time_customer_restriction": False,
            "one_application_per_customer": False
        },
        "value": value,
        "value_type": value_type,
        "ends_at": ends_at_str
    }

    recharge_headers = {
        "Content-Type": "application/json",
        "X-Recharge-Version": "2021-11",
        "X-Recharge-Access-Token": recharge_access_token
    }

    # Create the request for Recharge API
    recharge_request = urllib.request.Request(
        recharge_api_url,
        data=json.dumps(recharge_payload).encode('utf-8'),
        headers=recharge_headers
    )

    try:
        with urllib.request.urlopen(recharge_request) as response:
            if response.status != 201:
                return {
                    "statusCode": response.status,
                    "body": json.dumps({"message": "Error creating discount in Recharge", "details": response.read().decode('utf-8')})
                }
    except urllib.error.HTTPError as e:
        return {
            "statusCode": e.code,
            "body": json.dumps({"message": "Error creating discount in Recharge", "details": e.read().decode('utf-8')})
        }

    return {
        "statusCode": 200,
        "body": json.dumps({"message": "Discount created successfully in Recharge"})
    }

This code looks much more complicated than it is. This python script does the following 3 things:

  1. Unpacks incoming payload of created discount

  2. Gets more info about the discount using Shopify GraphQL API

  3. Adds the discount to our ReCharge account

Conclusion

Now, discounts will automatically sync from your Shopify admin into ReCharge, increasing quality-of-life for both developers and customers alike. Now, when a customer receives an automatically generated discount code from a loyalty rewards system, it will also be available for subscription-based products. And next time you are manually creating discounts, you only have to create them within Shopify and let our cloud function do the rest!

0
Subscribe to my newsletter

Read articles from David Williford directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

David Williford
David Williford

I am a developer from North Carolina. I have always been fascinated by the internet, and scince high school I have been trying to understand the magic behind it all. ECU Computer Science Graduate