├── .github └── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md └── frontend ├── lightningTip.css ├── lightningTip.js ├── lightningTip.php └── lightningTip_light.css /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Background 2 | 3 | Describe your issue here. 4 | 5 | ### Your environment 6 | 7 | * which operating system? 8 | * any other relevant environment details? 9 | * are you running LightningTip behind a reverse proxy? 10 | 11 | ### Steps to reproduce 12 | 13 | Tell us how to reproduce this issue. Please provide stacktraces and links to code in question. 14 | 15 | ### Expected behaviour 16 | 17 | Tell us what should happen. 18 | 19 | ### Actual behaviour 20 | 21 | Tell us what happens instead. 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 michael1011, 2018 robclark56 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LightningTip-PHP 2 | A simple way to accept tips via the Lightning Network on your website. 3 | 4 | If want to tip me you can use my LightningTip as below. 5 | 6 | * [mainnet](http://raspibolt.epizy.com/LT/lightningTip.php) 7 | * [testnet](http://raspibolt.epizy.com/LT/lightningTip.php?testnet=1) 8 | 9 | 10 | 11 | ## Credit ## 12 | Kudos to [michael1011](https://github.com/michael1011/lightningtip) for the original [LightningTip](https://github.com/michael1011/lightningtip/blob/master/README.md). The difference between the two projects are shown in this table. 13 | 14 | ||LightningTip|LightningTip-PHP| 15 | |--|--|--| 16 | |Backend|An executable
(always running)|PHP| 17 | |Email notification|Yes|Yes| 18 | |lnd communication|gRPC|REST| 19 | |testnet/mainnet selection|No|Yes| 20 | |Keeps track
of tips?|Yes|No| 21 | 22 | ## Requirements ## 23 | * one [lnd](https://github.com/lightningnetwork/lnd) instance 24 | * a webserver that supports [PHP](http://www.php.net/) and [curl](https://curl.haxx.se/) 25 | ## Why PHP? ## 26 | Installing an executable either on the lnd host, or on a 3rd party web host can be problematic. Using PHP improves portability and removes the need for a separate executable running as a service. 27 | ## Security ## 28 | The _invoice.macroon_ file limits the functionality available to LightningTip.php to only invoice related functions. Importantly, if someone steals your _invoice.macaroon_, they can NOT spend any of your funds. 29 | ## Prepare LND ## 30 | * Enable REST on your lnd instance(s). See the _restlisten_ parameter in the [lnd documentation](https://github.com/lightningnetwork/lnd/blob/master/sample-lnd.conf). 31 | * Open any necessary firewall ports on your lnd host, and router port-forwards as needed. 32 | * Generate a hex version of the _invoice.macaroon_ file on your lnd instance. 33 | * Linux: `xxd -ps -u -c 1000 /path/to/invoice.macaroon ` 34 | * Generic: [http://tomeko.net/online_tools/file_to_hex.php?lang=en](http://tomeko.net/online_tools/file_to_hex.php?lang=en) 35 | 36 | ## Prepare Web Server ## 37 | Your webserver will need to have the _php-curl_ package installed. 38 | 39 | On a typical Linux webserver you can check as follows. The example below shows that it is installed. 40 | ```bash 41 | $ dpkg -l php-curl 42 | Desired=Unknown/Install/Remove/Purge/Hold 43 | | Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend 44 | |/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad) 45 | ||/ Name Version Architecture Description 46 | +++-======================-================-================-================================================= 47 | ii php-curl 1:7.0+49 all CURL module for PHP [default] 48 | ``` 49 | If you see `no packages found matching php-curl` then install as follows. 50 | ``` 51 | $ sudo apt-get update 52 | $ sudo apt-get install php-curl 53 | ``` 54 | 55 | 56 | ## How to install ## 57 | * Download the [latest release](https://github.com/robclark56/lightningtip/releases), and unzip. 58 | * From the _frontend_ folder: Upload these files to your webserver: 59 | * lightningTip.php 60 | * lightningTip.js 61 | * lightningTip.css 62 | * lightningTip_light.css (Optional) 63 | * Edit the _CHANGE ME_ section of `lightningTip.php`. This is where you enter the HEX version of your _invoice.macaroon_. 64 | * Edit the _CHANGE ME_ section of `lightningTip.js`. 65 | * Copy the contents of the head tag from `lightningTip.php` into the head section of the HTML file you want to show LightningTip in. The div below the head tag is LightningTip itself. Paste it into any place in the already edited HTML file on your server. 66 | 67 | 68 | There is a light theme available for LightningTip. If you want to use it **add** this to the head tag of your HTML file: 69 | 70 | ``` 71 | 72 | ``` 73 | 74 | **Do not use LightningTip on XHTML** sites. That causes some weird scaling issues. 75 | 76 | That's it! The only things you need to take care of is keeping the LND node and web server online. LightningTip will take care of everything else. 77 | 78 | ## How to run ## 79 | Use your browser to visit either of these: 80 | 81 | * `https://your.web.server/path/lightningTip.php` 82 | * `https://your.web.server/path/lightningTip.php?testnet=1` 83 | 84 | 85 | -------------------------------------------------------------------------------- /frontend/lightningTip.css: -------------------------------------------------------------------------------- 1 | #lightningTip { 2 | width: 12em; 3 | 4 | padding: 1em; 5 | 6 | background-color: #212121; 7 | 8 | border-radius: 4px; 9 | 10 | color: #F5F5F5; 11 | 12 | font-size: 20px; 13 | font-family: Arial, Helvetica, sans-serif; 14 | 15 | text-align: center; 16 | } 17 | 18 | .lightningTipInput { 19 | width: 100%; 20 | 21 | display: inline-block; 22 | 23 | padding: 6px 10px; 24 | 25 | border: none; 26 | border-radius: 4px; 27 | 28 | font-size: 15px; 29 | 30 | color: #212121; 31 | 32 | background-color: #F5F5F5; 33 | 34 | outline: none; 35 | resize: none; 36 | 37 | overflow-y: hidden; 38 | } 39 | 40 | .lightningTipButton { 41 | padding: 0.4em 1em; 42 | 43 | font-size: 17px; 44 | 45 | color: #212121; 46 | 47 | background-color: #FFC83D; 48 | 49 | border: none; 50 | border-radius: 4px; 51 | 52 | outline: none; 53 | cursor: pointer; 54 | } 55 | 56 | .lightningTipButton:focus { 57 | outline: none; 58 | } 59 | 60 | .lightningTipButton::-moz-focus-inner { 61 | outline: none; 62 | 63 | border: 0; 64 | } 65 | 66 | #lightningTipLogo { 67 | margin-top: 0; 68 | margin-bottom: 0.6em; 69 | 70 | font-size: 25px; 71 | } 72 | 73 | #lightningTipInputs { 74 | margin-top: 0.8em; 75 | } 76 | 77 | #lightningTipMessage { 78 | min-height: 55px; 79 | 80 | margin-top: 0.5em; 81 | padding: 8px 10px; 82 | 83 | display: inline-block; 84 | box-sizing: border-box; 85 | 86 | text-align: left; 87 | 88 | font-family: Arial, Helvetica, sans-serif; 89 | } 90 | 91 | /* Hack for using placeholders on divs */ 92 | #lightningTipMessage:empty:before { 93 | content: attr(placeholder); 94 | color: gray; 95 | } 96 | 97 | #lightningTipGetInvoice { 98 | margin-top: 1em; 99 | } 100 | 101 | #lightningTipError { 102 | font-size: 17px; 103 | 104 | color: #F44336; 105 | } 106 | 107 | #lightningTipInvoice { 108 | margin-top: 1em; 109 | margin-bottom: 0.5em; 110 | } 111 | 112 | #lightningTipQR { 113 | margin-bottom: 0.8em; 114 | } 115 | 116 | #lightningTipTools { 117 | height: 100px; 118 | } 119 | 120 | #lightningTipCopy { 121 | border-right: 1px solid #F5F5F5; 122 | 123 | border-top-right-radius: 0; 124 | border-bottom-right-radius: 0; 125 | 126 | float: left; 127 | } 128 | 129 | #lightningTipOpen { 130 | border-top-left-radius: 0; 131 | border-bottom-left-radius: 0; 132 | 133 | float: left; 134 | } 135 | 136 | #lightningTipExpiry { 137 | padding: 0.3em 0; 138 | 139 | float: right; 140 | } 141 | 142 | #lightningTipFinished { 143 | margin-bottom: 0.2em; 144 | 145 | display: block; 146 | } 147 | 148 | .spinner { 149 | width: 12px; 150 | height: 12px; 151 | 152 | display: inline-block; 153 | 154 | border: 3px solid #F5F5F5; 155 | border-top: 3px solid #212121; 156 | border-radius: 50%; 157 | 158 | animation: spin 1.5s linear infinite; 159 | } 160 | 161 | @keyframes spin { 162 | 0% { 163 | transform: rotate(0deg); 164 | } 165 | 100% { 166 | transform: rotate(360deg); 167 | } 168 | } 169 | 170 | #lightningTip.testnet { 171 | background-color: #27b09d; 172 | } 173 | -------------------------------------------------------------------------------- /frontend/lightningTip.js: -------------------------------------------------------------------------------- 1 | ///////// CHANGE ME //////// 2 | var requestUrl = window.location.protocol + "//" + window.location.hostname + "/lightningTip.php"; 3 | ///////// END CHANGE ME //////// 4 | 5 | // To prohibit multiple requests at the same time 6 | var running = false; 7 | 8 | var invoice; 9 | var qrCode; 10 | var defaultGetInvoice; 11 | 12 | // Data capacities for QR codes with mode byte and error correction level L (7%) 13 | // Shortest invoice: 194 characters 14 | // Longest invoice: 1223 characters (as far as I know) 15 | var qrCodeDataCapacities = [ 16 | {"typeNumber": 9, "capacity": 230}, 17 | {"typeNumber": 10, "capacity": 271}, 18 | {"typeNumber": 11, "capacity": 321}, 19 | {"typeNumber": 12, "capacity": 367}, 20 | {"typeNumber": 13, "capacity": 425}, 21 | {"typeNumber": 14, "capacity": 458}, 22 | {"typeNumber": 15, "capacity": 520}, 23 | {"typeNumber": 16, "capacity": 586}, 24 | {"typeNumber": 17, "capacity": 644}, 25 | {"typeNumber": 18, "capacity": 718}, 26 | {"typeNumber": 19, "capacity": 792}, 27 | {"typeNumber": 20, "capacity": 858}, 28 | {"typeNumber": 21, "capacity": 929}, 29 | {"typeNumber": 22, "capacity": 1003}, 30 | {"typeNumber": 23, "capacity": 1091}, 31 | {"typeNumber": 24, "capacity": 1171}, 32 | {"typeNumber": 25, "capacity": 1273} 33 | ]; 34 | 35 | // TODO: solve this without JavaScript 36 | // Fixes weird bug which moved the button up one pixel when its content was changed 37 | window.onload = function () { 38 | var button = document.getElementById("lightningTipGetInvoice"); 39 | 40 | button.style.height = (button.clientHeight + 1) + "px"; 41 | button.style.width = (button.clientWidth + 1) + "px"; 42 | 43 | }; 44 | 45 | var testnet = getVal('testnet'); //see if GET param testnet is set (URL?testnet=1) 46 | console.log('Testnet = ' + testnet); 47 | if ( testnet !== null ) { 48 | requestUrl = requestUrl + '?testnet=1'; 49 | console.log('requestUrl = ' + requestUrl ); 50 | } 51 | 52 | // TODO: show invoice even if JavaScript is disabled 53 | // TODO: fix scaling on phones 54 | // TODO: show price in dollar? 55 | function getInvoice() { 56 | if (running === false) { 57 | running = true; 58 | 59 | var tipValue = document.getElementById("lightningTipAmount").value.trim(); 60 | 61 | if (tipValue === "") { 62 | showErrorMessage("No tip amount set"); 63 | return; 64 | } else if (isNaN(tipValue)) { 65 | showErrorMessage("Tip amount must be a number"); 66 | return; 67 | } else if (tipValue < 1) { 68 | showErrorMessage("Tip amount must be at least 1 sat"); 69 | return; 70 | } else if (parseInt(tipValue).toString() != tipValue) { 71 | showErrorMessage("Tip amount should be a whole number of sats (no decimals)"); 72 | return; 73 | } 74 | 75 | var request = new XMLHttpRequest(); 76 | 77 | request.onreadystatechange = function () { 78 | if (request.readyState === 4) { 79 | //console.log("RESPONSE: " + request.responseText); 80 | try { 81 | var json = JSON.parse(request.responseText); 82 | 83 | if (request.status === 200) { 84 | console.log("Got invoice: " + json.Invoice); 85 | console.log("Invoice expires in: " + json.Expiry); 86 | console.log("Starting listening for invoice to get settled"); 87 | 88 | listenInvoiceSettled(json.r_hash_str); 89 | 90 | invoice = json.Invoice; 91 | 92 | // Update UI 93 | var wrapper = document.getElementById("lightningTip"); 94 | 95 | wrapper.innerHTML = "Your tip request"; 96 | wrapper.innerHTML += ""; 97 | wrapper.innerHTML += "
"; 98 | 99 | wrapper.innerHTML += "
" + 100 | "" + 101 | "" + 102 | "" + 103 | "
"; 104 | 105 | startTimer(json.Expiry, document.getElementById("lightningTipExpiry")); 106 | 107 | // Fixes bug which caused the content of #lightningTipTools to be visually outside of #lightningTip 108 | document.getElementById("lightningTipTools").style.height = document.getElementById("lightningTipCopy").clientHeight + "px"; 109 | 110 | document.getElementById("lightningTipOpen").onclick = function () { 111 | location.href = "lightning:" + json.Invoice; 112 | }; 113 | 114 | showQRCode(); 115 | 116 | running = false; 117 | 118 | } else { 119 | showErrorMessage(json.Error); 120 | } 121 | 122 | } catch (exception) { 123 | console.error(exception); 124 | 125 | showErrorMessage("Failed to reach backend"); 126 | } 127 | 128 | } 129 | 130 | }; 131 | 132 | request.open("POST", requestUrl , true); 133 | request.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 134 | var params = "Action=getinvoice&Amount=" + parseInt(tipValue) + "&Message=" + encodeURIComponent(document.getElementById("lightningTipMessage").innerText); 135 | console.log(params); 136 | request.send(params); 137 | 138 | var button = document.getElementById("lightningTipGetInvoice"); 139 | 140 | defaultGetInvoice = button.innerHTML; 141 | 142 | button.innerHTML = "
"; 143 | 144 | } else { 145 | console.warn("Last request still pending"); 146 | } 147 | 148 | } 149 | 150 | function listenInvoiceSettled(r_hash_str) { 151 | var interval = setInterval(function () { 152 | var request = new XMLHttpRequest(); 153 | 154 | //Prevent multiple calls for same invoice settled over slow networks. 155 | var IsSettled = false; 156 | if ( IsSettled == true) { 157 | return; 158 | } 159 | 160 | request.onreadystatechange = function () { 161 | if (request.readyState === 4 && request.status === 200) { 162 | //console.log("RESPONSE: " + request.responseText); 163 | var json = JSON.parse(request.responseText); 164 | 165 | if (json.settled) { 166 | console.log("Invoice settled"); 167 | IsSettled = true; 168 | clearInterval(interval); 169 | showThankYouScreen(); 170 | } 171 | 172 | } 173 | 174 | }; 175 | 176 | 177 | 178 | request.open("POST", requestUrl, true); 179 | request.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 180 | var params = "Action=invoicesettled&r_hash_str=" + r_hash_str; 181 | request.send(params); 182 | 183 | }, 2000); 184 | 185 | // } 186 | 187 | } 188 | 189 | function showThankYouScreen() { 190 | var wrapper = document.getElementById("lightningTip"); 191 | 192 | wrapper.innerHTML = "

