├── .gitignore ├── README.md ├── build.sh ├── composer.json ├── composer.lock ├── css ├── complete.css └── payment.css ├── img └── loader.gif ├── release.sh ├── templates ├── completed.php └── payment.php └── woocommerce-gateway-lightning.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | .vimrc 3 | woocommerce-gateway-lightning.zip 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WooCommerce Gateway Plugin for Lightning 2 | 3 | Gateway plugin to accept Lightning payments at [WooCommerce](https://woocommerce.com) stores, 4 | based on [Lightning Charge](https://github.com/ElementsProject/lightning-charge). 5 | 6 | ## Installation 7 | 8 | Requires PHP >= 5.6 and the `php-curl` and `php-gd` extensions. 9 | 10 | 1. Setup [Lightning Charge](https://github.com/ElementsProject/lightning-charge). 11 | 12 | 2. [Download woocommerce-gateway-lightning.zip](https://github.com/ElementsProject/woocommerce-gateway-lightning/releases/download/v0.2.7/woocommerce-gateway-lightning.zip) 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` to configure your Lightning Charge server URL and API token. 17 | 18 | That's it! The "Bitcoin Lightning" payment option should now be available in your checkout page. 19 | 20 | ## Screenshots 21 | 22 | 23 | 24 | 25 | 26 | 27 | ## License 28 | 29 | MIT 30 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | composer install --no-dev 3 | zip -r woocommerce-gateway-lightning.zip composer.json woocommerce-gateway-lightning.php vendor templates css img README.md --exclude='vendor/bacon/bacon-qr-code/tests/*' 4 | 5 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elementsproject/woocommerce-gateway-lightning", 3 | "description": "WooCommerce Gateway Plugin for Lightning Charge", 4 | "version": "0.2.7", 5 | "license": "MIT", 6 | "minimum-stability": "dev", 7 | "require": { 8 | "bacon/bacon-qr-code": "^1.0", 9 | "elementsproject/lightning-charge-client-php": "^0.1.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "b6a2854067c61917fde3f4ad286638f4", 8 | "content-hash": "f6ec27bba43744175813baa5f5b325da", 9 | "packages": [ 10 | { 11 | "name": "bacon/bacon-qr-code", 12 | "version": "1.0.3", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/Bacon/BaconQrCode.git", 16 | "reference": "5a91b62b9d37cee635bbf8d553f4546057250bee" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/5a91b62b9d37cee635bbf8d553f4546057250bee", 21 | "reference": "5a91b62b9d37cee635bbf8d553f4546057250bee", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "ext-iconv": "*", 26 | "php": "^5.4|^7.0" 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "^4.8" 30 | }, 31 | "suggest": { 32 | "ext-gd": "to generate QR code images" 33 | }, 34 | "type": "library", 35 | "autoload": { 36 | "psr-0": { 37 | "BaconQrCode": "src/" 38 | } 39 | }, 40 | "notification-url": "https://packagist.org/downloads/", 41 | "license": [ 42 | "BSD-2-Clause" 43 | ], 44 | "authors": [ 45 | { 46 | "name": "Ben Scholzen 'DASPRiD'", 47 | "email": "mail@dasprids.de", 48 | "homepage": "http://www.dasprids.de", 49 | "role": "Developer" 50 | } 51 | ], 52 | "description": "BaconQrCode is a QR code generator for PHP.", 53 | "homepage": "https://github.com/Bacon/BaconQrCode", 54 | "time": "2017-10-17 09:59:25" 55 | }, 56 | { 57 | "name": "elementsproject/lightning-charge-client-php", 58 | "version": "0.1.3", 59 | "source": { 60 | "type": "git", 61 | "url": "https://github.com/ElementsProject/lightning-charge-client-php.git", 62 | "reference": "1ee22161e90318296770135519cbdd624dcb796a" 63 | }, 64 | "dist": { 65 | "type": "zip", 66 | "url": "https://api.github.com/repos/ElementsProject/lightning-charge-client-php/zipball/1ee22161e90318296770135519cbdd624dcb796a", 67 | "reference": "1ee22161e90318296770135519cbdd624dcb796a", 68 | "shasum": "" 69 | }, 70 | "require": { 71 | "tcdent/php-restclient": "^0.1" 72 | }, 73 | "require-dev": { 74 | "clue/phar-composer": "^1.0", 75 | "phpunit/phpunit": "^6.4" 76 | }, 77 | "type": "library", 78 | "autoload": { 79 | "files": [ 80 | "client.php" 81 | ] 82 | }, 83 | "notification-url": "https://packagist.org/downloads/", 84 | "license": [ 85 | "MIT" 86 | ], 87 | "description": "PHP client for the Lightning Charge REST API", 88 | "time": "2017-12-29 19:18:18" 89 | }, 90 | { 91 | "name": "tcdent/php-restclient", 92 | "version": "0.1.7", 93 | "source": { 94 | "type": "git", 95 | "url": "https://github.com/tcdent/php-restclient.git", 96 | "reference": "4522e8518eaef770d715977fcb45f187f8ad7499" 97 | }, 98 | "dist": { 99 | "type": "zip", 100 | "url": "https://api.github.com/repos/tcdent/php-restclient/zipball/4522e8518eaef770d715977fcb45f187f8ad7499", 101 | "reference": "4522e8518eaef770d715977fcb45f187f8ad7499", 102 | "shasum": "" 103 | }, 104 | "require": { 105 | "ext-curl": "*", 106 | "ext-json": "*", 107 | "php": ">=5.4.0" 108 | }, 109 | "require-dev": { 110 | "php": ">=5.5.7", 111 | "phpunit/phpunit": ">=4.5" 112 | }, 113 | "type": "library", 114 | "autoload": { 115 | "files": [ 116 | "restclient.php" 117 | ] 118 | }, 119 | "notification-url": "https://packagist.org/downloads/", 120 | "license": [ 121 | "MIT" 122 | ], 123 | "authors": [ 124 | { 125 | "name": "Travis Dent", 126 | "email": "tcdent@gmail.com", 127 | "role": "Developer" 128 | } 129 | ], 130 | "description": "A generic REST API client for PHP", 131 | "homepage": "http://github.com/tcdent/php-restclient", 132 | "keywords": [ 133 | "api", 134 | "client", 135 | "curl", 136 | "json", 137 | "rest", 138 | "xml" 139 | ], 140 | "time": "2017-09-07 20:44:36" 141 | } 142 | ], 143 | "packages-dev": [], 144 | "aliases": [], 145 | "minimum-stability": "dev", 146 | "stability-flags": [], 147 | "prefer-stable": false, 148 | "prefer-lowest": false, 149 | "platform": [], 150 | "platform-dev": [] 151 | } 152 | -------------------------------------------------------------------------------- /css/complete.css: -------------------------------------------------------------------------------- 1 | .woocommerce-order .order_details .payreq { 2 | word-wrap: break-word; 3 | } 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /img/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementsProject/woocommerce-gateway-lightning/068d5e7cd9415715bc2878f7c2cd0e41f681ff02/img/loader.gif -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eo pipefail 4 | set -x 5 | 6 | [ -z "$1" ] && { echo >&2 version required; exit 1; } 7 | 8 | sed -i 's!"version": ".*"!"version": "'"$1"'"!' composer.json 9 | sed -ri "s!Version:( *).*!Version:\\1$1!" woocommerce-gateway-lightning.php 10 | sed -ri s!download/v[^/]+/woocommerce-gateway-lightning.zip!download/v$1/woocommerce-gateway-lightning.zip! README.md 11 | 12 | composer update 13 | 14 | ./build.sh 15 | 16 | read -p "Release v$1 ready, press Enter to publish" 17 | 18 | git add README.md composer.json composer.lock woocommerce-gateway-lightning.php 19 | git commit -m v$1 && git tag v$1 20 | git push && git push --tags 21 | 22 | echo Attach zip file: https://github.com/ElementsProject/woocommerce-gateway-lightning/releases/edit/v$1 23 | -------------------------------------------------------------------------------- /templates/completed.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Payment completed successfully

4 | 18 | -------------------------------------------------------------------------------- /templates/payment.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | expires_at); ?> 5 | 6 |
7 |

