Skip to content

Instantly share code, notes, and snippets.

@elghorfi
Last active April 29, 2024 19:31
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save elghorfi/ce7e5b1080aae37d0a415643f33bc79e to your computer and use it in GitHub Desktop.
Save elghorfi/ce7e5b1080aae37d0a415643f33bc79e to your computer and use it in GitHub Desktop.
{%comment%}
#############################################
# Mohamed El-Ghorfi Discount Code on Cart #
# [UPDATED] #
#############################################
# Paypal Me: https://paypal.me/elghorfimed #
# Buy Me A Coffee: #
# https://www.buymeacoffee.com/elghorfi #
#############################################
# elghorfi.med@gmail.com #
#############################################
{%endcomment%}
<style>
.cart-sidebar-discount {
display: flex;
flex-direction: column;
width:300px;
margin: 20px auto;
}
.cart-sidebar-discount input {
margin-top:20px;
background: #eee;
border: 1px solid #eee;
height:50px;
outline: none;
font-size: 18px;
letter-spacing: .75px;
text-align: center;
}
#apply-discount-btn {
background-color: #000;
color:#fff;
border: 0;
height: 60px;
width: 100%;
font-size: 18px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .75px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
}
span.applied-discount-code-value>small {
background: #eee;
padding: 0px 10px;
color: #000;
font-weight: bold;
border-radius: 20px;
}
.loader {
border: 5px solid #f3f3f3;
border-top: 4px solid #000;
border-radius: 50%;
width: 25px;
height: 25px;
animation: spin .5s linear infinite;
}
#discount-code-error {
background: #ff00004f;
color: #e22120;
padding: 5px;
border-radius: 4px;
font-size: 13px;
line-height: 1;
}
.applied-discount-code-wrapper {
display: none;
background: #ddd;
padding: 3px 6px;
border-radius: 25px;
}
.applied-discount-code-value {
font-size: 13px;
text-transform: uppercase;
}
#discount-code-error:empty {
display: none;
}
.applied-discount-code-value:empty+button {
display: none;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
<div class="cart-sidebar-discount">
<span id="applied-discount-code">
Discount Code:
<span class="applied-discount-code-wrapper">
<span class="applied-discount-code-value"></span>
<button id="clear-discount-btn">X</button>
</span>
</span>
<small id="discount-code-error"></small>
<input type="text" id="discount-code-input" autocomplete="on" value="">
<button id="apply-discount-btn">APPLY</button>
</div>
<script>
document.addEventListener("DOMContentLoaded", function(event) {
let clearBtn = document.querySelector("#clear-discount-btn");
let applyBtn = document.querySelector("#apply-discount-btn");
let discountCodeError = document.querySelector("#discount-code-error");
let discountCodeWrapper = document.querySelector("#applied-discount-code .applied-discount-code-wrapper");
let discountCodeValue = document.querySelector("#applied-discount-code .applied-discount-code-value");
let discountCodeInput = document.querySelector("#discount-code-input");
let totalCartSelector = document.querySelector(".cart__subtotal .money"); // Total Cart Selector to update the total amount.
let authorization_token;
let checkoutContainer = document.createElement('div');
document.body.appendChild(checkoutContainer);
if (localStorage.discountCode) applyDiscount( JSON.parse(localStorage.discountCode).code);
if(applyBtn)
applyBtn.addEventListener("click", function(e){
e.preventDefault()
applyDiscount(discountCodeInput.value);
});
if(clearBtn)
clearBtn.addEventListener("click", function(e){
e.preventDefault()
clearDiscount();
});
function clearDiscount() {
discountCodeValue.innerHTML = "";
discountCodeError.innerHTML = "";
clearLocalStorage();
fetch("/checkout?discount=%20");
}
function clearLocalStorage() {
if(discountCodeWrapper) discountCodeWrapper.style.display = "none";
if(totalCartSelector) totalCartSelector.innerHTML = JSON.parse(localStorage.discountCode).totalCart;
localStorage.removeItem("discountCode");
}
function applyDiscount(code) {
if(applyBtn) {
applyBtn.innerHTML = "APPLYING <div class='loader'></div>";
applyBtn.style.pointerEvents = "none";
}
fetch("/payments/config", {"method": "GET"})
.then(function(response) { return response.json() })
.then(function(data) {
const checkout_json_url = '/wallets/checkouts/';
authorization_token = btoa(data.paymentInstruments.accessToken)
fetch('/cart.js', {}).then(function(res){return res.json();})
.then(function(data){
let body = {"checkout": { "country": Shopify.country,"discount_code": code,"line_items": data.items, 'presentment_currency': Shopify.currency.active } }
fetch(checkout_json_url, {
"headers": {
"accept": "*/*", "cache-control": "no-cache",
"authorization": "Basic " + authorization_token,
"content-type": "application/json, text/javascript",
"pragma": "no-cache", "sec-fetch-dest": "empty",
"sec-fetch-mode": "cors", "sec-fetch-site": "same-origin"
},
"referrerPolicy": "strict-origin-when-cross-origin",
"method": "POST", "mode": "cors", "credentials": "include",
"body": JSON.stringify(body)
})
.then(function(response) { return response.json() })
.then(function(data) {
console.log(data.checkout);
if(data.checkout && data.checkout.applied_discounts.length > 0){
let discountApplyUrl = "/discount/"+code+"?v="+Date.now()+"&redirect=/checkout/";
fetch(discountApplyUrl, {}).then(function(response) { return response.text(); })
if(discountCodeWrapper) discountCodeWrapper.style.display = "inline";
if(discountCodeError) discountCodeError.innerHTML = "";
if(discountCodeValue) discountCodeValue.innerHTML = data.checkout.applied_discounts[0].title + " (" + data.checkout.applied_discounts[0].amount + ' ' + Shopify.currency.active + ")";
let localStorageValue = {
'code': code.trim(),
'totalCart': data.checkout.total_line_items_price
};
localStorage.setItem("discountCode", JSON.stringify(localStorageValue));
if(totalCartSelector) totalCartSelector.innerHTML = "<s>" + data.checkout.total_line_items_price + "</s>" + data.checkout.total_price;
}else{
if(discountCodeValue) discountCodeValue.innerHTML = "";
clearLocalStorage();
if(discountCodeError) discountCodeError.innerHTML = "Please Enter Valid Coupon Code."
}
}).finally(function(params) {
if(applyBtn){
applyBtn.innerHTML = "APPLY";
applyBtn.style.pointerEvents = "all";
}
});
});
});
}
});
</script>
@MaxDesignFR
Copy link

MaxDesignFR commented Apr 17, 2024

As far as I can tell the /wallets/checkouts/ endpoint seems some sort of pre-flight validation check to see if the discount code can be applied

Something like that. In my testing, it seems it can be more than that though. For instance, you could verify a shipping discount with /wallets/checkouts/ to pre-apply it in cart page, whereas /checkout/ won't let you know if this shipping discount even exist (since shipping discount can't be outputed with liquid, while order and product discounts can). Less popular, but nevertheless can't be ignored.

I also have not tried gift card codes. Those are technically processed as payment methods and not discount codes, so they can't be outputed with liquid in cart page. If /wallets/checkouts/ can verify those, then it's one more thing /checkout/ can't do. I have no idea, but that's something worth checking.

And lastly, I have sometimes noticed issue where fetching /checkout/ + re-rendering cart section can be prone to error when called multiple times in a row. My guess is that Shopify starts caching the cart section after a while, and at some point it will not be in sync with newly added or removed discounts, it's confusing when it happens. To edge against this scenario, the cart could be updated (line item quantity, random cart attribute or cart note). I have not checked if fetching /wallets/checkouts/ before fetching /checkout/ avoids this caching issue.

Those are a few things I have noticed and still need to investigate more, let me know if you find out more, it's a tedious process for sure ;)

@ddegroot1985 what's the reason for this parameter &=${Date.now()}?

@pramathesh
Copy link

This worked flawlessly for me on Dawn 14.0.0. However, the cart amount is not updated to factor the coupon discount unless the page is refreshed, any workaround for that? Same for when the coupon is deleted, the cart value still shows the discounted amount until the page is refreshed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment