Skip to main content
The Order Protection widget integrates with Ordergroove and your Shopify store, enhancing the subscription order experience for users with all relevant information in one place.The widget addition to the cart simplifies the claim-filing process for users reducing friction and eliminating the need for manual navigation to the Order Protection website.

Installation

1

Generate an API Token

Access the Ordergroove admin page: Ordergroove Admin
  • Go to Developers menu and click on API Keys
  • Click on Create button
  • Enter a Name and Email
    • Name: Order Protection
    • Email: onboarding@orderprotection.com
    • Allow bulk queries?: Yes
  • Click on Save button
  • Copy the API Key for Step 3
API Token
2

Create a Webhook

Since Ordergroove does not have a way to automate the creation of webhooks, we will need to create them manually.
  • Go to Developers menu and click on Webhooks
  • Click on Create button
  • Enter a Name and Email
    • Webhook Name: Order Protection
    • Webhook URL: https://subscriptions.production.orderprotection.com/api/v1/webhooks/ordergroove/[PLATFORM_DOMAIN]/webhook
      • Replace [PLATFORM_DOMAIN] with your platform domain. e.g. for Shopify it would be example.myshopify.com
  • For events, tick only subscription.create
    • Note: there is a similar event called subscriber.create, please note to tick the correct one Webhook
  • No actions on Advance Settings
  • Click on Save button
  • Copy the Verification Key for Step 3
Webhook
3

Integrate Ordergroove within the Order Protection Platform

Within the Order Protection app, do the following:
  • Navigate to Integrations in the left navigation.
  • Find the Ordergroove block under the Available tab.
  • Take the API token you created in Step 1 and the Webhook Verification Key you created in Step 2 and add it to the corresponding fields and save.
  • Once saved, click on the Active tab and the API token and Webhook Verification Key fields within the Ordergroove block will have a hidden value. This means that your token and webhook successfully uploaded. ordergroove api token in op app
4

