├── README.md └── btcpay-shopify-checkout.js /README.md: -------------------------------------------------------------------------------- 1 | This script serves as a basic integration between [BTCPayServer](https://btcpayserver.org) and [Shopify](shopify.com), allowing you to accept non-custodail Bitcoin and Cryptocurrency payments in your Shopify store. 2 | 3 | Long-term, a more complete implementation should be possible in BTCPayServer. Work on this integration is tracked in this issue: https://github.com/btcpayserver/btcpayserver/issues/36 4 | 5 | # Setup 6 | 1. In your BTCPayServer store, enable "Allow anyone to create invoice" 7 | 2. In Shopify Settings > Payment Providers > Manual Payment Methods add one which contains "Bitcoin" 8 | 3. In Shopify Settings > Checkout > Additional Scripts input the following script, with the details from your BTCPayServer instead of the placeholder values. 9 | 10 | ``` 11 | 15 | 16 | 17 | 18 | ``` 19 | 20 | # Using Other Currencies 21 | By default the script assumes your Shopify store's currency is USD -- if that's not the case you'll have to set the following variables as well, so that the script can scrape the correct value from the Shopify page and create the invoice in BTCPayServer with the correct currency. 22 | 23 | ``` 24 | const currencySymbol = '£'; 25 | const currency='GBP'; 26 | ``` 27 | 28 | # Limitations 29 | 30 | * Script will not automatically complete orders in Shopify, so you will have to manually crosscheck payments in BTCPayServer based on the order ID in Shopify 31 | -------------------------------------------------------------------------------- /btcpay-shopify-checkout.js: -------------------------------------------------------------------------------- 1 | ! function () { 2 | "use strict"; 3 | const pageElements = document.querySelector.bind(document), 4 | insertElement = (document.querySelectorAll.bind(document), 5 | (e, 6 | n) => { 7 | n.parentNode.insertBefore(e, 8 | n.nextSibling) 9 | }); 10 | 11 | let pageItems = {}, 12 | pageheader = "Thank you!", 13 | buttonElement = null; 14 | 15 | const setPageItems = () => { 16 | pageItems = { 17 | mainHeader: pageElements("#main-header"), 18 | orderConfirmed: pageElements(".os-step__title"), 19 | orderConfirmedDescription: pageElements(".os-step__description"), 20 | continueButton: pageElements(".step__footer__continue-btn"), 21 | checkMarkIcon: pageElements(".os-header__hanging-icon"), 22 | orderStatus: pageElements(".os-header__title"), 23 | paymentMethod: pageElements(".payment-method-list__item__info"), 24 | price: pageElements(".payment-due__price"), 25 | finalPrice: pageElements(".total-recap__final-price"), 26 | orderNumber: pageElements(".os-order-number"), 27 | } 28 | }, 29 | orderPaid = () => { 30 | pageItems.mainHeader.innerText = pageheader, 31 | pageItems.orderConfirmed && (pageItems.orderConfirmed.style.display = "block"), 32 | pageItems.orderConfirmedDescription && (pageItems.orderConfirmedDescription.style.display = "block"), 33 | pageItems.continueButton && (pageItems.continueButton.style.visibility = "visible"), 34 | pageItems.checkMarkIcon && (pageItems.checkMarkIcon.style.visibility = "visible"), 35 | buttonElement && (buttonElement.style.display = "none"); 36 | }; 37 | window.setOrderAsPaid = orderPaid, 38 | window.openNodeShopify = function waitForPaymentMethod() { 39 | if (setPageItems(), 40 | "Order canceled" === pageItems.orderStatus.innerText) return; 41 | 42 | const paymentMethod = pageItems.paymentMethod; 43 | 44 | if (null === paymentMethod) return void setTimeout(() => { 45 | waitForPaymentMethod(); 46 | }, 47 | 10); 48 | 49 | if (-1 === paymentMethod.innerText.toLowerCase().indexOf("bitcoin")) return; 50 | 51 | // If payment method is bitcoin, display instructions and payment button. 52 | pageheader = pageItems.mainHeader.innerText, 53 | pageItems.mainHeader && (pageItems.mainHeader.innerText = "Review and pay!"), 54 | pageItems.continueButton && (pageItems.continueButton.style.visibility = "hidden"), 55 | pageItems.checkMarkIcon && (pageItems.checkMarkIcon.style.visibility = "hidden"), 56 | pageItems.orderConfirmed && (pageItems.orderConfirmed.style.display = "none"), 57 | pageItems.orderConfirmedDescription && (pageItems.orderConfirmedDescription.style.display = "none"), 58 | buttonElement = document.createElement("div"); 59 | 60 | const priceElement = pageItems.finalPrice || pageItems.price; 61 | const price = priceElement.innerText.replace((typeof currencySymbol === 'undefined') ? "$" : currencySymbol, ""); 62 | const orderId = pageItems.orderNumber.innerText.replace("Order #", ""); 63 | 64 | const url = btcPayServerUrl + "/invoices" + "?storeId=" + storeId + "&orderId=" + orderId + "&status=complete"; 65 | 66 | // Check if already paid. 67 | fetch(url, { 68 | method: "GET", 69 | mode: "cors", // no-cors, cors, *same-origin, 70 | headers: { 71 | "Content-Type": "application/json", 72 | "accept": "application/json", 73 | }, 74 | }) 75 | .then(function (response) { 76 | return response.json(); 77 | }) 78 | .then(function (json) { 79 | return json.data; 80 | }) 81 | .then(function(data) { 82 | if(data.length != 0) { 83 | orderPaid(); 84 | } 85 | }); 86 | 87 | window.waitForPayment = function () { 88 | BtcPayServerModal.show( 89 | btcPayServerUrl, 90 | storeId, 91 | { 92 | price: price, 93 | currency: (typeof currency === 'undefined') ? 'USD': currency, 94 | orderId: orderId 95 | } 96 | ) 97 | .then(function (invoice) { 98 | if (invoice != null) { 99 | orderPaid(); 100 | } 101 | }); 102 | } 103 | 104 | // Payment button that opens modal 105 | buttonElement.innerHTML = `\n \n`, 106 | insertElement(buttonElement, pageItems.orderConfirmed); 107 | 108 | } 109 | window.openNodeShopify(); 110 | }(); 111 | --------------------------------------------------------------------------------