├── .gitignore ├── .DS_Store ├── img ├── logo.png └── loader.gif ├── templates ├── completed.php └── payment.php ├── README.md ├── css └── payment.css ├── LICENSE ├── Lnd_wrapper.php └── woocommerce-gateway-lightning.php /.gitignore: -------------------------------------------------------------------------------- 1 | tls/.DS_Store 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaodealmeida/woocommerce-gateway-lightning/HEAD/.DS_Store -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaodealmeida/woocommerce-gateway-lightning/HEAD/img/logo.png -------------------------------------------------------------------------------- /img/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaodealmeida/woocommerce-gateway-lightning/HEAD/img/loader.gif -------------------------------------------------------------------------------- /templates/completed.php: -------------------------------------------------------------------------------- 1 |

Payment completed successfully

2 | get_id(), 'LN_HASH', true ); 4 | $invoiceRep = $this->lndCon->getInvoiceInfoFromHash( bin2hex(base64_decode($payHash)) ); 5 | ?> 6 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WooCommerce Plugin for Bitcoin Lightning 2 | 3 | Plugin to accept Bitcoin Lightning payments at [WooCommerce](https://woocommerce.com) stores, 4 | using [LND](https://github.com/lightningnetwork/lnd). 5 | 6 | ## Installation 7 | 8 | Requires PHP >= 5.6 and the `php-curl` and `php-gd` extensions. 9 | 10 | 1. Setup a [LND] node (https://github.com/lightningnetwork/lnd). 11 | 12 | 2. Get the Node server IP and macaroon key 13 | 14 | 3. Install and enable the plugin on your WordPress installation. 15 | 16 | 4. Under the WordPress administration panel, go to `WooCommerce -> Settings -> Checkout -> Lightning` and set LND endpoint and macaroon key. 17 | 18 | 5. Add LND tls.cert to plugin-folder/tls or input the exact path on plugin settings. 19 | 20 | 21 | The payment option should now be available in your checkout page. 22 | 23 | ## License 24 | 25 | MIT 26 | -------------------------------------------------------------------------------- /css/payment.css: -------------------------------------------------------------------------------- 1 | /* Hide WooCommerce default title payment details */ 2 | header.entry-header, .order_details { 3 | display: none; 4 | } 5 | 6 | .woocommerce-breadcrumb { 7 | margin-bottom: 0; 8 | } 9 | 10 | .ln-pay { margin-bottom: 2em; } 11 | 12 | .ln-pay h2 { margin-bottom: 0; } 13 | 14 | .ln-pay h4 { 15 | color: #999; 16 | clear: none; 17 | } 18 | 19 | .ln-pay .qr { 20 | display: block; 21 | float: right; 22 | width: 200px; 23 | margin-top: -10px; 24 | } 25 | 26 | .ln-pay .payreq { 27 | word-wrap: break-word; 28 | margin-right: 220px; 29 | } 30 | 31 | .ln-pay code { 32 | display: block; 33 | margin: 1em 0; 34 | padding: 0.75em; 35 | } 36 | 37 | .ln-pay p { 38 | margin: 1em 0; 39 | } 40 | 41 | .ln-pay .loader { 42 | display: inline; 43 | } 44 | 45 | @media (max-width: 550px) { 46 | .ln-pay { text-align: center; } 47 | .ln-pay .qr { float: none; margin: 0 auto; } 48 | .ln-pay .payreq { margin-right: 0; } 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 João Almeida 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 | -------------------------------------------------------------------------------- /templates/payment.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | creation_date + $callResponse->expiry; 5 | $payReq = $callResponse->payment_request; 6 | ?> 7 | 8 |
9 |

Pay with Lightning

10 |

11 | get_currency() !== 'BTC'): ?> get_total() ?> get_currency() ?> = 12 | value, $this->lndCon->getCoin()) ?> 13 |

14 | 15 | 16 |

17 | 18 | loading Awaiting payment. 19 | The invoice expires . 20 |