Install Scripts in Ordergroove's Subscription Manager

  • Go to Subscriptions menu and click on Subscription Manager
  • Click on Advanced toggle button
  • On the side menu, click on View
  • Click on ADD NEW FILE, and name it orderprotection.liquid with content:
    orderprotection.liquid
    {# required non-null: order, subscriptions, addresses, current_order_items #}
    <div style="padding-left: 12px; padding-right: 12px;">
      <div id="order-protection-{{ order.public_id }}"></div>
    </div>
    {{ 'bootstrapOrderProtection({ order, subscriptions, addresses, current_order_items })' | js }}
    
  • Go to /views/order/main.liquid, enter new line after {% include 'order-summary/main' %} and add:
    order/main.liquid
    `{% include 'orderprotection' %}`
    
    Like below: Paste code
  • Go to /views/order-level-actions/send-order-now.liquid and change the button:
    order-level-actions/send-order-now.liquid
    <button class="og-button" type="submit" name="send_now">{{ 'modal_send_now_save' | t }}</button>
    
    to:
    order-level-actions/send-order-now.liquid
    <button class="og-button" type="button" @click="{{ 'orderProtectionSendNow' | js }}" data-order-id="{{ order.public_id }}">
      {{ 'modal_send_now_save' | t }}
    </button>
    
  • Lastly, go to /scripts/script.js and add the following code to the bottom of the file:
    script.js
    /*
    * BEGIN ORDERPROCTION ORDERGROOVE MANAGER
    */
    
    /* Environment variables */
    const OP_CORE_CSS_URL = 'https://cdn.orderprotection.com/widget/core/latest/style.css';
    const OP_SCRIPT_URL = 'https://cdn.orderprotection.com/widget/subscriptions/latest/orderprotection.js';
    const OP_DEBUG = true;
    const OP_LOG_PREFIX = '[OP]';
    const OP_MAX_RETRIES = 5;
    
    /* State variables */
    let opScriptLoaded = false;
    const opWidgets = [];
    
    /* Utilities */
    function opLog(...message) {
      if (OP_DEBUG) console.log(OP_LOG_PREFIX, ...message);
    }
    
    function opDelay(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    function opCreateCurrencyFormatter() {
      const formatter = new Intl.NumberFormat(undefined, {
        style: 'currency',
        currency: window.Shopify.currency.active,
      });
      const symbol = formatter.formatToParts(1).find(p => p.type === 'currency').value;
      return {
        format: cents => formatter.format(cents / 100),
        symbol,
      };
    }
    
    function opQuerySelector(orderId, selector) {
      return document.querySelector(`.og-content-wrapper[data-order-id="${orderId}"] ${selector}`);
    }
    
    function opCreateElement(tag, attrs = {}, children = []) {
      const el = document.createElement(tag);
      Object.assign(el, attrs);
      el.append(...children);
      return el;
    }
    
    function opCreateCostBreakdownItem(label, value) {
      return opCreateElement('div', { className: 'op-cost-breakdown-item' }, [
        opCreateElement('dt', { textContent: label }),
        opCreateElement('dd', { className: 'og-text-align-right', textContent: value }),
      ]);
    }
    
    function opOnWidgetToggledHandler(orderId) {
      const opWidget = window.orderProtection?.[orderId];
      if (!opWidget || !opWidget.cart.subtotal || !opWidget.price) return;
    
      const { format, symbol: currency } = opCreateCurrencyFormatter();
      const total = opWidget.enabled ? opWidget.cart.subtotal + opWidget.price : opWidget.cart.subtotal;
      const formattedTotal = format(total);
    
      // Update price heading
      const priceHeadingEl = opQuerySelector(orderId, '.og-summary-heading.og-price-heading');
      if (priceHeadingEl) {
        priceHeadingEl.textContent = formattedTotal;
      } else {
        opLog('priceHeading not found:', orderId);
      }
    
      // Update total element
      const totalEl = opQuerySelector(orderId, '.og-total');
      if (totalEl) {
        totalEl.childNodes.forEach(child => {
          if (child.textContent.includes(currency)) {
            child.textContent = formattedTotal;
          }
        });
      } else {
        opLog('totalEl not found:', orderId);
      }
    
      // Update subtotal element
      const subtotalEl = opQuerySelector(orderId, 'dd[data-testid="order-sub-total"]');
      const items = opWidget.cart.preexistingData?.current_order_items ?? [];
      const products = window?.og?.smi?.store?.getState()?.products ?? [];
      const subTotalWithoutOp = items.reduce((sum, i) => {
        const product = products.find(p => p.external_product_id === i.product);
        if (product?.name?.startsWith('Order Protection')) return sum;
        return sum + (+i.total_price || 0) * 100;
      }, 0);
      if (subtotalEl && subTotalWithoutOp) {
        subtotalEl.textContent = format(subTotalWithoutOp);
      } else {
        opLog('subtotalEl not found:', !!subtotalEl, subTotalWithoutOp, orderId);
      }
    
      // Update cost breakdown
      const costBreakdownEl = opQuerySelector(orderId, 'div.og-cost-breakdown > dl');
      if (costBreakdownEl) {
        const existingItem = costBreakdownEl.querySelector('.op-cost-breakdown-item');
    
        if (opWidget.enabled && !existingItem) {
          const item = opCreateCostBreakdownItem('Shipping Protection', format(opWidget.price));
          costBreakdownEl.appendChild(item);
        }
    
        if (!opWidget.enabled) {
          costBreakdownEl.querySelectorAll('.op-cost-breakdown-item').forEach(el => el.remove());
        }
      } else {
        opLog('costBreakdownEl not found:', orderId);
      }
    }
    
    /* Load assets */
    function opLoadAsset({ tag, attrs, parent = document.head }) {
      return new Promise((resolve, reject) => {
        const el = Object.assign(document.createElement(tag), attrs, {
          onload: () => resolve(el),
          onerror: reject,
        });
        parent.appendChild(el);
      });
    }
    
    Promise.all([
      opLoadAsset({ tag: 'link', attrs: { rel: 'stylesheet', href: OP_CORE_CSS_URL } }).then(() =>
        opLog('Core CSS loaded:', OP_CORE_CSS_URL),
      ),
      opLoadAsset({ tag: 'script', attrs: { src: OP_SCRIPT_URL }, parent: document.body }).then(() => {
        opScriptLoaded = true;
        opLog('script loaded:', opScriptLoaded);
      }),
    ]);
    
    /* Functions */
    // Bootstrap Order Protection, for use in the liquid template
    function bootstrapOrderProtection(params) {
      initOrderProtectionWidget(params);
    
      return '';
    }
    
    // Initialize the Order Protection widget
    async function initOrderProtectionWidget(params, retries = 0) {
      const { order } = params;
    
      // Check if the script has loaded
      if (!opScriptLoaded) {
        if (retries >= OP_MAX_RETRIES) {
          opLog('Script failed to load after max retries:', order.public_id);
          return;
        }
        await opDelay(300);
        opLog('Script not loaded, retrying...', order.public_id);
    
        return initOrderProtectionWidget(params, retries + 1);
      }
      if (opWidgets.some(widget => widget.subscription === order.public_id)) {
        return opLog('Already bootstrapped:', order.public_id);
      }
    
      opLog('Bootstrapping Order Protection for subscription:', order.public_id);
    
      // Create the Order Protection widget
      const opWidget = OrderProtection.createOrderProtectionWidget({
        instanceName: order.public_id,
        integration: 'ordergroove',
        attach: true,
        debug: OP_DEBUG,
        locations: [{ id: `order-protection-${order.public_id}` }],
        version: 'v2',
        preexistingData: params,
      });
    
      opWidget.cart.debug = OP_DEBUG;
      opWidget.widget.on('toggled', () => opOnWidgetToggledHandler(order.public_id));
    
      opWidgets.push({ subscription: order.public_id });
    
      // Wait for the cart to be ready, for final setup and initialization
      try {
        while (!opWidget.cart.isReady()) {
          opLog(order.public_id, 'Cart not ready, retrying...');
          opWidget.cart.subscription = {};
          await opWidget.cart.loadSubscription();
          await opDelay(500);
        }
    
        opWidget.cart.setup();
        await opWidget.init();
      } catch (error) {
        console.error(error);
      }
    }
    
    async function orderProtectionSendNow(e) {
      opLog('OP Send Now called');
    
      const btn = e?.currentTarget;
    
      if (!btn) {
        opLog('missing required parameters: btn');
        return;
      }
    
      const dialog = btn.closest('dialog');
      const orderId = btn.dataset.orderId || dialog?.querySelector('input[name="order"]')?.value;
      const opWidget = orderId ? window.orderProtection?.[orderId] : null;
    
      if (!dialog || !orderId || !opWidget) {
        opLog(`missing required parameters: dialog: ${!!dialog}, orderId: ${!!orderId}, opWidget: ${!!opWidget}`);
        return;
      }
    
      // UX: disable to prevent double-clicks
      const btnText = btn.textContent;
      btn.disabled = true;
      btn.textContent = 'Loading...';
    
      try {
        // 1) Sync the Order Protection item to the cart
        await opWidget.cart.syncOpItem();
    
        const getAuthorizationHeader = () => {
          const { public_id, sig_field, ts, sig } = og.smi.store.getState().customer;
    
          return JSON.stringify({ public_id, sig_field, ts, sig });
        };
    
        // 2) Send the order now via Ordergroove REST API
        const response = await fetch(`https://restapi.ordergroove.com/orders/${orderId}/send_now/`, {
          method: 'PATCH',
          headers: {
            'Content-Type': 'application/json',
            Authorization: getAuthorizationHeader(),
          },
        });
    
        if (!response.ok) {
          throw new Error(`Failed to send order now: ${response.statusText}`);
        }
    
        // 3) Close modal + refresh state so UI updates (order moves to processing)
        dialog.close();
        await window.og.smi.refresh_page_state();
      } catch (err) {
        console.error(err);
      } finally {
        btn.disabled = false;
        btn.textContent = btnText;
      }
    }
    
    /*
    * END ORDERPROCTION ORDERGROOVE MANAGER
    */
    
Once set up, Order Protection will be added to all cart instances within Shopify subscription orders using Ordergroove. Customers can choose to opt-in or opt out of Order Protection for their subscription orders.
Customers will be able to file/edit claims per your normal store settings once an order confirmation email has been sent.