-
-
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 # | |
############################################# | |
# [email protected] # | |
############################################# | |
{%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("/discount/CLEAR"); | |
} | |
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> |
@ThomasB-Sunology Thank you for the suggestion, I've updated the gist.
@anuragku77 you need to adapt the script to match your cart page layout/code
As a heads-up, working to integrate the new multi-discounts on the above script so you can combine discount codes.
If you have any suggestions, you can reach out. Or leave a comment below.
@ThomasB-Sunology Thanks for your contribution! Do you have a link to the documentation you are using for : fetch("/discount/CLEAR");
ps: it works for me :)
@Raysbaelo Actually I found this blog article. It's not always CLEAR
we need to pass a non-existing discount code :
@ThomasB-Sunology Cool, that's an interesting way to delete a discount code, that means if my code has been made for multiple discount codes it is not viable anymore, I guess.. ( depends on the code structure ). Does anyone have a functional code for a combination of discount codes without weird behavior when applying/deleting a code?
This is great! Thank you so much for the help, I managed to make this with Claude and it is fully functional for multiple codes:
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");
let appliedDiscounts = JSON.parse(localStorage.getItem("appliedDiscounts") || "[]");
if (typeof customerHasLowestPriceTag !== 'undefined' && customerHasLowestPriceTag) {
applyAutomaticDiscounts(['custom-code-1', 'custom-code-2', 'custom-code-3', ... more codes]);
}
if (clearBtn)
clearBtn.addEventListener("click", function(e){
e.preventDefault();
clearDiscount();
});
if (applyBtn)
applyBtn.addEventListener("click", function(e){
e.preventDefault();
applyDiscount(discountCodeInput.value);
});
function clearDiscount() {
appliedDiscounts = [];
localStorage.setItem("appliedDiscounts", JSON.stringify(appliedDiscounts));
discountCodeValue.innerHTML = "";
discountCodeError.innerHTML = "";
fetch(`${Shopify.routes.root}checkout/clear`);
location.reload();
}
function applyAutomaticDiscounts(discountCodes) {
discountCodes.forEach(code => applyDiscount(code));
}
async function applyDiscount(code) {
if(applyBtn) {
applyBtn.innerHTML = "APPLYING <div class='loader'></div>";
applyBtn.style.pointerEvents = "none";
}
try {
// First, validate the new discount code
const validationResponse = await validateDiscountCode(code);
if (!validationResponse.isValid) {
throw new Error(validationResponse.error);
}
// If valid, add to applied discounts and apply all
if (!appliedDiscounts.includes(code)) {
appliedDiscounts.push(code);
}
const applyResponse = await applyDiscounts(appliedDiscounts);
if (applyResponse.success) {
updateUI(applyResponse.discounts);
localStorage.setItem("appliedDiscounts", JSON.stringify(appliedDiscounts));
} else {
throw new Error(applyResponse.error);
}
} catch (error) {
console.error('Error:', error);
if(discountCodeError) discountCodeError.innerHTML = error.message;
} finally {
if(applyBtn) {
applyBtn.innerHTML = "APPLY";
applyBtn.style.pointerEvents = "all";
}
}
}
async function validateDiscountCode(code) {
const shopify_features_script = document.querySelector("script[id='shopify-features']");
const shopify_features_json = JSON.parse(shopify_features_script.innerHTML);
const cart = await fetch(`${Shopify.routes.root}cart.js`).then(response => response.json());
const headers = {
Authorization: 'Basic ' + btoa(shopify_features_json.accessToken),
Accept: '*/*',
'Content-Type': 'application/json',
};
const body = {
checkout: {
line_items: cart.items,
discount_code: code,
country: Shopify.country,
presentment_currency: cart.currency,
},
};
const response = await fetch('/wallets/checkouts/', {
method: 'POST',
headers: headers,
body: JSON.stringify(body),
referrerPolicy: 'no-referrer',
});
const data = await response.json();
if (data.errors && data.errors.discount_code) {
return { isValid: false, error: data.errors.discount_code[0].message };
}
return { isValid: true };
}
async function applyDiscounts(discountCodes) {
const response = await fetch(`${Shopify.routes.root}checkout?discount=${discountCodes.join(',')}&_=${Date.now()}`);
const html = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const discountScript = doc.querySelector('script[data-discount-allocations]');
if (discountScript) {
const discountData = JSON.parse(discountScript.textContent);
return { success: true, discounts: discountData };
} else {
return { success: false, error: "Failed to apply discount" };
}
}
function updateUI(discounts) {
if(discountCodeWrapper) discountCodeWrapper.style.display = "inline";
if(discountCodeError) discountCodeError.innerHTML = "";
if(discountCodeValue) {
discountCodeValue.innerHTML = discounts.map(discount =>
`${discount.title} (${formatMoney(discount.amount)})`
).join(', ');
}
// update total if needed
//might need to adjust this based on how your cart data is structured
if(totalCartSelector) {
const totalPrice = discounts.reduce((total, discount) => total - discount.amount, cart.total_price);
totalCartSelector.innerHTML = `<s>${formatMoney(cart.total_price)}</s> ${formatMoney(totalPrice)}`;
}
// Refresh the cart to reflect changes
location.reload();
}
function formatMoney(cents) {
return (cents / 100).toLocaleString('en-US', {
style: 'currency',
currency: Shopify.currency.active
});
}
});
@MaxDesignFR Thank you for those insights, I have been playing with it this past two days and believe I have a pretty decent solution. It is not 100% bullet proof yet. 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. Unfortunately as far as I can tell this only works for single discounts, it does not return any errors when trying to apply a discount that does not stack with an already applied discount.Applying multiple discounts is possible using
fetch(`${Shopify.routes.root}checkout?discount=${[newDiscountCode, ...alreadyAppliedDiscounts].join(',')}&=${Date.now()}`);
This seems to never return any errors, even when no discounts are applied so you still have to check if the discount is actually applied. So far the best way I found is by creating a
application/json
script tag in the html when re-rendering the section, parsing that and cross referencing that with the code that was just added.
Intresting, it did applied multiple coupons, however, the first call returned 302 and a subsequent call to checkout returned 403
Did anyone notice this error when fetching /checkout?discount=HELLO10
on iOS platform when ShopPay is enabled?
Fetch API cannot load https://shop.app/checkout/62371266751/cn/Z2NwLXVzLWVhc3QxOjAxSjg3NkY2MFhFNkFXVjJIRDkwMThXU1Ew/shop_pay_callback?discount_code=HELLO10&locale=fr-NL&.....[immense string].... due to access control checks. TypeError: Load failed
It works fine on Android, but seemingly fetching /checkout
when ShopPay is enabled on an iOS device generate this error. Curious if someone can replicate this issue, looking for solutions right now (damn iOS, always something!)
Thank you, kind Sir.
To see the changes u have to refresh page eveytime can anyone have solved this issues.can someone please guide for this problem