21 | Pay with Lightning 22 |
23 | 24 | 60 | 61 | -------------------------------------------------------------------------------- /Lnd_wrapper.php: -------------------------------------------------------------------------------- 1 | tlsPath); 79 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 80 | 81 | 82 | $output = curl_exec($ch); 83 | 84 | curl_close($ch); 85 | return $output; 86 | } 87 | 88 | /** 89 | * Set endpoint credentials 90 | */ 91 | public function setCredentials ( $endpoint , $macaroonHex , $tlsPath){ 92 | $this->endpoint = $endpoint; 93 | $this->macaroonHex = $macaroonHex; 94 | $this->tlsPath = $tlsPath; 95 | } 96 | 97 | public function setCoin ( $coin ){ 98 | $this->coin = $coin; 99 | } 100 | 101 | public function getCoin () { 102 | return $this->coin; 103 | } 104 | 105 | public function generateQr( $paymentRequest ){ 106 | $size = "150x150"; 107 | $encoding = "UTF-8"; 108 | return 'https://chart.googleapis.com/chart?cht=qr' . '&chs=' . $size . '&chl=' . $paymentRequest . '&choe=' . $encoding; 109 | } 110 | 111 | /** 112 | * Generate Payment Request 113 | */ 114 | public function createInvoice ( $invoice ) { 115 | $header = array('Grpc-Metadata-macaroon: ' . $this->macaroonHex , 'Content-type: application/json'); 116 | $createInvoiceResponse = $this->curlWrap( $this->endpoint . '/v1/invoices', json_encode( $invoice ), 'POST', $header ); 117 | $createInvoiceResponse = json_decode($createInvoiceResponse); 118 | return $createInvoiceResponse; 119 | } 120 | 121 | public function getInvoiceInfoFromPayReq ($paymentRequest) { 122 | $header = array('Grpc-Metadata-macaroon: ' . $this->macaroonHex , 'Content-type: application/json'); 123 | $invoiceInfoResponse = $this->curlWrap( $this->endpoint . '/v1/payreq/' . $paymentRequest,'', "GET", $header ); 124 | $invoiceInfoResponse = json_decode( $invoiceInfoResponse ); 125 | return $invoiceInfoResponse; 126 | } 127 | 128 | public function getInvoiceInfoFromHash ( $paymentHash ) { 129 | $header = array('Grpc-Metadata-macaroon: ' . $this->macaroonHex , 'Content-type: application/json'); 130 | $invoiceInfoResponse = $this->curlWrap( $this->endpoint . '/v1/invoice/' . $paymentHash,'', "GET", $header ); 131 | $invoiceInfoResponse = json_decode( $invoiceInfoResponse ); 132 | return $invoiceInfoResponse; 133 | } 134 | 135 | public function getLivePrice() { 136 | 137 | $ticker = "BTCUSD"; 138 | if($this->coin === 'LTC'){ 139 | $ticker = 'LTCUSD'; 140 | } 141 | $tickerUrl = "https://apiv2.bitcoinaverage.com/indices/global/ticker/" . $ticker; 142 | $aHTTP = array( 143 | 'http' => 144 | array( 145 | 'method' => 'GET', 146 | ) 147 | ); 148 | $content = file_get_contents($tickerUrl, false); 149 | 150 | return json_decode($content, true)['ask']; 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /woocommerce-gateway-lightning.php: -------------------------------------------------------------------------------- 1 | id = 'lightning'; 34 | $this->order_button_text = __('Proceed to Lightning Payment', 'woocommerce'); 35 | $this->method_title = __('Lightning', 'woocommerce'); 36 | $this->method_description = __('Lightning Network Payment'); 37 | $this->icon = plugin_dir_url(__FILE__).'img/logo.png'; 38 | $this->supports = array(); 39 | 40 | // Load the settings. 41 | $this->init_form_fields(); 42 | $this->init_settings(); 43 | 44 | // Define user set variables. 45 | $this->title = $this->get_option('title'); 46 | $this->description = $this->get_option('description'); 47 | $this->endpoint = $this->get_option( 'endpoint' ); 48 | $this->macaroon = $this->get_option( 'macaroon' ); 49 | $this->lndCon = LndWrapper::instance(); 50 | $this->lndCon->setCredentials ( $this->get_option( 'endpoint' ), $this->get_option( 'macaroon' ), $this->get_option( 'ssl' )); 51 | $this->lndCon->setCoin( $this->get_option( 'coin' ) ); 52 | 53 | add_action('woocommerce_payment_gateways', array($this, 'register_gateway')); 54 | add_action('woocommerce_update_options_payment_gateways_lightning', array($this, 'process_admin_options')); 55 | add_action('woocommerce_receipt_lightning', array($this, 'show_payment_info')); 56 | add_action('woocommerce_thankyou_lightning', array($this, 'show_payment_info')); 57 | add_action('wp_ajax_ln_wait_invoice', array($this, 'wait_invoice')); 58 | add_action('wp_ajax_nopriv_ln_wait_invoice', array($this, 'wait_invoice')); 59 | } 60 | 61 | /** 62 | * Initialise Gateway Settings Form Fields. 63 | */ 64 | public function init_form_fields() { 65 | $tlsPath = plugin_dir_path(__FILE__).'tls/tls.cert'; 66 | $this->form_fields = array( 67 | 'enabled' => array( 68 | 'title' => __( 'Enable/Disable', 'woocommerce-gateway-lightning' ), 69 | 'label' => __( 'Enable Lightning payments', 'woocommerce-gateway-lightning' ), 70 | 'type' => 'checkbox', 71 | 'description' => '', 72 | 'default' => 'no', 73 | ), 74 | 'title' => array( 75 | 'title' => __('Title', 'lightning'), 76 | 'type' => 'text', 77 | 'description' => __('Controls the name of this payment method as displayed to the customer during checkout.', 'lightning'), 78 | 'default' => __('Bitcoin Lightning', 'lightning'), 79 | 'desc_tip' => true, 80 | ), 81 | 'coin' => array( 82 | 'title' => __('Coin', 'lightning'), 83 | 'type' => 'select', 84 | 'description' => __('Select the coin network your LND is connected to.', 'lightning'), 85 | 'default' => __('BTC', 'lightning'), 86 | 'options' => array( 87 | 'BTC' => __('BTC','lightning'), 88 | 'LTC' => __('LTC','lightning') 89 | ), 90 | 'desc_tip' => true, 91 | ), 92 | 'endpoint' => array( 93 | 'title' => __( 'Endpoint', 'lightning' ), 94 | 'type' => 'textarea', 95 | 'description' => __( 'Place here the API endpoint', 'lightning' ), 96 | 'default' => 'https://localhost:8080', 97 | 'desc_tip' => true, 98 | ), 99 | 'macaroon' => array( 100 | 'title' => __('Macaroon Hex', 'lightning'), 101 | 'type' => 'textarea', 102 | 'description' => __('Input Macaroon Hex to get access to LND API', 'lightning'), 103 | 'default' => '', 104 | 'desc_tip' => true, 105 | ), 106 | 'description' => array( 107 | 'title' => __('Customer Message', 'lightning'), 108 | 'type' => 'textarea', 109 | 'description' => __('Message to explain how the customer will be paying for the purchase.', 'lightning'), 110 | 'default' => 'You will pay using the Lightning Network.', 111 | 'desc_tip' => true, 112 | ), 113 | 'ssl' => array( 114 | 'title' => __('SSL Certificate Path', 'lightning'), 115 | 'type' => 'textarea', 116 | 'description' => __('Put your LND SSL certificate path.', 'lightning'), 117 | 'default' => $tlsPath, 118 | 'desc_tip' => true, 119 | ) 120 | 121 | ); 122 | } 123 | 124 | /** 125 | * Process the payment and return the result. 126 | * @param int $order_id 127 | * @return array 128 | */ 129 | public function process_payment( $order_id ) { 130 | $order = wc_get_order($order_id); 131 | $usedCurrency = get_woocommerce_currency(); 132 | $livePrice = $this->lndCon->getLivePrice(); 133 | 134 | $invoiceInfo = array(); 135 | $btcPrice = $order->get_total() * ((float)1/ $livePrice); 136 | 137 | $invoiceInfo['value'] = round($btcPrice * 100000000); 138 | $invoiceInfo['memo'] = "Order key: " . $order->get_checkout_order_received_url(); 139 | 140 | $invoiceResponse = $this->lndCon->createInvoice ( $invoiceInfo ); 141 | 142 | if(property_exists($invoiceResponse, 'error')){ 143 | wc_add_notice( __('Error: ', 'lightning') . $invoiceResponse->error, 'error' ); 144 | return; 145 | } 146 | 147 | if(!property_exists($invoiceResponse, 'payment_request')) { 148 | wc_add_notice( __('Error: ', 'lightning') . 'Lightning Node is not reachable at this time. Please contact the store administrator.', 'error' ); 149 | return; 150 | } 151 | 152 | update_post_meta( $order->get_id(), 'LN_INVOICE', $invoiceResponse->payment_request, true); 153 | update_post_meta( $order->get_id(), 'LN_HASH', $invoiceResponse->r_hash, true); 154 | $order->add_order_note("Awaiting payment of " . number_format((float)$btcPrice, 7, '.', '') . " " . $this->lndCon->getCoin() . "@ 1 " . $this->lndCon->getCoin() . " ~ " . $livePrice ." USD.
Invoice ID: " . $invoiceResponse->payment_request); 155 | 156 | return array( 157 | 'result' => 'success', 158 | 'redirect' => $order->get_checkout_payment_url(true) 159 | ); 160 | } 161 | 162 | 163 | /** 164 | * JSON endpoint for long polling payment updates. 165 | */ 166 | public function wait_invoice() { 167 | 168 | $order = wc_get_order($_POST['invoice_id']); 169 | 170 | if($order->get_status() == 'processing'){ 171 | status_header(200); 172 | wp_send_json(true); 173 | return; 174 | } 175 | /** 176 | * 177 | * Check if invoice is paid 178 | */ 179 | 180 | $payHash = get_post_meta( $_POST['invoice_id'], 'LN_HASH', true ); 181 | 182 | $callResponse = $this->lndCon->getInvoiceInfoFromHash( bin2hex(base64_decode( $payHash ) ) ); 183 | if(!property_exists( $callResponse, 'r_hash' )) { 184 | status_header(410); 185 | wp_send_json(false); 186 | return; 187 | } 188 | 189 | $invoiceTime = $callResponse->creation_date + $callResponse->expiry; 190 | if($invoiceTime < time()) { 191 | 192 | //Invoice expired 193 | $livePrice = $this->lndCon->getLivePrice(); 194 | $invoiceInfo = array(); 195 | $btcPrice = $order->get_total() * ((float)1/ $livePrice); 196 | 197 | $invoiceInfo['value'] = round($btcPrice * 100000000); 198 | $invoiceInfo['memo'] = "Order key: " . $order->get_checkout_order_received_url(); 199 | 200 | $invoiceResponse = $this->lndCon->createInvoice ( $invoiceInfo ); 201 | update_post_meta( $order->get_id(), 'LN_INVOICE', $invoiceResponse->payment_request); 202 | update_post_meta( $order->get_id(), 'LN_HASH', $invoiceResponse->r_hash); 203 | $order->add_order_note("Awaiting payment of " . number_format((float)$btcPrice, 7, '.', '') . " " . $this->lndCon->getCoin() . "@ 1 " . $this->lndCon->getCoin() . " ~ " . $livePrice ." USD.
Invoice ID: " . $invoiceResponse->payment_request); 204 | status_header(410); 205 | wp_send_json(false); 206 | return; 207 | } 208 | 209 | if(!property_exists( $callResponse, 'settled' )){ 210 | status_header(402); 211 | wp_send_json(false); 212 | return; 213 | } 214 | 215 | if ($callResponse->settled) { 216 | $order->payment_complete(); 217 | $order->add_order_note('Lightning Payment received on ' . $invoiceRep->settle_date); 218 | status_header(200); 219 | wp_send_json(true); 220 | return; 221 | } 222 | 223 | } 224 | 225 | /** 226 | * Hooks into the checkout page to display Lightning-related payment info. 227 | */ 228 | public function show_payment_info($order_id) { 229 | global $wp; 230 | 231 | $order = wc_get_order($order_id); 232 | 233 | if (!empty($wp->query_vars['order-received']) && $order->needs_payment()) { 234 | // thankyou page requested, but order is still unpaid 235 | wp_redirect($order->get_checkout_payment_url(true)); 236 | exit; 237 | } 238 | 239 | if ($order->has_status('cancelled')) { 240 | // invoice expired, reload page to display expiry message 241 | wp_redirect($order->get_checkout_payment_url(true)); 242 | exit; 243 | } 244 | 245 | if ($order->needs_payment()) { 246 | //Prepare information for payment page 247 | $qr_uri = $this->lndCon->generateQr( get_post_meta( $order_id, 'LN_INVOICE', true ) ); 248 | $payHash = get_post_meta( $order_id, 'LN_HASH', true ); 249 | $callResponse = $this->lndCon->getInvoiceInfoFromHash( bin2hex(base64_decode($payHash)) ); 250 | require __DIR__.'/templates/payment.php'; 251 | 252 | } elseif ($order->has_status(array('processing', 'completed'))) { 253 | require __DIR__.'/templates/completed.php'; 254 | } 255 | } 256 | 257 | /** 258 | * Register as a WooCommerce gateway. 259 | */ 260 | public function register_gateway($methods) { 261 | $methods[] = $this; 262 | return $methods; 263 | } 264 | 265 | protected static function format_msat($msat, $coin) { 266 | return rtrim(rtrim(number_format($msat/100000000, 8), '0'), '.') . ' ' . $coin; 267 | } 268 | } 269 | 270 | new WC_Gateway_Lightning(); 271 | } 272 | 273 | add_action('plugins_loaded', 'init_wc_lightning'); 274 | } 275 | --------------------------------------------------------------------------------