Updating subscriptions in bulk using the Theme Engine

Manage subscriptions in bulk using the Recharge Theme Engine.

Use the Recharge Theme Engine to bulk update or create multiple subscriptions tied to the same customer address, all at once. This functionality includes updating subscription details such as the next charge date or quantity, canceling multiple subscriptions, and re-activating subscriptions.

This guide provides instructions on using the Theme Engine to update subscriptions in bulk.


Before you start


Open your template file

The following sections are divided based on the features you can add to the Theme Engine. Each feature is independent of the others, and you can pick which ones you want to add on a case-by-case basis. All of the additional functionality is added in the same place.

  1. Click Storefront in the merchant portal and select Theme Editor.
  2. Click Edit code.
  3. Click the subscriptions.html template theme file.

📘

Note

You must place HTML code snippets inside the for loop, ie. {% for address in addresses %}, to access the customer's address ID for bulk updates. While the handler function (bulkSubscriptionUpdate) remains consistent, the helper functions for populating data objects varies.


Update next charge

The following code snippet creates an element with an on-click event that toggles a form that is initially hidden. The form includes an input date so that your customer can choose a new date for all subscriptions tied to a specific address. The submit button is initially disabled, so a new date must be passed.

On the submit button there is an on-click event and handler function that determines what type of update is requested. Based on that call, a helper function populates the data object to send, fires off an Ajax call, and reloads the page when the call is successful.

HTML

<!-- Bulk subscriptions - Next charge date code begin -->
<div>
 <p>
   <a href="#" onclick="Recharge.Helpers.toggle('RechargeForm_bulkUpdate_nextChargeDate-{{ address.id }}'); return false;" class="btn">Bulk update - Next charge date</a>
 </p>
 <form id="RechargeForm_bulkUpdate_nextChargeDate-{{address.id}}" data-address-id="{{ address.id }}" style="display: none">
   <p>
     <input type="date" id="next_charge_scheduled_at-{{ address.id }}" required name="next_charge_scheduled_at" value="" onchange="document.querySelector('#RechargeForm_bulkUpdate_nextChargeDate_submitButton-{{ address.id }}').disabled=false;">
   </p>
   <button id="RechargeForm_bulkUpdate_nextChargeDate_submitButton-{{ address.id }}" type="button" data-address-id="{{ address.id }}" class="btn" onclick="bulkSubscriptionUpdate(event, 'NEXT_CHARGE_DATE'); return false;" disabled>
     Submit
   </button>
   <a href="#" onclick="Recharge.Helpers.toggle('RechargeForm_bulkUpdate_nextChargeDate-{{ address.id }}'); return false;" class="btn btn--secondary">Cancel</a>
 </form>
 <br>
</div>
<!-- Bulk subscriptions - Next charge date code end --> 

JavaScript

function bulkUpdateRequest(data, addressId) {
   return $.ajax({
     url: Recharge.Endpoints.base + `addresses/${addressId}/subscriptions-bulk-update?token=${window.customerToken}`,
     type: 'post',
     dataType: 'json',
     contentType: 'application/json',
     data: JSON.stringify(data)
   });
 }


function bulkSubscriptionUpdate(event, type) {
  event.preventDefault();
  event.target.disabled = true;
  event.target.innerText = 'Processing...';

  // get address id
  var addressId = +event.target.getAttribute('data-address-id');
  // get Subscriptions
  var subs = {{ subscriptions | json }};
  // create empty object to send
  var dataToSend = {
    subscriptions: []
  };

  if (type === 'REACTIVATE') {
    dataToSend = getReactivateData(subs, addressId);
  } else if (type === 'QUANTITY') {
    dataToSend = getQuantityData(subs, addressId);
  } else if (type === 'FREQUENCY') {
    dataToSend = getFrequencyData(subs, addressId);
  } else if (type === 'ADDRESS') {
    dataToSend = getAddressChangeData(subs, addressId);
  } else if (type === 'CANCEL') {
    dataToSend = getCancelData(subs, addressId);
  } else if (type === 'NEXT_CHARGE_DATE') {
    dataToSend = getNextChargeDateData(subs, addressId);
  }

  // send POST request to update all subscriptions tied to that address
  bulkUpdateRequest(dataToSend, addressId)
    .done(function(response) {
      console.log('response', response)
      // Successful request made 
      Recharge.Toast.addToast('success', 'Updated subscriptions successfully');
      window.location.reload();
    }).fail(function(response) {
      // Request failed
      console.error(response);
      Recharge.Toast.addToast('error', 'Subscriptions could not be updated!');
      event.target.disabled = false;
    }); 
}

