# Integration

## Subscription apps

* The app automatically detects subscription products and can handle a checkout where one-time purchases and subscription products are combined.
* The app was tested with:
  * Shopify's subscription app
  * [Seal](https://www.sealsubscriptions.com/)
  * [Appstle](https://appstle.com/)
  * [Recharge](https://rechargepayments.com/)
  * [Yotpo](https://www.yotpo.com/)
  * [Stay.ai](https://stay.ai/)
* Some subscription apps provide a table or box on product pages (PDP) that presents the one-time-purchase and subscription prices. If you need to display discounted prices for the one-time-purchase and subscription prices, you'll need to make some code edits. Learn more about this use case [here](/discount-ninja-developer-hub/theme-edits/code-edits/product-detail-page-pdp/price.md#subscription-apps).

## Email marketing apps

### Trigger a promotion from an email

You can trigger a promotion using a discount link. The discount link is essentially a link to a landing page followed by the token of the promotion. The app includes a handy tool to build those links. A discount link automatically triggers the promotion for the customer and navigates to the landing page you have selected.

Most email marketing tools allow you to add a link to a word or a button through their user interface. If you need to manually add them using HTML, here are some examples that can get you started:

* include a simple link

```html
Activate your discount now, by clicking <a href="https://YOUR-SHOP-URL-HERE/?token=ABCD">here</a>.
```

* include a button

```html
<div style="display:flex;align-items:center;justify-content:center;text-align:center;margin:20px;max-width:200px;height:50px;background:#000;border:2px solid #fff;border-radius:6px;">
    <a href="https://YOUR-SHOP-URL-HERE/?token=ABCD" style="color:#fff;text-decoration:none;">
        SHOP&nbsp;THE&nbsp;DEAL
    </a>
</div>
```

{% hint style="info" %}

### Note that <https://YOUR-SHOP-URL-HERE/?token=ABCD> must be replaced by the discount link of the promotion you want to trigger. For example: <https://www.mystore.com/?token=EY1UM>

{% endhint %}

### Discount code wrapper

Discount Ninja can also detect discount codes generated by email marketing apps, more specifically:

* Klaviyo: Email marketing

## Loyalty apps

* Discount codes generated by loyalty apps can be redeemed using Discount Ninja's promotion code field.
* The app includes an offer type to build "wrapper" promotions to display Discount Ninja widgets when a loyalty discount code is entered in the promotion code field.
* Discount Ninja can detect discount codes generated by the following apps:
  * Appstle Loyalty & Rewards
  * Influenci.io: Loyalty Rewards
  * Loyalty & referrals by Yotpo (Swell)
  * Loyalty, rewards and referrals by LoyaltyLion
  * Loyalty, Wishlist, Reviews UGC by Growave
  * Referral Candy
  * Smile: Rewards & Loyalty
  * Stamped.io Loyalty & Rewards

## Pickup + delivery apps

* Discount Ninja has built in integration with Zapiet.

  The integration relies on a method named `Zapiet.Widget.checkoutEnabled()` which must evaluate to `true` to allow the app to continue the checkout pipeline.

## Prefill checkout information

* Discount Ninja requires that third party developers add query parameters to the action attribute of the cart form, which is a standard technique to pass information from the cart to the checkout.
* To prefill shipping information at checkout based on a selection made in the checkout, third party developers must ensure the `action` attribute of the cart form included the correct query parameters. This can be done using JavaScript.&#x20;
* Shopify provides a number of query parameters to prefill the checkout: <https://shopify.dev/docs/apps/build/checkout/cart-permalinks/create-cart-permalinks>
* Example of an action attribute, rendered by Liquid and updated by JavaScript:

```html
<form action="/cart?checkout[shipping_address][city]=London" method="POST">
    ...
</form>
```

## Currency conversion switchers

* The app detects the currency based on the `Shopify.currency` object
* We are compatible with all apps that rely on this object

## Installment apps

* The app can apply discounts to installment payments proposed by:
  * AfterPay
  * QuadPay
  * Sezzle
  * Hoolah
  * ShopPay

## Search and filter apps

* Rendering discounted prices in search results and collections managed by apps requires the following:
  * Discount Ninja must be able to identify where prices are rendered. This can be achieved in one of two ways:
    * Using a CSS selector
    * By marking the section with an attribute
  * For each collection product the app must, at a minimum, have access to the product handle
* The app has been tested with the following apps:
  * [Boost AI Search & Filter](https://apps.shopify.com/product-filter-search) (formerly known as PFS)
  * [Searchanise Search & Filter](https://apps.shopify.com/searchanise)
  * [Globo Smart Search & Product Filter](https://apps.shopify.com/product-filter-and-search) (SPF)
  * [AI Search & Product Filter](https://apps.shopify.com/ultimate-search-and-filter-1) (Ultimate Search)
* For information on how to configure these apps to show strikethrough pricing, please refer to [this page](/discount-ninja-developer-hub/theme-edits/code-edits/product-list-page-plp-and-collections/price.md).

## Page builders

* The app has different levels of support for different page builders. More information about each integration is available from the following support articles:
  * [PageFly](https://support.discountninja.io/en/articles/5056758-enable-discount-ninja-on-a-pagefly-page)
  * [Zipify Pages](https://support.discountninja.io/en/articles/3722543-zipify-pages-and-discount-ninja)
  * GemPages
  * [Shogun](https://support.discountninja.io/en/articles/5173770-enable-discount-ninja-on-shogun-pages)

## Terms and conditions checkboxes

#### Behavior

The app automatically checks for known checkboxes that are related to terms and conditions. The app will not proceed the checkout pipeline until those checkboxes (if marked mandatory and visible) are checked.&#x20;

{% hint style="warning" %}
Important: note that the logic to cancel the checkout based on the status of the checkbox should be present in the code of your theme (or the app that handles this checkbox). Discount Ninja does not display an error message, it relies on the theme to cancel the checkout and display a message.
{% endhint %}

#### Explicitly marking a checkbox

If you use a custom checkbox that isn't automatically detected by the app, you may need to mark it explicitly. To explicitly mark a checkbox as required before checkout, add the following attribute to the input field:

```html
la-dn-checkout-required-checkbox
```

#### Syntax <a href="#h_46648c7982" id="h_46648c7982"></a>

```javascript
<input 
    type="checkbox" 
    name="termsAndConditions" 
    form="cart" 
    la-dn-checkout-required-checkbox>
<label for="termsAndConditions">I accept the terms and conditions</label>
```

## Markets

* The app can be used on stores that have multiple markets configured.
* Shopify functions mode supports most features included in Shopify Markets:
  * &#x20;[International pricing](https://help.shopify.com/en/manual/markets/pricing/product-prices-by-country): fixed product prices configured in Shopify Markets are used.
  * &#x20;[Exchange rates](https://help.shopify.com/en/manual/markets/pricing/exchange-rates): exchange rates will be applied to product prices as configured in Shopify Markets.
  * &#x20;[Rounding](https://help.shopify.com/en/manual/markets/pricing/rounding): rounding will be applied to product prices as configured in Shopify Markets.
* Caveats
  * Thresholds and fixed amounts: thresholds and fixed amount discounts are calculated based on the conversion rate defined in Shopify Markets.
  * &#x20;[Rounding](https://help.shopify.com/en/manual/markets/pricing/rounding): discounted prices are only rounded at checkout, not in the cart.

## Other discount apps

* Discount Ninja strives to be as compatible as technically possible with other discount apps.
* Note that the app is not compatible with:
  * Shopify's automatic discounts
  * Apps that use a discounted checkout prepared by the Draft Order API
  * Apps that require more than two discount functions
  * Apps that add/remove products at checkout using Checkout UI extensions

## Custom checkout logic

To integrate with custom checkout logic, you can :

* add a checkout pipeline rule (as documented [here](/discount-ninja-developer-hub/storefront-api/promotion-engine/javascript-api/functions.md#add-pipeline-rule)); this is typically a good solution if you wish to execute a validation rule before proceeding to the checkout
* use an event to trigger the checkout (as documented [here](/discount-ninja-developer-hub/storefront-api/promotion-engine/javascript-api/events.md#trigger-checkout)); this is typically a good solution if you wish to take control of the checkout is some situations and leave Discount Ninja in control for other situations

Note: if your code redirects to the checkout by setting `window.location` you may want to leverage the above mentioned event as documented in the code snippet [here](/discount-ninja-developer-hub/storefront-api/promotion-engine/javascript-api/events.md#redirect-to-checkout).

## Show/hide custom HTML <a href="#h_46648c7982" id="h_46648c7982"></a>

Discount Ninja includes building blocks that can display specific HTML content to help you promote offers.

If you need to show or hide custom HTML blocks, you can use the following classes:

* `limoniapps-discountninja-whenproductdiscounted-show` and `limoniapps-discountninja-whenproductdiscounted-hide`: these classes can be used on product pages only; they hide or show the content of the element based on whether the selected variant is discounted by Discount Ninja or not
* `limoniapps-discountninja-whenactivepromotions-show` and `limoniapps-discountninja-whenactivepromotions-hide`: hide or show the content of the element based on whether Discount Ninja active promotions are available for the visitor or not
* `limoniapps-discountninja-whenpromotionsincart-show` and `limoniapps-discountninja-whenpromotionsincart-hide`: hide or show the content of the element based on whether Discount Ninja promotions have been applied to the cart or not
* `limoniapps-discountninja-whencartdiscounted-show` and `limoniapps-discountninja-whencartdscounted-hide`: hide or show the content of the element based on whether the total amount of the cart is discounted by Discount Ninja promotions or not

## Google Tag Manager Data Layer

To integrate with Google's data layer you may need to leverage the discounted price calculated by the app. The app publishes this information using [an event](/discount-ninja-developer-hub/storefront-api/promotion-engine/javascript-api/events.md#product-discount-calculated). The following code provides an example of how you may use the [data returned by that event](/discount-ninja-developer-hub/storefront-api/promotion-engine/javascript-api/objects.md#product-variant-price-info):

```javascript
// Subscribe to the event
document.addEventListener("la:dn:product:discount:calculated", function(event) {
    // Retrieve the data about the product after discounts have been calculated
    const productVariantPriceInfo = event.detail.data[0];
    const discountedPrice = productVariantPriceInfo.discountedPrice;  
    console.log('Discounted price is ' + discountedPrice, productVariantPriceInfo);
    
    // Your code to integrate with the data layer goes here
    // ...
});
```

## Drawer cart split line items

When your cart contains multiple items of the same product variant, Shopify will split those items into multiple rows in the cart (and drawer cart) when:

* **Line item property**: One of the items has a different line item property (for example: 1 item has Color 'blue', the other item has a different value for 'Color' or does not specify a value for this property)
* **Selling plan**: One of the items is sold using a selling plan (subscription) and the other is not
* **Discount**: One of the items has a server-side discount associated with and the other does not

Discount Ninja applies hidden line item properties to BOGO and GWP products. This causes Shopify to split those lines.

To be able to change the quantity for each of those split lines individually, it is important that your theme updates quantity based on the key of the line item. The key of a line item is unique (also for split lines). If you update quantities based on the variant then Shopify will update the quantity of all lines that contain that variant, which leads to unexpected results.

See Shopify's documentation for more details:

{% embed url="<https://shopify.dev/docs/api/ajax/reference/cart#post-locale-cart-change-js>" %}
Cart.js API /cart/change.js
{% endembed %}

## Drawer cart

Discount Ninja applies discounts using Shopify Functions. Most (if not all) themes have built-in support for visualizing the discounts applied in this way. However, they require that the page is reloaded to render those discounts. This causes a broken user experience for the customer when the cart or drawer cart is refreshed using JavaScript instead of reloading the page.

To ensure the drawer cart immediately reflects the discounts, you'll need to:

* Enable strikethrough pricing as documented [here](/discount-ninja-developer-hub/theme-edits/code-edits/cart.md)
* If you wish to re-render the drawer cart after Discount Ninja has applied the discount to the cart, subscribe to this [event](/discount-ninja-developer-hub/storefront-api/promotion-engine/javascript-api/events.md#cart-discounted) to be notified when the discount is applied.

Also check the following:

* Ensure you hide the native discount labels rendered by your theme using the `limoniapps-discountninja-whenpromotionsincart-hide` css class, as outlined [here](#h_46648c7982-1)
* Ensure your theme [updates the quantity of line items based on the line item key](#drawer-cart-quantity-spinners) (as opposed to the variant)
* If your theme re-renders the drawer cart after it is opened, you need to instruct Discount Ninja to re-apply strikethrough pricing and re-render cart text and other widgets that may have been overwritten. Please refer to this [event](/discount-ninja-developer-hub/storefront-api/promotion-engine/javascript-api/events.md#drawer-cart-opened) to implement this.

For the best customer experience, you'll also want to avoid that DN reloads the page when a gift is added/removed or a line is split (BOGO). To avoid reloading the page, configure DN to execute a line of JavaScript instead. This line should refresh the content of the drawer cart by fetching the content of the cart from Shopify and redrawing the drawer cart. This line is configured in the app under the Settings menu > General > Advanced > Refresh behavior

<figure><img src="/files/AbV9CaaLI20O13ZwFYNi" alt=""><figcaption><p>Refresh behavior JavaScript method</p></figcaption></figure>

## Progress bar

If your theme includes a progress bar that indicates how close a visitor is to unlocking a goal (e.g. free shipping), you may find that the progress bar isn't using the discounted total of the cart.

Discount Ninja applies discounts using Shopify Functions which ensures Shopify understands what the discounted total is. However, your theme's progress bar is not aware that discounts are applied immediately. This results in the progress bar showing incorrect data until the visitor reloads the page.

To avoid this, you'll need to programmatically update the progress bar whenever discounts are applied. This can easily be achieved with the [Cart updated](/discount-ninja-developer-hub/storefront-api/promotion-engine/javascript-api/events.md#cart-updated) event.

Example:

<pre class="language-javascript"><code class="lang-javascript">document.addEventListener("la:dn:cart:updated", function(event) {
    const discountedCart = event.detail.data[0]; 
        
    //The code below simply adds a line in the console log    
    console.log('Ready to update the progress bar based on the discounted total', discountedCart.total.discountedPrice); 

    //Run a sample implementation for a shipping bar
    updateFreeShippingBar(discountedCart.total.discountedPrice);
});

function updateFreeShippingBar(discountedTotal) {
    //In this example we assume that the threshold is $50
    //We multiply by 100 to express the threshold in cents as opposed to dollars.
    //This allows us to compare the threshold to the discounted total.
    //We then multiply by the currency conversion rate provided by Shopify,
    //this allows us to handle multiple currencies.
    const conversionRate = Shopify.currency.rate;
    const freeShippingThresholdInCents = 50 * 100 * conversionRate;    
    
    //We can now calculate the amount remaining (i.e. how much more the visitor must
    //spend to reach the threshold). Note that the discountedTotal is provided
    //in cents and in the currently select currency.
    const centsRemaining = freeShippingThresholdInCents - discountedTotal; 
    
    //Finally, we can calculate this amount as a percentage of the total.
    const percentageRemaining = Math.max(centsRemaining, 0) / freeShippingThresholdInCents * 100;
    const percentageComplete = 100 - percentageRemaining;
     
    //TODO: Update the progress bar
<strong>    //You'll need to update the code below to update the progress bar 
</strong>    //based on the discounted total.
    //How that is accomplished depends on the specific theme you use. 
    console.log(`Free shipping is ${percentageComplete}% complete, ${percentageRemaining}% left`);
}
</code></pre>

## Order bump

An order bump is an upsell that can be added to the cart with a simple checkbox. \
Examples include:

* Shipping protection
* Gift wrapping
* Priority processing

Unfortunately order bumps are often implemented in a way that is not compatible with Discount Ninja. Specifically, the code that checks for the order bump is executed when the submit button of the cart is clicked and the code typically redirects to the checkout by setting `window.location`.

This technique is not compatible with DN. Read more about how to integrate this approach properly [here](/discount-ninja-developer-hub/storefront-api/promotion-engine/javascript-api/events.md#redirect-to-checkout).

## Custom variant apps / swatch apps on the PDP

When configured correctly the app re-evaluates and re-renders the (discounted) price when the customer selects a different variant.

Discount Ninja automatically detects when a variant is selected on the PDP (product page). The app automatically detects this in one of the following two ways:

* The selected variant is set in the query parameter using the `variant` query parameter. Example: <https://my-shop.com/products/my-product?variant=123456>
* The selected variant is set as the `selectedOption` of the `select` input control with its `name` attribute set to `id`.

If your PDP is not compatible:

* Change the code of your variant selector to allow the app to detect the change in one of the two aforementioned ways
* Or inform the app that a new variant has been selected using a [variant changed event](/discount-ninja-developer-hub/storefront-api/promotion-engine/javascript-api/events.md#variant-changed).

Example of using the variant changed event:

```javascript
function myVariantChangedEventHandler() {
    // This function represents the function that handles the event
    // when your custom solution or swatch app has detected a new
    // variant has been selected.
    const variantId = 123456;
    
    // After processing the variant change in your theme
    // Publish this event to let Discount Ninja know
    document.dispatchEvent(new CustomEvent("la:dn:variant:changed", { 
        detail: { 
            data: { 
                variant: { 
                    id: variantId 
                } 
            } 
        } 
    }));
}
```

## Variants on the PLP

By default, the PLP (Collection page) shows the price of a single variant. Some themes allow the customer to select a different variant on the PLP without navigating to the PDP (Product page). To support this:

* custom JavaScript code must be added to set the `data-la-dn-product-variant` of the product card when the customer selects a different variant, cf. [this article](/discount-ninja-developer-hub/theme-edits/code-edits/product-list-page-plp-and-collections/attributes.md#attributes)
* after setting the attribute, the custom JavaScript code must dispatch the `collection:updated` event, cf. [this article](/discount-ninja-developer-hub/storefront-api/promotion-engine/javascript-api/events.md#collection-products-updated)

```javascript
function variantSelectedEventHandler() {
    // Set the attribute
    // We're assuming "this" is an element (a color swatch for example) 
    // that is located inside the product card
    const productCardWithAttribute = this.closest('[data-la-dn-product-variant]');
    if (productCardWithAttribute) {
        // Get the currently selected variant
        // In this example, we're assuming that the variant is a property
        // available on "this"
        const variant = this.variant;
        
        // Set the attribute of the card 
        productCardWithAttribute.setAttribute('data-la-dn-product-variant', variant);
    }
    
    // Custom logic that updates collection products
    document.dispatchEvent(new CustomEvent('la:dn:collection:updated'));
}
```

## Pagination on the PLP

To handle Infinite scrolling and Load more pagination you'll need to either implement an event or mark the button. See [this page](/discount-ninja-developer-hub/theme-edits/code-edits/product-list-page-plp-and-collections/pagination.md) for more details.

## Add to Cart Button

Discount Ninja does not integrate with the Add to Cart button specifically, nor does the app need themes or developers to make changes to those buttons for the app to work correctly.

### Problem: multiple items added to cart

However, occasionally issues will occur where an Add to Cart button will appear to malfunction when Discount Ninja is installed. Specifically, in rare occasions, users may find that their Add to Cart button adds multiple instances of a product when clicked once.

### Why this happens

This can happen when the theme's code contains a bug where clicking the Add to Cart results in two simultaneous network requests (to /cart.add.js). Shopify cannot process multiple simultaneous requests and, as a result, drops one of the requests and only honours one. This results in the expected behavior for the customer: only one item is added to the cart.

Discount Ninja forces network requests to be handled sequentially. This avoids situations where Shopify's inability to process simultaneous requests results in unexpected failures. This mechanism is important to ensure the functionality of the app works reliably.

As a result of processing the network requests sequentially, themes that accidentally send multiple simultaneous requests when the Add to Cart button is clicked, see a different behavior after installing Discount Ninja: the requests are now both honoured by Shopify, which results in multiple products being added to cart (2x the expected amount).

### How to solve this

To resolve this, the user must fix the bug in the theme's code. This typically involves:

* ensuring the click/submit events are not subscribed to multiple times
* ensuring the click event of the button is not bubbled up to the form

Example: the Marble 4.0.1 theme from BigStart contains a bug where the click event of the submit button is subscribed to multiple times. In some instances old script tags of Fastundle can also cause this issue.&#x20;


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://developers.discountninja.io/discount-ninja-developer-hub/integration.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