Pay with Lightning

8 |

9 | get_currency() !== 'BTC'): ?> get_total() ?> get_currency() ?> = 10 | msatoshi) ?> 11 |

12 | 13 | payreq ?> 14 |

15 | 16 | loading Awaiting payment. 17 | The invoice expires . 18 |

19 | Pay with Lightning 20 |
21 | 22 | 59 | 60 | -------------------------------------------------------------------------------- /woocommerce-gateway-lightning.php: -------------------------------------------------------------------------------- 1 | id = 'lightning'; 37 | $this->order_button_text = __('Proceed to Lightning Payment', 'woocommerce'); 38 | $this->method_title = __('Lightning', 'woocommerce'); 39 | $this->method_description = __('Lightning Network Payment'); 40 | //$this->icon = plugin_dir_url(__FILE__).'assets/img/icon.png'; 41 | $this->supports = array(); 42 | 43 | // Load the settings. 44 | $this->init_form_fields(); 45 | $this->init_settings(); 46 | 47 | // Define user set variables. 48 | $this->title = $this->get_option('title'); 49 | $this->description = $this->get_option('description'); 50 | 51 | // Lightning Charge REST client 52 | $this->charge = new LightningChargeClient($this->get_option('charge_url', 'http://localhost:9112'), $this->get_option('charge_token')); 53 | 54 | add_action('woocommerce_payment_gateways', array($this, 'register_gateway')); 55 | add_action('woocommerce_update_options_payment_gateways_lightning', array($this, 'process_admin_options')); 56 | add_action('woocommerce_api_wc_gateway_lightning', array($this, 'webhook_callback')); 57 | add_action('woocommerce_receipt_lightning', array($this, 'show_payment_info')); 58 | add_action('woocommerce_thankyou_lightning', array($this, 'show_payment_info')); 59 | add_action('wp_ajax_ln_wait_invoice', array($this, 'wait_invoice')); 60 | add_action('wp_ajax_nopriv_ln_wait_invoice', array($this, 'wait_invoice')); 61 | } 62 | 63 | /** 64 | * Initialise Gateway Settings Form Fields. 65 | */ 66 | public function init_form_fields() { 67 | $this->form_fields = array( 68 | 'enabled' => array( 69 | 'title' => __( 'Enable/Disable', 'woocommerce-gateway-lightning' ), 70 | 'label' => __( 'Enable Lightning payments', 'woocommerce-gateway-lightning' ), 71 | 'type' => 'checkbox', 72 | 'description' => '', 73 | 'default' => 'no', 74 | ), 75 | 'title' => array( 76 | 'title' => __('Title', 'lightning'), 77 | 'type' => 'text', 78 | 'description' => __('Controls the name of this payment method as displayed to the customer during checkout.', 'lightning'), 79 | 'default' => __('Bitcoin Lightning', 'lightning'), 80 | 'desc_tip' => true, 81 | ), 82 | 'charge_url' => array ( 83 | 'title' => __('Lightning Charge server', 'lightning'), 84 | 'type' => 'text', 85 | 'description' => __('URL of the Lightning Charge REST server to connect to.', 'lightning'), 86 | 'default' => __('http://localhost:9112', 'lightning'), 87 | 'desc_tip' => true, 88 | ), 89 | 'charge_token' => array ( 90 | 'title' => __('Lightning Charge API token', 'lightning'), 91 | 'type' => 'text', 92 | 'description' => __('API access token configured in the Lightning Charge server.', 'lightning'), 93 | 'desc_tip' => true, 94 | ), 95 | 'description' => array( 96 | 'title' => __('Customer Message', 'lightning'), 97 | 'type' => 'textarea', 98 | 'description' => __('Message to explain how the customer will be paying for the purchase.', 'lightning'), 99 | 'default' => 'You will pay using the Lightning Network.', 100 | 'desc_tip' => true, 101 | ), 102 | 'invoice_expiry' => array( 103 | 'title' => __('Invoice Expiry', 'lightning'), 104 | 'type' => 'text', 105 | 'description' => __('Time til an invoice expires. Examples: 2w (2 weeks), 1m (1 month), 3h (3 hours), 3600s (3600 seconds). Defaults to 2h.', 'lightning'), 106 | 'default' => '2h', 107 | 'desc_tip' => true, 108 | ), 109 | ); 110 | } 111 | 112 | /** 113 | * Process the payment and return the result. 114 | * @param int $order_id 115 | * @return array 116 | */ 117 | public function process_payment( $order_id ) { 118 | $order = wc_get_order($order_id); 119 | $invoice = $order->get_meta('_lightning_invoice'); 120 | 121 | if (!$invoice) { 122 | $invoice = $this->charge->invoice([ 123 | 'currency' => $order->get_currency(), 124 | 'amount' => $order->get_total(), 125 | 'description' => self::make_desc($order), 126 | 'expiry' => $this->get_option('invoice_expiry', '2h'), 127 | 'metadata' => [ 'source' => 'woocommerce-gateway', 'order_id' => $order->get_id(), 'url' => get_home_url() ], 128 | 'webhook' => self::get_webhook_url($order->get_id()) 129 | ]); 130 | $this->update_invoice($order, $invoice); 131 | 132 | $order->add_order_note(sprintf(__('Lightning Charge invoice created, id=%s, rhash=%s.', 'lightning'), $invoice->id, $invoice->rhash)); 133 | } 134 | 135 | return array( 136 | 'result' => 'success', 137 | 'redirect' => $order->get_checkout_payment_url(true) 138 | ); 139 | } 140 | 141 | /** 142 | * Process webhook callbacks. 143 | */ 144 | public function webhook_callback() { 145 | if (!self::verify_token($_GET['order'], $_GET['token'])) wp_die('invalid token'); 146 | 147 | $order = wc_get_order($_GET['order']); 148 | $invoice = json_decode(file_get_contents("php://input")); 149 | 150 | if (!$invoice) { 151 | status_header(400); 152 | wp_die('cannot decode request body invoice'); 153 | } 154 | 155 | $order->add_order_note(__('Lightning webhook notification received.', 'lightning')); 156 | $this->update_invoice($order, $invoice); 157 | } 158 | 159 | /** 160 | * JSON endpoint for long polling payment updates. 161 | */ 162 | public function wait_invoice() { 163 | $paid_inv = $this->charge->wait($_POST['invoice_id'], LIGHTNING_LONGPOLL_TIMEOUT); 164 | 165 | if ($paid_inv) { 166 | $order = wc_get_order($paid_inv->metadata->order_id); 167 | $this->update_invoice($order, $paid_inv); 168 | wp_send_json(true); 169 | } else { 170 | status_header($paid_inv === false ? 410 : 402); 171 | wp_send_json(false); 172 | } 173 | } 174 | 175 | /** 176 | * Hooks into the checkout page to display Lightning-related payment info. 177 | */ 178 | public function show_payment_info($order_id) { 179 | global $wp; 180 | 181 | $order = wc_get_order($order_id); 182 | 183 | if (!empty($wp->query_vars['order-received']) && $order->needs_payment()) { 184 | // thankyou page requested, but order is still unpaid 185 | wp_redirect($order->get_checkout_payment_url(true)); 186 | exit; 187 | } 188 | 189 | $invoice = $order->get_meta('_lightning_invoice'); 190 | if ($invoice->status == 'unpaid') { 191 | $invoice = $this->charge->fetch($invoice->id); 192 | $this->update_invoice($order, $invoice); 193 | } 194 | 195 | if ($order->has_status('cancelled')) { 196 | // invoice expired, reload page to display expiry message 197 | wp_redirect($order->get_checkout_payment_url(true)); 198 | exit; 199 | } 200 | 201 | if ($order->needs_payment()) { 202 | $qr_uri = self::get_qr_uri($invoice); 203 | require __DIR__.'/templates/payment.php'; 204 | } elseif ($order->has_status(array('processing', 'completed'))) { 205 | require __DIR__.'/templates/completed.php'; 206 | } 207 | } 208 | 209 | /** 210 | * Register as a WooCommerce gateway. 211 | */ 212 | public function register_gateway($methods) { 213 | $methods[] = $this; 214 | return $methods; 215 | } 216 | 217 | protected function update_invoice($order, $invoice) { 218 | $order->update_meta_data('_lightning_invoice', $invoice); 219 | $order->update_meta_data('lightning_id', $invoice->id); 220 | $order->update_meta_data('lightning_rhash', $invoice->rhash); 221 | $order->update_meta_data('lightning_msatoshi', $invoice->msatoshi); 222 | if ($invoice->status === 'paid') $order->update_meta_data('lightning_msatoshi_received', $invoice->msatoshi_received); 223 | $order->save_meta_data(); 224 | 225 | if ($invoice->status == 'paid' && $order->needs_payment()) { 226 | $order->payment_complete($invoice->id); 227 | } else if ($invoice->status == 'expired' && $order->has_status(array('pending', 'on-hold'))) { 228 | $order->update_status('cancelled', 'Lightning invoice expired'); 229 | } 230 | } 231 | 232 | protected static function make_token($order_id) { 233 | return hash_hmac('sha256', $order_id, LIGHTNING_HOOK_KEY); 234 | } 235 | 236 | protected static function verify_token($order_id, $token) { 237 | return self::make_token($order_id) === $token; 238 | } 239 | 240 | protected static function get_webhook_url($order_id) { 241 | return add_query_arg(array('order' => $order_id, 'token' => self::make_token($order_id)), 242 | WC()->api_request_url('WC_Gateway_Lightning')); 243 | } 244 | 245 | protected static function get_qr_uri($invoice) { 246 | $renderer = new \BaconQrCode\Renderer\Image\Png; 247 | $renderer->setWidth(180); 248 | $renderer->setHeight(180); 249 | $renderer->setMargin(0); 250 | $writer = new \BaconQrCode\Writer($renderer); 251 | $image = $writer->writeString(strtoupper('lightning:' . $invoice->payreq)); 252 | return 'data:image/png;base64,' . base64_encode($image); 253 | } 254 | 255 | protected static function make_desc($order) { 256 | $total = $order->get_total() . ' ' .$order->get_currency(); 257 | $desc = get_bloginfo('name') . ': ' . $total . ' for '; 258 | $products = $order->get_items(); 259 | for ($n=0; strlen($desc) < 100 && count($products); $n++) { 260 | $product = array_shift($products); 261 | if (count($products)) $desc .= $product['name'] . ' x ' . $product['qty'] . ', '; 262 | else $desc = ($n ? substr($desc, 0, -2) . ' and ' : $desc) . $product['name'].' x '.$product['qty']; 263 | } 264 | if (count($products)) $desc = substr($desc, 0, -2) . ' and ' . count($products) . ' more items'; 265 | return $desc; 266 | } 267 | 268 | protected static function format_msat($msat) { 269 | return rtrim(rtrim(number_format($msat/100000000, 8), '0'), '.') . ' mBTC'; 270 | } 271 | } 272 | 273 | new WC_Gateway_Lightning(); 274 | } 275 | 276 | add_action('plugins_loaded', 'init_wc_lightning'); 277 | } 278 | --------------------------------------------------------------------------------