├── .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 |
7 |
8 | Payment completed at: settle_date) ?>
9 |
10 |
11 | Lightning rhash: r_hash ?>
12 |
13 |
14 | Invoice amount: value, $this->lndCon->getCoin()) ?>
15 |
16 |
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 | Your browser has JavaScript turned off. Please refresh the page manually after making the payment.
18 | 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 |
--------------------------------------------------------------------------------