One Time SKU Swap Configuration in the Subscription Manager

Allow customers to swap their product for a single upcoming order without permanently changing their subscription
View as Markdown

SKU Swap is an out-of-the-box feature that allows customers to change from one SKU to another without needing to cancel one product and resubscribe to another. This feature helps retention by getting ahead of common subscription cancel symptoms — often referred to as “flavor fatigue” — whether that’s flavor, size, scent, or anything else.


What Are the Benefits of a One-Time Swap?

By default, SKU Swap permanently changes the subscription to the newly chosen product. In some cases, a customer may only want to swap their next upcoming order without permanently switching. A common example is when an item is out of stock — customers may want to swap to avoid waiting, but keep their original subscription going. The instructions below add the ability for customers to choose between a permanent swap or a one-time swap.


Setting Up One Time SKU Swap

There are two main steps: access the advanced editor, then modify the relevant files.

1. Accessing the Subscription Manager Advanced Editor

You can access the advanced editor through your Ordergroove Admin:

  1. Log in to Ordergroove.
  2. Go to Subscriptions on the top toolbar, and select Subscription Manager.
  3. Toggle Advanced on the top left.
Support

Some aspects of this article require technical expertise with coding languages. This is self-serve and outside of the normal support policy.

2. Adding the Code Snippets

File: locales/en-US.js

Update the message for the existing SKU swap confirm button and add a new key for the one-time change button:

1"modal_change_product_save": "Change for all the Future Orders",
2"modal_change_product_change_one_time": "One Time Change",

File: change-product.liquid

Update the SKU Swap experience with the following changes:

  1. Add the current product to the SKU Swap modal. Normally the current product is excluded since customers are swapping away from it — but for a one-time swap, the customer may want to swap back. This snippet makes the existing product visible in the swap experience:

    1{% set customized_sku_options = 'add_items(alternative_ids, subscription.product)' | js %}
  2. Replace the default product list (alternative_ids) with the customized options from step 1:

    1{% for alternative_id in customized_sku_options %}
  3. Add a hidden input with the order_id for use in the one-time swap call:

    1<input type="hidden" value="{{ order_item.public_id }}" name="order_item" />
  4. Create the new one-time change button. This button calls the custom function change_item_product defined in script.js:

    1<button class="og-button" name="change_product_one_time" type="button" @click={{ 'change_item_product' | js }}>
    2 {{ 'modal_change_product_change_one_time' | t }}
    3</button>
  5. Add a call to the custom function within the regular SKU swap form so that a permanent swap also updates the item for the current shipment:

    1@finally={{ 'change_item_product' | js }}

    The top of the form code will look something like this:

    1<form
    2 action="{{ 'change_product' | action }}"
    3 name="og_change_product"
    4 @success={{ 'close_closest_modal' | js }}
    5 @reset={{ 'close_closest_modal' | js }}
    6 @finally={{ 'change_item_product' | js }}>

File: script.js

Add this code at the end of script.js:

1const change_item_product = async function( ev ) {
2 const { customer, environment: { lego_url } } = og.smi.store.getState();
3 const form = ev.target.closest('form');
4 const formComponents = form.querySelectorAll('select,input,button,div');
5 const subscription_id = form.querySelector('[name="subscription"]').value;
6 const disclaimer = document.querySelector('#og-one-time-change-disclaimer-'+subscription_id);
7 const order_item = form.querySelector('[name="order_item"]').value;
8 const product = form.querySelector('[name="product"]:checked').value;
9 const one_time_change_params = {
10 product: product
11 }
12
13 if ( disclaimer ) {
14 disclaimer.setAttribute('style', 'display: none');
15 }
16 formComponents.forEach( it => {
17 it.toggleAttribute('disabled', true)
18 it.setAttribute('style', 'opacity:0.6')
19 })
20
21 const response = await fetch(`${lego_url}/items/${order_item}/change_product/`, {
22 method: 'PATCH',
23 headers:{
24 'Authorization': JSON.stringify(customer),
25 'Accept': 'application/json',
26 'Content-Type': 'application/json',
27 },
28 body: JSON.stringify(one_time_change_params)
29 })
30
31 if (response.status === 200) {
32 og.smi.api.request_subscriptions()
33 og.smi.api.request_orders({ status: 1 })
34 og.smi.api.request_orders_items({ status: 1 })
35 .then(() => formComponents.forEach(it => {
36 it.toggleAttribute('disabled', false)
37 it.setAttribute("style", 'opacity: 1')
38 }))
39 .then(() => (disclaimer) ? disclaimer.setAttribute('style', 'display: block') : '')
40 .then(() => displayCustomToast('Success! Your order item has been changed.'))
41 .then(() => close_closest_modal(ev))
42 }
43}
44
45function displayCustomToast (msg) {
46 let customToast = document.createElement('li');
47 let toastContainer = smiElement.querySelector('.og-toasts');
48 customToast.classList.add('og-toast', 'og-toast-success', 'og-show');
49 customToast.innerHTML = msg;
50 toastContainer.appendChild(customToast);
51 toast_notification_animation();
52
53 setTimeout(() => {
54 toastContainer.removeChild(customToast);
55 }, TOAST_NOTIFICATION_TIMEOUT);
56}
57
58const add_items = og.smi.memoize(function (a, b) {
59 return [...a, b];
60})

Styling

File: order-item.less

Add this snippet inside the .og-product class to style the one-time change disclaimer:

1.og-one-time-change-disclaimer {
2 font-size: .8em;
3 font-style: italic;
4 background-color: #FFFF0099;
5 max-width: 80%;
6}

Class: .og-change-product-control

To style the buttons, add this snippet inside the .og-change-product-control class:

1.og-dialog-footer {
2 justify-content: space-between;
3}