Checkout Extension

Checkout Extension

If you use Shopify Plus and have not yet updated to the new "extensible checkout" system, you can use the "Edit Code" feature in your Shopify theme to add the Quikly checkout snippet.

  1. Create a new snippet called 'quikly.liquid'. Make sure you replace ##REPLACE_WITH_BRAND_ID## with your config key provided by Quikly. You can find the full source of this file below.
create snippet
paste in code
  1. Edit the checkout.liquid file, and add {% render 'quikly' %} to include the new snippet. Typically you will want to place this directly above the {{ content_for_layout }} tag
edit checkout.liquid
1<main class="main__content" role="main">
2 {% render 'quikly' %}
3 {{ content_for_layout }}
4</main>

Here is the contents of snippets/quikly.liquid.

1<script type="application/javascript">
2 (function (d) {
3 if (d.getElementById('quikly-embed-js')) {
4 return;
5 }
6 s = d.createElement("script");
7 s.id = 'quikly-embed-js';
8 s.src = 'https://pixel.quikly.com/embed/js';
9 s.async = true;
10 f = d.scripts[0];
11 f.parentNode.insertBefore(s, f);
12 })(document);
13 window.qData || (window.qData = function() {
14 (window.qDataLayer = window.qDataLayer || []).push(arguments);
15 });
16 qData('config', '##REPLACE_WITH_YOUR_BRAND_KEY##');
17 try {
18 var qCheckoutTemplate = '{% if checkout %}checkout{% else %}{{ template }}{% endif %}';
19 if (window.Shopify && window.Shopify.Checkout) {
20 if (window.Shopify.Checkout.step === 'thank_you') {
21 qCheckoutTemplate = 'thank_you';
22 } else if (Shopify.Checkout.isOrderStatusPage) {
23 qCheckoutTemplate = 'order_status';
24 }
25 }
26 qData('ui', {
27 placements: 'auto',
28 platformData: {
29 platform: 'shopify',
30 template: qCheckoutTemplate,
31 }
32 });
33 } catch (error) {
34 console.log(error);
35 }
36
37 var quiklyAppExtension = (function() {
38 const Q_SCOPE = 'q_scope';
39 function getLastClickedScope(scopes) {
40 const scopesArray = [];
41 Object.keys(scopes).forEach((key) => {
42 scopesArray.push({ [Q_SCOPE]: key, clicked: scopes[key].clicked });
43 });
44
45 // Sort descending by click time
46 scopesArray.sort((a, b) => {
47 return b.clicked - a.clicked;
48 });
49
50 // First in array has highest click UNIX timestamp === last to be clicked
51 return scopesArray[0][Q_SCOPE];
52 }
53
54 function getCookieValue(key) {
55 // if cookie is not set do not try to split
56 const cookie = document.cookie
57 .split("; ")
58 .find((row) => row.startsWith(key));
59 if (!cookie) {
60 return;
61 }
62
63 return cookie.split("=")[1];
64 }
65
66 function notifyQuikly(cart) {
67 const evt = new CustomEvent("quikly:cart", {
68 detail: {
69 cart,
70 },
71 });
72
73 window.dispatchEvent(evt);
74 }
75
76 function getBaseUrl(key, defaultUrl) {
77 if (window.QuiklyRoutes && window.QuiklyRoutes[key]) {
78 return window.QuiklyRoutes[key] + ".js";
79 }
80
81 if (window.Shopify && window.Shopify.routes && window.Shopify.routes.root) {
82 return window.Shopify.routes.root + defaultUrl;
83 }
84
85 return defaultUrl;
86 }
87
88 function cartUrl() {
89 return getBaseUrl("cart_url", "{{ routes.cart_url | default: 'cart' }}.js");
90 }
91
92 function cartUpdateUrl() {
93 return getBaseUrl("cart_update_url", "{{ routes.cart_update_url | default: 'cart/update' }}.js");
94 }
95
96 async function fetchCart() {
97 try {
98 const response = await fetch(cartUrl());
99 return response.json();
100 } catch (error) {
101 console.error("Error fetching cart:", error);
102 throw error;
103 }
104 }
105
106 function getCookieScope() {
107 const cookieScope = getCookieValue(Q_SCOPE);
108 if (cookieScope) {
109 const decodedQscopeCookieValue = decodeURIComponent(cookieScope);
110 const parsedQscope = JSON.parse(decodedQscopeCookieValue);
111 const qscope = getLastClickedScope(parsedQscope);
112 return qscope;
113 }
114 }
115
116 function fetchConfig(type = "json") {
117 return {
118 method: "POST",
119 headers: {
120 "Content-Type": "application/json",
121 Accept: `application/${type}`,
122 },
123 };
124 }
125
126 async function synchronizeCart() {
127 let updateCart = false;
128 let cart;
129 let attributes;
130
131 try {
132 cart = await fetchCart();
133 notifyQuikly(cart);
134 attributes = cart["attributes"] || {};
135
136 const cookieQScope = getCookieScope();
137 if (cookieQScope && attributes[Q_SCOPE] !== cookieQScope) {
138 attributes[Q_SCOPE] = cookieQScope;
139 updateCart = true;
140 }
141
142 const receiptTokens = getCookieValue("quikly-order-receipt-tokens");
143 if (receiptTokens && attributes["q_tokens"] !== receiptTokens) {
144 attributes["q_tokens"] = receiptTokens;
145 updateCart = true;
146 }
147
148 if (updateCart) {
149 const body = JSON.stringify({ attributes });
150
151 const fetchResult = await fetch(cartUpdateUrl(), {
152 ...fetchConfig(),
153 ...{ body },
154 });
155
156 if (!fetchResult.ok) {
157 throw new Error(
158 `Cart update failed with status ${fetchResult.status}`
159 );
160 }
161
162 return fetchResult.json();
163 }
164 } catch (error) {
165 console.error("Error updating cart:", error);
166 }
167 }
168
169 async function applyDiscount(code) {
170 const res = await fetch(`/checkout?discount=${code}`);
171 return res.text();
172 }
173
174 synchronizeCart();
175
176 return {
177 fetchCart: fetchCart,
178 applyDiscount: applyDiscount,
179 }
180 })();
181
182{% if checkout %}
183 (function($) {
184 $(document).on("page:change", function() {
185 quiklyAppExtension.fetchCart();
186 });
187 })(Checkout.$);
188{% endif %}
189</script>
190