"; 193 | wrapper.innerHTML += "Thank you for your tip!"; 194 | } 195 | 196 | function startTimer(duration, element) { 197 | showTimer(duration, element); 198 | 199 | var interval = setInterval(function () { 200 | if (duration > 1) { 201 | duration--; 202 | 203 | showTimer(duration, element); 204 | 205 | } else { 206 | showExpired(); 207 | 208 | clearInterval(interval); 209 | } 210 | 211 | }, 1000); 212 | 213 | } 214 | 215 | function showTimer(duration, element) { 216 | var seconds = Math.floor(duration % 60); 217 | var minutes = Math.floor((duration / 60) % 60); 218 | var hours = Math.floor((duration / (60 * 60)) % 24); 219 | 220 | seconds = addLeadingZeros(seconds); 221 | minutes = addLeadingZeros(minutes); 222 | 223 | if (hours > 0) { 224 | element.innerHTML = hours + ":" + minutes + ":" + seconds; 225 | 226 | } else { 227 | element.innerHTML = minutes + ":" + seconds; 228 | } 229 | 230 | } 231 | 232 | function showExpired() { 233 | var wrapper = document.getElementById("lightningTip"); 234 | 235 | wrapper.innerHTML = "

"; 236 | wrapper.innerHTML += "Your tip request expired!"; 237 | } 238 | 239 | function addLeadingZeros(value) { 240 | return ("0" + value).slice(-2); 241 | } 242 | 243 | function showQRCode() { 244 | var element = document.getElementById("lightningTipQR"); 245 | 246 | createQRCode(); 247 | 248 | element.innerHTML = qrCode; 249 | 250 | var size = document.getElementById("lightningTipInvoice").clientWidth + "px"; 251 | 252 | var qrElement = element.children[0]; 253 | 254 | qrElement.style.height = size; 255 | qrElement.style.width = size; 256 | } 257 | 258 | function createQRCode() { 259 | var invoiceLength = invoice.length; 260 | 261 | // Just in case an invoice bigger than expected gets created 262 | var typeNumber = 26; 263 | 264 | for (var i = 0; i < qrCodeDataCapacities.length; i++) { 265 | var dataCapacity = qrCodeDataCapacities[i]; 266 | 267 | if (invoiceLength < dataCapacity.capacity) { 268 | typeNumber = dataCapacity.typeNumber; 269 | 270 | break; 271 | } 272 | 273 | } 274 | 275 | console.log("Creating QR code with type number: " + typeNumber); 276 | 277 | var qr = qrcode(typeNumber, "L"); 278 | 279 | qr.addData(invoice); 280 | qr.make(); 281 | 282 | qrCode = qr.createImgTag(6, 6); 283 | } 284 | 285 | function copyInvoiceToClipboard() { 286 | var element = document.getElementById("lightningTipInvoice"); 287 | 288 | element.select(); 289 | 290 | document.execCommand('copy'); 291 | 292 | console.log("Copied invoice to clipboard"); 293 | } 294 | 295 | function showErrorMessage(message) { 296 | running = false; 297 | 298 | console.error(message); 299 | 300 | var error = document.getElementById("lightningTipError"); 301 | 302 | error.parentElement.style.marginTop = "0.5em"; 303 | error.innerHTML = message; 304 | 305 | var button = document.getElementById("lightningTipGetInvoice"); 306 | 307 | // Only necessary if it has a child (div with class spinner) 308 | if (button.children.length !== 0) { 309 | button.innerHTML = defaultGetInvoice; 310 | } 311 | 312 | } 313 | 314 | function divRestorePlaceholder(element) { 315 | //
and