function getNextChargeDateData(subs, addressId) {
  var nextChargeDate = document.querySelector(`#next_charge_scheduled_at-${addressId}`).value;

  var subsToUpdate = subs
      .filter(function(sub) {
        return sub.address_id === addressId;
      }).map(function(sub) {
        return {
          id: sub.id,
          next_charge_scheduled_at: nextChargeDate
        };
      });

  return {
    subscriptions: subsToUpdate
  };


Update addresses

The following code snippet creates an element with an on-click event that toggles a hidden form. The form includes a select element that displays all other addresses that aren't the current ones registered under the customer's profile. The customer can choose a new address for all their subscriptions at once. The submit button is initially disabled, so a new address must be passed.

On the submit button, there is an on-click event and handler function that determines what type of update is requested. Based on that call, a helper function populates the data object to send, fires off an Ajax call, and reloads the page when the call is successful.

HTML

<!-- Bulk subscriptions - Change address code begin -->
<div>
  <p>
    <a href="#" onclick="Recharge.Helpers.toggle('RechargeForm_bulkUpdate_changeAddress-{{ address.id }}'); return false;" class="btn">Bulk update - Change address</a>
  </p>
  <form id="RechargeForm_bulkUpdate_changeAddress-{{ address.id }}" data-address-id="{{ address.id }}"  style="display: none">
    <p>
      <label for="order_interval_unit-{{ address.id }}">New address</label>
      <select id="new_address_id-{{ address.id }}" required name="new_address_id">
        {% for addr in addresses | rejectattr("id", "equalto", address.id) | list %}
          <option value="{{ addr.id }}">{{ addr.address1 }} {{ addr.address2 }}</option>
        {% endfor %}
      </select>
     </p>
     <button type="button" data-address-id="{{ address.id }}" class="btn" onclick="bulkSubscriptionUpdate(event, 'ADDRESS'); return false;">
       Change subscriptions address
     </button>
     <a href="#" onclick="Recharge.Helpers.toggle('RechargeForm_bulkUpdate_changeAddress-{{ address.id }}'); return false;" class="btn btn--secondary">Cancel</a>
   </form>
   <br>
</div>
<!-- Bulk subscriptions - Change address code end -->

JavaScript

function getAddressChangeData(subs, addressId) {
  var newAddressId = document.querySelector(`#new_address_id-${addressId}`).value;
  var subsToUpdate = subs
      .filter(function(sub) {
        return sub.address_id === addressId;
      }).map(function(sub) {
        return {
          id: sub.id,
          address_id: newAddressId
        };
      });

  return {
    subscriptions: subsToUpdate
  };
}

Update quantity

The following code snippet creates an element with an on-click event that toggles a hidden form. The form includes an input number element with an initial value of 1 that your customer can use to set a new quantity for all of their subscription items.

On the submit button, there is an on-click event and handler function that determines what type of update is requested. Based on that call, a helper function populates the data object to send, fires off an Ajax call, and reloads the page when the call is successful.

HTML

<!-- Bulk subscriptions - Change quantity begin -->
<div>
  <p>
    <a href="#" onclick="Recharge.Helpers.toggle('RechargeForm_bulkUpdate_quantity-{{ address.id }}'); return false;" class="btn">Bulk update - Change quantity</a>
  </p>
  <form id="RechargeForm_bulkUpdate_quantity-{{ address.id }}" data-address-id="{{ address.id }}"  style="display: none">
    <p>
      <label for="quantity-{{ address.id }}">Quantity</label>
      <input type="number" id="quantity-{{ address.id }}" required name="quantity" value="1" min="1">
    </p>
    <button type="button" data-address-id="{{ address.id }}" class="btn" onclick="bulkSubscriptionUpdate(event, 'QUANTITY'); return false;">
      Change subscriptions quantity
    </button>
    <a href="#" onclick="Recharge.Helpers.toggle('RechargeForm_bulkUpdate_quantity-{{ address.id }}'); return false;" class="btn btn--secondary">Cancel</a>
 </form>
 <br>
</div>
<!-- Bulk subscriptions - Change quantity end -->

JavaScript

function getQuantityData(subs, addressId) {
  var newQuantity = document.querySelector(`#quantity-${addressId}`).value;
  var subsToUpdate = subs
      .filter(function(sub) {
         return sub.address_id === addressId;
      }).map(function(sub) {
         return {
           id: sub.id,
           quantity: newQuantity
         };
      });
  return {
    subscriptions: subsToUpdate
  };
}

Update frequency

The following code snippet creates an element with an on-click event that toggles a hidden form. The form includes a select element with options for order interval units. The customer is presented with two input number elements that represent order and charge interval frequency with the default value set to 1.

On the submit button, there is an on-click event and handler function that determines what type of update is requested. Based on that call, a helper function populates the data object to send, fires off an Ajax call, and reloads the page when the call is successful.

HTML

<!-- Bulk subscriptions - Change frequency begin -->
<div>
  <p>
    <a href="#" onclick="Recharge.Helpers.toggle('RechargeForm_bulkUpdate_frequency-{{ address.id }}'); return false;" class="btn">Bulk update - Change frequency</a>
  </p>
  <form id="RechargeForm_bulkUpdate_frequency-{{ address.id }}" data-address-id="{{ address.id }}"  style="display: none">
    <p>
      <label for="order_interval_unit-{{ address.id }}">Interval unit</label>
      <select type="text" id="order_interval_unit-{{ address.id }}" required name="order_interval_unit">
        <option value="day">Days</option>
        <option value="week">Weeks</option>
        <option value="month">Months</option>
      </select>
    </p>
    <p>
      <label for="order_interval_frequency-{{ address.id }}">Interval frequency</label>
      <input type="number" id="order_interval_frequency-{{ address.id }}" name="order_interval_frequency-{{ address.id }}" value="1" min="1">
    </p>
    <p>
      <label for="charge_interval_frequency-{{ address.id }}">Interval frequency</label>
      <input type="number" id="charge_interval_frequency-{{ address.id }}" name="charge_interval_frequency-{{ address.id }}" value="1" min="1">
    </p>
    <button type="button" data-address-id="{{ address.id }}" class="btn" onclick="bulkSubscriptionUpdate(event, 'FREQUENCY'); return false;">
       Change subscriptions frequency
    </button>
    <a href="#" onclick="Recharge.Helpers.toggle('RechargeForm_bulkUpdate_frequency-{{ address.id }}'); return false;" class="btn btn--secondary">Cancel</a>
  </form>
  <br>
</div>
<!-- Bulk subscriptions - Change frequency end -->

JavaScript

function getFrequencyData(subs, addressId) {
  var newOrderIntervalUnit = document.querySelector(`#order_interval_unit-${addressId}`).value;
  var newOrderIntervalFrequency = +document.querySelector(`#order_interval_frequency-${addressId}`).value;
  var newChargeIntervalFrequency = +document.querySelector(`#charge_interval_frequency-${addressId}`).value;
  var subsToUpdate = subs
      .filter(function(sub) {
        return sub.address_id === addressId;
      }).map(function(sub) {
        return {
          id: sub.id,
          order_interval_unit: newOrderIntervalUnit,
          order_interval_frequency: newOrderIntervalFrequency,
          charge_interval_frequency: newChargeIntervalFrequency,
        };
      });
  return {
    subscriptions: subsToUpdate
  };
}

Re-activate subscriptions

The following HTML snippet creates an element with an on-click event that toggles a hidden form. The form allows customers to reactivate all dormant subscription products.

On the submit button, there is an on-click event and handler function that determines what type of update is requested. Based on that call, a helper function populates the data object to send, fires off an Ajax call, and reloads the page when the call is successful.

HTML

<!-- Bulk subscriptions - Reactivate code begin -->
<div>
   <button type="button" data-address-id="{{ address.id }}" class="btn" onclick="bulkSubscriptionUpdate(event, 'REACTIVATE'); return false;">
       Bulk update - Re-activate subscriptions
   </button>
   <br>
</div>
<br>
<!-- Bulk subscriptions - Reactivate code end -->

JavaScript

function getReactivateData(subs, addressId) {
  var subsToUpdate = subs
      .filter(function(sub) {
        return sub.address_id === addressId && sub.status === 'CANCELLED';
      }).map(function(sub) {
        return {
          id: sub.id,
          status: 'ACTIVE'
        };
      });
   return {
     subscriptions: subsToUpdate
   };
}

Cancel subscriptions

The following code snippets create an element with an on-click event that toggles a hidden form. The form includes an input select element displaying retention strategy cancellation reasons and a text area element for cancellation reason comments.

When you click the button to toggle the form, an Ajax call retrieves your store's retention strategy cancellation reasons and displays them as options. Once the customer selects a reason, a text area appears for customers to provide additional information.

Once submitted, there is an on-click event and handler function that determines what type of update is requested. Based on that call, a helper function populates the data object to send, fires off an Ajax call, and reloads the page when the call is successful.

HTML

<!-- THESE ARE TO BE PLACED AFTER LINE 76 ON "subscriptions.html" -->
<!-- JUST AFTER THE CLOSING DIV AND BEFORE THE 2 "endif" STATEMENTS -->
<!-- Bulk subscriptions - Cancel html code begin -->
<div>
   <p>
       <a href="#" onclick="toggleCancellationFlowForm({{ address.id }}); return false;" class="btn">Bulk update - Cancel subscriptions</a>
   </p>
   <form id="RechargeForm_bulkUpdate_cancel-{{ address.id }}" data-address-id="{{ address.id }}"  style="display: none" class="cancellation-flow__form">
       <div class="cancellation-flow__form__message">
           <p>
               Fetching cancellation reasons...
           </p>
       </div>
       <div style="display: none;" class="cancellation-flow__form__reason">
           <label for="cancellation_reason-{{ address.id }}">Cancellation reason</label>
           <select id="cancellation_reason-{{ address.id }}" name="cancellation_reason-{{ address.id }}" onchange="handleCancellationReasonSelected(event, {{ address.id }})"></select>
       </div>
       <br>
       <div style="display: none;" class="cancellation-flow__form__comments">
           <label for="cancellation_reason_comments-{{ address.id }}">Cancellation reason comments</label>
           <textarea id="cancellation_reason_comments-{{ address.id }}" name="cancellation_reason_comments-{{ address.id }}" onchange="updateCancellationButtonState({{ address.id}})"></textarea>
       </div>
       <button type="button" data-address-id="{{ address.id }}" class="btn cancellation-flow__form__button" onclick="bulkSubscriptionUpdate(event, 'CANCEL'); return false;" disabled>
           Cancel subscriptions
       </button>
       <a href="#" onclick="toggleCancellationFlowForm({{ address.id }}); return false;" class="btn btn--secondary">Cancel</a>
   </form>
   <br>
</div>
<!-- Bulk subscriptions - Cancel html code end -->

JavaScript - Cancellation Flow and Retention Strategies

<!-- THIS IS TO BE PLACED AT THE BOTTOM OF THE PAGE -->
<!-- BUT BEFORE THE endblock STATEMENT -->
<!-- Bulk subscription - Cancel js code begins -->
<script>
  function getRetentionStrategies() {
    return $.ajax({
             url: Recharge.Endpoints.base + 'request_objects?token=' + window.customerToken,
             type: 'get',
             dataType: 'json',
             data: { schema: `{ "retention_strategies": {} }` }
            });
 }

 function toggleCancellationFlowForm(addressId) {
   var formId = `RechargeForm_bulkUpdate_cancel-${addressId}`;
   Recharge.Helpers.toggle(formId);
   if (document.querySelector(`#${formId}`).style.display === 'none') {
     resetCancellationFormState(addressId);
   } else {
     loadCancellationData(addressId);
   }
 }

 function resetCancellationFormState(addressId) {
   var select = document.querySelector(`#cancellation_reason-${addressId}`),
       comments = document.querySelector(`#cancellation_reason_comments-${addressId}`);
       select.value = '';
       comments.value = '';
       select.dispatchEvent(new Event('change'));
       updateCancellationButtonState(addressId);
 }
   

 function loadCancellationData(addressId) {
   var formId = `RechargeForm_bulkUpdate_cancel-${addressId}`,
       form = document.querySelector(`#${formId}`),
       select = form.querySelector(`#cancellation_reason-${addressId}`),
       button = form.querySelector('.cancellation-flow__form__button');
   if (select.hasAttribute('data-options-loaded')) {
     // Hide loading message
     form.querySelector('.cancellation-flow__form__message').style.display = 'none';
     // Show select container
     form.querySelector('.cancellation-flow__form__reason').style.display = 'block';
     return;
   }

   getRetentionStrategies()
      .done(function(response) {
        // map strategies to HTML options
        var options = response.retention_strategies
            .map(function(strategy) {
               return `<option value="${strategy.cancellation_reason}">${strategy.cancellation_reason}</option>`;
            });
        options.unshift('<option value="">Select a reason for cancelling</option>');
        // Populate select with options
        select.innerHTML = options.join('');
        // Set attribute to avoid future unnecessary fetching
        select.setAttribute('data-options-loaded', 'true');
        // Hide loading message
        form.querySelector('.cancellation-flow__form__message').style.display = 'none';
        // Show select container
        form.querySelector('.cancellation-flow__form__reason').style.display = 'block';
      })
      .fail(function () {
        console.error(response);
        Recharge.Toast.addToast('error', 'Cancellation options could not be loaded!');
      });
 }
 
 function handleCancellationReasonSelected(event, addressId) {
   var value = event.target.value,
       commentSection = document.querySelector(`#RechargeForm_bulkUpdate_cancel-${addressId} .cancellation-flow__form__comments`);
   // Toggle comments section
   commentSection.style.display = !value ? 'none' : 'block';
   updateCancellationButtonState(addressId);
 }

 function updateCancellationButtonState(addressId) {
   var selectedValue = document.querySelector(`#cancellation_reason-${addressId}`).value,
       comments = document.querySelector(`#cancellation_reason_comments-${addressId}`).value,
       button = document.querySelector(`#RechargeForm_bulkUpdate_cancel-${addressId} .cancellation-flow__form__button`);
   if (!!selectedValue && !!comments.trim()) {
     button.disabled = false;
   } else {
     button.disabled = true;
   }
 }
</script>
<!-- Bulk subscription - Cancel js code ends -->

JavaScript - Helper function

function getCancelData(subs, addressId) {
  var newCancellationReason = document.querySelector(`#cancellation_reason-${addressId}`).value;
  var newCancellationReasonComments = document.querySelector(`#cancellation_reason_comments-${addressId}`).value;
  var subsToUpdate = subs
      .filter(function(sub) {
         return sub.address_id === addressId;
      }).map(function(sub) {
         return {
           id: sub.id,
           status: 'CANCELLED',
           cancellation_reason: newCancellationReason,
           cancellation_reason_comments: newCancellationReasonComments,
         };
      });
   return {
     subscriptions: subsToUpdate
   };
 }

Need Help? Contact Us