mean that there is no user input 316 | if (element.innerHTML === "
" || element.innerHTML === "

") { 317 | element.innerHTML = ""; 318 | } 319 | } 320 | 321 | function getVal(str) { 322 | var v = window.location.search.match(new RegExp('(?:[\?\&]'+str+'=)([^&]+)')); 323 | return v ? v[1] : null; 324 | } 325 | -------------------------------------------------------------------------------- /frontend/lightningTip.php: -------------------------------------------------------------------------------- 1 | 43 | 44 | Design: 45 | [Web Browser]<----HTTP---->[.php,.css,.js]<----HTTP---->[LND] 46 | /\ 47 | | 48 | | 49 | [LN Wallet] --------------------gRPC-----------------------+ 50 | 51 | 52 | 53 | Instructions: 54 | 1. LND: 55 | Make sure 56 | * REST is enabled. 57 | * Firewalls and/or router port forwarders are updated so that 58 | the REST port is accessable from the web server hosting lightningTip. 59 | 60 | 2. Convert the invoice.macaroon from your lnd to HEX. 61 | Linux: xxd -ps -u -c 1000 /path/to/invoice.macaroon 62 | Generic: http://tomeko.net/online_tools/file_to_hex.php?lang=en 63 | 64 | 3. Update the CHANGE_ME section below. 65 | 66 | 4. Install these files on your webserver. 67 | Note: Due to JavaScript security, lightningTip.php must be hosted at the same domain as lightningTip.js 68 | 69 | lightningTip.js 70 | lightningTip.php 71 | lightningTip.css 72 | lightningTip_light.css (optional) 73 | 74 | 5. Open with browser: 75 | https://your.domain/path/lightningTip.php 76 | https://your.domain/path/lightningTip.php?testnet=1 77 | 78 | */ 79 | 80 | //////// CHANGE ME SECTION /////// 81 | define('LND_IP','CHANGE_ME'); //IP address or FQDN (domain name) of lnd server 82 | define('LND_PORT_MAINNET','CHANGE_ME'); 83 | define('LND_PORT_TESTNET','CHANGE_ME'); //Optional 84 | define('INVOICE_MACAROON_HEX', //No spaces 85 | 'CHANGE_ME' 86 | ); 87 | define('EXPIRY','1800'); //seconds 88 | define('TESTNET', FALSE) 89 | 90 | //Optional EMAIL notifications 91 | // To disable, leave EMAIL_TO as '' (empty string) 92 | define('EMAIL_TO' ,'CHANGE_ME'); //e.g. 'me@my.domain' Empty string disables emails 93 | define('EMAIL_TO_NAME' ,'CHANGE_ME'); //e.g. 'My Name' 94 | define('EMAIL_FROM' ,'CHANGE_ME'); //e.g. 'me@my.domain' 95 | define('EMAIL_FROM_NAME','LightningTip'); 96 | 97 | //////// END CHANGE ME SECTION /////// 98 | 99 | 100 | if($_GET['testnet']){ 101 | define('LND_PORT',LND_PORT_TESTNET); 102 | } else { 103 | define('LND_PORT',LND_PORT_MAINNET); 104 | } 105 | 106 | function getPaymentRequest($memo='',$satoshi=0,$expiry=EXPIRY){ 107 | $lnd_ip = LND_IP; 108 | $lnd_port = LND_PORT; 109 | $invoice_macaroon_hex= INVOICE_MACAROON_HEX; 110 | 111 | $data = json_encode(array( 112 | "memo" => (TESTNET=='true'?'[TESTNET] ':'').$memo, 113 | "value" => "$satoshi", 114 | "expiry" => $expiry 115 | ) 116 | ); 117 | $ch = curl_init("https://$lnd_ip:$lnd_port/v1/invoices"); 118 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 119 | curl_setopt($ch, CURLOPT_POST, 1); 120 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data); 121 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 122 | curl_setopt($ch, CURLOPT_HTTPHEADER, array( 123 | "Grpc-Metadata-macaroon: $invoice_macaroon_hex" 124 | )); 125 | $response = curl_exec($ch); 126 | curl_close($ch); 127 | return json_decode($response); 128 | } 129 | 130 | function lookupInvoice($r_hash_str){ 131 | $lnd_ip = LND_IP; 132 | $lnd_port = LND_PORT; 133 | $invoice_macaroon_hex= INVOICE_MACAROON_HEX; 134 | 135 | $ch = curl_init("https://$lnd_ip:$lnd_port/v1/invoice/$r_hash_str"); 136 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 137 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 138 | curl_setopt($ch, CURLOPT_HTTPHEADER, array( 139 | "Grpc-Metadata-macaroon: $invoice_macaroon_hex" 140 | )); 141 | $response = curl_exec($ch); 142 | curl_close($ch); 143 | return json_decode($response); 144 | } 145 | 146 | switch($_POST['Action']){ 147 | 148 | case 'getinvoice': 149 | $PR = getPaymentRequest($_POST['Message'],$_POST['Amount'], EXPIRY); 150 | //Comment out next 1 line if you do not want to receive GetInvoice messages 151 | lightningTipSendEmail(EMAIL_TO, EMAIL_TO_NAME,'[LightningTip] GetInvoice',print_r($PR,1)); 152 | 153 | echo json_encode(array( 154 | 'Invoice' =>$PR->payment_request, 155 | 'Expiry' =>EXPIRY, 156 | 'r_hash_str'=>bin2hex(base64_decode($PR->r_hash)) 157 | ) 158 | ); 159 | exit; 160 | 161 | case 'invoicesettled': 162 | $Invoice = lookupInvoice($_POST['r_hash_str']); 163 | if(isset($Invoice->settled) && $Invoice->settled){ 164 | lightningTipSendEmail( 165 | EMAIL_TO, EMAIL_TO_NAME, 166 | "[LightningTip] Invoice Settled: $Invoice->value sat", 167 | "Memo: $Invoice->memo\nValue: $Invoice->value (sat)" 168 | ); 169 | } 170 | echo json_encode($Invoice); 171 | exit; 172 | 173 | default: 174 | // fall through to displaying the HTML 175 | } 176 | ?> 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 |
> 185 | 186 | Send a tip via Lightning 187 | 188 |
189 | 190 |
191 |
192 | 193 | 194 | 195 |
196 | 197 |
198 | 199 |
200 | 201 |
202 | 203 | ",$subject,$body,"From: ".EMAIL_FROM_NAME." <".EMAIL_FROM.">"); 216 | } else { 217 | date_default_timezone_set('Australia/Perth'); 218 | require '../PHPMailer/PHPMailerAutoload.php'; 219 | $mail = new PHPMailer; 220 | $mail->isSMTP(); 221 | $mail->Host = 'CHANGE_ME'; // Which SMTP server to use. 222 | $mail->Port = CHANGE_ME; // Which port to use, 587 is the default port for TLS security. 223 | $mail->SMTPSecure = 'tls'; // Which security method to use. TLS is most secure. 224 | $mail->SMTPAuth = true; // Whether you need to login. This is almost always required. 225 | $mail->Username = 'CHANGE_ME'; 226 | $mail->Password = 'CHANGE_ME'; 227 | $mail->setFrom(EMAIL_FROM, EMAIL_FROM_NAME); 228 | $mail->addAddress($to, $name); 229 | $mail->Subject = $subject; 230 | $mail->Body = $body; 231 | $mail->send(); 232 | } 233 | } 234 | ?> 235 | -------------------------------------------------------------------------------- /frontend/lightningTip_light.css: -------------------------------------------------------------------------------- 1 | #lightningTip > a { 2 | font-weight: 600 !important; 3 | } 4 | 5 | #lightningTip { 6 | background-color: #FFFFFF !important; 7 | 8 | border: 2px #BDBDBD solid !important; 9 | 10 | color: #FFB300 !important; 11 | 12 | } 13 | 14 | .lightningTipInput { 15 | border: 1px #BDBDBD solid !important; 16 | 17 | background-color: #FFFFFF !important; 18 | } 19 | 20 | .lightningTipButton { 21 | color: #FFFFFF !important; 22 | 23 | background-color: #FFB300 !important; 24 | } 25 | 26 | #lightningTipError { 27 | color: #D50000 !important; 28 | 29 | font-weight: 600 !important; 30 | } 31 | 32 | #lightningTipCopy { 33 | border-right: 1px solid #FFFFFF !important; 34 | } 35 | 36 | #lightningTipExpiry { 37 | font-weight: 600 !important; 38 | } --------------------------------------------------------------------------------