From 20b7b151924dc2aa32498bf8116674b210057a66 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Tue, 5 Dec 2017 19:46:45 -0600 Subject: [PATCH 01/87] Allow payment to be sent in several "chunks" Addresses issue #31 --- monero/include/monero_payments.php | 43 +++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index 8367d1b..9b37829 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -394,7 +394,21 @@ class Monero_Gateway extends WC_Payment_Gateway return $price; } } - + + private function on_verified($payment_id, $amount_atomic_units, $order_id) + { + $message = "Payment has been received and confirmed. Thanks!"; + $this->log->add('Monero_gateway', '[SUCCESS] Payment has been recorded. Congratulations!'); + $this->confirmed = true; + $order = wc_get_order($order_id); + $order->update_status('completed', __('Payment has been received', 'monero_gateway')); + global $wpdb; + $wpdb->query("DROP TABLE $payment_id"); // Drop the table from database after payment has been confirmed as it is no longer needed + + $this->reloadTime = 3000000000000; // Greatly increase the reload time as it is no longer needed + return $message; + } + public function verify_payment($payment_id, $amount, $order_id) { /* @@ -405,16 +419,25 @@ class Monero_Gateway extends WC_Payment_Gateway $amount_atomic_units = $amount * 1000000000000; $get_payments_method = $this->monero_daemon->get_payments($payment_id); if (isset($get_payments_method["payments"][0]["amount"])) { - if ($get_payments_method["payments"][0]["amount"] >= $amount_atomic_units) { - $message = "Payment has been received and confirmed. Thanks!"; - $this->log->add('Monero_gateway', '[SUCCESS] Payment has been recorded. Congratulations!'); - $this->confirmed = true; - $order = wc_get_order($order_id); - $order->update_status('completed', __('Payment has been received', 'monero_gateway')); - global $wpdb; - $wpdb->query("DROP TABLE $payment_id"); // Drop the table from database after payment has been confirmed as it is no longer needed + if ($get_payments_method["payments"][0]["amount"] >= $amount_atomic_units) + { + $message = $this->on_verified($payment_id, $amount_atomic_units, $order_id); + } + if ($get_payments_method["payments"][0]["amount"] < $amount_atomic_units) + { + $totalPayed = $get_payments_method["payments"][0]["amount"]; + $outputs_count = count($get_payments_method["payments"]); // number of outputs recieved with this payment id + $output_counter = 1; - $this->reloadTime = 3000000000000; // Greatly increase the reload time as it is no longer needed + while($output_counter < $outputs_count) + { + $totalPayed += $get_payments_method["payments"][$output_counter]["amount"]; + $output_counter++; + } + if($totalPayed >= $amount_atomic_units) + { + $message = $this->on_verified($payment_id, $amount_atomic_units, $order_id); + } } } return $message; From c453febd7922b1153ac80a8f17a73a65e60574df Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Wed, 27 Dec 2017 18:30:34 -0600 Subject: [PATCH 02/87] Switch to instruction for view-only wallet A view only wallet is easier and more secure than a password for the real wallet --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ad731af..42d41d4 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,9 @@ The easiest way to find a remote node to connect to is to visit [moneroworld.com * Setup a monero wallet using the monero-wallet-cli tool. If you do not know how to do this you can learn about it at [getmonero.org](https://getmonero.org/resources/user-guides/monero-wallet-cli.html) -* Start the Wallet RPC and leave it running in the background. This can be accomplished by running `./monero-wallet-rpc --rpc-bind-port 18082 --rpc-login username:password --log-level 2 --wallet-file /path/walletfile` where "username:password" is the username and password that you want to use, seperated by a colon and "/path/walletfile" is your actual wallet file. If you wish to use a remote node you can add the `--daemon-address` flag followed by the address of the node. `--daemon-address node.moneroworld.com:18089` for example. +* Create a view-only wallet from that wallet for security. + +* Start the Wallet RPC and leave it running in the background. This can be accomplished by running `./monero-wallet-rpc --rpc-bind-port 18082 --disable-rpc-login --log-level 2 --wallet-file /path/viewOnlyWalletFile` where "/path/viewOnlyWalletFile" is the wallet file for your view-only wallet. If you wish to use a remote node you can add the `--daemon-address` flag followed by the address of the node. `--daemon-address node.moneroworld.com:18089` for example. ## Step 4: Setup Monero Gateway in WooCommerce @@ -58,8 +60,5 @@ The easiest way to find a remote node to connect to is to visit [moneroworld.com * Click on "Save changes" -## Info on server authentication -It is reccommended that you specify a username/password with your wallet rpc. This can be done by starting your wallet rpc with `monero-wallet-rpc --rpc-bind-port 18082 --rpc-login username:password --wallet-file /path/walletfile` where "username:password" is the username and password that you want to use, seperated by a colon. Alternatively, you can use the `--restricted-rpc` flag with the wallet rpc like so `./monero-wallet-rpc --testnet --rpc-bind-port 18082 --restricted-rpc --wallet-file wallet/path`. - -## Donating Me +## Donating to the Devs :) XMR Address : `44krVcL6TPkANjpFwS2GWvg1kJhTrN7y9heVeQiDJ3rP8iGbCd5GeA4f3c2NKYHC1R4mCgnW7dsUUUae2m9GiNBGT4T8s2X` From f561d4badf5e97ead636a028467f43edd356a702 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Wed, 27 Dec 2017 18:41:36 -0600 Subject: [PATCH 03/87] Remove password for view-only wallet A view-only wallet should be used instead as it is more secure and doesn't require a password to stay secure. --- monero/library.php | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/monero/library.php b/monero/library.php index 1b948fc..1e3d777 100644 --- a/monero/library.php +++ b/monero/library.php @@ -9,7 +9,6 @@ * @author Kacper Rowinski * http://implix.com * Modified to work with monero-rpc wallet by Serhack and cryptochangements - * This code isn't for Dark Net Markets, please report them to Authority! */ class Monero_Library { @@ -18,8 +17,8 @@ class Monero_Library CURLOPT_CONNECTTIMEOUT => 8, CURLOPT_TIMEOUT => 8 ); - private $username; - private $password; + protected $host; + protected $port; private $httpErrors = array( 400 => '400 Bad Request', 401 => '401 Unauthorized', @@ -33,14 +32,14 @@ class Monero_Library 503 => '503 Service Unavailable' ); - public function __construct($pUrl, $pUser, $pPass) + public function __construct($pHost, $pPort) { $this->validate(false === extension_loaded('curl'), 'The curl extension must be loaded to use this class!'); $this->validate(false === extension_loaded('json'), 'The json extension must be loaded to use this class!'); - - $this->url = $pUrl; - $this->username = $pUser; - $this->password = $pPass; + + $this->host = $pHost; + $this->port = $pPort; + $this->url = $pHost . ':' . $pPort . '/json_rpc'; } public function validate($pFailed, $pErrMsg) @@ -166,8 +165,6 @@ class Monero_Library throw new RuntimeException('Could\'t initialize a cURL session'); } curl_setopt($ch, CURLOPT_URL, $this->url); - curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); - curl_setopt($ch, CURLOPT_USERPWD, $this->username . ":" . $this->password); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $pRequest); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/json')); @@ -187,7 +184,7 @@ class Monero_Library } // check for curl error if (0 < curl_errno($ch)) { - echo 'Unable to connect to ' . $this->url . ' Error: ' . curl_error($ch); + echo '[ERROR] Failed to connect to monero-wallet-rpc at ' . $this->host . ' port '. $this->port .'
'; } // close the connection curl_close($ch); From 76602ebc3a53f54fa1f8d7e6175d55b257cafd15 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Wed, 27 Dec 2017 18:44:13 -0600 Subject: [PATCH 04/87] Updates Remove password fields, a view-only wallet should instead be used. Improve some of the text. --- monero/include/monero_payments.php | 55 ++++++++++++------------------ 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index 9b37829..e507a59 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -30,8 +30,6 @@ class Monero_Gateway extends WC_Payment_Gateway $this->host = $this->get_option('daemon_host'); $this->port = $this->get_option('daemon_port'); $this->address = $this->get_option('monero_address'); - $this->username = $this->get_option('username'); - $this->password = $this->get_option('password'); $this->discount = $this->get_option('discount'); // After init_settings() is called, you can get the settings and load them into variables, e.g: @@ -53,7 +51,7 @@ class Monero_Gateway extends WC_Payment_Gateway add_filter('woocommerce_currency_symbol', 'add_my_currency_symbol', 10, 2); add_action('woocommerce_email_before_order_table', array($this, 'email_instructions'), 10, 2); } - $this->monero_daemon = new Monero_Library($this->host . ':' . $this->port . '/json_rpc', $this->username, $this->password); + $this->monero_daemon = new Monero_Library($this->host, $this->port); } public function init_form_fields() @@ -97,21 +95,6 @@ class Monero_Gateway extends WC_Payment_Gateway 'desc_tip' => __('This is the Daemon Host/IP to authorize the payment with port', 'monero_gateway'), 'default' => '18080', ), - 'username' => array( - 'title' => __('Monero Wallet username', 'monero_gateway'), - 'desc_tip' => __('This is the username that you used with your monero wallet-rpc', 'monero_gateway'), - 'type' => __('text'), - 'default' => __('username', 'monero_gateway'), - - ), - 'password' => array( - 'title' => __('Monero wallet RPC password', 'monero_gateway'), - 'desc_tip' => __('This is the password that you used with your monero wallet-rpc', 'monero_gateway'), - 'description' => __('you can leave these fields empty if you did not set', 'monero_gateway'), - 'type' => __('text'), - 'default' => '' - - ), 'discount' => array( 'title' => __('% discount for using XMR', 'monero_gateway'), @@ -122,15 +105,15 @@ class Monero_Gateway extends WC_Payment_Gateway ), 'environment' => array( - 'title' => __(' Test Mode', 'monero_gateway'), - 'label' => __('Enable Test Mode', 'monero_gateway'), + 'title' => __(' Testnet', 'monero_gateway'), + 'label' => __(' Check this if you are using testnet ', 'monero_gateway'), 'type' => 'checkbox', 'description' => __('Check this box if you are using testnet', 'monero_gateway'), 'default' => 'no' ), 'onion_service' => array( - 'title' => __(' Onion Service', 'monero_gateway'), - 'label' => __('Enable Onion Service', 'monero_gateway'), + 'title' => __(' SSL warnings ', 'monero_gateway'), + 'label' => __(' Check to Silence SSL warnings', 'monero_gateway'), 'type' => 'checkbox', 'description' => __('Check this box if you are running on an Onion Service (Suppress SSL errors)', 'monero_gateway'), 'default' => 'no' @@ -174,18 +157,21 @@ class Monero_Gateway extends WC_Payment_Gateway { $wallet_amount = $this->monero_daemon->getbalance(); if (!isset($wallet_amount)) { - $this->log->add('Monero_gateway', '[ERROR] No connection with daemon'); - $wallet_amount['balance'] = "0"; - $wallet_amount['unlocked_balance'] = "0"; + $this->log->add('Monero_gateway', '[ERROR] Can not connect to monero-wallet-rpc'); + echo "
Your balance is: Not Avaliable
"; + echo "Unlocked balance: Not Avaliable"; } - $real_wallet_amount = $wallet_amount['balance'] / 1000000000000; - $real_amount_rounded = round($real_wallet_amount, 6); + else + { + $real_wallet_amount = $wallet_amount['balance'] / 1000000000000; + $real_amount_rounded = round($real_wallet_amount, 6); - $unlocked_wallet_amount = $wallet_amount['unlocked_balance'] / 1000000000000; - $unlocked_amount_rounded = round($unlocked_wallet_amount, 6); - - echo "Your balance is: " . $real_amount_rounded . " XMR
"; - echo "Unlocked balance: " . $unlocked_amount_rounded . " XMR
"; + $unlocked_wallet_amount = $wallet_amount['unlocked_balance'] / 1000000000000; + $unlocked_amount_rounded = round($unlocked_wallet_amount, 6); + + echo "Your balance is: " . $real_amount_rounded . " XMR
"; + echo "Unlocked balance: " . $unlocked_amount_rounded . " XMR
"; + } } public function process_payment($order_id) @@ -243,7 +229,7 @@ class Monero_Gateway extends WC_Payment_Gateway $uri = "monero:$address?amount=$amount?payment_id=$payment_id"; $array_integrated_address = $this->monero_daemon->make_integrated_address($payment_id); if (!isset($array_integrated_address)) { - $this->log->add('Monero_Gateway', '[ERROR] Unable to getting integrated address'); + $this->log->add('Monero_Gateway', '[ERROR] Unable get integrated address'); // Seems that we can't connect with daemon, then set array_integrated_address, little hack $array_integrated_address["integrated_address"] = $address; } @@ -344,7 +330,8 @@ class Monero_Gateway extends WC_Payment_Gateway $stored_rate_transformed = $stored_rate[0]->rate / 100; //this will turn the stored rate back into a decimaled number if (isset($this->discount)) { - $discount_decimal = $this->discount / 100; + $sanatized_discount = preg_replace('/[^0-9]/', '', $this->discount); + $discount_decimal = $sanatized_discount / 100; $new_amount = $amount / $stored_rate_transformed; $discount = $new_amount * $discount_decimal; $final_amount = $new_amount - $discount; From fbd51c059359d3b8004d7fead56f8cc5f16d6760 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Wed, 27 Dec 2017 22:45:56 -0600 Subject: [PATCH 05/87] Add view-only tutorial link Add a link to a tutorial for creating view-only wallets on the monero stackexchange --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 42d41d4..efefcb7 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ The easiest way to find a remote node to connect to is to visit [moneroworld.com * Setup a monero wallet using the monero-wallet-cli tool. If you do not know how to do this you can learn about it at [getmonero.org](https://getmonero.org/resources/user-guides/monero-wallet-cli.html) -* Create a view-only wallet from that wallet for security. +* [Create a view-only wallet from that wallet for security.](https://monero.stackexchange.com/questions/3178/how-to-create-a-view-only-wallet-for-the-gui/4582#4582) * Start the Wallet RPC and leave it running in the background. This can be accomplished by running `./monero-wallet-rpc --rpc-bind-port 18082 --disable-rpc-login --log-level 2 --wallet-file /path/viewOnlyWalletFile` where "/path/viewOnlyWalletFile" is the wallet file for your view-only wallet. If you wish to use a remote node you can add the `--daemon-address` flag followed by the address of the node. `--daemon-address node.moneroworld.com:18089` for example. From 1ca11855feb046b82d97acdfa33596acceb74e09 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Thu, 28 Dec 2017 09:22:55 -0600 Subject: [PATCH 06/87] Use different order statuses for virtual goods --- monero/include/monero_payments.php | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index e507a59..16dedb0 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -213,7 +213,22 @@ class Monero_Gateway extends WC_Payment_Gateway } return false; } - + + public function is_virtual_in_cart($order_id) + { + $order = wc_get_order( $order_id ); + $items = $order->get_items(); + + foreach ( $items as $item ) { + $product = new WC_Product( $item['product_id'] ); + if ( $product->is_virtual() ) { + return true; + } + } + + return false; + } + public function instruction($order_id) { $order = wc_get_order($order_id); @@ -233,6 +248,9 @@ class Monero_Gateway extends WC_Payment_Gateway // Seems that we can't connect with daemon, then set array_integrated_address, little hack $array_integrated_address["integrated_address"] = $address; } + if($this->is_virtual_in_cart($order_id) == true){ + echo "test"; + } $message = $this->verify_payment($payment_id, $amount_xmr2, $order); if ($this->confirmed) { $color = "006400"; @@ -240,6 +258,7 @@ class Monero_Gateway extends WC_Payment_Gateway $color = "DC143C"; } echo "

" . $message . "

"; + echo " @@ -388,7 +407,13 @@ class Monero_Gateway extends WC_Payment_Gateway $this->log->add('Monero_gateway', '[SUCCESS] Payment has been recorded. Congratulations!'); $this->confirmed = true; $order = wc_get_order($order_id); - $order->update_status('completed', __('Payment has been received', 'monero_gateway')); + + if($this->is_virtual_in_cart($order_id) == true){ + $order->update_status('completed', __('Payment has been received', 'monero_gateway')); + } + else{ + $order->update_status('processing', __('Payment has been received', 'monero_gateway')); + } global $wpdb; $wpdb->query("DROP TABLE $payment_id"); // Drop the table from database after payment has been confirmed as it is no longer needed From 09ae3868058b3a4979d3a633a9f38a465970f5f6 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Thu, 28 Dec 2017 15:58:57 -0600 Subject: [PATCH 07/87] Create readme.txt --- readme.txt | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 readme.txt diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..924ad08 --- /dev/null +++ b/readme.txt @@ -0,0 +1,70 @@ +=== Monero WooCommerce Extension === +Contributors: serhack +Donate link: http://monerointegrations.com/donate.html +Tags: monero, woocommerce, integration, payment, merchant, cryptocurrency, accept monero, monero woocommerce +Requires at least: 4.0 +Tested up to: 4.8 +Stable tag: trunk +License: GPLv2 or later +License URI: http://www.gnu.org/licenses/gpl-2.0.html + +Monero WooCommerce Extension is a Wordpress plugin that allows to accept bitcoins at WooCommerce-powered online stores. + +== Description == + +An extension to WooCommerce for accepting Monero as payment in your store. + += Benefits = + +* Accept payment directly into your personal Monero wallet. +* Accept payment in monero for physical and digital downloadable products. +* Add monero payments option to your existing online store with alternative main currency. +* Flexible exchange rate calculations fully managed via administrative settings. +* Zero fees and no commissions for monero payments processing from any third party. +* Automatic conversion to Monero via realtime exchange rate feed and calculations. +* Ability to set exchange rate calculation multiplier to compensate for any possible losses due to bank conversions and funds transfer fees. + +== Installation == + +1. Install "Monero WooCommerce extension" wordpress plugin just like any other Wordpress plugin. +2. Activate +3. Setup your monero-wallet-rpc with a view-only wallet +4. Add your monero-wallet-rpc host address and Monero address in the settings panel +5. Click “Enable this payment gateway” +6. Enjoy it! + +== Remove plugin == + +1. Deactivate plugin through the 'Plugins' menu in WordPress +2. Delete plugin through the 'Plugins' menu in WordPress + +== Screenshots == +1. Monero Payment Box +2. Monero Options + +== Changelog == + += 0.1 = +* First version ! Yay! + += 0.2 = +* Bug fixes + +== Upgrade Notice == + +soon + +== Frequently Asked Questions == + +* What is Monero ? +Monero is completely private, cryptographically secure, digital cash used across the globe. See https://getmonero.org for more information + +* What is a Monero wallet? +A Monero wallet is a piece of software that allows you to store your funds and interact with the Monero network. You can get a Monero wallet from https://getmonero.org/downloads + +* What is monero-wallet-rpc ? +The monero-wallet-rpc is an RPC server that will allow this plugin to communicate with the Monero network. You can download it from https://getmonero.org/downloads with the command-line tools. + +* Why do I see `[ERROR] Failed to connect to monero-wallet-rpc at localhost port 18080 +Syntax error: Invalid response data structure: Request id: 1 is different from Response id: ` ? +This is most likely because this plugin can not reach your monero-wallet-rpc. Make sure that you have supplied the correct host IP and port to the plugin in their fields. If your monero-wallet-rpc is on a different server than your wordpress site, make sure that the appropriate port is open with port forwarding enabled. From 05695f766d612f0625993f1b70bd8dfd3cf7b4ef Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Thu, 28 Dec 2017 22:51:37 -0600 Subject: [PATCH 08/87] Update 'learn more' link --- monero/include/monero_payments.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index 16dedb0..0f105a9 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -150,7 +150,7 @@ class Monero_Gateway extends WC_Payment_Gateway echo ""; $this->generate_settings_html(); echo "
"; - echo "

Learn more about using a password with the monero wallet-rpc here

"; + echo "

Learn more about using monero-wallet-rpc here

"; } public function getamountinfo() From 598b7085edc5adc1f07895697b12529f2e61797e Mon Sep 17 00:00:00 2001 From: serhack <27734319+serhack@users.noreply.github.com> Date: Tue, 2 Jan 2018 11:31:07 +0100 Subject: [PATCH 09/87] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index efefcb7..dd424c1 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ This Monero plugin is an extension of WooCommerce, which works with WordPress ### Option 1: Running a full node yourself -To do this: start the monero daemon on your server and leave it running in the background. This can be accomplished by running `./monerod` inside your monero downloads folder. The first time that you start your node, the monero daemon will download and sync the entire monero blockchain. This can take several hours and is best done on a machine with at least 4GB of ram, an SSD hard drive (with at least 15GB of free space), and a high speed internet connection. +To do this: start the monero daemon on your server and leave it running in the background. This can be accomplished by running `./monerod` inside your monero downloads folder. The first time that you start your node, the monero daemon will download and sync the entire monero blockchain. This can take several hours and is best done on a machine with at least 4GB of ram, an SSD hard drive (with at least 40GB of free space), and a high speed internet connection. ### Option 2: Connecting to a remote node The easiest way to find a remote node to connect to is to visit [moneroworld.com](https://moneroworld.com/#nodes) and use one of the nodes offered. It is probably easiest to use node.moneroworld.com:18089 which will automatically connect you to a random node. From 9f1fb367e2b7ddfaed1e2087ad02ed586920cd1a Mon Sep 17 00:00:00 2001 From: serhack <27734319+serhack@users.noreply.github.com> Date: Sat, 6 Jan 2018 20:22:00 +0100 Subject: [PATCH 10/87] Update monero_payments.php --- monero/include/monero_payments.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index 0f105a9..3ba2c22 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -379,7 +379,7 @@ class Monero_Gateway extends WC_Payment_Gateway public function retriveprice($currency) { - $xmr_price = file_get_contents('https://min-api.cryptocompare.com/data/price?fsym=XMR&tsyms=BTC,USD,EUR,CAD,INR,GBP&extraParams=monero_woocommerce'); + $xmr_price = file_get_contents('https://min-api.cryptocompare.com/data/price?fsym=XMR&tsyms=BTC,USD,EUR,CAD,INR,GBP,COP&extraParams=monero_woocommerce'); $price = json_decode($xmr_price, TRUE); if (!isset($price)) { $this->log->add('Monero_Gateway', '[ERROR] Unable to get the price of Monero'); @@ -395,6 +395,8 @@ class Monero_Gateway extends WC_Payment_Gateway return $price['GBP']; case 'INR': return $price['INR']; + case 'COP': + return $price['COP']; case 'XMR': $price = '1'; return $price; From f4822f237ee547b5f7cf15597d84e5443e1e61c1 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Tue, 23 Jan 2018 16:36:08 -0600 Subject: [PATCH 11/87] Add block explorer tools --- monero/library.php | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/monero/library.php b/monero/library.php index 1e3d777..774a6a1 100644 --- a/monero/library.php +++ b/monero/library.php @@ -319,3 +319,62 @@ class Monero_Library return $get_bulk_payments; } } + +class NodeTools +{ + public function get_last_block_height() + { + $curl = curl_init(); + + curl_setopt_array($curl, array( + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_URL => 'https://xmrchain.net/api/networkinfo', + )); + $resp = curl_exec($curl); + curl_close($curl); + + $array = json_decode($resp, true); + return $array['data']['height'] - 1; + } + + public function get_txs_from_block($height) + { + $curl = curl_init(); + + curl_setopt_array($curl, array( + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_URL => 'https://xmrchain.net/api/search/' . $height, + )); + $resp = curl_exec($curl); + curl_close($curl); + + $array = json_decode($resp, true); + + return $array['data']['txs']; + } + + public function check_tx($tx_hash, $address, $viewKey) + { + $curl = curl_init(); + curl_setopt_array($curl, array( + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_URL => 'https://xmrchain.net/api/outputs?txhash=' .$tx_hash . '&address='. $address . '&viewkey='. $viewKey .'&txprove=0', + )); + $resp = curl_exec($curl); + curl_close($curl); + $array = json_decode($resp, true); + $output_count = count($array['data']['outputs']); + $i = 0; + while($i < $output_count) + { + if($array['data']['outputs'][$i]['match']) + { + return $array['data']['outputs'][$i]; + } + + $i++; + } + + } + +} From 5da38e31caaa814f17c09b406da1223aa820915e Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Tue, 23 Jan 2018 16:49:41 -0600 Subject: [PATCH 12/87] Allow Transaction Verification w/o wallet-rpc Use a block explorer API to verify transactions without monero-wallet-rpc --- monero/include/monero_payments.php | 351 +++++++++++++++++++++-------- 1 file changed, 263 insertions(+), 88 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index 0f105a9..cc4a55c 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -12,6 +12,7 @@ class Monero_Gateway extends WC_Payment_Gateway private $discount; private $confirmed = false; private $monero_daemon; + private $non_rpc = false; function __construct() { @@ -30,7 +31,20 @@ class Monero_Gateway extends WC_Payment_Gateway $this->host = $this->get_option('daemon_host'); $this->port = $this->get_option('daemon_port'); $this->address = $this->get_option('monero_address'); + $this->viewKey = $this->get_option('viewKey'); $this->discount = $this->get_option('discount'); + + $this->use_viewKey = $this->get_option('use_viewKey'); + $this->use_rpc = $this->get_option('use_rpc'); + + if($this->use_viewKey == 'yes') + { + $this->non_rpc = true; + } + if($this->use_rpc == 'yes') + { + $this->non_rpc = false; + } // After init_settings() is called, you can get the settings and load them into variables, e.g: // $this->title = $this->get_option('title' ); @@ -77,12 +91,32 @@ class Monero_Gateway extends WC_Payment_Gateway 'default' => __('Pay securely using XMR.', 'monero_gateway') ), + 'use_viewKey' => array( + 'title' => __('Use ViewKey', 'monero_gateway'), + 'label' => __(' Verify Transaction with ViewKey ', 'monero_gateway'), + 'type' => 'checkbox', + 'description' => __('Fill in the Address and ViewKey fields to verify transactions with your ViewKey', 'monero_gateway'), + 'default' => 'no' + ), 'monero_address' => array( 'title' => __('Monero Address', 'monero_gateway'), 'label' => __('Useful for people that have not a daemon online'), 'type' => 'text', 'desc_tip' => __('Monero Wallet Address', 'monero_gateway') ), + 'viewKey' => array( + 'title' => __('Secret ViewKey', 'monero_gateway'), + 'label' => __('Secret ViewKey'), + 'type' => 'text', + 'desc_tip' => __('Your secret ViewKey', 'monero_gateway') + ), + 'use_rpc' => array( + 'title' => __('Use monero-wallet-rpc', 'monero_gateway'), + 'label' => __(' Verify transactions with the monero-wallet-rpc ', 'monero_gateway'), + 'type' => 'checkbox', + 'description' => __('This must be setup seperatly', 'monero_gateway'), + 'default' => 'no' + ), 'daemon_host' => array( 'title' => __('Monero wallet rpc Host/ IP', 'monero_gateway'), 'type' => 'text', @@ -100,8 +134,8 @@ class Monero_Gateway extends WC_Payment_Gateway 'desc_tip' => __('Provide a discount to your customers for making a private payment with XMR!', 'monero_gateway'), 'description' => __('Do you want to spread the word about Monero? Offer a small discount! Leave this empty if you do not wish to provide a discount', 'monero_gateway'), - 'type' => __('text'), - 'default' => '5%' + 'type' => __('number'), + 'default' => '5' ), 'environment' => array( @@ -140,17 +174,19 @@ class Monero_Gateway extends WC_Payment_Gateway public function admin_options() { $this->log->add('Monero_gateway', '[SUCCESS] Monero Settings OK'); - echo "

Monero Payment Gateway

"; echo "

Welcome to Monero Extension for WooCommerce. Getting started: Make a connection with daemon Contact Me"; echo "

"; - $this->getamountinfo(); + + if(!$this->non_rpc) // only try to get balance data if using wallet-rpc + $this->getamountinfo(); + echo "
"; echo ""; $this->generate_settings_html(); echo "
"; - echo "

Learn more about using monero-wallet-rpc here

"; + echo "

Learn more about using monero-wallet-rpc here and viewkeys here

"; } public function getamountinfo() @@ -197,7 +233,15 @@ class Monero_Gateway extends WC_Payment_Gateway public function validate_fields() { if ($this->check_monero() != TRUE) { - echo "

Your Monero Address doesn't seem valid. Have you checked it?

"; + echo "

Your Monero Address doesn't look valid. Have you checked it?

"; + } + if(!$this->check_viewKey()) + { + echo "

Your ViewKey doesn't look valid. Have you checked it?

"; + } + if($this->check_checkedBoxes()) + { + echo "

You must choose to either use monero-wallet-rpc or a ViewKey, not both

"; } } @@ -213,6 +257,29 @@ class Monero_Gateway extends WC_Payment_Gateway } return false; } + public function check_viewKey() + { + if($this->use_viewKey == 'yes') + { + if (strlen($this->viewKey) == 64) { + return true; + } + return false; + } + return true; + } + public function check_checkedBoxes() + { + if($this->use_viewKey == 'yes') + { + if($this->use_rpc == 'yes') + { + return true; + } + } + else + return false; + } public function is_virtual_in_cart($order_id) { @@ -231,93 +298,164 @@ class Monero_Gateway extends WC_Payment_Gateway public function instruction($order_id) { - $order = wc_get_order($order_id); - $amount = floatval(preg_replace('#[^\d.]#', '', $order->get_total())); - $payment_id = $this->set_paymentid_cookie(); - $currency = $order->get_currency(); - $amount_xmr2 = $this->changeto($amount, $currency, $payment_id); - $address = $this->address; - if (!isset($address)) { - // If there isn't address (merchant missed that field!), $address will be the Monero address for donating :) - $address = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; + if($this->non_rpc) + { + $order = wc_get_order($order_id); + $amount = floatval(preg_replace('#[^\d.]#', '', $order->get_total())); + $payment_id = $this->set_paymentid_cookie(32); + $currency = $order->get_currency(); + $amount_xmr2 = $this->changeto($amount, $currency, $payment_id); + $address = $this->address; + if (!isset($address)) { + // If there isn't address (merchant missed that field!), $address will be the Monero address for donating :) + $address = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; + } + $uri = "monero:$address?amount=$amount?payment_id=$payment_id"; + + if($this->verify_non_rpc($payment_id, $amount_xmr2, $order_id) == false); + { + echo "

We are waiting for your transaction to be confirmed

"; + } + + echo " + + + + + + + + + + +
+ +
+ +
+ +

MONERO PAYMENT

+
+ + +
+
+ Send: +
".$amount_xmr2."
+ Payment ID: +
".$payment_id."
+
+
+ To this address: +
".$address."
+
+
+ Or scan QR: +
+
+
+
+ + + + +
+ +
+ + + "; + + echo " + "; } - $uri = "monero:$address?amount=$amount?payment_id=$payment_id"; - $array_integrated_address = $this->monero_daemon->make_integrated_address($payment_id); - if (!isset($array_integrated_address)) { - $this->log->add('Monero_Gateway', '[ERROR] Unable get integrated address'); - // Seems that we can't connect with daemon, then set array_integrated_address, little hack - $array_integrated_address["integrated_address"] = $address; - } - if($this->is_virtual_in_cart($order_id) == true){ - echo "test"; - } - $message = $this->verify_payment($payment_id, $amount_xmr2, $order); - if ($this->confirmed) { - $color = "006400"; - } else { - $color = "DC143C"; - } - echo "

" . $message . "

"; + else + { + $order = wc_get_order($order_id); + $amount = floatval(preg_replace('#[^\d.]#', '', $order->get_total())); + $payment_id = $this->set_paymentid_cookie(8); + $currency = $order->get_currency(); + $amount_xmr2 = $this->changeto($amount, $currency, $payment_id); + $address = $this->address; + if (!isset($address)) { + // If there isn't address (merchant missed that field!), $address will be the Monero address for donating :) + $address = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; + } + $uri = "monero:$address?amount=$amount?payment_id=$payment_id"; + $array_integrated_address = $this->monero_daemon->make_integrated_address($payment_id); + if (!isset($array_integrated_address)) { + $this->log->add('Monero_Gateway', '[ERROR] Unable get integrated address'); + // Seems that we can't connect with daemon, then set array_integrated_address, little hack + $array_integrated_address["integrated_address"] = $address; + } + $message = $this->verify_payment($payment_id, $amount_xmr2, $order); + if ($this->confirmed) { + $color = "006400"; + } else { + $color = "DC143C"; + } + echo "

" . $message . "

"; - echo " - - - - - - - - - - -
- -
- -
- -

MONERO PAYMENT

-
- - -
-
- Send: -
".$amount_xmr2."
XMR
-
-
- To this address: -
".$array_integrated_address['integrated_address']."
-
-
- Or scan QR: -
-
-
-
- - - - -
- -
- - - "; - - - - echo " - "; + echo " + + + + + + + + + + +
+ +
+ +
+ +

MONERO PAYMENT

+
+ + +
+
+ Send: +
".$amount_xmr2."
+
+
+ To this address: +
".$array_integrated_address['integrated_address']."
+
+
+ Or scan QR: +
+
+
+
+ + + + +
+ +
+ + + "; + + echo " + "; + } } - private function set_paymentid_cookie() + private function set_paymentid_cookie($size) { if (!isset($_COOKIE['payment_id'])) { - $payment_id = bin2hex(openssl_random_pseudo_bytes(8)); + $payment_id = bin2hex(openssl_random_pseudo_bytes($size)); setcookie('payment_id', $payment_id, time() + 2700); } else{ @@ -379,7 +517,7 @@ class Monero_Gateway extends WC_Payment_Gateway public function retriveprice($currency) { - $xmr_price = file_get_contents('https://min-api.cryptocompare.com/data/price?fsym=XMR&tsyms=BTC,USD,EUR,CAD,INR,GBP&extraParams=monero_woocommerce'); + $xmr_price = file_get_contents('https://min-api.cryptocompare.com/data/price?fsym=XMR&tsyms=BTC,USD,EUR,CAD,INR,GBP,COP&extraParams=monero_woocommerce'); $price = json_decode($xmr_price, TRUE); if (!isset($price)) { $this->log->add('Monero_Gateway', '[ERROR] Unable to get the price of Monero'); @@ -395,6 +533,8 @@ class Monero_Gateway extends WC_Payment_Gateway return $price['GBP']; case 'INR': return $price['INR']; + case 'COP': + return $price['COP']; case 'XMR': $price = '1'; return $price; @@ -454,6 +594,41 @@ class Monero_Gateway extends WC_Payment_Gateway } return $message; } + + public function verify_non_rpc($payment_id, $amount, $order_id) + { + $tools = new NodeTools(); + $bc_height = $tools->get_last_block_height(); + $txs_from_block = $tools->get_txs_from_block($bc_height); + $tx_count = count($txs_from_block) - 1; // The tx at index 0 is a coinbase tx so it can be ignored + + $i = 1; + $output_found; + $block_index; + while($i <= $tx_count) + { + $tx_hash = $txs_from_block[$i]['tx_hash']; + $result = $tools->check_tx($tx_hash, $this->address, $this->viewKey); + if($result) + { + $output_found = $result; + $block_index = $i; + $i = $tx_count; // finish loop + } + $i++; + } + if(isset($output_found)) + { + $amount_atomic_units = $amount * 1000000000000; + if($txs_from_block[$block_index]['payment_id'] == $payment_id && $output_found >= $amount) + { + $this->on_verified($payment_id, $amount_atomic_units, $order_id); + } + + return true; + } + return false; + } public function do_ssl_check() { From 590779c263d7a98496974ab4d3035358f2241580 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Tue, 23 Jan 2018 21:47:49 -0600 Subject: [PATCH 13/87] Check if payment id is set first Check that a payment id is set before verifying that the merchant owns that output instead of just checking every output in a block --- monero/include/monero_payments.php | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index cc4a55c..352caa1 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -312,10 +312,15 @@ class Monero_Gateway extends WC_Payment_Gateway } $uri = "monero:$address?amount=$amount?payment_id=$payment_id"; - if($this->verify_non_rpc($payment_id, $amount_xmr2, $order_id) == false); + $this->verify_non_rpc($payment_id, $amount_xmr2, $order_id); + if($this->confirmed == false) { echo "

We are waiting for your transaction to be confirmed

"; - } + } + if($this->confirmed) + { + echo "

Your transaction has been successfully confirmed!

"; + } echo " @@ -608,12 +613,15 @@ class Monero_Gateway extends WC_Payment_Gateway while($i <= $tx_count) { $tx_hash = $txs_from_block[$i]['tx_hash']; - $result = $tools->check_tx($tx_hash, $this->address, $this->viewKey); - if($result) + if(strlen($txs_from_block[$i]['payment_id']) != 0) { - $output_found = $result; - $block_index = $i; - $i = $tx_count; // finish loop + $result = $tools->check_tx($tx_hash, $this->address, $this->viewKey); + if($result) + { + $output_found = $result; + $block_index = $i; + $i = $tx_count; // finish loop + } } $i++; } From 3e9b4acd4867bb6515a152f11507a739fea421f7 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Wed, 24 Jan 2018 16:35:49 -0600 Subject: [PATCH 14/87] add get_mempool_txs --- monero/library.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/monero/library.php b/monero/library.php index 774a6a1..a859f64 100644 --- a/monero/library.php +++ b/monero/library.php @@ -377,4 +377,18 @@ class NodeTools } + function get_mempool_txs() + { + $curl = curl_init(); + + curl_setopt_array($curl, array( + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_URL => 'https://xmrchain.net/api/mempool', + )); + $resp = curl_exec($curl); + curl_close($curl); + $array = json_decode($resp, true); + return $array; + } + } From fa49096c1531419483de9be6468a85a7b8508838 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Wed, 24 Jan 2018 16:37:33 -0600 Subject: [PATCH 15/87] add optional 0-conf txs Add the option to search the mempool for unconfirmed transactions for quicker payments --- monero/include/monero_payments.php | 59 ++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index 352caa1..ce8343f 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -13,6 +13,7 @@ class Monero_Gateway extends WC_Payment_Gateway private $confirmed = false; private $monero_daemon; private $non_rpc = false; + private $zero_cofirm = false; function __construct() { @@ -33,6 +34,7 @@ class Monero_Gateway extends WC_Payment_Gateway $this->address = $this->get_option('monero_address'); $this->viewKey = $this->get_option('viewKey'); $this->discount = $this->get_option('discount'); + $this->accept_zero_conf = $this->get_option('zero_conf'); $this->use_viewKey = $this->get_option('use_viewKey'); $this->use_rpc = $this->get_option('use_rpc'); @@ -45,7 +47,10 @@ class Monero_Gateway extends WC_Payment_Gateway { $this->non_rpc = false; } - + if($this->accept_zero_conf == 'yes') + { + $this->zero_confirm = true; + } // After init_settings() is called, you can get the settings and load them into variables, e.g: // $this->title = $this->get_option('title' ); $this->init_settings(); @@ -145,6 +150,13 @@ class Monero_Gateway extends WC_Payment_Gateway 'description' => __('Check this box if you are using testnet', 'monero_gateway'), 'default' => 'no' ), + 'zero_conf' => array( + 'title' => __(' Accept 0 conf txs', 'monero_gateway'), + 'label' => __(' Accept 0-confirmation transactions ', 'monero_gateway'), + 'type' => 'checkbox', + 'description' => __('This is faster but less secure', 'monero_gateway'), + 'default' => 'no' + ), 'onion_service' => array( 'title' => __(' SSL warnings ', 'monero_gateway'), 'label' => __(' Check to Silence SSL warnings', 'monero_gateway'), @@ -312,7 +324,12 @@ class Monero_Gateway extends WC_Payment_Gateway } $uri = "monero:$address?amount=$amount?payment_id=$payment_id"; - $this->verify_non_rpc($payment_id, $amount_xmr2, $order_id); + if($this->zero_confirm){ + $this->verify_zero_conf($payment_id, $amount, $order_id); + } + else{ + $this->verify_non_rpc($payment_id, $amount_xmr2, $order_id); + } if($this->confirmed == false) { echo "

We are waiting for your transaction to be confirmed

"; @@ -628,7 +645,7 @@ class Monero_Gateway extends WC_Payment_Gateway if(isset($output_found)) { $amount_atomic_units = $amount * 1000000000000; - if($txs_from_block[$block_index]['payment_id'] == $payment_id && $output_found >= $amount) + if($txs_from_block[$block_index]['payment_id'] == $payment_id && $output_found['amount'] >= $amount_atomic_units) { $this->on_verified($payment_id, $amount_atomic_units, $order_id); } @@ -637,6 +654,42 @@ class Monero_Gateway extends WC_Payment_Gateway } return false; } + + public function verify_zero_conf($payment_id, $amount, $order_id) + { + $tools = new NodeTools(); + $txs_from_mempool = $tools->get_mempool_txs();; + $tx_count = count($txs_from_mempool['data']['txs']); + $i = 0; + $output_found; + + while($i <= $tx_count) + { + $tx_hash = $txs_from_mempool['data']['txs'][$i]['tx_hash']; + if(strlen($txs_from_mempool['data']['txs'][$i]['payment_id']) != 0) + { + $result = $tools->check_tx($tx_hash, $this->address, $this->viewKey); + if($result) + { + $output_found = $result; + $tx_i = $i; + $i = $tx_count; // finish loop + } + } + $i++; + } + if(isset($output_found)) + { + $amount_atomic_units = $amount * 1000000000000; + if($txs_from_mempool['data']['txs'][$tx_i]['payment_id'] == $payment_id && $output_found['amount'] >= $amount_atomic_units) + { + $this->on_verified($payment_id, $amount_atomic_units, $order_id); + } + return true; + } + else + return false; + } public function do_ssl_check() { From 7259659dad0e7dddf0de69b3ca83772c87a00a1a Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Wed, 24 Jan 2018 16:44:08 -0600 Subject: [PATCH 16/87] font colors --- monero/include/monero_payments.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index ce8343f..24a089c 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -332,11 +332,11 @@ class Monero_Gateway extends WC_Payment_Gateway } if($this->confirmed == false) { - echo "

We are waiting for your transaction to be confirmed

"; + echo "

We are waiting for your transaction to be confirmed

"; } if($this->confirmed) { - echo "

Your transaction has been successfully confirmed!

"; + echo "

Your transaction has been successfully confirmed!

"; } echo " From dcbc682660fe54a069ca18efa942ee6128cb9738 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Wed, 24 Jan 2018 19:55:41 -0500 Subject: [PATCH 17/87] Update README.md --- README.md | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dd424c1..edc0f26 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This plugin is rather simple but there are a few things that need to be set up b * A web server! Ideally with the most recent versions of PHP and mysql -* The Monero wallet-cli and Monero wallet-rpc tools found [here](https://getmonero.org/downloads/) +* A Monero wallet. You can find the official wallet [here](https://getmonero.org/downloads/) * [WordPress](https://wordpress.org) Wordpress is the backend tool that is needed to use WooCommerce and this Monero plugin @@ -23,7 +23,14 @@ This Monero plugin is an extension of WooCommerce, which works with WordPress * Activate the plugin from the WordPress admin panel: Once you login to the admin panel in WordPress, click on "Installed Plugins" under "Plugins". Then simply click "Activate" where it says "Monero - WooCommerce Gateway" -## Step 2: Get a monero daemon to connect to +## Step 2 Option 1: Use your wallet address and viewkey + +* Get your Monero wallet address starting with '4' +* Get your wallet secret viewkey from your wallet + +A note on privacy: When you validate transactions with your private viewkey, your viewkey is sent to (but not stored on) xmrchain.net over HTTPS. This could potentally allow an attacker to see your incoming, but not outgoing, transactions if he were to get his hands on your viewkey. Even if this were to happen, your funds would still be safe and it would be impossible for somebody to steal your money. For maximum privacy use your own monero-wallet-rpc instance. + +## Step 2 Option 2: Get a monero daemon to connect to ### Option 1: Running a full node yourself @@ -32,7 +39,7 @@ To do this: start the monero daemon on your server and leave it running in the b ### Option 2: Connecting to a remote node The easiest way to find a remote node to connect to is to visit [moneroworld.com](https://moneroworld.com/#nodes) and use one of the nodes offered. It is probably easiest to use node.moneroworld.com:18089 which will automatically connect you to a random node. -## Step 3: Setup your monero wallet-rpc +### Setup your monero wallet-rpc * Setup a monero wallet using the monero-wallet-cli tool. If you do not know how to do this you can learn about it at [getmonero.org](https://getmonero.org/resources/user-guides/monero-wallet-cli.html) @@ -50,13 +57,23 @@ The easiest way to find a remote node to connect to is to visit [moneroworld.com * Check the box labeled "Enable this payment gateway" +* Check either "Use ViewKey" or "Use monero-wallet-rpc" + +If You chose to use viewkey: + +* Enter your monero wallet address in the box labled "Monero Address". If you do not know your address, you can run the `address` commmand in your monero wallet + +* Enter your secret viewkey in the box labeled "ViewKey" + +If you chose to use monero-wallet-rpc: + * Enter your monero wallet address in the box labled "Monero Address". If you do not know your address, you can run the `address` commmand in your monero wallet * Enter the IP address of your server in the box labeled "Monero wallet rpc Host/IP" * Enter the port number of the Wallet RPC in the box labeled "Monero wallet rpc port" (will be `18082` if you used the above example). -* Enter the username and password that you want to use in their respective feilds +Finally: * Click on "Save changes" From 27fa98b31f7bfa6ee5d45cf943001da06ec2926f Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Wed, 24 Jan 2018 20:00:49 -0500 Subject: [PATCH 18/87] Update version --- monero/include/monero_payments.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index 24a089c..7942e57 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -21,7 +21,7 @@ class Monero_Gateway extends WC_Payment_Gateway $this->method_title = __("Monero GateWay", 'monero_gateway'); $this->method_description = __("Monero Payment Gateway Plug-in for WooCommerce. You can find more information about this payment gateway on our website. You'll need a daemon online for your address.", 'monero_gateway'); $this->title = __("Monero Gateway", 'monero_gateway'); - $this->version = "0.3"; + $this->version = "2.0"; // $this->icon = apply_filters('woocommerce_offline_icon', ''); $this->has_fields = false; From 5cb6d1756b725b6c0f9ca85c3bda1fe85af6f748 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Wed, 24 Jan 2018 20:01:12 -0500 Subject: [PATCH 19/87] Update version --- monero/monero_gateway.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monero/monero_gateway.php b/monero/monero_gateway.php index a835ff2..8d1972d 100644 --- a/monero/monero_gateway.php +++ b/monero/monero_gateway.php @@ -3,7 +3,7 @@ Plugin Name: Monero - WooCommerce Gateway Plugin URI: http://monerointegrations.com Description: Extends WooCommerce by Adding the Monero Gateway -Version: 1.0 +Version: 2.0 Author: SerHack Author URI: http://monerointegrations.com */ From 33a982107f68ddd9af14ea4d4bd74bbc66d6ea48 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Tue, 30 Jan 2018 19:32:19 -0600 Subject: [PATCH 20/87] Fix bug with discount --- monero/include/monero_payments.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index 7942e57..d4e9dc4 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -523,11 +523,20 @@ class Monero_Gateway extends WC_Payment_Gateway { $xmr_live_price = $this->retriveprice($currency); $live_for_storing = $xmr_live_price * 100; //This will remove the decimal so that it can easily be stored as an integer - $new_amount = $amount / $xmr_live_price; - $rounded_amount = round($new_amount, 12); - $wpdb->query("INSERT INTO $payment_id (rate) - VALUES ($live_for_storing)"); + $wpdb->query("INSERT INTO $payment_id (rate) VALUES ($live_for_storing)"); + if(isset($this->discount)) + { + $new_amount = $amount / $xmr_live_price; + $discount = $new_amount * $this->discount / 100; + $discounted_price = $new_amount - $discount; + $rounded_amount = round($discounted_price, 12); + } + else + { + $new_amount = $amount / $xmr_live_price; + $rounded_amount = round($new_amount, 12); + } } return $rounded_amount; From 19a2b2cfe167c3fba7157cfb57750e6043a4510c Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Tue, 30 Jan 2018 19:35:14 -0600 Subject: [PATCH 21/87] Only use integrated address w RPC --- monero/include/monero_payments.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index d4e9dc4..d3a1a1f 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -400,11 +400,7 @@ class Monero_Gateway extends WC_Payment_Gateway $payment_id = $this->set_paymentid_cookie(8); $currency = $order->get_currency(); $amount_xmr2 = $this->changeto($amount, $currency, $payment_id); - $address = $this->address; - if (!isset($address)) { - // If there isn't address (merchant missed that field!), $address will be the Monero address for donating :) - $address = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; - } + $uri = "monero:$address?amount=$amount?payment_id=$payment_id"; $array_integrated_address = $this->monero_daemon->make_integrated_address($payment_id); if (!isset($array_integrated_address)) { From acd3ca568c24ca6617a21073e46e3fadb5845660 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Tue, 30 Jan 2018 19:38:40 -0600 Subject: [PATCH 22/87] fix payment id with QR codes should be tx_payment_id not payment_id --- monero/include/monero_payments.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index d3a1a1f..43e73a1 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -322,7 +322,7 @@ class Monero_Gateway extends WC_Payment_Gateway // If there isn't address (merchant missed that field!), $address will be the Monero address for donating :) $address = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; } - $uri = "monero:$address?amount=$amount?payment_id=$payment_id"; + $uri = "monero:$address?tx_payment_id=$payment_id"; if($this->zero_confirm){ $this->verify_zero_conf($payment_id, $amount, $order_id); @@ -401,7 +401,7 @@ class Monero_Gateway extends WC_Payment_Gateway $currency = $order->get_currency(); $amount_xmr2 = $this->changeto($amount, $currency, $payment_id); - $uri = "monero:$address?amount=$amount?payment_id=$payment_id"; + $uri = "monero:$address?tx_payment_id=$payment_id"; $array_integrated_address = $this->monero_daemon->make_integrated_address($payment_id); if (!isset($array_integrated_address)) { $this->log->add('Monero_Gateway', '[ERROR] Unable get integrated address'); From 460f4f6c75c8be1808c7a6134412f48d836658ca Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Fri, 2 Feb 2018 17:11:23 -0600 Subject: [PATCH 23/87] Add support for SGD Add support for converting Singapore Dollar (SGD) to Monero (XMR) --- monero/include/monero_payments.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index 43e73a1..0b870b4 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -544,7 +544,7 @@ class Monero_Gateway extends WC_Payment_Gateway public function retriveprice($currency) { - $xmr_price = file_get_contents('https://min-api.cryptocompare.com/data/price?fsym=XMR&tsyms=BTC,USD,EUR,CAD,INR,GBP,COP&extraParams=monero_woocommerce'); + $xmr_price = file_get_contents('https://min-api.cryptocompare.com/data/price?fsym=XMR&tsyms=BTC,USD,EUR,CAD,INR,GBP,COP,SGD&extraParams=monero_woocommerce'); $price = json_decode($xmr_price, TRUE); if (!isset($price)) { $this->log->add('Monero_Gateway', '[ERROR] Unable to get the price of Monero'); @@ -560,8 +560,10 @@ class Monero_Gateway extends WC_Payment_Gateway return $price['GBP']; case 'INR': return $price['INR']; - case 'COP': - return $price['COP']; + case 'COP': + return $price['COP']; + case 'SGD': + return $price['SGD']; case 'XMR': $price = '1'; return $price; From 0b15e31672793fc5897e541f16563bcb42bc0516 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Mon, 5 Feb 2018 15:46:28 -0600 Subject: [PATCH 24/87] Dont miss blocks Set a cookie for the last seen block so that one is not missed in the case that two blocks are mined in a very short period of time. --- monero/include/monero_payments.php | 47 ++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index 0b870b4..aea1338 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -623,17 +623,57 @@ class Monero_Gateway extends WC_Payment_Gateway } return $message; } - + public function last_block_seen($height) // sometimes 2 blocks are mined within a few seconds of eacher. Make sure we don't miss one + { + if (!isset($_COOKIE['last_seen_block'])) + { + setcookie('last_seen_block', $height, time() + 2700); + return 0; + } + else{ + $cookie_block = $_COOKIE['last_seen_block']; + $difference = $height - $cookie_block; + setcookie('last_seen_block', $height, time() + 2700); + return $difference; + } + } public function verify_non_rpc($payment_id, $amount, $order_id) { $tools = new NodeTools(); $bc_height = $tools->get_last_block_height(); + + $block_difference = $this->last_block_seen($bc_height); + $txs_from_block = $tools->get_txs_from_block($bc_height); $tx_count = count($txs_from_block) - 1; // The tx at index 0 is a coinbase tx so it can be ignored - $i = 1; $output_found; $block_index; + + if($difference != 0) + { + $txs_from_block_2 = $tools->get_txs_from_block($bc_height - 1); + $tx_count_2 = count($txs_from_block_2) - 1; + + $i = 1; + while($i <= $tx_count_2) + { + $tx_hash = $txs_from_block_2[$i]['tx_hash']; + if(strlen($txs_from_block_2[$i]['payment_id']) != 0) + { + $result = $tools->check_tx($tx_hash, $this->address, $this->viewKey); + if($result) + { + $output_found = $result; + $block_index = $i; + $i = $tx_count_2; // finish loop + } + } + $i++; + } + } + + $i = 1; while($i <= $tx_count) { $tx_hash = $txs_from_block[$i]['tx_hash']; @@ -649,6 +689,7 @@ class Monero_Gateway extends WC_Payment_Gateway } $i++; } + if(isset($output_found)) { $amount_atomic_units = $amount * 1000000000000; @@ -661,7 +702,7 @@ class Monero_Gateway extends WC_Payment_Gateway } return false; } - + public function verify_zero_conf($payment_id, $amount, $order_id) { $tools = new NodeTools(); From b51952289958f37f766cce40d7705c626d107f03 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Mon, 5 Feb 2018 15:52:20 -0600 Subject: [PATCH 25/87] Fix typo --- monero/include/monero_payments.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index aea1338..f5cff4d 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -650,7 +650,7 @@ class Monero_Gateway extends WC_Payment_Gateway $output_found; $block_index; - if($difference != 0) + if($block_difference != 0) { $txs_from_block_2 = $tools->get_txs_from_block($bc_height - 1); $tx_count_2 = count($txs_from_block_2) - 1; From 309b141477f1ac4e69ee29e83b511d2a62499a79 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Sun, 11 Feb 2018 17:07:58 -0600 Subject: [PATCH 26/87] payment verification fixes --- monero/include/monero_payments.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index f5cff4d..b8c4fcb 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -8,7 +8,7 @@ class Monero_Gateway extends WC_Payment_Gateway { - private $reloadTime = 30000; + private $reloadTime = 17000; private $discount; private $confirmed = false; private $monero_daemon; @@ -325,7 +325,7 @@ class Monero_Gateway extends WC_Payment_Gateway $uri = "monero:$address?tx_payment_id=$payment_id"; if($this->zero_confirm){ - $this->verify_zero_conf($payment_id, $amount, $order_id); + $this->verify_zero_conf($payment_id, $amount_xmr2, $order_id); } else{ $this->verify_non_rpc($payment_id, $amount_xmr2, $order_id); @@ -693,10 +693,15 @@ class Monero_Gateway extends WC_Payment_Gateway if(isset($output_found)) { $amount_atomic_units = $amount * 1000000000000; + if($txs_from_block[$block_index]['payment_id'] == $payment_id && $output_found['amount'] >= $amount_atomic_units) { $this->on_verified($payment_id, $amount_atomic_units, $order_id); } + if($txs_from_block_2[$block_index]['payment_id'] == $payment_id && $output_found['amount'] >= $amount_atomic_units) + { + $this->on_verified($payment_id, $amount_atomic_units, $order_id); + } return true; } From ad1edf568cb2614e622c654fbb4116e29863319e Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Sun, 11 Feb 2018 17:13:00 -0600 Subject: [PATCH 27/87] make a little more verbose --- monero/include/monero_payments.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index b8c4fcb..7cefd4c 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -578,10 +578,10 @@ class Monero_Gateway extends WC_Payment_Gateway $order = wc_get_order($order_id); if($this->is_virtual_in_cart($order_id) == true){ - $order->update_status('completed', __('Payment has been received', 'monero_gateway')); + $order->update_status('completed', __('Payment has been received' . $payment_id, 'monero_gateway')); } else{ - $order->update_status('processing', __('Payment has been received', 'monero_gateway')); + $order->update_status('processing', __('Payment has been received' . $payment_id, 'monero_gateway')); // Show payment id used for order } global $wpdb; $wpdb->query("DROP TABLE $payment_id"); // Drop the table from database after payment has been confirmed as it is no longer needed @@ -652,6 +652,10 @@ class Monero_Gateway extends WC_Payment_Gateway if($block_difference != 0) { + if($block_difference > 2){ + $this->log->add('[WARNING] Block difference is greater than 2'); + } + $txs_from_block_2 = $tools->get_txs_from_block($bc_height - 1); $tx_count_2 = count($txs_from_block_2) - 1; From 39361a247b15807239c88751b76f8c464064c520 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Sun, 11 Feb 2018 17:30:32 -0600 Subject: [PATCH 28/87] text formatting --- monero/include/monero_payments.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index 7cefd4c..a4e30a3 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -578,10 +578,10 @@ class Monero_Gateway extends WC_Payment_Gateway $order = wc_get_order($order_id); if($this->is_virtual_in_cart($order_id) == true){ - $order->update_status('completed', __('Payment has been received' . $payment_id, 'monero_gateway')); + $order->update_status('completed', __('Payment has been received. Payment ID: ' . $payment_id, 'monero_gateway')); } else{ - $order->update_status('processing', __('Payment has been received' . $payment_id, 'monero_gateway')); // Show payment id used for order + $order->update_status('processing', __('Payment has been received. Payment ID: ' . $payment_id, 'monero_gateway')); // Show payment id used for order } global $wpdb; $wpdb->query("DROP TABLE $payment_id"); // Drop the table from database after payment has been confirmed as it is no longer needed From fc386057726ac3f90d83b0b3ee084e2285000895 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Sun, 18 Feb 2018 13:38:46 -0600 Subject: [PATCH 29/87] add warnings about Payment IDs and JS --- monero/include/monero_payments.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index a4e30a3..d733527 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -312,6 +312,7 @@ class Monero_Gateway extends WC_Payment_Gateway { if($this->non_rpc) { + echo ""; $order = wc_get_order($order_id); $amount = floatval(preg_replace('#[^\d.]#', '', $order->get_total())); $payment_id = $this->set_paymentid_cookie(32); @@ -341,6 +342,7 @@ class Monero_Gateway extends WC_Payment_Gateway echo " +

*don't forget to include the payment ID in your transaction

@@ -652,8 +654,8 @@ class Monero_Gateway extends WC_Payment_Gateway if($block_difference != 0) { - if($block_difference > 2){ - $this->log->add('[WARNING] Block difference is greater than 2'); + if($block_difference >= 2){ + $this->log->add('[WARNING] Block difference is greater or equal to 2'); } $txs_from_block_2 = $tools->get_txs_from_block($bc_height - 1); From 552792625da7ae5e2d1e0de843c029c3aef4b4d2 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Mon, 26 Feb 2018 19:02:32 -0600 Subject: [PATCH 30/87] Store some basic info in metadata This should help merchants with book keeping --- monero/include/monero_payments.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index d733527..c9878fc 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -319,6 +319,11 @@ class Monero_Gateway extends WC_Payment_Gateway $currency = $order->get_currency(); $amount_xmr2 = $this->changeto($amount, $currency, $payment_id); $address = $this->address; + + $order->update_meta_data( "Payment ID", $payment_id); + $order->update_meta_data( "Amount requested (XMR)", $amount_xmr2); + $order->save(); + if (!isset($address)) { // If there isn't address (merchant missed that field!), $address will be the Monero address for donating :) $address = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; @@ -402,6 +407,10 @@ class Monero_Gateway extends WC_Payment_Gateway $payment_id = $this->set_paymentid_cookie(8); $currency = $order->get_currency(); $amount_xmr2 = $this->changeto($amount, $currency, $payment_id); + + $order->update_meta_data( "Payment ID", $payment_id); + $order->update_meta_data( "Amount requested (XMR)", $amount_xmr2); + $order->save(); $uri = "monero:$address?tx_payment_id=$payment_id"; $array_integrated_address = $this->monero_daemon->make_integrated_address($payment_id); From d0071b12a9c8d93c3c1ff4ba6aded6646a40f093 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Mon, 26 Feb 2018 19:03:57 -0600 Subject: [PATCH 31/87] remove no longer needed note --- monero/include/monero_payments.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index c9878fc..f30056d 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -589,10 +589,10 @@ class Monero_Gateway extends WC_Payment_Gateway $order = wc_get_order($order_id); if($this->is_virtual_in_cart($order_id) == true){ - $order->update_status('completed', __('Payment has been received. Payment ID: ' . $payment_id, 'monero_gateway')); + $order->update_status('completed', __('Payment has been received.', 'monero_gateway')); } else{ - $order->update_status('processing', __('Payment has been received. Payment ID: ' . $payment_id, 'monero_gateway')); // Show payment id used for order + $order->update_status('processing', __('Payment has been received.', 'monero_gateway')); // Show payment id used for order } global $wpdb; $wpdb->query("DROP TABLE $payment_id"); // Drop the table from database after payment has been confirmed as it is no longer needed From 235ae5e4422322be244a2816cf3145f7fbc0991a Mon Sep 17 00:00:00 2001 From: Indeed Miners <32953696+IndeedMiners@users.noreply.github.com> Date: Tue, 20 Mar 2018 15:28:31 +0100 Subject: [PATCH 32/87] Monero Icon Fix in WP Admin Wrong file name. Now it displays correctly. --- monero/monero_gateway.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monero/monero_gateway.php b/monero/monero_gateway.php index 8d1972d..f7cce64 100644 --- a/monero/monero_gateway.php +++ b/monero/monero_gateway.php @@ -56,7 +56,7 @@ function monero_create_menu() 'manage_options', 'admin.php?page=wc-settings&tab=checkout§ion=monero_gateway', '', - plugins_url('monero/assets/icon.png'), + plugins_url('monero/assets/monero_icon.png'), 56 // Position on menu, woocommerce has 55.5, products has 55.6 ); From 35c9ef14db9a200d591c9c85e716d0d8afd7828d Mon Sep 17 00:00:00 2001 From: Phillip Whelan Date: Wed, 21 Mar 2018 06:37:24 -0300 Subject: [PATCH 33/87] Show icon when choosing payment method. --- monero/include/monero_payments.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index f30056d..4f42c6f 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -73,6 +73,11 @@ class Monero_Gateway extends WC_Payment_Gateway $this->monero_daemon = new Monero_Library($this->host, $this->port); } + public function get_icon() + { + return apply_filters('woocommerce_gateway_icon', ""); + } + public function init_form_fields() { $this->form_fields = array( From ee6fa74f02aaf81890a05a6f39b7a02965deb4cb Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Thu, 29 Mar 2018 11:53:41 -0400 Subject: [PATCH 34/87] link with https --- monero/monero_gateway.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monero/monero_gateway.php b/monero/monero_gateway.php index f7cce64..35e3a04 100644 --- a/monero/monero_gateway.php +++ b/monero/monero_gateway.php @@ -1,11 +1,11 @@ Date: Thu, 29 Mar 2018 19:40:40 -0400 Subject: [PATCH 35/87] add cryptonote tools --- monero/include/SHA3.php | 322 ++++++++++++++++++++++ monero/include/base58.php | 354 ++++++++++++++++++++++++ monero/include/cryptonote.php | 305 +++++++++++++++++++++ monero/include/ed25519.php | 498 ++++++++++++++++++++++++++++++++++ 4 files changed, 1479 insertions(+) create mode 100644 monero/include/SHA3.php create mode 100644 monero/include/base58.php create mode 100644 monero/include/cryptonote.php create mode 100644 monero/include/ed25519.php diff --git a/monero/include/SHA3.php b/monero/include/SHA3.php new file mode 100644 index 0000000..385561f --- /dev/null +++ b/monero/include/SHA3.php @@ -0,0 +1,322 @@ +. + + @license LGPL-3+ + @file +*/ + + +/** + SHA-3 (FIPS-202) for PHP strings (byte arrays) (PHP 5.2.1+) + PHP 7.0 computes SHA-3 about 4 times faster than PHP 5.2 - 5.6 (on x86_64) + + Based on the reference implementations, which are under CC-0 + Reference: http://keccak.noekeon.org/ + + This uses PHP's native byte strings. Supports 32-bit as well as 64-bit + systems. Also for LE vs. BE systems. +*/ +class SHA3 { + const SHA3_224 = 1; + const SHA3_256 = 2; + const SHA3_384 = 3; + const SHA3_512 = 4; + + const SHAKE128 = 5; + const SHAKE256 = 6; + + const KECCAK_256 = 7; + + + public static function init ($type = null) { + switch ($type) { + case self::SHA3_224: return new self (1152, 448, 0x06, 28); + case self::SHA3_256: return new self (1088, 512, 0x06, 32); + case self::SHA3_384: return new self (832, 768, 0x06, 48); + case self::SHA3_512: return new self (576, 1024, 0x06, 64); + case self::SHAKE128: return new self (1344, 256, 0x1f); + case self::SHAKE256: return new self (1088, 512, 0x1f); + case self::KECCAK_256: return new self (1088, 512, 0x01, 32); + } + + throw new Exception ('Invalid operation type'); + } + + + /** + Feed input to SHA-3 "sponge" + */ + public function absorb ($data) { + if (self::PHASE_INPUT != $this->phase) { + throw new Exception ('No more input accepted'); + } + + $rateInBytes = $this->rateInBytes; + $this->inputBuffer .= $data; + while (strlen ($this->inputBuffer) >= $rateInBytes) { + list ($input, $this->inputBuffer) = array ( + substr ($this->inputBuffer, 0, $rateInBytes) + , substr ($this->inputBuffer, $rateInBytes)); + + $blockSize = $rateInBytes; + for ($i = 0; $i < $blockSize; $i++) { + $this->state[$i] = $this->state[$i] ^ $input[$i]; + } + + $this->state = self::keccakF1600Permute ($this->state); + $this->blockSize = 0; + } + + return $this; + } + + /** + Get hash output + */ + public function squeeze ($length = null) { + $outputLength = $this->outputLength; // fixed length output + if ($length && 0 < $outputLength && $outputLength != $length) { + throw new Exception ('Invalid length'); + } + + if (self::PHASE_INPUT == $this->phase) { + $this->finalizeInput (); + } + + if (self::PHASE_OUTPUT != $this->phase) { + throw new Exception ('No more output allowed'); + } + if (0 < $outputLength) { + $this->phase = self::PHASE_DONE; + return $this->getOutputBytes ($outputLength); + } + + $blockLength = $this->rateInBytes; + list ($output, $this->outputBuffer) = array ( + substr ($this->outputBuffer, 0, $length) + , substr ($this->outputBuffer, $length)); + $neededLength = $length - strlen ($output); + $diff = $neededLength % $blockLength; + if ($diff) { + $readLength = (($neededLength - $diff) / $blockLength + 1) + * $blockLength; + } else { + $readLength = $neededLength; + } + + $read = $this->getOutputBytes ($readLength); + $this->outputBuffer .= substr ($read, $neededLength); + return $output . substr ($read, 0, $neededLength); + } + + + // internally used + const PHASE_INIT = 1; + const PHASE_INPUT = 2; + const PHASE_OUTPUT = 3; + const PHASE_DONE = 4; + + private $phase = self::PHASE_INIT; + private $state; // byte array (string) + private $rateInBytes; // positive integer + private $suffix; // 8-bit unsigned integer + private $inputBuffer = ''; // byte array (string): max length = rateInBytes + private $outputLength = 0; + private $outputBuffer = ''; + + + public function __construct ($rate, $capacity, $suffix, $length = 0) { + if (1600 != ($rate + $capacity)) { + throw new Error ('Invalid parameters'); + } + if (0 != ($rate % 8)) { + throw new Error ('Invalid rate'); + } + + $this->suffix = $suffix; + $this->state = str_repeat ("\0", 200); + $this->blockSize = 0; + + $this->rateInBytes = $rate / 8; + $this->outputLength = $length; + $this->phase = self::PHASE_INPUT; + return; + } + + protected function finalizeInput () { + $this->phase = self::PHASE_OUTPUT; + + $input = $this->inputBuffer; + $inputLength = strlen ($input); + if (0 < $inputLength) { + $blockSize = $inputLength; + for ($i = 0; $i < $blockSize; $i++) { + $this->state[$i] = $this->state[$i] ^ $input[$i]; + } + + $this->blockSize = $blockSize; + } + + // Padding + $rateInBytes = $this->rateInBytes; + $this->state[$this->blockSize] = $this->state[$this->blockSize] + ^ chr ($this->suffix); + if (($this->suffix & 0x80) != 0 + && $this->blockSize == ($rateInBytes - 1)) { + $this->state = self::keccakF1600Permute ($this->state); + } + $this->state[$rateInBytes - 1] = $this->state[$rateInBytes - 1] ^ "\x80"; + $this->state = self::keccakF1600Permute ($this->state); + } + + protected function getOutputBytes ($outputLength) { + // Squeeze + $output = ''; + while (0 < $outputLength) { + $blockSize = min ($outputLength, $this->rateInBytes); + $output .= substr ($this->state, 0, $blockSize); + $outputLength -= $blockSize; + if (0 < $outputLength) { + $this->state = self::keccakF1600Permute ($this->state); + } + } + + return $output; + } + + /** + 1600-bit state version of Keccak's permutation + */ + protected static function keccakF1600Permute ($state) { + $lanes = str_split ($state, 8); + $R = 1; + $values = "\1\2\4\10\20\40\100\200"; + + for ($round = 0; $round < 24; $round++) { + // θ step + $C = array (); + for ($x = 0; $x < 5; $x++) { + // (x, 0) (x, 1) (x, 2) (x, 3) (x, 4) + $C[$x] = $lanes[$x] ^ $lanes[$x + 5] ^ $lanes[$x + 10] + ^ $lanes[$x + 15] ^ $lanes[$x + 20]; + } + for ($x = 0; $x < 5; $x++) { + //$D = $C[($x + 4) % 5] ^ self::rotL64 ($C[($x + 1) % 5], 1); + $D = $C[($x + 4) % 5] ^ self::rotL64One ($C[($x + 1) % 5]); + for ($y = 0; $y < 5; $y++) { + $idx = $x + 5 * $y; // x, y + $lanes[$idx] = $lanes[$idx] ^ $D; + } + } + unset ($C, $D); + + // ρ and π steps + $x = 1; + $y = 0; + $current = $lanes[1]; // x, y + for ($t = 0; $t < 24; $t++) { + list ($x, $y) = array ($y, (2 * $x + 3 * $y) % 5); + $idx = $x + 5 * $y; + list ($current, $lanes[$idx]) = array ($lanes[$idx] + , self::rotL64 ($current + , (($t + 1) * ($t + 2) / 2) % 64)); + } + unset ($temp, $current); + + // χ step + $temp = array (); + for ($y = 0; $y < 5; $y++) { + for ($x = 0; $x < 5; $x++) { + $temp[$x] = $lanes[$x + 5 * $y]; + } + for ($x = 0; $x < 5; $x++) { + $lanes[$x + 5 * $y] = $temp[$x] + ^ ((~ $temp[($x + 1) % 5]) & $temp[($x + 2) % 5]); + + } + } + unset ($temp); + + // ι step + for ($j = 0; $j < 7; $j++) { + $R = (($R << 1) ^ (($R >> 7) * 0x71)) & 0xff; + if ($R & 2) { + $offset = (1 << $j) - 1; + $shift = $offset % 8; + $octetShift = ($offset - $shift) / 8; + $n = "\0\0\0\0\0\0\0\0"; + $n[$octetShift] = $values[$shift]; + + $lanes[0] = $lanes[0] + ^ $n; + //^ self::rotL64 ("\1\0\0\0\0\0\0\0", (1 << $j) - 1); + } + } + } + + return implode ($lanes); + } + + protected static function rotL64_64 ($n, $offset) { + return ($n << $offset) & ($n >> (64 - $offset)); + } + + /** + 64-bit bitwise left rotation (Little endian) + */ + protected static function rotL64 ($n, $offset) { + + //$n = (binary) $n; + //$offset = ((int) $offset) % 64; + //if (8 != strlen ($n)) throw new Exception ('Invalid number'); + //if ($offset < 0) throw new Exception ('Invalid offset'); + + $shift = $offset % 8; + $octetShift = ($offset - $shift) / 8; + $n = substr ($n, - $octetShift) . substr ($n, 0, - $octetShift); + + $overflow = 0x00; + for ($i = 0; $i < 8; $i++) { + $a = ord ($n[$i]) << $shift; + $n[$i] = chr (0xff & $a | $overflow); + $overflow = $a >> 8; + } + $n[0] = chr (ord ($n[0]) | $overflow); + return $n; + } + + /** + 64-bit bitwise left rotation (Little endian) + */ + protected static function rotL64One ($n) { + list ($n[0], $n[1], $n[2], $n[3], $n[4], $n[5], $n[6], $n[7]) + = array ( + chr (((ord ($n[0]) << 1) & 0xff) ^ (ord ($n[7]) >> 7)) + ,chr (((ord ($n[1]) << 1) & 0xff) ^ (ord ($n[0]) >> 7)) + ,chr (((ord ($n[2]) << 1) & 0xff) ^ (ord ($n[1]) >> 7)) + ,chr (((ord ($n[3]) << 1) & 0xff) ^ (ord ($n[2]) >> 7)) + ,chr (((ord ($n[4]) << 1) & 0xff) ^ (ord ($n[3]) >> 7)) + ,chr (((ord ($n[5]) << 1) & 0xff) ^ (ord ($n[4]) >> 7)) + ,chr (((ord ($n[6]) << 1) & 0xff) ^ (ord ($n[5]) >> 7)) + ,chr (((ord ($n[7]) << 1) & 0xff) ^ (ord ($n[6]) >> 7))); + return $n; + } +} diff --git a/monero/include/base58.php b/monero/include/base58.php new file mode 100644 index 0000000..e0c1604 --- /dev/null +++ b/monero/include/base58.php @@ -0,0 +1,354 @@ +hex_to_bin(): Invalid input type (must be a string)'); + } + if (strlen($hex) % 2 != 0) { + throw new Exception('base58->hex_to_bin(): Invalid input length (must be even)'); + } + + $res = array_fill(0, strlen($hex) / 2, 0); + for ($i = 0; $i < strlen($hex) / 2; $i++) { + $res[$i] = intval(substr($hex, $i * 2, $i * 2 + 2 - $i * 2), 16); + } + return $res; + } + + /** + * + * Convert a binary array to a hexadecimal string + * + * @param array $bin A binary array to convert to a hexadecimal string + * @return string + * + */ + private function bin_to_hex($bin) { + if (gettype($bin) != 'array') { + throw new Exception('base58->bin_to_hex(): Invalid input type (must be an array)'); + } + + $res = []; + for ($i = 0; $i < count($bin); $i++) { + $res[] = substr('0'.dechex($bin[$i]), -2); + } + return join($res); + } + + /** + * + * Convert a string to a binary array + * + * @param string $str A string to convert to a binary array + * @return array + * + */ + private function str_to_bin($str) { + if (gettype($str) != 'string') { + throw new Exception('base58->str_to_bin(): Invalid input type (must be a string)'); + } + + $res = array_fill(0, strlen($str), 0); + for ($i = 0; $i < strlen($str); $i++) { + $res[$i] = ord($str[$i]); + } + return $res; + } + + /** + * + * Convert a binary array to a string + * + * @param array $bin A binary array to convert to a string + * @return string + * + */ + private function bin_to_str($bin) { + if (gettype($bin) != 'array') { + throw new Exception('base58->bin_to_str(): Invalid input type (must be an array)'); + } + + $res = array_fill(0, count($bin), 0); + for ($i = 0; $i < count($bin); $i++) { + $res[$i] = chr($bin[$i]); + } + return preg_replace('/[[:^print:]]/', '', join($res)); // preg_replace necessary to strip errant non-ASCII characters eg. '' + } + + /** + * + * Convert a UInt8BE (one unsigned big endian byte) array to UInt64 + * + * @param array $data A UInt8BE array to convert to UInt64 + * @return number + * + */ + private function uint8_be_to_64($data) { + if (gettype($data) != 'array') { + throw new Exception ('base58->uint8_be_to_64(): Invalid input type (must be an array)'); + } + + $res = 0; + $i = 0; + switch (9 - count($data)) { + case 1: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 2: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 3: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 4: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 5: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 6: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 7: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 8: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + break; + default: + throw new Exception('base58->uint8_be_to_64: Invalid input length (1 <= count($data) <= 8)'); + } + return $res; + } + + /** + * + * Convert a UInt64 (unsigned 64 bit integer) to a UInt8BE array + * + * @param number $num A UInt64 number to convert to a UInt8BE array + * @param integer $size Size of array to return + * @return array + * + */ + private function uint64_to_8_be($num, $size) { + if (gettype($num) != ('integer' || 'double')) { + throw new Exception ('base58->uint64_to_8_be(): Invalid input type ($num must be a number)'); + } + if (gettype($size) != 'integer') { + throw new Exception ('base58->uint64_to_8_be(): Invalid input type ($size must be an integer)'); + } + if ($size < 1 || $size > 8) { + throw new Exception ('base58->uint64_to_8_be(): Invalid size (1 <= $size <= 8)'); + } + + $res = array_fill(0, $size, 0); + for ($i = $size - 1; $i >= 0; $i--) { + $res[$i] = bcmod($num, bcpow(2, 8)); + $num = bcdiv($num, bcpow(2, 8)); + } + return $res; + } + + /** + * + * Convert a hexadecimal (Base16) array to a Base58 string + * + * @param array $data + * @param array $buf + * @param number $index + * @return array + * + */ + private function encode_block($data, $buf, $index) { + if (gettype($data) != 'array') { + throw new Exception('base58->encode_block(): Invalid input type ($data must be an array)'); + } + if (gettype($buf) != 'array') { + throw new Exception('base58->encode_block(): Invalid input type ($buf must be an array)'); + } + if (gettype($index) != ('integer' || 'double')) { + throw new Exception('base58->encode_block(): Invalid input type ($index must be a number)'); + } + if (count($data) < 1 or count($data) > self::$full_encoded_block_size) { + throw new Exception('base58->encode_block(): Invalid input length (1 <= count($data) <= 8)'); + } + + $num = self::uint8_be_to_64($data); + $i = self::$encoded_block_sizes[count($data)] - 1; + while ($num > 0) { + $remainder = bcmod($num, 58); + $num = bcdiv($num, 58); + $buf[$index + $i] = ord(self::$alphabet[$remainder]); + $i--; + } + return $buf; + } + + /** + * + * Encode a hexadecimal (Base16) string to Base58 + * + * @param string $hex A hexadecimal (Base16) string to convert to Base58 + * @return string + * + */ + public function encode($hex) { + if (gettype($hex) != 'string') { + throw new Exception ('base58->encode(): Invalid input type (must be a string)'); + } + + $data = self::hex_to_bin($hex); + if (count($data) == 0) { + return ''; + } + + $full_block_count = floor(count($data) / self::$full_block_size); + $last_block_size = count($data) % self::$full_block_size; + $res_size = $full_block_count * self::$full_encoded_block_size + self::$encoded_block_sizes[$last_block_size]; + + $res = array_fill(0, $res_size, 0); + for ($i = 0; $i < $res_size; $i++) { + $res[$i] = self::$alphabet[0]; + } + + for ($i = 0; $i < $full_block_count; $i++) { + $res = self::encode_block(array_slice($data, $i * self::$full_block_size, ($i * self::$full_block_size + self::$full_block_size) - ($i * self::$full_block_size)), $res, $i * self::$full_encoded_block_size); + } + + if ($last_block_size > 0) { + $res = self::encode_block(array_slice($data, $full_block_count * self::$full_block_size, $full_block_count * self::$full_block_size + $last_block_size), $res, $full_block_count * self::$full_encoded_block_size); + } + + return self::bin_to_str($res); + } + + /** + * + * Convert a Base58 input to hexadecimal (Base16) + * + * @param array $data + * @param array $buf + * @param integer $index + * @return array + * + */ + private function decode_block($data, $buf, $index) { + if (gettype($data) != 'array') { + throw new Exception('base58->decode_block(): Invalid input type ($data must be an array)'); + } + if (gettype($buf) != 'array') { + throw new Exception('base58->decode_block(): Invalid input type ($buf must be an array)'); + } + if (gettype($index) != ('integer' || 'double')) { + throw new Exception('base58->decode_block(): Invalid input type ($index must be a number)'); + } + + $res_size = self::index_of(self::$encoded_block_sizes, count($data)); + if ($res_size <= 0) { + throw new Exception('base58->decode_block(): Invalid input length ($data must be a value from base58::$encoded_block_sizes)'); + } + + $res_num = 0; + $order = 1; + for ($i = count($data) - 1; $i >= 0; $i--) { + $digit = strpos(self::$alphabet, chr($data[$i])); + if ($digit < 0) { + throw new Exception("base58->decode_block(): Invalid character ($digit \"{$digit}\" not found in base58::$alphabet)"); + } + + $product = bcadd(bcmul($order, $digit), $res_num); + if ($product > bcpow(2, 64)) { + throw new Exception('base58->decode_block(): Integer overflow ($product exceeds the maximum 64bit integer)'); + } + + $res_num = $product; + $order = bcmul($order, 58); + } + if ($res_size < self::$full_block_size && bcpow(2, 8 * $res_size) <= 0) { + throw new Exception('base58->decode_block(): Integer overflow (bcpow(2, 8 * $res_size) exceeds the maximum 64bit integer)'); + } + + $tmp_buf = self::uint64_to_8_be($res_num, $res_size); + for ($i = 0; $i < count($tmp_buf); $i++) { + $buf[$i + $index] = $tmp_buf[$i]; + } + return $buf; + } + + /** + * + * Decode a Base58 string to hexadecimal (Base16) + * + * @param string $hex A Base58 string to convert to hexadecimal (Base16) + * @return string + * + */ + public function decode($enc) { + if (gettype($enc) != 'string') { + throw new Exception ('base58->decode(): Invalid input type (must be a string)'); + } + + $enc = self::str_to_bin($enc); + if (count($enc) == 0) { + return ''; + } + $full_block_count = floor(bcdiv(count($enc), self::$full_encoded_block_size)); + $last_block_size = bcmod(count($enc), self::$full_encoded_block_size); + $last_block_decoded_size = self::index_of(self::$encoded_block_sizes, $last_block_size); + + $data_size = $full_block_count * self::$full_block_size + $last_block_decoded_size; + + $data = array_fill(0, $data_size, 0); + for ($i = 0; $i <= $full_block_count; $i++) { + $data = self::decode_block(array_slice($enc, $i * self::$full_encoded_block_size, ($i * self::$full_encoded_block_size + self::$full_encoded_block_size) - ($i * self::$full_encoded_block_size)), $data, $i * self::$full_block_size); + } + + if ($last_block_size > 0) { + $data = self::decode_block(array_slice($enc, $full_block_count * self::$full_encoded_block_size, $full_block_count * self::$full_encoded_block_size + $last_block_size), $data, $full_block_count * self::$full_block_size); + } + + return self::bin_to_hex($data); + } + + /** + * + * Search an array for a value + * Source: https://stackoverflow.com/a/30994678 + * + * @param array $haystack An array to search + * @param string $needle A string to search for + * @return number The index of the element found (or -1 for no match) + * + */ + private function index_of($haystack, $needle) { + if (gettype($haystack) != 'array') { + throw new Exception ('base58->decode(): Invalid input type ($haystack must be an array)'); + } + // if (gettype($needle) != 'string') { + // throw new Exception ('base58->decode(): Invalid input type ($needle must be a string)'); + // } + + foreach ($haystack as $key => $value) if ($value === $needle) return $key; + return -1; + } +} + +?> diff --git a/monero/include/cryptonote.php b/monero/include/cryptonote.php new file mode 100644 index 0000000..a6dafaf --- /dev/null +++ b/monero/include/cryptonote.php @@ -0,0 +1,305 @@ +ed25519 = new ed25519(); + $this->base58 = new base58(); + } + + /* + * @param string Hex encoded string of the data to hash + * @return string Hex encoded string of the hashed data + * + */ + public function keccak_256($message) + { + $keccak256 = SHA3::init (SHA3::KECCAK_256); + $keccak256->absorb (hex2bin($message)); + return bin2hex ($keccak256->squeeze (32)) ; + } + + /* + * @return string A hex encoded string of 32 random bytes + * + */ + public function gen_new_hex_seed() + { + $bytes = random_bytes(32); + return bin2hex($bytes); + } + + public function sc_reduce($input) + { + $integer = $this->ed25519->decodeint(hex2bin($input)); + + $modulo = bcmod($integer , $this->ed25519->l); + + $result = bin2hex($this->ed25519->encodeint($modulo)); + return $result; + } + + /* + * Hs in the cryptonote white paper + * + * @param string Hex encoded data to hash + * + * @return string A 32 byte encoded integer + */ + public function hash_to_scalar($data) + { + $hash = $this->keccak_256($data); + $scalar = $this->sc_reduce($hash); + return $scalar; + } + + /* + * Derive a deterministic private view key from a private spend key + * @param string A private spend key represented as a 32 byte hex string + * + * @return string A deterministic private view key represented as a 32 byte hex string + */ + public function derive_viewKey($spendKey) + { + return $this->hash_to_scalar($spendkey); + } + + /* + * Generate a pair of random private keys + * + * @param string A hex string to be used as a seed (this should be random) + * + * @return array An array containing a private spend key and a deterministic view key + */ + public function gen_private_keys($seed) + { + $spendKey = $this->sc_reduce($seed); + $viewKey = $this->derive_viewKey($spendKey); + $result = array("spendKey" => $spendKey, + "viewKey" => $viewKey); + + return $result; + } + + /* + * Get a public key from a private key on the ed25519 curve + * + * @param string a 32 byte hex encoded private key + * + * @return string a 32 byte hex encoding of a point on the curve to be used as a public key + */ + public function pk_from_sk($privKey) + { + $keyInt = $this->ed25519->decodeint(hex2bin($privKey)); + $aG = $this->ed25519->scalarmult_base($keyInt); + return bin2hex($this->ed25519->encodepoint($aG)); + } + + /* + * Generate key derivation + * + * @param string a 32 byte hex encoding of a point on the ed25519 curve used as a public key + * @param string a 32 byte hex encoded private key + * + * @return string The hex encoded key derivation + */ + public function gen_key_derivation($public, $private) + { + $point = $this->ed25519->scalarmult($this->ed25519->decodepoint(hex2bin($public)), $this->ed25519->decodeint(hex2bin($private))); + $res = $this->ed25519->scalarmult($point, 8); + return bin2hex($this->ed25519->encodepoint($res)); + } + + public function encode_varint($data) + { + $orig = $data; + + if ($data < 0x80) + { + return bin2hex(pack('C', $data)); + } + + $encodedBytes = []; + while ($data > 0) + { + $encodedBytes[] = 0x80 | ($data & 0x7f); + $data >>= 7; + } + + $encodedBytes[count($encodedBytes)-1] &= 0x7f; + $bytes = call_user_func_array('pack', array_merge(array('C*'), $encodedBytes));; + return bin2hex($bytes); + } + + public function derivation_to_scalar($der, $index) + { + $encoded = $this->encode_varint($index); + $data = $der . $encoded; + return $this->hash_to_scalar($data); + } + + // this is a one way function used for both encrypting and decrypting 8 byte payment IDs + public function stealth_payment_id($payment_id, $tx_pub_key, $viewkey) + { + if(strlen($payment_id) != 16) + { + throw new Exception("Error: Incorrect payment ID size. Should be 8 bytes"); + } + $der = $this->gen_key_derivation($tx_pub_key, $viewkey); + $data = $der . '8d'; + $hash = $this->keccak_256($data); + $key = substr($hash, 0, 16); + $result = bin2hex(pack('H*',$payment_id) ^ pack('H*',$key)); + return $result; + } + + // takes transaction extra field as hex string and returns transaction public key 'R' as hex string + public function txpub_from_extra($extra) + { + $parsed = array_map("hexdec", str_split($extra, 2)); + + if($parsed[0] == 1) + { + return substr($extra, 2, 64); + } + + if($parsed[0] == 2) + { + if($parsed[0] == 2 || $parsed[2] == 1) + { + $offset = (($parsed[1] + 2) *2) + 2; + return substr($extra, (($parsed[1] + 2) *2) + 2, 64); + } + } + } + + public function derive_public_key($der, $index, $pub) + { + $scalar = $this->derivation_to_scalar($der, $index); + $sG = $this->ed25519->scalarmult_base($this->ed25519->decodeint(hex2bin($scalar))); + $pubPoint = $this->ed25519->decodepoint(hex2bin($pub)); + $key = $this->ed25519->encodepoint($this->ed25519->edwards($pubPoint, $sG)); + return bin2hex($key); + } + + /* + * Perform the calculation P = P' as described in the cryptonote whitepaper + * + * @param string 32 byte transaction public key R + * @param string 32 byte reciever private view key a + * @param string 32 byte reciever public spend key B + * @param int output index + * @param string output you want to check against P + */ + public function is_output_mine($txPublic, $privViewkey, $publicSpendkey, $index, $P) + { + $derivation = $this->gen_key_derivation($txPublic, $privViewkey); + $Pprime = $this->derive_public_key($derivation, $index, $publicSpendkey); + + if($P == $Pprime) + { + return true; + } + else + return false; + } + + /* + * Create a valid base58 encoded Monero address from public keys + * + * @param string Public spend key + * @param string Public view key + * + * @return string Base58 encoded Monero address + */ + public function encode_address($pSpendKey, $pViewKey) + { + // mainnet network byte is 18 (0x12) + $data = "12" . $pSpendKey . $pViewKey; + $encoded = $this->base58->encode($data); + return $encoded; + } + + public function verify_checksum($address) + { + $decoded = $this->base58->decode($address); + $checksum = substr($decoded, -8); + $checksum_hash = $this->keccak_256(substr($decoded, 0, 130)); + $calculated = substr($checksum_hash, 0, 8); + if($checksum == $calculated){ + return true; + } + else + return false; + } + + /* + * Decode a base58 encoded Monero address + * + * @param string A base58 encoded Monero address + * + * @return array An array containing the Address network byte, public spend key, and public view key + */ + public function decode_address($address) + { + $decoded = $this->base58->decode($address); + + if(!$this->verify_checksum($address)){ + throw new Exception("Error: invalid checksum"); + } + + $network_byte = substr($decoded, 0, 2); + $public_spendKey = substr($decoded, 2, 64); + $public_viewKey = substr($decoded, 66, 64); + + $result = array("networkByte" => $network_byte, + "spendKey" => $public_spendKey, + "viewKey" => $public_viewKey); + return $result; + } + + /* + * Get an integrated address from public keys and a payment id + * + * @param string A 32 byte hex encoded public spend key + * @param string A 32 byte hex encoded public view key + * @param string An 8 byte hex string to use as a payment id + */ + public function integrated_addr_from_keys($public_spendkey, $public_viewkey, $payment_id) + { + // 0x13 is the mainnet network byte for integrated addresses + $data = "13".$public_spendkey.$public_viewkey.$payment_id; + $checksum = substr($this->keccak_256($data), 0, 8); + $result = $this->base58->encode($data.$checksum); + return $result; + } + + /* + * Generate a Monero address from seed + * + * @param string Hex string to use as seed + * + * @return string A base58 encoded Monero address + */ + public function address_from_seed($hex_seed) + { + $private_keys = $this->gen_private_keys($hex_seed); + $private_viewKey = $private_keys["viewKey"]; + $private_spendKey = $private_keys["spendKey"]; + + $public_spendKey = $this->pk_from_sk($private_spendKey); + $public_viewKey = $this->pk_from_sk($private_viewKey); + + $address = $this->encode_address($public_spendKey, $public_viewKey); + return $address; + } + } + diff --git a/monero/include/ed25519.php b/monero/include/ed25519.php new file mode 100644 index 0000000..9852b68 --- /dev/null +++ b/monero/include/ed25519.php @@ -0,0 +1,498 @@ +b = 256; + $this->q = "57896044618658097711785492504343953926634992332820282019728792003956564819949"; //bcsub(bcpow(2, 255),19); + $this->l = "7237005577332262213973186563042994240857116359379907606001950938285454250989"; //bcadd(bcpow(2,252),27742317777372353535851937790883648493); + $this->d = "-4513249062541557337682894930092624173785641285191125241628941591882900924598840740"; //bcmul(-121665,$this->inv(121666)); + $this->I = "19681161376707505956807079304988542015446066515923890162744021073123829784752"; //$this->expmod(2, bcdiv((bcsub($this->q,1)),4),$this->q); + $this->By = "46316835694926478169428394003475163141307993866256225615783033603165251855960"; //bcmul(4,$this->inv(5)); + $this->Bx = "15112221349535400772501151409588531511454012693041857206046113283949847762202"; //$this->xrecover($this->By); + $this->B = array( + "15112221349535400772501151409588531511454012693041857206046113283949847762202", + "46316835694926478169428394003475163141307993866256225615783033603165251855960" + ); //array(bcmod($this->Bx,$this->q),bcmod($this->By,$this->q)); + + $this->gmp = extension_loaded('gmp'); + } + + public function H($m) + { + return hash('sha512', $m, true); + } + + //((n % M) + M) % M //python modulus craziness + public function pymod($x, $m) + { + if ($this->gmp) { + $mod = gmp_mod($x, $m); + if ($mod < 0) { + $mod = gmp_add($mod, $m); + } + } else { + $mod = bcmod($x, $m); + if ($mod < 0) { + $mod = bcadd($mod, $m); + } + } + + return $mod; + } + + public function expmod($b, $e, $m) + { + //if($e==0){return 1;} + if ($this->gmp) { + $t = gmp_powm($b, $e, $m); + if ($t < 0) { + $t = gmp_add($t, $m); + } + } else { + $t = bcpowmod($b, $e, $m); + if ($t[0] === '-') { + $t = bcadd($t, $m); + } + } + + return $t; + } + + public function inv($x) + { + if ($this->gmp) { + return $this->expmod($x, gmp_sub($this->q, 2), $this->q); + } else { + return $this->expmod($x, bcsub($this->q, 2), $this->q); + } + } + + public function xrecover($y) + { + if ($this->gmp) { + $y2 = gmp_pow($y, 2); + $xx = gmp_mul(gmp_sub($y2, 1), $this->inv(gmp_add(gmp_mul($this->d, $y2), 1))); + $x = $this->expmod($xx, gmp_div(gmp_add($this->q, 3), 8, 0), $this->q); + if ($this->pymod(gmp_sub(gmp_pow($x, 2), $xx), $this->q) != 0) { + $x = $this->pymod(gmp_mul($x, $this->I), $this->q); + } + if (substr($x, -1)%2 != 0) { + $x = gmp_sub($this->q, $x); + } + } else { + $y2 = bcpow($y, 2); + $xx = bcmul(bcsub($y2, 1), $this->inv(bcadd(bcmul($this->d, $y2), 1))); + $x = $this->expmod($xx, bcdiv(bcadd($this->q, 3), 8, 0), $this->q); + if ($this->pymod(bcsub(bcpow($x, 2), $xx), $this->q) != 0) { + $x = $this->pymod(bcmul($x, $this->I), $this->q); + } + if (substr($x, -1)%2 != 0) { + $x = bcsub($this->q, $x); + } + } + + return $x; + } + + public function edwards($P, $Q) + { + if ($this->gmp) { + list($x1, $y1) = $P; + list($x2, $y2) = $Q; + $xmul = gmp_mul($x1, $x2); + $ymul = gmp_mul($y1, $y2); + $com = gmp_mul($this->d, gmp_mul($xmul, $ymul)); + $x3 = gmp_mul(gmp_add(gmp_mul($x1, $y2), gmp_mul($x2, $y1)), $this->inv(gmp_add(1, $com))); + $y3 = gmp_mul(gmp_add($ymul, $xmul), $this->inv(gmp_sub(1, $com))); + + return array($this->pymod($x3, $this->q), $this->pymod($y3, $this->q)); + } else { + list($x1, $y1) = $P; + list($x2, $y2) = $Q; + $xmul = bcmul($x1, $x2); + $ymul = bcmul($y1, $y2); + $com = bcmul($this->d, bcmul($xmul, $ymul)); + $x3 = bcmul(bcadd(bcmul($x1, $y2), bcmul($x2, $y1)), $this->inv(bcadd(1, $com))); + $y3 = bcmul(bcadd($ymul, $xmul), $this->inv(bcsub(1, $com))); + + return array($this->pymod($x3, $this->q), $this->pymod($y3, $this->q)); + } + } + + public function scalarmult($P, $e) + { + if ($this->gmp) { + if ($e == 0) { + return array(0, 1); + } + $Q = $this->scalarmult($P, gmp_div($e, 2, 0)); + $Q = $this->edwards($Q, $Q); + if (substr($e, -1)%2 == 1) { + $Q = $this->edwards($Q, $P); + } + } else { + if ($e == 0) { + return array(0, 1); + } + $Q = $this->scalarmult($P, bcdiv($e, 2, 0)); + $Q = $this->edwards($Q, $Q); + if (substr($e, -1)%2 == 1) { + $Q = $this->edwards($Q, $P); + } + } + + return $Q; + } + + public function scalarloop($P, $e) + { + if ($this->gmp) { + $temp = array(); + $loopE = $e; + while ($loopE > 0) { + array_unshift($temp, $loopE); + $loopE = gmp_div($loopE, 2, 0); + } + $Q = array(); + foreach ($temp as $e) { + if ($e == 1) { + $Q = $this->edwards(array(0, 1), $P); + } elseif (substr($e, -1)%2 == 1) { + $Q = $this->edwards($this->edwards($Q, $Q), $P); + } else { + $Q = $this->edwards($Q, $Q); + } + } + } else { + $temp = array(); + $loopE = $e; + while ($loopE > 0) { + array_unshift($temp, $loopE); + $loopE = bcdiv($loopE, 2, 0); + } + $Q = array(); + foreach ($temp as $e) { + if ($e == 1) { + $Q = $this->edwards(array(0, 1), $P); + } elseif (substr($e, -1)%2 == 1) { + $Q = $this->edwards($this->edwards($Q, $Q), $P); + } else { + $Q = $this->edwards($Q, $Q); + } + } + } + + return $Q; + } + + public function bitsToString($bits) + { + $string = ''; + for ($i = 0; $i < $this->b/8; $i++) { + $sum = 0; + for ($j = 0; $j < 8; $j++) { + $bit = $bits[$i*8+$j]; + $sum += (int) $bit << $j; + } + $string .= chr($sum); + } + + return $string; + } + + public function dec2bin_i($decimal_i) + { + if ($this->gmp) { + $binary_i = ''; + do { + $binary_i = substr($decimal_i, -1)%2 .$binary_i; + $decimal_i = gmp_div($decimal_i, '2', 0); + } while (gmp_cmp($decimal_i, '0')); + } else { + $binary_i = ''; + do { + $binary_i = substr($decimal_i, -1)%2 .$binary_i; + $decimal_i = bcdiv($decimal_i, '2', 0); + } while (bccomp($decimal_i, '0')); + } + + return ($binary_i); + } + + public function encodeint($y) + { + $bits = substr(str_pad(strrev($this->dec2bin_i($y)), $this->b, '0', STR_PAD_RIGHT), 0, $this->b); + + return $this->bitsToString($bits); + } + + public function encodepoint($P) + { + list($x, $y) = $P; + $bits = substr(str_pad(strrev($this->dec2bin_i($y)), $this->b-1, '0', STR_PAD_RIGHT), 0, $this->b-1); + $bits .= (substr($x, -1)%2 == 1 ? '1' : '0'); + + return $this->bitsToString($bits); + } + + public function bit($h, $i) + { + if ($this->gmp) { + return (ord($h[(int) gmp_div($i, 8, 0)]) >> substr($i, -3)%8) & 1; + } else { + return (ord($h[(int) bcdiv($i, 8, 0)]) >> substr($i, -3)%8) & 1; + } + } + + /** + * Generates the public key of a given private key + * + * @param string $sk the secret key + * + * @return string + */ + public function publickey($sk) + { + if ($this->gmp) { + $h = $this->H($sk); + $sum = 0; + for ($i = 3; $i < $this->b-2; $i++) { + $sum = gmp_add($sum, gmp_mul(gmp_pow(2, $i), $this->bit($h, $i))); + } + $a = gmp_add(gmp_pow(2, $this->b-2), $sum); + $A = $this->scalarmult($this->B, $a); + $data = $this->encodepoint($A); + } else { + $h = $this->H($sk); + $sum = 0; + for ($i = 3; $i < $this->b-2; $i++) { + $sum = bcadd($sum, bcmul(bcpow(2, $i), $this->bit($h, $i))); + } + $a = bcadd(bcpow(2, $this->b-2), $sum); + $A = $this->scalarmult($this->B, $a); + $data = $this->encodepoint($A); + } + + return $data; + } + + public function Hint($m) + { + if ($this->gmp) { + $h = $this->H($m); + $sum = 0; + for ($i = 0; $i < $this->b*2; $i++) { + $sum = gmp_add($sum, gmp_mul(gmp_pow(2, $i), $this->bit($h, $i))); + } + } else { + $h = $this->H($m); + $sum = 0; + for ($i = 0; $i < $this->b*2; $i++) { + $sum = bcadd($sum, bcmul(bcpow(2, $i), $this->bit($h, $i))); + } + } + + return $sum; + } + + public function signature($m, $sk, $pk) + { + if ($this->gmp) { + $h = $this->H($sk); + $a = gmp_pow(2, (gmp_sub($this->b, 2))); + for ($i = 3; $i < $this->b-2; $i++) { + $a = gmp_add($a, gmp_mul(gmp_pow(2, $i), $this->bit($h, $i))); + } + $r = $this->Hint(substr($h, $this->b/8, ($this->b/4-$this->b/8)).$m); + $R = $this->scalarmult($this->B, $r); + $encR = $this->encodepoint($R); + $S = $this->pymod(gmp_add($r, gmp_mul($this->Hint($encR.$pk.$m), $a)), $this->l); + } else { + $h = $this->H($sk); + $a = bcpow(2, (bcsub($this->b, 2))); + for ($i = 3; $i < $this->b-2; $i++) { + $a = bcadd($a, bcmul(bcpow(2, $i), $this->bit($h, $i))); + } + $r = $this->Hint(substr($h, $this->b/8, ($this->b/4-$this->b/8)).$m); + $R = $this->scalarmult($this->B, $r); + $encR = $this->encodepoint($R); + $S = $this->pymod(bcadd($r, bcmul($this->Hint($encR.$pk.$m), $a)), $this->l); + } + + return $encR.$this->encodeint($S); + } + + public function isoncurve($P) + { + if ($this->gmp) { + list($x, $y) = $P; + $x2 = gmp_pow($x, 2); + $y2 = gmp_pow($y, 2); + + return $this->pymod(gmp_sub(gmp_sub(gmp_sub($y2, $x2), 1), gmp_mul($this->d, gmp_mul($x2, $y2))), $this->q) == 0; + } else { + list($x, $y) = $P; + $x2 = bcpow($x, 2); + $y2 = bcpow($y, 2); + + return $this->pymod(bcsub(bcsub(bcsub($y2, $x2), 1), bcmul($this->d, bcmul($x2, $y2))), $this->q) == 0; + } + } + + public function decodeint($s) + { + if ($this->gmp) { + $sum = 0; + for ($i = 0; $i < $this->b; $i++) { + $sum = gmp_add($sum, gmp_mul(gmp_pow(2, $i), $this->bit($s, $i))); + } + } else { + $sum = 0; + for ($i = 0; $i < $this->b; $i++) { + $sum = bcadd($sum, bcmul(bcpow(2, $i), $this->bit($s, $i))); + } + } + + return $sum; + } + + /* + * def decodepoint(s): + y = sum(2**i * bit(s,i) for i in range(0,b-1)) + x = xrecover(y) + if x & 1 != bit(s,b-1): x = q-x + P = [x,y] + if not isoncurve(P): raise Exception("decoding point that is not on curve") + return P + + */ + public function decodepoint($s) + { + if ($this->gmp) { + $y = 0; + for ($i = 0; $i < $this->b-1; $i++) { + $y = gmp_add($y, gmp_mul(gmp_pow(2, $i), $this->bit($s, $i))); + } + $x = $this->xrecover($y); + if (substr($x, -1)%2 != $this->bit($s, $this->b-1)) { + $x = gmp_sub($this->q, $x); + } + $P = array($x, $y); + if (!$this->isoncurve($P)) { + throw new \Exception("Decoding point that is not on curve"); + } + } else { + $y = 0; + for ($i = 0; $i < $this->b-1; $i++) { + $y = bcadd($y, bcmul(bcpow(2, $i), $this->bit($s, $i))); + } + $x = $this->xrecover($y); + if (substr($x, -1)%2 != $this->bit($s, $this->b-1)) { + $x = bcsub($this->q, $x); + } + $P = array($x, $y); + if (!$this->isoncurve($P)) { + throw new \Exception("Decoding point that is not on curve"); + } + } + + return $P; + } + + public function checkvalid($s, $m, $pk) + { + if (strlen($s) != $this->b/4) { + throw new \Exception('Signature length is wrong'); + } + if (strlen($pk) != $this->b/8) { + throw new \Exception('Public key length is wrong: '.strlen($pk)); + } + $R = $this->decodepoint(substr($s, 0, $this->b/8)); + try { + $A = $this->decodepoint($pk); + } catch (\Exception $e) { + return false; + } + $S = $this->decodeint(substr($s, $this->b/8, $this->b/4)); + $h = $this->Hint($this->encodepoint($R).$pk.$m); + + return $this->scalarmult($this->B, $S) == $this->edwards($R, $this->scalarmult($A, $h)); + } + + // The code below is by the Monero-Integrations team + + public function scalarmult_base($e) + { + if ($this->gmp) { + if ($e == 0) { + return array(0, 1); + } + $Q = $this->scalarmult($this->B, gmp_div($e, 2, 0)); + $Q = $this->edwards($Q, $Q); + if (substr($e, -1)%2 == 1) { + $Q = $this->edwards($Q, $this->B); + } + } else { + if ($e == 0) { + return array(0, 1); + } + $Q = $this->scalarmult($this->B, bcdiv($e, 2, 0)); + $Q = $this->edwards($Q, $Q); + if (substr($e, -1)%2 == 1) { + $Q = $this->edwards($Q, $this->B); + } + } + + return $Q; + } +} From ecc19adb83f06e37a40341d78490f3746911e7ea Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Thu, 29 Mar 2018 20:40:24 -0400 Subject: [PATCH 36/87] verify address checksum --- monero/include/monero_payments.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index 4f42c6f..c58fd7c 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -5,6 +5,7 @@ * Authors: Serhack and cryptochangements */ +require_once("cryptonote.php"); class Monero_Gateway extends WC_Payment_Gateway { @@ -14,6 +15,7 @@ class Monero_Gateway extends WC_Payment_Gateway private $monero_daemon; private $non_rpc = false; private $zero_cofirm = false; + private $cryptonote; function __construct() { @@ -71,6 +73,7 @@ class Monero_Gateway extends WC_Payment_Gateway add_action('woocommerce_email_before_order_table', array($this, 'email_instructions'), 10, 2); } $this->monero_daemon = new Monero_Library($this->host, $this->port); + $this->cryptonote = new Cryptonote(); } public function get_icon() @@ -269,8 +272,12 @@ class Monero_Gateway extends WC_Payment_Gateway public function check_monero() { $monero_address = $this->settings['monero_address']; - if (strlen($monero_address) == 95 && substr($monero_address, 1)) { - return true; + if (strlen($monero_address) == 95 && substr($monero_address, 1)) + { + if($this->cryptonote->verify_checksum($monero_address)) + { + return true; + } } return false; } From ab8ef9fa2fa365a68c3fe6e65b5cb1ba78b41edc Mon Sep 17 00:00:00 2001 From: IndeedMiners <32953696+IndeedMiners@users.noreply.github.com> Date: Fri, 30 Mar 2018 17:26:47 +0200 Subject: [PATCH 37/87] style url fix --- monero/include/monero_payments.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index c58fd7c..4b98c7d 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -444,7 +444,7 @@ class Monero_Gateway extends WC_Payment_Gateway - + From df6adb4ab89729d942855bfc8aee2a4f8a9e5a93 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Sat, 14 Apr 2018 13:45:02 -0500 Subject: [PATCH 38/87] allow testnet explorer --- monero/library.php | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/monero/library.php b/monero/library.php index a859f64..d62dfd2 100644 --- a/monero/library.php +++ b/monero/library.php @@ -322,13 +322,26 @@ class Monero_Library class NodeTools { + private $url; + public function __construct($testnet = false) + { + if(!testnet) + { + $this->url = 'https://xmrchain.net' + } + if(testnet) + { + $this->url = 'https://testnet.xmrchain.net' + } + } + public function get_last_block_height() { $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_RETURNTRANSFER => 1, - CURLOPT_URL => 'https://xmrchain.net/api/networkinfo', + CURLOPT_URL => $this->url . 'api/networkinfo', )); $resp = curl_exec($curl); curl_close($curl); @@ -343,7 +356,7 @@ class NodeTools curl_setopt_array($curl, array( CURLOPT_RETURNTRANSFER => 1, - CURLOPT_URL => 'https://xmrchain.net/api/search/' . $height, + CURLOPT_URL => $this->url . '/api/search/' . $height, )); $resp = curl_exec($curl); curl_close($curl); @@ -358,7 +371,7 @@ class NodeTools $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_RETURNTRANSFER => 1, - CURLOPT_URL => 'https://xmrchain.net/api/outputs?txhash=' .$tx_hash . '&address='. $address . '&viewkey='. $viewKey .'&txprove=0', + CURLOPT_URL => $this-url . '/api/outputs?txhash=' .$tx_hash . '&address='. $address . '&viewkey='. $viewKey .'&txprove=0', )); $resp = curl_exec($curl); curl_close($curl); @@ -383,7 +396,7 @@ class NodeTools curl_setopt_array($curl, array( CURLOPT_RETURNTRANSFER => 1, - CURLOPT_URL => 'https://xmrchain.net/api/mempool', + CURLOPT_URL => $this->url . '/api/mempool', )); $resp = curl_exec($curl); curl_close($curl); From 9f8fee17ebae57f21c37f527a94a36e59d877bc6 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Sat, 14 Apr 2018 13:55:25 -0500 Subject: [PATCH 39/87] fix missing semicolons --- monero/library.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monero/library.php b/monero/library.php index d62dfd2..f1cf6d3 100644 --- a/monero/library.php +++ b/monero/library.php @@ -327,11 +327,11 @@ class NodeTools { if(!testnet) { - $this->url = 'https://xmrchain.net' + $this->url = 'https://xmrchain.net'; } if(testnet) { - $this->url = 'https://testnet.xmrchain.net' + $this->url = 'https://testnet.xmrchain.net'; } } From 380cd24527a3b98eb1dada7387cce0cd5c73e0b4 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Sat, 14 Apr 2018 13:55:53 -0500 Subject: [PATCH 40/87] Update monero_payments.php --- monero/include/monero_payments.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index 4b98c7d..ae226b4 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -16,6 +16,7 @@ class Monero_Gateway extends WC_Payment_Gateway private $non_rpc = false; private $zero_cofirm = false; private $cryptonote; + private $testnet = false; function __construct() { @@ -41,6 +42,8 @@ class Monero_Gateway extends WC_Payment_Gateway $this->use_viewKey = $this->get_option('use_viewKey'); $this->use_rpc = $this->get_option('use_rpc'); + $env = $this->get_option('environment'); + if($this->use_viewKey == 'yes') { $this->non_rpc = true; @@ -53,6 +56,12 @@ class Monero_Gateway extends WC_Payment_Gateway { $this->zero_confirm = true; } + + if($env == 'yes') + { + $this->testnet = true; + } + // After init_settings() is called, you can get the settings and load them into variables, e.g: // $this->title = $this->get_option('title' ); $this->init_settings(); @@ -662,7 +671,7 @@ class Monero_Gateway extends WC_Payment_Gateway } public function verify_non_rpc($payment_id, $amount, $order_id) { - $tools = new NodeTools(); + $tools = new NodeTools($this->testnet); $bc_height = $tools->get_last_block_height(); $block_difference = $this->last_block_seen($bc_height); @@ -737,7 +746,7 @@ class Monero_Gateway extends WC_Payment_Gateway public function verify_zero_conf($payment_id, $amount, $order_id) { - $tools = new NodeTools(); + $tools = new NodeTools($this->testnet); $txs_from_mempool = $tools->get_mempool_txs();; $tx_count = count($txs_from_mempool['data']['txs']); $i = 0; From fa9d6b7162525f8b28ef7da01b3d33747ba67d7b Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Fri, 20 Apr 2018 17:52:03 -0400 Subject: [PATCH 41/87] check if *all* cart items are virtual --- monero/include/monero_payments.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index ae226b4..ab060eb 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -318,15 +318,22 @@ class Monero_Gateway extends WC_Payment_Gateway { $order = wc_get_order( $order_id ); $items = $order->get_items(); - + $cart_size = count($items); + $virtual_items = 0; + foreach ( $items as $item ) { $product = new WC_Product( $item['product_id'] ); if ( $product->is_virtual() ) { - return true; + $virtual_items += 1; } } - - return false; + if($virtual_items == $cart_size) + { + return true; + } + else{ + return false; + } } public function instruction($order_id) From 9e924a1a61c3b19a5a4c2616dcc44fb547e5076e Mon Sep 17 00:00:00 2001 From: pitifultermite <38870971+pitifultermite@users.noreply.github.com> Date: Tue, 1 May 2018 02:01:02 +0200 Subject: [PATCH 42/87] Update monero_payments.php --- monero/include/monero_payments.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index ab060eb..87014f0 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -356,8 +356,7 @@ class Monero_Gateway extends WC_Payment_Gateway // If there isn't address (merchant missed that field!), $address will be the Monero address for donating :) $address = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; } - $uri = "monero:$address?tx_payment_id=$payment_id"; - + $uri = urlencode("monero:".$address."?tx_amount=".$amount_xmr2."&tx_payment_id=".$payment_id); if($this->zero_confirm){ $this->verify_zero_conf($payment_id, $amount_xmr2, $order_id); } @@ -440,7 +439,7 @@ class Monero_Gateway extends WC_Payment_Gateway $order->update_meta_data( "Amount requested (XMR)", $amount_xmr2); $order->save(); - $uri = "monero:$address?tx_payment_id=$payment_id"; + $uri = urlencode("monero:".$address."?tx_amount=".$amount_xmr2."&tx_payment_id=".$payment_id); $array_integrated_address = $this->monero_daemon->make_integrated_address($payment_id); if (!isset($array_integrated_address)) { $this->log->add('Monero_Gateway', '[ERROR] Unable get integrated address'); From 903bd8b2d796fcadb4236a0e3ddb4a9f1e30bb09 Mon Sep 17 00:00:00 2001 From: cameleater <34056915+cameleater@users.noreply.github.com> Date: Fri, 18 May 2018 10:57:33 +0800 Subject: [PATCH 43/87] update monero_payments.php should now support all the woocommerce currencies as long as the currency is also supported cryptocompare api --- monero/include/monero_payments.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index ab060eb..2e5fb44 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -583,7 +583,8 @@ class Monero_Gateway extends WC_Payment_Gateway public function retriveprice($currency) { - $xmr_price = file_get_contents('https://min-api.cryptocompare.com/data/price?fsym=XMR&tsyms=BTC,USD,EUR,CAD,INR,GBP,COP,SGD&extraParams=monero_woocommerce'); + $api_link = 'https://min-api.cryptocompare.com/data/price?fsym=XMR&tsyms=BTC,USD,EUR,CAD,INR,GBP,COP,SGD' . ',' . $currency . '&extraParams=monero_woocommerce'; + $xmr_price = file_get_contents($api_link); $price = json_decode($xmr_price, TRUE); if (!isset($price)) { $this->log->add('Monero_Gateway', '[ERROR] Unable to get the price of Monero'); @@ -603,6 +604,8 @@ class Monero_Gateway extends WC_Payment_Gateway return $price['COP']; case 'SGD': return $price['SGD']; + case $currency: + return $price[$currency]; case 'XMR': $price = '1'; return $price; From 8def077ba45344a0a824e1339b325dcdc25b4d17 Mon Sep 17 00:00:00 2001 From: Dimitris Apostolou Date: Sun, 20 May 2018 22:17:38 +0300 Subject: [PATCH 44/87] Fix typos --- README.md | 26 +++++++++++++------------- monero/include/ed25519.php | 2 +- monero/include/monero_payments.php | 26 +++++++++++++------------- monero/library.php | 4 ++-- readme.txt | 4 ++-- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index edc0f26..b9fdee1 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ # MoneroWP A WooCommerce extension for accepting Monero -## Dependancies -This plugin is rather simple but there are a few things that need to be set up before hand. +## Dependencies +This plugin is rather simple but there are a few things that need to be set up beforehand. * A web server! Ideally with the most recent versions of PHP and mysql * A Monero wallet. You can find the official wallet [here](https://getmonero.org/downloads/) * [WordPress](https://wordpress.org) -Wordpress is the backend tool that is needed to use WooCommerce and this Monero plugin +WordPress is the backend tool that is needed to use WooCommerce and this Monero plugin * [WooCommerce](https://woocommerce.com) This Monero plugin is an extension of WooCommerce, which works with WordPress @@ -19,7 +19,7 @@ This Monero plugin is an extension of WooCommerce, which works with WordPress * Unzip the file monerowp_release.zip if you downloaded the zip from the releases page [here](https://github.com/monero-integrations/monerowp/releases). -* Put the plugin in the correct directory: You will need to put the folder named `monero` from this repo/unzipped release into the wordpress plugins directory. This can be found at `path/to/wordpress/folder/wp-content/plugins` +* Put the plugin in the correct directory: You will need to put the folder named `monero` from this repo/unzipped release into the WordPress plugins directory. This can be found at `path/to/wordpress/folder/wp-content/plugins` * Activate the plugin from the WordPress admin panel: Once you login to the admin panel in WordPress, click on "Installed Plugins" under "Plugins". Then simply click "Activate" where it says "Monero - WooCommerce Gateway" @@ -28,20 +28,20 @@ This Monero plugin is an extension of WooCommerce, which works with WordPress * Get your Monero wallet address starting with '4' * Get your wallet secret viewkey from your wallet -A note on privacy: When you validate transactions with your private viewkey, your viewkey is sent to (but not stored on) xmrchain.net over HTTPS. This could potentally allow an attacker to see your incoming, but not outgoing, transactions if he were to get his hands on your viewkey. Even if this were to happen, your funds would still be safe and it would be impossible for somebody to steal your money. For maximum privacy use your own monero-wallet-rpc instance. +A note on privacy: When you validate transactions with your private viewkey, your viewkey is sent to (but not stored on) xmrchain.net over HTTPS. This could potentially allow an attacker to see your incoming, but not outgoing, transactions if he were to get his hands on your viewkey. Even if this were to happen, your funds would still be safe and it would be impossible for somebody to steal your money. For maximum privacy use your own monero-wallet-rpc instance. -## Step 2 Option 2: Get a monero daemon to connect to +## Step 2 Option 2: Get a Monero daemon to connect to ### Option 1: Running a full node yourself -To do this: start the monero daemon on your server and leave it running in the background. This can be accomplished by running `./monerod` inside your monero downloads folder. The first time that you start your node, the monero daemon will download and sync the entire monero blockchain. This can take several hours and is best done on a machine with at least 4GB of ram, an SSD hard drive (with at least 40GB of free space), and a high speed internet connection. +To do this: start the Monero daemon on your server and leave it running in the background. This can be accomplished by running `./monerod` inside your Monero downloads folder. The first time that you start your node, the Monero daemon will download and sync the entire Monero blockchain. This can take several hours and is best done on a machine with at least 4GB of ram, an SSD hard drive (with at least 40GB of free space), and a high speed internet connection. ### Option 2: Connecting to a remote node The easiest way to find a remote node to connect to is to visit [moneroworld.com](https://moneroworld.com/#nodes) and use one of the nodes offered. It is probably easiest to use node.moneroworld.com:18089 which will automatically connect you to a random node. -### Setup your monero wallet-rpc +### Setup your Monero wallet-rpc -* Setup a monero wallet using the monero-wallet-cli tool. If you do not know how to do this you can learn about it at [getmonero.org](https://getmonero.org/resources/user-guides/monero-wallet-cli.html) +* Setup a Monero wallet using the monero-wallet-cli tool. If you do not know how to do this you can learn about it at [getmonero.org](https://getmonero.org/resources/user-guides/monero-wallet-cli.html) * [Create a view-only wallet from that wallet for security.](https://monero.stackexchange.com/questions/3178/how-to-create-a-view-only-wallet-for-the-gui/4582#4582) @@ -61,17 +61,17 @@ The easiest way to find a remote node to connect to is to visit [moneroworld.com If You chose to use viewkey: -* Enter your monero wallet address in the box labled "Monero Address". If you do not know your address, you can run the `address` commmand in your monero wallet +* Enter your Monero wallet address in the box labeled "Monero Address". If you do not know your address, you can run the `address` command in your Monero wallet * Enter your secret viewkey in the box labeled "ViewKey" If you chose to use monero-wallet-rpc: -* Enter your monero wallet address in the box labled "Monero Address". If you do not know your address, you can run the `address` commmand in your monero wallet +* Enter your Monero wallet address in the box labeled "Monero Address". If you do not know your address, you can run the `address` command in your Monero wallet -* Enter the IP address of your server in the box labeled "Monero wallet rpc Host/IP" +* Enter the IP address of your server in the box labeled "Monero wallet RPC Host/IP" -* Enter the port number of the Wallet RPC in the box labeled "Monero wallet rpc port" (will be `18082` if you used the above example). +* Enter the port number of the Wallet RPC in the box labeled "Monero wallet RPC port" (will be `18082` if you used the above example). Finally: diff --git a/monero/include/ed25519.php b/monero/include/ed25519.php index 9852b68..e6d443b 100644 --- a/monero/include/ed25519.php +++ b/monero/include/ed25519.php @@ -48,7 +48,7 @@ class ed25519 public $B; - private $gmp; // Is the GMP extention available? + private $gmp; // Is the GMP extension available? public function __construct() { diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index 2e5fb44..1ad6468 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -136,17 +136,17 @@ class Monero_Gateway extends WC_Payment_Gateway 'title' => __('Use monero-wallet-rpc', 'monero_gateway'), 'label' => __(' Verify transactions with the monero-wallet-rpc ', 'monero_gateway'), 'type' => 'checkbox', - 'description' => __('This must be setup seperatly', 'monero_gateway'), + 'description' => __('This must be setup seperately', 'monero_gateway'), 'default' => 'no' ), 'daemon_host' => array( - 'title' => __('Monero wallet rpc Host/ IP', 'monero_gateway'), + 'title' => __('Monero wallet RPC Host/ IP', 'monero_gateway'), 'type' => 'text', 'desc_tip' => __('This is the Daemon Host/IP to authorize the payment with port', 'monero_gateway'), 'default' => 'localhost', ), 'daemon_port' => array( - 'title' => __('Monero wallet rpc port', 'monero_gateway'), + 'title' => __('Monero wallet RPC port', 'monero_gateway'), 'type' => 'text', 'desc_tip' => __('This is the Daemon Host/IP to authorize the payment with port', 'monero_gateway'), 'default' => '18080', @@ -222,9 +222,9 @@ class Monero_Gateway extends WC_Payment_Gateway { $wallet_amount = $this->monero_daemon->getbalance(); if (!isset($wallet_amount)) { - $this->log->add('Monero_gateway', '[ERROR] Can not connect to monero-wallet-rpc'); - echo "
Your balance is: Not Avaliable
"; - echo "Unlocked balance: Not Avaliable"; + $this->log->add('Monero_gateway', '[ERROR] Cannot connect to monero-wallet-rpc'); + echo "
Your balance is: Not Available
"; + echo "Unlocked balance: Not Available"; } else { @@ -386,7 +386,7 @@ class Monero_Gateway extends WC_Payment_Gateway
- +
@@ -419,7 +419,7 @@ class Monero_Gateway extends WC_Payment_Gateway
- +
@@ -467,7 +467,7 @@ class Monero_Gateway extends WC_Payment_Gateway
- +
@@ -498,7 +498,7 @@ class Monero_Gateway extends WC_Payment_Gateway
- +
@@ -552,7 +552,7 @@ class Monero_Gateway extends WC_Payment_Gateway $rounded_amount = round($final_amount, 12); } else { $new_amount = $amount / $stored_rate_transformed; - $rounded_amount = round($new_amount, 12); //the moneo wallet can't handle decimals smaller than 0.000000000001 + $rounded_amount = round($new_amount, 12); //the Monero wallet can't handle decimals smaller than 0.000000000001 } } else // If the row has not been created then the live exchange rate will be grabbed and stored { @@ -665,7 +665,7 @@ class Monero_Gateway extends WC_Payment_Gateway } return $message; } - public function last_block_seen($height) // sometimes 2 blocks are mined within a few seconds of eacher. Make sure we don't miss one + public function last_block_seen($height) // sometimes 2 blocks are mined within a few seconds of each other. Make sure we don't miss one { if (!isset($_COOKIE['last_seen_block'])) { @@ -810,7 +810,7 @@ class Monero_Gateway extends WC_Payment_Gateway

"; } else { - $this->log->add('Monero_gateway', '[ERROR] Plugin can not reach wallet rpc.'); + $this->log->add('Monero_gateway', '[ERROR] Plugin cannot reach wallet RPC.'); echo "

Error with connection of daemon, see documentation!

"; } } diff --git a/monero/library.php b/monero/library.php index f1cf6d3..d5e895f 100644 --- a/monero/library.php +++ b/monero/library.php @@ -220,7 +220,7 @@ class Monero_Library } /* - * The following functions can all be called to interact with the monero rpc wallet + * The following functions can all be called to interact with the Monero RPC wallet * They will majority of them will return the result as an array * Example: $daemon->address(); where $daemon is an instance of this class, will return the wallet address as string within an array */ @@ -266,7 +266,7 @@ class Monero_Library } /* A payment id can be passed as a string - A random payment id will be generatd if one is not given */ + A random payment id will be generated if one is not given */ public function split_integrated_address($integrated_address) { diff --git a/readme.txt b/readme.txt index 924ad08..2c71579 100644 --- a/readme.txt +++ b/readme.txt @@ -21,12 +21,12 @@ An extension to WooCommerce for accepting Monero as payment in your store. * Add monero payments option to your existing online store with alternative main currency. * Flexible exchange rate calculations fully managed via administrative settings. * Zero fees and no commissions for monero payments processing from any third party. -* Automatic conversion to Monero via realtime exchange rate feed and calculations. +* Automatic conversion to Monero via real time exchange rate feed and calculations. * Ability to set exchange rate calculation multiplier to compensate for any possible losses due to bank conversions and funds transfer fees. == Installation == -1. Install "Monero WooCommerce extension" wordpress plugin just like any other Wordpress plugin. +1. Install "Monero WooCommerce extension" WordPress plugin just like any other WordPress plugin. 2. Activate 3. Setup your monero-wallet-rpc with a view-only wallet 4. Add your monero-wallet-rpc host address and Monero address in the settings panel From d5dd832b910a9d479129aad05342c82de1cba841 Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Fri, 8 Jun 2018 14:39:50 -0500 Subject: [PATCH 45/87] add get_outputs function --- monero/library.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/monero/library.php b/monero/library.php index d5e895f..3fa9f17 100644 --- a/monero/library.php +++ b/monero/library.php @@ -366,6 +366,22 @@ class NodeTools return $array['data']['txs']; } + public function get_outputs($address, $viewkey) + { + $curl = curl_init(); + + curl_setopt_array($curl, array( + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_URL => $this->url . '/api/outputsblocks?address=' . $address . '&viewkey=' . $viewkey . '&limit=5&mempool=0', + )); + $resp = curl_exec($curl); + curl_close($curl); + + $array = json_decode($resp, true); + + return $array['data']['outputs']; + } + public function check_tx($tx_hash, $address, $viewKey) { $curl = curl_init(); From 34eac09175293c00eee8a06aef5284c77691aacf Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Fri, 8 Jun 2018 14:43:30 -0500 Subject: [PATCH 46/87] more efficient non rpc validation --- monero/include/monero_payments.php | 73 ++++++------------------------ 1 file changed, 14 insertions(+), 59 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index 1ad6468..b34f793 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -679,79 +679,34 @@ class Monero_Gateway extends WC_Payment_Gateway return $difference; } } + public function verify_non_rpc($payment_id, $amount, $order_id) { $tools = new NodeTools($this->testnet); - $bc_height = $tools->get_last_block_height(); - - $block_difference = $this->last_block_seen($bc_height); - $txs_from_block = $tools->get_txs_from_block($bc_height); - $tx_count = count($txs_from_block) - 1; // The tx at index 0 is a coinbase tx so it can be ignored + $amount_atomic_units = $amount * 1000000000000; + + $outputs = $tools->get_outputs($this->address, $this->viewKey); + $outs_count = count($outputs); - $output_found; - $block_index; - - if($block_difference != 0) + $i = 0; + $tx_hash; + if($outs_count != 0) { - if($block_difference >= 2){ - $this->log->add('[WARNING] Block difference is greater or equal to 2'); - } - - $txs_from_block_2 = $tools->get_txs_from_block($bc_height - 1); - $tx_count_2 = count($txs_from_block_2) - 1; - - $i = 1; - while($i <= $tx_count_2) + while($i < $outs_count ) { - $tx_hash = $txs_from_block_2[$i]['tx_hash']; - if(strlen($txs_from_block_2[$i]['payment_id']) != 0) + if($outputs[$i]['payment_id'] == $payment_id) { - $result = $tools->check_tx($tx_hash, $this->address, $this->viewKey); - if($result) + if($outputs[$i]['amount'] >= $amount_atomic_units) { - $output_found = $result; - $block_index = $i; - $i = $tx_count_2; // finish loop + $this->on_verified($payment_id, $amount_atomic_units, $order_id); + return true; } } $i++; } } - - $i = 1; - while($i <= $tx_count) - { - $tx_hash = $txs_from_block[$i]['tx_hash']; - if(strlen($txs_from_block[$i]['payment_id']) != 0) - { - $result = $tools->check_tx($tx_hash, $this->address, $this->viewKey); - if($result) - { - $output_found = $result; - $block_index = $i; - $i = $tx_count; // finish loop - } - } - $i++; - } - - if(isset($output_found)) - { - $amount_atomic_units = $amount * 1000000000000; - - if($txs_from_block[$block_index]['payment_id'] == $payment_id && $output_found['amount'] >= $amount_atomic_units) - { - $this->on_verified($payment_id, $amount_atomic_units, $order_id); - } - if($txs_from_block_2[$block_index]['payment_id'] == $payment_id && $output_found['amount'] >= $amount_atomic_units) - { - $this->on_verified($payment_id, $amount_atomic_units, $order_id); - } - - return true; - } - return false; + return false; } public function verify_zero_conf($payment_id, $amount, $order_id) From 8ff6da2d5212cc7310570b9f37bf11ec1dabee4f Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Fri, 8 Jun 2018 15:08:55 -0500 Subject: [PATCH 47/87] always use integrated addresses --- monero/include/monero_payments.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index b34f793..7eded4e 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -343,7 +343,7 @@ class Monero_Gateway extends WC_Payment_Gateway echo ""; $order = wc_get_order($order_id); $amount = floatval(preg_replace('#[^\d.]#', '', $order->get_total())); - $payment_id = $this->set_paymentid_cookie(32); + $payment_id = $this->set_paymentid_cookie(8); $currency = $order->get_currency(); $amount_xmr2 = $this->changeto($amount, $currency, $payment_id); $address = $this->address; @@ -356,8 +356,14 @@ class Monero_Gateway extends WC_Payment_Gateway // If there isn't address (merchant missed that field!), $address will be the Monero address for donating :) $address = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; } - $uri = "monero:$address?tx_payment_id=$payment_id"; + $decoded_address = $this->cryptonote->decode_address($address); + $pub_spendKey = $decoded_address['spendKey']; + $pub_viewKey = $decoded_address['viewKey']; + $integrated_addr = $this->cryptonote->integrated_addr_from_keys($pub_spendKey, $pub_viewKey, $payment_id); + + $uri = "monero:$address?tx_payment_id=$payment_id"; + if($this->zero_confirm){ $this->verify_zero_conf($payment_id, $amount_xmr2, $order_id); } @@ -366,7 +372,7 @@ class Monero_Gateway extends WC_Payment_Gateway } if($this->confirmed == false) { - echo "

We are waiting for your transaction to be confirmed

"; + echo "

We are waiting for your transaction to be confirmed

"; } if($this->confirmed) { @@ -375,7 +381,6 @@ class Monero_Gateway extends WC_Payment_Gateway echo " -

*don't forget to include the payment ID in your transaction

@@ -399,12 +404,10 @@ class Monero_Gateway extends WC_Payment_Gateway
Send:
".$amount_xmr2."
- Payment ID: -
".$payment_id."
To this address: -
".$address."
+
".$integrated_addr."
Or scan QR: From b30ee78993ca9a84802111a00b0c53fcf40f509b Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Fri, 8 Jun 2018 23:56:06 -0400 Subject: [PATCH 48/87] add BCMath to dependencies --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index b9fdee1..087fd6a 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ WordPress is the backend tool that is needed to use WooCommerce and this Monero * [WooCommerce](https://woocommerce.com) This Monero plugin is an extension of WooCommerce, which works with WordPress +* [BCMath](http://php.net/manual/en/book.bc.php) +A PHP extension used for arbitrary precision maths + ## Step 1: Activating the plugin * Downloading: First of all, you will need to download the plugin. You can download the latest release as a .zip file from https://github.com/monero-integrations/monerowp/releases If you wish, you can also download the latest source code from GitHub. This can be done with the command `git clone https://github.com/monero-integrations/monerowp.git` or can be downloaded as a zip file from the GitHub web page. From 4cbcde5e7b6fb21e9e9721c3205c76a2ed97dd2f Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Fri, 8 Jun 2018 23:16:53 -0500 Subject: [PATCH 49/87] add zero_conf option to get_outputs --- monero/library.php | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/monero/library.php b/monero/library.php index 3fa9f17..8a75108 100644 --- a/monero/library.php +++ b/monero/library.php @@ -366,14 +366,27 @@ class NodeTools return $array['data']['txs']; } - public function get_outputs($address, $viewkey) + public function get_outputs($address, $viewkey, $zero_conf = false) { $curl = curl_init(); - curl_setopt_array($curl, array( - CURLOPT_RETURNTRANSFER => 1, - CURLOPT_URL => $this->url . '/api/outputsblocks?address=' . $address . '&viewkey=' . $viewkey . '&limit=5&mempool=0', - )); + if(!$zero_conf) + { + curl_setopt_array($curl, array( + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_URL => $this->url . '/api/outputsblocks?address=' . $address . '&viewkey=' . $viewkey . '&limit=5&mempool=0', + )); + } + + // also look in mempool if accepting zero confirmation transactions + if($zero_conf) + { + curl_setopt_array($curl, array( + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_URL => $this->url . '/api/outputsblocks?address=' . $address . '&viewkey=' . $viewkey . '&limit=5&mempool=1', + )); + } + $resp = curl_exec($curl); curl_close($curl); From 55356ace73ee545658d2f694d2a07962015f569d Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Fri, 8 Jun 2018 23:21:54 -0500 Subject: [PATCH 50/87] refactor zero confirmation verification code --- monero/include/monero_payments.php | 52 ++++-------------------------- 1 file changed, 6 insertions(+), 46 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index 7eded4e..3a8f0aa 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -364,12 +364,8 @@ class Monero_Gateway extends WC_Payment_Gateway $uri = "monero:$address?tx_payment_id=$payment_id"; - if($this->zero_confirm){ - $this->verify_zero_conf($payment_id, $amount_xmr2, $order_id); - } - else{ - $this->verify_non_rpc($payment_id, $amount_xmr2, $order_id); - } + $this->verify_non_rpc($payment_id, $amount_xmr2, $order_id, $this->zero_confirm); + if($this->confirmed == false) { echo "

We are waiting for your transaction to be confirmed

"; @@ -683,13 +679,13 @@ class Monero_Gateway extends WC_Payment_Gateway } } - public function verify_non_rpc($payment_id, $amount, $order_id) + public function verify_non_rpc($payment_id, $amount, $order_id, $accept_zero_conf = false) { $tools = new NodeTools($this->testnet); - - $amount_atomic_units = $amount * 1000000000000; - $outputs = $tools->get_outputs($this->address, $this->viewKey); + $amount_atomic_units = $amount * 1000000000000; + + $outputs = $tools->get_outputs($this->address, $this->viewKey, $accept_zero_conf); $outs_count = count($outputs); $i = 0; @@ -711,42 +707,6 @@ class Monero_Gateway extends WC_Payment_Gateway } return false; } - - public function verify_zero_conf($payment_id, $amount, $order_id) - { - $tools = new NodeTools($this->testnet); - $txs_from_mempool = $tools->get_mempool_txs();; - $tx_count = count($txs_from_mempool['data']['txs']); - $i = 0; - $output_found; - - while($i <= $tx_count) - { - $tx_hash = $txs_from_mempool['data']['txs'][$i]['tx_hash']; - if(strlen($txs_from_mempool['data']['txs'][$i]['payment_id']) != 0) - { - $result = $tools->check_tx($tx_hash, $this->address, $this->viewKey); - if($result) - { - $output_found = $result; - $tx_i = $i; - $i = $tx_count; // finish loop - } - } - $i++; - } - if(isset($output_found)) - { - $amount_atomic_units = $amount * 1000000000000; - if($txs_from_mempool['data']['txs'][$tx_i]['payment_id'] == $payment_id && $output_found['amount'] >= $amount_atomic_units) - { - $this->on_verified($payment_id, $amount_atomic_units, $order_id); - } - return true; - } - else - return false; - } public function do_ssl_check() { From 8ec095d9d62dff068d465bac7ccd1e437e458384 Mon Sep 17 00:00:00 2001 From: SerHack <27734319+serhack@users.noreply.github.com> Date: Thu, 21 Jun 2018 18:24:41 +0200 Subject: [PATCH 51/87] Fix #62 . Thanks @moryoav --- monero/include/monero_payments.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index 3a8f0aa..8f1e030 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -77,8 +77,8 @@ class Monero_Gateway extends WC_Payment_Gateway if (is_admin()) { /* Save Settings */ add_action('woocommerce_update_options_payment_gateways_' . $this->id, array($this, 'process_admin_options')); - add_filter('woocommerce_currencies', 'add_my_currency'); - add_filter('woocommerce_currency_symbol', 'add_my_currency_symbol', 10, 2); + add_filter('woocommerce_currencies', array($this,'add_my_currency')); + add_filter('woocommerce_currency_symbol', array($this,'add_my_currency_symbol'), 10, 2); add_action('woocommerce_email_before_order_table', array($this, 'email_instructions'), 10, 2); } $this->monero_daemon = new Monero_Library($this->host, $this->port); @@ -190,7 +190,7 @@ class Monero_Gateway extends WC_Payment_Gateway return $currencies; } - function add_my_currency_symbol($currency_symbol, $currency) + public function add_my_currency_symbol($currency_symbol, $currency) { switch ($currency) { case 'XMR': From 0c64b9ce5f3c3c12567f70b5b4e167cdef23d4de Mon Sep 17 00:00:00 2001 From: SerHack <27734319+serhack@users.noreply.github.com> Date: Fri, 6 Jul 2018 21:50:06 +0200 Subject: [PATCH 52/87] Adding support for subscriptions --- monero/include/monero_payments.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php index 92c8d04..1e44cec 100644 --- a/monero/include/monero_payments.php +++ b/monero/include/monero_payments.php @@ -83,6 +83,18 @@ class Monero_Gateway extends WC_Payment_Gateway } $this->monero_daemon = new Monero_Library($this->host, $this->port); $this->cryptonote = new Cryptonote(); + + $this->supports = array( 'subscriptions', 'products' ); + $this->supports = array( + 'products', + 'subscriptions', + 'subscription_cancellation', + 'subscription_suspension', + 'subscription_reactivation', + 'subscription_amount_changes', + 'subscription_date_changes', + 'subscription_payment_method_change' + ); } public function get_icon() From 9e1d4b50c71f968e236798a7bdb7454703925f1e Mon Sep 17 00:00:00 2001 From: cryptochangements34 Date: Fri, 27 Jul 2018 12:58:13 -0500 Subject: [PATCH 53/87] Update 2018 --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 1812388..1c38054 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Monero Integrations +Copyright (c) 2017-2018 Monero Integrations Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 4def1b771e8b4d08e550e158bbb9fba1b865da95 Mon Sep 17 00:00:00 2001 From: mosu forge Date: Sat, 22 Sep 2018 07:57:35 -0700 Subject: [PATCH 54/87] Rewrite plugin to v3.0 --- .gitignore | 5 + LICENSE | 3 +- README.md | 129 ++- assets/css/monero-gateway-order-page.css | 78 ++ assets/images/monero-accepted-here.png | Bin 0 -> 4393 bytes .../images/monero-icon-admin.png | Bin assets/images/monero-icon.png | Bin 0 -> 2835 bytes assets/js/clipboard.js | 939 ++++++++++++++++++ assets/js/clipboard.min.js | 7 + assets/js/monero-gateway-order-page.js | 161 +++ assets/js/qrcode.js | 614 ++++++++++++ assets/js/qrcode.min.js | 1 + .../monero-wallet-rpc.service | 14 + assets/systemd-unit-files/monerod.service | 14 + .../admin/class-monero-admin-interface.php | 133 +++ .../class-monero-admin-payments-list.php | 276 +++++ .../admin/monero-gateway-admin-settings.php | 114 +++ include/class-monero-base58.php | 354 +++++++ include/class-monero-cryptonote.php | 312 ++++++ include/class-monero-explorer-tools.php | 90 ++ include/class-monero-gateway.php | 789 +++++++++++++++ .../class-monero-wallet-rpc.php | 228 ++--- {monero/include => include/crypto}/SHA3.php | 4 +- .../include => include/crypto}/ed25519.php | 3 + monero-woocommerce-gateway.php | 249 +++++ monero/include/base58.php | 354 ------- monero/include/cryptonote.php | 305 ------ monero/include/monero_payments.php | 748 -------------- monero/monero_gateway.php | 65 -- readme.txt | 7 +- .../admin/order-history-error-page.php | 1 + .../admin/order-history-page.php | 82 ++ .../monero-gateway/admin/settings-page.php | 54 + .../customer/order-email-block.php | 56 ++ .../customer/order-email-error-block.php | 5 + .../customer/order-error-page.php | 4 + .../monero-gateway/customer/order-page.php | 100 ++ 37 files changed, 4613 insertions(+), 1685 deletions(-) create mode 100644 assets/css/monero-gateway-order-page.css create mode 100644 assets/images/monero-accepted-here.png rename monero/assets/monero_icon.png => assets/images/monero-icon-admin.png (100%) create mode 100644 assets/images/monero-icon.png create mode 100644 assets/js/clipboard.js create mode 100644 assets/js/clipboard.min.js create mode 100644 assets/js/monero-gateway-order-page.js create mode 100644 assets/js/qrcode.js create mode 100644 assets/js/qrcode.min.js create mode 100644 assets/systemd-unit-files/monero-wallet-rpc.service create mode 100644 assets/systemd-unit-files/monerod.service create mode 100644 include/admin/class-monero-admin-interface.php create mode 100644 include/admin/class-monero-admin-payments-list.php create mode 100644 include/admin/monero-gateway-admin-settings.php create mode 100644 include/class-monero-base58.php create mode 100644 include/class-monero-cryptonote.php create mode 100644 include/class-monero-explorer-tools.php create mode 100644 include/class-monero-gateway.php rename monero/library.php => include/class-monero-wallet-rpc.php (67%) rename {monero/include => include/crypto}/SHA3.php (99%) rename {monero/include => include/crypto}/ed25519.php (99%) create mode 100644 monero-woocommerce-gateway.php delete mode 100644 monero/include/base58.php delete mode 100644 monero/include/cryptonote.php delete mode 100644 monero/include/monero_payments.php delete mode 100644 monero/monero_gateway.php create mode 100644 templates/monero-gateway/admin/order-history-error-page.php create mode 100644 templates/monero-gateway/admin/order-history-page.php create mode 100644 templates/monero-gateway/admin/settings-page.php create mode 100644 templates/monero-gateway/customer/order-email-block.php create mode 100644 templates/monero-gateway/customer/order-email-error-block.php create mode 100644 templates/monero-gateway/customer/order-error-page.php create mode 100644 templates/monero-gateway/customer/order-page.php diff --git a/.gitignore b/.gitignore index a681dcf..e8f8c86 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ *.xml *.iml + +# Emacs +*~ +\#* +.\#* \ No newline at end of file diff --git a/LICENSE b/LICENSE index 1c38054..1dba848 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License -Copyright (c) 2017-2018 Monero Integrations +Copyright (c) 2018, Ryo Currency Project +Portions Copyright (c) 2017-2018, Monero Integrations Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 087fd6a..aea0a99 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,107 @@ -# MoneroWP -A WooCommerce extension for accepting Monero +# Monero Gateway for WooCommerce -## Dependencies -This plugin is rather simple but there are a few things that need to be set up beforehand. +## Features -* A web server! Ideally with the most recent versions of PHP and mysql +* Payment validation done through either `monero-wallet-rpc` or the [xmrchain.net blockchain explorer](https://xmrchain.net/). +* Validates payments with `cron`, so does not require users to stay on the order confirmation page for their order to validate. +* Order status updates are done through AJAX instead of Javascript page reloads. +* Customers can pay with multiple transactions and are notified as soon as transactions hit the mempool. +* Configurable block confirmations, from `0` for zero confirm to `60` for high ticket purchases. +* Live price updates every minute; total amount due is locked in after the order is placed for a configurable amount of time (default 60 minutes) so the price does not change after order has been made. +* Hooks into emails, order confirmation page, customer order history page, and admin order details page. +* View all payments received to your wallet with links to the blockchain explorer and associated orders. +* Optionally display all prices on your store in terms of Monero. +* Shortcodes! Display exchange rates in numerous currencies. -* A Monero wallet. You can find the official wallet [here](https://getmonero.org/downloads/) +## Requirements -* [WordPress](https://wordpress.org) -WordPress is the backend tool that is needed to use WooCommerce and this Monero plugin +* Monero wallet to receive payments - [GUI](https://github.com/monero-project/monero-gui/releases) - [CLI](https://github.com/monero-project/monero/releases) - [Paper](https://moneroaddress.org/) +* [BCMath](http://php.net/manual/en/book.bc.php) - A PHP extension used for arbitrary precision maths -* [WooCommerce](https://woocommerce.com) -This Monero plugin is an extension of WooCommerce, which works with WordPress +## Installing the plugin -* [BCMath](http://php.net/manual/en/book.bc.php) -A PHP extension used for arbitrary precision maths +* Download the plugin from the [releases page](https://github.com/monero-integrations/monerowp) or clone with `git clone https://github.com/monero-integrations/monerowp` +* Unzip or place the `monero-woocommerce-gateway` folder in the `wp-content/plugins` directory. +* Activate "Monero Woocommerce Gateway" in your WordPress admin dashboard. +* It is highly recommended that you use native cronjobs instead of WordPress's "Poor Man's Cron" by adding `define('DISABLE_WP_CRON', true);` into your `wp-config.php` file and adding `* * * * * wget -q -O - https://yourstore.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1` to your crontab. -## Step 1: Activating the plugin -* Downloading: First of all, you will need to download the plugin. You can download the latest release as a .zip file from https://github.com/monero-integrations/monerowp/releases If you wish, you can also download the latest source code from GitHub. This can be done with the command `git clone https://github.com/monero-integrations/monerowp.git` or can be downloaded as a zip file from the GitHub web page. +## Option 1: Use your wallet address and viewkey -* Unzip the file monerowp_release.zip if you downloaded the zip from the releases page [here](https://github.com/monero-integrations/monerowp/releases). +This is the easiest way to start accepting Monero on your website. You'll need: -* Put the plugin in the correct directory: You will need to put the folder named `monero` from this repo/unzipped release into the WordPress plugins directory. This can be found at `path/to/wordpress/folder/wp-content/plugins` +* Your Monero wallet address starting with `4` +* Your wallet's secret viewkey -* Activate the plugin from the WordPress admin panel: Once you login to the admin panel in WordPress, click on "Installed Plugins" under "Plugins". Then simply click "Activate" where it says "Monero - WooCommerce Gateway" +Then simply select the `viewkey` option in the settings page and paste your address and viewkey. You're all set! -## Step 2 Option 1: Use your wallet address and viewkey +Note on privacy: when you validate transactions with your private viewkey, your viewkey is sent to (but not stored on) xmrchain.net over HTTPS. This could potentially allow an attacker to see your incoming, but not outgoing, transactions if they were to get his hands on your viewkey. Even if this were to happen, your funds would still be safe and it would be impossible for somebody to steal your money. For maximum privacy use your own `monero-wallet-rpc` instance. -* Get your Monero wallet address starting with '4' -* Get your wallet secret viewkey from your wallet +## Option 2: Using `monero-wallet-rpc` -A note on privacy: When you validate transactions with your private viewkey, your viewkey is sent to (but not stored on) xmrchain.net over HTTPS. This could potentially allow an attacker to see your incoming, but not outgoing, transactions if he were to get his hands on your viewkey. Even if this were to happen, your funds would still be safe and it would be impossible for somebody to steal your money. For maximum privacy use your own monero-wallet-rpc instance. +The most secure way to accept Monero on your website. You'll need: -## Step 2 Option 2: Get a Monero daemon to connect to +* Root access to your webserver +* Latest [Monero-currency binaries](https://github.com/monero-project/monero/releases) -### Option 1: Running a full node yourself +After downloading (or compiling) the Monero binaries on your server, install the [systemd unit files](https://github.com/monero-integrations/monerowp/tree/master/assets/systemd-unit-files) or run `monerod` and `monero-wallet-rpc` with `screen` or `tmux`. You can skip running `monerod` by using a remote node with `monero-wallet-rpc` by adding `--daemon-address node.moneroworld.com:18089` to the `monero-wallet-rpc.service` file. -To do this: start the Monero daemon on your server and leave it running in the background. This can be accomplished by running `./monerod` inside your Monero downloads folder. The first time that you start your node, the Monero daemon will download and sync the entire Monero blockchain. This can take several hours and is best done on a machine with at least 4GB of ram, an SSD hard drive (with at least 40GB of free space), and a high speed internet connection. +Note on security: using this option, while the most secure, requires you to run the Monero wallet RPC program on your server. Best practice for this is to use a view-only wallet since otherwise your server would be running a hot-wallet and a security breach could allow hackers to empty your funds. -### Option 2: Connecting to a remote node -The easiest way to find a remote node to connect to is to visit [moneroworld.com](https://moneroworld.com/#nodes) and use one of the nodes offered. It is probably easiest to use node.moneroworld.com:18089 which will automatically connect you to a random node. +## Configuration -### Setup your Monero wallet-rpc +* `Enable / Disable` - Turn on or off Monero gateway. (Default: Disable) +* `Title` - Name of the payment gateway as displayed to the customer. (Default: Monero Gateway) +* `Discount for using Monero` - Percentage discount applied to orders for paying with Monero. Can also be negative to apply a surcharge. (Default: 0) +* `Order valid time` - Number of seconds after order is placed that the transaction must be seen in the mempool. (Default: 3600 [1 hour]) +* `Number of confirmations` - Number of confirmations the transaction must recieve before the order is marked as complete. Use `0` for nearly instant confirmation. (Default: 5) +* `Confirmation Type` - Confirm transactions with either your viewkey, or by using `monero-wallet-rpc`. (Default: viewkey) +* `Monero Address` (if confirmation type is viewkey) - Your public Monero address starting with 4. (No default) +* `Secret Viewkey` (if confirmation type is viewkey) - Your *private* viewkey (No default) +* `Monero wallet RPC Host/IP` (if confirmation type is `monero-wallet-rpc`) - IP address where the wallet rpc is running. It is highly discouraged to run the wallet anywhere other than the local server! (Default: 127.0.0.1) +* `Monero wallet RPC port` (if confirmation type is `monero-wallet-rpc`) - Port the wallet rpc is bound to with the `--rpc-bind-port` argument. (Default 18080) +* `Testnet` - Check this to change the blockchain explorer links to the testnet explorer. (Default: unchecked) +* `SSL warnings` - Check this to silence SSL warnings. (Default: unchecked) +* `Show QR Code` - Show payment QR codes. There is no Monero software that can read QR codes at this time (Default: unchecked) +* `Show Prices in Monero` - Convert all prices on the frontend to Monero. Experimental feature, only use if you do not accept any other payment option. (Default: unchecked) +* `Display Decimals` (if show prices in Monero is enabled) - Number of decimals to round prices to on the frontend. The final order amount will not be rounded and will be displayed down to the nanoMonero. (Default: 12) -* Setup a Monero wallet using the monero-wallet-cli tool. If you do not know how to do this you can learn about it at [getmonero.org](https://getmonero.org/resources/user-guides/monero-wallet-cli.html) +## Shortcodes -* [Create a view-only wallet from that wallet for security.](https://monero.stackexchange.com/questions/3178/how-to-create-a-view-only-wallet-for-the-gui/4582#4582) +This plugin makes available two shortcodes that you can use in your theme. -* Start the Wallet RPC and leave it running in the background. This can be accomplished by running `./monero-wallet-rpc --rpc-bind-port 18082 --disable-rpc-login --log-level 2 --wallet-file /path/viewOnlyWalletFile` where "/path/viewOnlyWalletFile" is the wallet file for your view-only wallet. If you wish to use a remote node you can add the `--daemon-address` flag followed by the address of the node. `--daemon-address node.moneroworld.com:18089` for example. +#### Live price shortcode -## Step 4: Setup Monero Gateway in WooCommerce +This will display the price of Monero in the selected currency. If no currency is provided, the store's default currency will be used. -* Navigate to the "settings" panel in the WooCommerce widget in the WordPress admin panel. +``` +[monero-price] +[monero-price currency="BTC"] +[monero-price currency="USD"] +[monero-price currency="CAD"] +[monero-price currency="EUR"] +[monero-price currency="GBP"] +``` +Will display: +``` +1 XMR = 123.68000 USD +1 XMR = 0.01827000 BTC +1 XMR = 123.68000 USD +1 XMR = 168.43000 CAD +1 XMR = 105.54000 EUR +1 XMR = 94.84000 GBP +``` -* Click on "Checkout" -* Select "Monero GateWay" +#### Monero accepted here badge -* Check the box labeled "Enable this payment gateway" +This will display a badge showing that you accept Monero-currency. -* Check either "Use ViewKey" or "Use monero-wallet-rpc" +`[monero-accepted-here]` -If You chose to use viewkey: +![Monero Accepted Here](/assets/images/monero-accepted-here.png?raw=true "Monero Accepted Here") -* Enter your Monero wallet address in the box labeled "Monero Address". If you do not know your address, you can run the `address` command in your Monero wallet +## Donations -* Enter your secret viewkey in the box labeled "ViewKey" +monero-integrations: 44krVcL6TPkANjpFwS2GWvg1kJhTrN7y9heVeQiDJ3rP8iGbCd5GeA4f3c2NKYHC1R4mCgnW7dsUUUae2m9GiNBGT4T8s2X -If you chose to use monero-wallet-rpc: - -* Enter your Monero wallet address in the box labeled "Monero Address". If you do not know your address, you can run the `address` command in your Monero wallet - -* Enter the IP address of your server in the box labeled "Monero wallet RPC Host/IP" - -* Enter the port number of the Wallet RPC in the box labeled "Monero wallet RPC port" (will be `18082` if you used the above example). - -Finally: - -* Click on "Save changes" - -## Donating to the Devs :) -XMR Address : `44krVcL6TPkANjpFwS2GWvg1kJhTrN7y9heVeQiDJ3rP8iGbCd5GeA4f3c2NKYHC1R4mCgnW7dsUUUae2m9GiNBGT4T8s2X` +mosu-forge: 4A6BQp7do5MTxpCguq1kAS27yMLpbHcf89Ha2a8Shayt2vXkCr6QRpAXr1gLYRV5esfzoK3vLJTm5bDWk5gKmNrT6s6xZep diff --git a/assets/css/monero-gateway-order-page.css b/assets/css/monero-gateway-order-page.css new file mode 100644 index 0000000..b4c51f3 --- /dev/null +++ b/assets/css/monero-gateway-order-page.css @@ -0,0 +1,78 @@ +#monero_payment_messages > span { + display:none; +} +.monero_details_row { + display: flex !important; + align-items: center; + margin:0 -8px; +} +.monero_details_row > * { + padding:0 8px; +} +.monero_details_left { +} +.monero_details_main { + flex-grow: 1; + word-break:break-all; +} +.monero_details_right.button-row { + display:flex; + margin-top: 5px; + align-self: self-start; +} +.monero_details_right.button-row button { + width: 32px; + height: 32px; + padding: 6px 2px; + margin: 0 4px; + line-height:28px; + text-align:center; +} +#monero_integrated_address { + line-height: 16px; +} +#monero_qr_code_container { + position:fixed; + top:0; + left:0; + right:0; + bottom:0; + z-index:9999; + background:rgba(0,0,0,0.5); +} +#monero_qr_code { + position: absolute; + width: 256px; + height: 256px; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + box-sizing: content-box; + padding: 20px; + background: white; + border-radius: 5px; +} +#monero_toast { + position: fixed; + z-index: 999; + top: 32px; + right: 12px; +} +#monero_toast > div { + display: block; + position: relative; + overflow: hidden; + margin-top: 10px; + margin-right: 10px; + padding: 20px; + width: 300px; + border-radius: 3px; + color: white; + right: -400px; +} +#monero_toast > div.success { + background: rgba(68, 190, 117, 0.8); +} +#monero_toast > div.error { + background: rgba(195, 60, 60, 0.8); +} \ No newline at end of file diff --git a/assets/images/monero-accepted-here.png b/assets/images/monero-accepted-here.png new file mode 100644 index 0000000000000000000000000000000000000000..27ba0c37b37dd0ab13c308b0b66f65c6ad5649a5 GIT binary patch literal 4393 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 literal 0 HcmV?d00001 diff --git a/monero/assets/monero_icon.png b/assets/images/monero-icon-admin.png similarity index 100% rename from monero/assets/monero_icon.png rename to assets/images/monero-icon-admin.png diff --git a/assets/images/monero-icon.png b/assets/images/monero-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..65240e11fbf8199caa7a4caf00093a90e1c522d3 GIT binary patch literal 2835 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 Ys2un6WJq1N=H_pz1JQ->E8aiuKO3v3Pyhe` literal 0 HcmV?d00001 diff --git a/assets/js/clipboard.js b/assets/js/clipboard.js new file mode 100644 index 0000000..5e4822a --- /dev/null +++ b/assets/js/clipboard.js @@ -0,0 +1,939 @@ +/*! + * clipboard.js v2.0.0 + * https://zenorocha.github.io/clipboard.js + * + * Licensed MIT © Zeno Rocha + */ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["ClipboardJS"] = factory(); + else + root["ClipboardJS"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // identity function for calling harmony imports with the correct context +/******/ __webpack_require__.i = function(value) { return value; }; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 3); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + +var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;(function (global, factory) { + if (true) { + !(__WEBPACK_AMD_DEFINE_ARRAY__ = [module, __webpack_require__(7)], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), + __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? + (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + } else if (typeof exports !== "undefined") { + factory(module, require('select')); + } else { + var mod = { + exports: {} + }; + factory(mod, global.select); + global.clipboardAction = mod.exports; + } +})(this, function (module, _select) { + 'use strict'; + + var _select2 = _interopRequireDefault(_select); + + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { + default: obj + }; + } + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; + } : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + var _createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; + }(); + + var ClipboardAction = function () { + /** + * @param {Object} options + */ + function ClipboardAction(options) { + _classCallCheck(this, ClipboardAction); + + this.resolveOptions(options); + this.initSelection(); + } + + /** + * Defines base properties passed from constructor. + * @param {Object} options + */ + + + _createClass(ClipboardAction, [{ + key: 'resolveOptions', + value: function resolveOptions() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + this.action = options.action; + this.container = options.container; + this.emitter = options.emitter; + this.target = options.target; + this.text = options.text; + this.trigger = options.trigger; + + this.selectedText = ''; + } + }, { + key: 'initSelection', + value: function initSelection() { + if (this.text) { + this.selectFake(); + } else if (this.target) { + this.selectTarget(); + } + } + }, { + key: 'selectFake', + value: function selectFake() { + var _this = this; + + var isRTL = document.documentElement.getAttribute('dir') == 'rtl'; + + this.removeFake(); + + this.fakeHandlerCallback = function () { + return _this.removeFake(); + }; + this.fakeHandler = this.container.addEventListener('click', this.fakeHandlerCallback) || true; + + this.fakeElem = document.createElement('textarea'); + // Prevent zooming on iOS + this.fakeElem.style.fontSize = '12pt'; + // Reset box model + this.fakeElem.style.border = '0'; + this.fakeElem.style.padding = '0'; + this.fakeElem.style.margin = '0'; + // Move element out of screen horizontally + this.fakeElem.style.position = 'absolute'; + this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px'; + // Move element to the same position vertically + var yPosition = window.pageYOffset || document.documentElement.scrollTop; + this.fakeElem.style.top = yPosition + 'px'; + + this.fakeElem.setAttribute('readonly', ''); + this.fakeElem.value = this.text; + + this.container.appendChild(this.fakeElem); + + this.selectedText = (0, _select2.default)(this.fakeElem); + this.copyText(); + } + }, { + key: 'removeFake', + value: function removeFake() { + if (this.fakeHandler) { + this.container.removeEventListener('click', this.fakeHandlerCallback); + this.fakeHandler = null; + this.fakeHandlerCallback = null; + } + + if (this.fakeElem) { + this.container.removeChild(this.fakeElem); + this.fakeElem = null; + } + } + }, { + key: 'selectTarget', + value: function selectTarget() { + this.selectedText = (0, _select2.default)(this.target); + this.copyText(); + } + }, { + key: 'copyText', + value: function copyText() { + var succeeded = void 0; + + try { + succeeded = document.execCommand(this.action); + } catch (err) { + succeeded = false; + } + + this.handleResult(succeeded); + } + }, { + key: 'handleResult', + value: function handleResult(succeeded) { + this.emitter.emit(succeeded ? 'success' : 'error', { + action: this.action, + text: this.selectedText, + trigger: this.trigger, + clearSelection: this.clearSelection.bind(this) + }); + } + }, { + key: 'clearSelection', + value: function clearSelection() { + if (this.trigger) { + this.trigger.focus(); + } + + window.getSelection().removeAllRanges(); + } + }, { + key: 'destroy', + value: function destroy() { + this.removeFake(); + } + }, { + key: 'action', + set: function set() { + var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy'; + + this._action = action; + + if (this._action !== 'copy' && this._action !== 'cut') { + throw new Error('Invalid "action" value, use either "copy" or "cut"'); + } + }, + get: function get() { + return this._action; + } + }, { + key: 'target', + set: function set(target) { + if (target !== undefined) { + if (target && (typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object' && target.nodeType === 1) { + if (this.action === 'copy' && target.hasAttribute('disabled')) { + throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'); + } + + if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) { + throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes'); + } + + this._target = target; + } else { + throw new Error('Invalid "target" value, use a valid Element'); + } + } + }, + get: function get() { + return this._target; + } + }]); + + return ClipboardAction; + }(); + + module.exports = ClipboardAction; +}); + +/***/ }), +/* 1 */ +/***/ (function(module, exports, __webpack_require__) { + +var is = __webpack_require__(6); +var delegate = __webpack_require__(5); + +/** + * Validates all params and calls the right + * listener function based on its target type. + * + * @param {String|HTMLElement|HTMLCollection|NodeList} target + * @param {String} type + * @param {Function} callback + * @return {Object} + */ +function listen(target, type, callback) { + if (!target && !type && !callback) { + throw new Error('Missing required arguments'); + } + + if (!is.string(type)) { + throw new TypeError('Second argument must be a String'); + } + + if (!is.fn(callback)) { + throw new TypeError('Third argument must be a Function'); + } + + if (is.node(target)) { + return listenNode(target, type, callback); + } + else if (is.nodeList(target)) { + return listenNodeList(target, type, callback); + } + else if (is.string(target)) { + return listenSelector(target, type, callback); + } + else { + throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList'); + } +} + +/** + * Adds an event listener to a HTML element + * and returns a remove listener function. + * + * @param {HTMLElement} node + * @param {String} type + * @param {Function} callback + * @return {Object} + */ +function listenNode(node, type, callback) { + node.addEventListener(type, callback); + + return { + destroy: function() { + node.removeEventListener(type, callback); + } + } +} + +/** + * Add an event listener to a list of HTML elements + * and returns a remove listener function. + * + * @param {NodeList|HTMLCollection} nodeList + * @param {String} type + * @param {Function} callback + * @return {Object} + */ +function listenNodeList(nodeList, type, callback) { + Array.prototype.forEach.call(nodeList, function(node) { + node.addEventListener(type, callback); + }); + + return { + destroy: function() { + Array.prototype.forEach.call(nodeList, function(node) { + node.removeEventListener(type, callback); + }); + } + } +} + +/** + * Add an event listener to a selector + * and returns a remove listener function. + * + * @param {String} selector + * @param {String} type + * @param {Function} callback + * @return {Object} + */ +function listenSelector(selector, type, callback) { + return delegate(document.body, selector, type, callback); +} + +module.exports = listen; + + +/***/ }), +/* 2 */ +/***/ (function(module, exports) { + +function E () { + // Keep this empty so it's easier to inherit from + // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3) +} + +E.prototype = { + on: function (name, callback, ctx) { + var e = this.e || (this.e = {}); + + (e[name] || (e[name] = [])).push({ + fn: callback, + ctx: ctx + }); + + return this; + }, + + once: function (name, callback, ctx) { + var self = this; + function listener () { + self.off(name, listener); + callback.apply(ctx, arguments); + }; + + listener._ = callback + return this.on(name, listener, ctx); + }, + + emit: function (name) { + var data = [].slice.call(arguments, 1); + var evtArr = ((this.e || (this.e = {}))[name] || []).slice(); + var i = 0; + var len = evtArr.length; + + for (i; i < len; i++) { + evtArr[i].fn.apply(evtArr[i].ctx, data); + } + + return this; + }, + + off: function (name, callback) { + var e = this.e || (this.e = {}); + var evts = e[name]; + var liveEvents = []; + + if (evts && callback) { + for (var i = 0, len = evts.length; i < len; i++) { + if (evts[i].fn !== callback && evts[i].fn._ !== callback) + liveEvents.push(evts[i]); + } + } + + // Remove event from queue to prevent memory leak + // Suggested by https://github.com/lazd + // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910 + + (liveEvents.length) + ? e[name] = liveEvents + : delete e[name]; + + return this; + } +}; + +module.exports = E; + + +/***/ }), +/* 3 */ +/***/ (function(module, exports, __webpack_require__) { + +var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;(function (global, factory) { + if (true) { + !(__WEBPACK_AMD_DEFINE_ARRAY__ = [module, __webpack_require__(0), __webpack_require__(2), __webpack_require__(1)], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), + __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? + (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + } else if (typeof exports !== "undefined") { + factory(module, require('./clipboard-action'), require('tiny-emitter'), require('good-listener')); + } else { + var mod = { + exports: {} + }; + factory(mod, global.clipboardAction, global.tinyEmitter, global.goodListener); + global.clipboard = mod.exports; + } +})(this, function (module, _clipboardAction, _tinyEmitter, _goodListener) { + 'use strict'; + + var _clipboardAction2 = _interopRequireDefault(_clipboardAction); + + var _tinyEmitter2 = _interopRequireDefault(_tinyEmitter); + + var _goodListener2 = _interopRequireDefault(_goodListener); + + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { + default: obj + }; + } + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; + } : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + var _createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; + }(); + + function _possibleConstructorReturn(self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && (typeof call === "object" || typeof call === "function") ? call : self; + } + + function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; + } + + var Clipboard = function (_Emitter) { + _inherits(Clipboard, _Emitter); + + /** + * @param {String|HTMLElement|HTMLCollection|NodeList} trigger + * @param {Object} options + */ + function Clipboard(trigger, options) { + _classCallCheck(this, Clipboard); + + var _this = _possibleConstructorReturn(this, (Clipboard.__proto__ || Object.getPrototypeOf(Clipboard)).call(this)); + + _this.resolveOptions(options); + _this.listenClick(trigger); + return _this; + } + + /** + * Defines if attributes would be resolved using internal setter functions + * or custom functions that were passed in the constructor. + * @param {Object} options + */ + + + _createClass(Clipboard, [{ + key: 'resolveOptions', + value: function resolveOptions() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + this.action = typeof options.action === 'function' ? options.action : this.defaultAction; + this.target = typeof options.target === 'function' ? options.target : this.defaultTarget; + this.text = typeof options.text === 'function' ? options.text : this.defaultText; + this.container = _typeof(options.container) === 'object' ? options.container : document.body; + } + }, { + key: 'listenClick', + value: function listenClick(trigger) { + var _this2 = this; + + this.listener = (0, _goodListener2.default)(trigger, 'click', function (e) { + return _this2.onClick(e); + }); + } + }, { + key: 'onClick', + value: function onClick(e) { + var trigger = e.delegateTarget || e.currentTarget; + + if (this.clipboardAction) { + this.clipboardAction = null; + } + + this.clipboardAction = new _clipboardAction2.default({ + action: this.action(trigger), + target: this.target(trigger), + text: this.text(trigger), + container: this.container, + trigger: trigger, + emitter: this + }); + } + }, { + key: 'defaultAction', + value: function defaultAction(trigger) { + return getAttributeValue('action', trigger); + } + }, { + key: 'defaultTarget', + value: function defaultTarget(trigger) { + var selector = getAttributeValue('target', trigger); + + if (selector) { + return document.querySelector(selector); + } + } + }, { + key: 'defaultText', + value: function defaultText(trigger) { + return getAttributeValue('text', trigger); + } + }, { + key: 'destroy', + value: function destroy() { + this.listener.destroy(); + + if (this.clipboardAction) { + this.clipboardAction.destroy(); + this.clipboardAction = null; + } + } + }], [{ + key: 'isSupported', + value: function isSupported() { + var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut']; + + var actions = typeof action === 'string' ? [action] : action; + var support = !!document.queryCommandSupported; + + actions.forEach(function (action) { + support = support && !!document.queryCommandSupported(action); + }); + + return support; + } + }]); + + return Clipboard; + }(_tinyEmitter2.default); + + /** + * Helper function to retrieve attribute value. + * @param {String} suffix + * @param {Element} element + */ + function getAttributeValue(suffix, element) { + var attribute = 'data-clipboard-' + suffix; + + if (!element.hasAttribute(attribute)) { + return; + } + + return element.getAttribute(attribute); + } + + module.exports = Clipboard; +}); + +/***/ }), +/* 4 */ +/***/ (function(module, exports) { + +var DOCUMENT_NODE_TYPE = 9; + +/** + * A polyfill for Element.matches() + */ +if (typeof Element !== 'undefined' && !Element.prototype.matches) { + var proto = Element.prototype; + + proto.matches = proto.matchesSelector || + proto.mozMatchesSelector || + proto.msMatchesSelector || + proto.oMatchesSelector || + proto.webkitMatchesSelector; +} + +/** + * Finds the closest parent that matches a selector. + * + * @param {Element} element + * @param {String} selector + * @return {Function} + */ +function closest (element, selector) { + while (element && element.nodeType !== DOCUMENT_NODE_TYPE) { + if (typeof element.matches === 'function' && + element.matches(selector)) { + return element; + } + element = element.parentNode; + } +} + +module.exports = closest; + + +/***/ }), +/* 5 */ +/***/ (function(module, exports, __webpack_require__) { + +var closest = __webpack_require__(4); + +/** + * Delegates event to a selector. + * + * @param {Element} element + * @param {String} selector + * @param {String} type + * @param {Function} callback + * @param {Boolean} useCapture + * @return {Object} + */ +function _delegate(element, selector, type, callback, useCapture) { + var listenerFn = listener.apply(this, arguments); + + element.addEventListener(type, listenerFn, useCapture); + + return { + destroy: function() { + element.removeEventListener(type, listenerFn, useCapture); + } + } +} + +/** + * Delegates event to a selector. + * + * @param {Element|String|Array} [elements] + * @param {String} selector + * @param {String} type + * @param {Function} callback + * @param {Boolean} useCapture + * @return {Object} + */ +function delegate(elements, selector, type, callback, useCapture) { + // Handle the regular Element usage + if (typeof elements.addEventListener === 'function') { + return _delegate.apply(null, arguments); + } + + // Handle Element-less usage, it defaults to global delegation + if (typeof type === 'function') { + // Use `document` as the first parameter, then apply arguments + // This is a short way to .unshift `arguments` without running into deoptimizations + return _delegate.bind(null, document).apply(null, arguments); + } + + // Handle Selector-based usage + if (typeof elements === 'string') { + elements = document.querySelectorAll(elements); + } + + // Handle Array-like based usage + return Array.prototype.map.call(elements, function (element) { + return _delegate(element, selector, type, callback, useCapture); + }); +} + +/** + * Finds closest match and invokes callback. + * + * @param {Element} element + * @param {String} selector + * @param {String} type + * @param {Function} callback + * @return {Function} + */ +function listener(element, selector, type, callback) { + return function(e) { + e.delegateTarget = closest(e.target, selector); + + if (e.delegateTarget) { + callback.call(element, e); + } + } +} + +module.exports = delegate; + + +/***/ }), +/* 6 */ +/***/ (function(module, exports) { + +/** + * Check if argument is a HTML element. + * + * @param {Object} value + * @return {Boolean} + */ +exports.node = function(value) { + return value !== undefined + && value instanceof HTMLElement + && value.nodeType === 1; +}; + +/** + * Check if argument is a list of HTML elements. + * + * @param {Object} value + * @return {Boolean} + */ +exports.nodeList = function(value) { + var type = Object.prototype.toString.call(value); + + return value !== undefined + && (type === '[object NodeList]' || type === '[object HTMLCollection]') + && ('length' in value) + && (value.length === 0 || exports.node(value[0])); +}; + +/** + * Check if argument is a string. + * + * @param {Object} value + * @return {Boolean} + */ +exports.string = function(value) { + return typeof value === 'string' + || value instanceof String; +}; + +/** + * Check if argument is a function. + * + * @param {Object} value + * @return {Boolean} + */ +exports.fn = function(value) { + var type = Object.prototype.toString.call(value); + + return type === '[object Function]'; +}; + + +/***/ }), +/* 7 */ +/***/ (function(module, exports) { + +function select(element) { + var selectedText; + + if (element.nodeName === 'SELECT') { + element.focus(); + + selectedText = element.value; + } + else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') { + var isReadOnly = element.hasAttribute('readonly'); + + if (!isReadOnly) { + element.setAttribute('readonly', ''); + } + + element.select(); + element.setSelectionRange(0, element.value.length); + + if (!isReadOnly) { + element.removeAttribute('readonly'); + } + + selectedText = element.value; + } + else { + if (element.hasAttribute('contenteditable')) { + element.focus(); + } + + var selection = window.getSelection(); + var range = document.createRange(); + + range.selectNodeContents(element); + selection.removeAllRanges(); + selection.addRange(range); + + selectedText = selection.toString(); + } + + return selectedText; +} + +module.exports = select; + + +/***/ }) +/******/ ]); +}); \ No newline at end of file diff --git a/assets/js/clipboard.min.js b/assets/js/clipboard.min.js new file mode 100644 index 0000000..b00ee51 --- /dev/null +++ b/assets/js/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.0 + * https://zenorocha.github.io/clipboard.js + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return function(t){function e(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return t[o].call(r.exports,r,r.exports,e),r.l=!0,r.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,o){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:o})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=3)}([function(t,e,n){var o,r,i;!function(a,c){r=[t,n(7)],o=c,void 0!==(i="function"==typeof o?o.apply(e,r):o)&&(t.exports=i)}(0,function(t,e){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}var o=function(t){return t&&t.__esModule?t:{default:t}}(e),r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function t(t,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};this.action=t.action,this.container=t.container,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function(){var t=this,e="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[e?"right":"left"]="-9999px";var n=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=n+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=(0,o.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=(0,o.default)(this.target),this.copyText()}},{key:"copyText",value:function(){var t=void 0;try{t=document.execCommand(this.action)}catch(e){t=!1}this.handleResult(t)}},{key:"handleResult",value:function(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(t){if(void 0!==t){if(!t||"object"!==(void 0===t?"undefined":r(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function(){return this._target}}]),t}();t.exports=a})},function(t,e,n){function o(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!c.string(e))throw new TypeError("Second argument must be a String");if(!c.fn(n))throw new TypeError("Third argument must be a Function");if(c.node(t))return r(t,e,n);if(c.nodeList(t))return i(t,e,n);if(c.string(t))return a(t,e,n);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function r(t,e,n){return t.addEventListener(e,n),{destroy:function(){t.removeEventListener(e,n)}}}function i(t,e,n){return Array.prototype.forEach.call(t,function(t){t.addEventListener(e,n)}),{destroy:function(){Array.prototype.forEach.call(t,function(t){t.removeEventListener(e,n)})}}}function a(t,e,n){return u(document.body,t,e,n)}var c=n(6),u=n(5);t.exports=o},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){function o(){r.off(t,o),e.apply(n,arguments)}var r=this;return o._=e,this.on(t,o,n)},emit:function(t){var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;for(o;o0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText,this.container="object"===d(t.container)?t.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=(0,f.default)(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new l.default({action:this.action(e),target:this.target(e),text:this.text(e),container:this.container,trigger:e,emitter:this})}},{key:"defaultAction",value:function(t){return u("action",t)}},{key:"defaultTarget",value:function(t){var e=u("target",t);if(e)return document.querySelector(e)}},{key:"defaultText",value:function(t){return u("text",t)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],e="string"==typeof t?[t]:t,n=!!document.queryCommandSupported;return e.forEach(function(t){n=n&&!!document.queryCommandSupported(t)}),n}}]),e}(s.default);t.exports=p})},function(t,e){function n(t,e){for(;t&&t.nodeType!==o;){if("function"==typeof t.matches&&t.matches(e))return t;t=t.parentNode}}var o=9;if("undefined"!=typeof Element&&!Element.prototype.matches){var r=Element.prototype;r.matches=r.matchesSelector||r.mozMatchesSelector||r.msMatchesSelector||r.oMatchesSelector||r.webkitMatchesSelector}t.exports=n},function(t,e,n){function o(t,e,n,o,r){var a=i.apply(this,arguments);return t.addEventListener(n,a,r),{destroy:function(){t.removeEventListener(n,a,r)}}}function r(t,e,n,r,i){return"function"==typeof t.addEventListener?o.apply(null,arguments):"function"==typeof n?o.bind(null,document).apply(null,arguments):("string"==typeof t&&(t=document.querySelectorAll(t)),Array.prototype.map.call(t,function(t){return o(t,e,n,r,i)}))}function i(t,e,n,o){return function(n){n.delegateTarget=a(n.target,e),n.delegateTarget&&o.call(t,n)}}var a=n(4);t.exports=r},function(t,e){e.node=function(t){return void 0!==t&&t instanceof HTMLElement&&1===t.nodeType},e.nodeList=function(t){var n=Object.prototype.toString.call(t);return void 0!==t&&("[object NodeList]"===n||"[object HTMLCollection]"===n)&&"length"in t&&(0===t.length||e.node(t[0]))},e.string=function(t){return"string"==typeof t||t instanceof String},e.fn=function(t){return"[object Function]"===Object.prototype.toString.call(t)}},function(t,e){function n(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}t.exports=n}])}); \ No newline at end of file diff --git a/assets/js/monero-gateway-order-page.js b/assets/js/monero-gateway-order-page.js new file mode 100644 index 0000000..da7ca24 --- /dev/null +++ b/assets/js/monero-gateway-order-page.js @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2018, Ryo Currency Project +*/ +function monero_showNotification(message, type='success') { + var toast = jQuery('
' + message + '
'); + jQuery('#monero_toast').append(toast); + toast.animate({ "right": "12px" }, "fast"); + setInterval(function() { + toast.animate({ "right": "-400px" }, "fast", function() { + toast.remove(); + }); + }, 2500) +} +function monero_showQR(show=true) { + jQuery('#monero_qr_code_container').toggle(show); +} +function monero_fetchDetails() { + var data = { + '_': jQuery.now(), + 'order_id': monero_details.order_id + }; + jQuery.get(monero_ajax_url, data, function(response) { + if (typeof response.error !== 'undefined') { + console.log(response.error); + } else { + monero_details = response; + monero_updateDetails(); + } + }); +} + +function monero_updateDetails() { + + var details = monero_details; + + jQuery('#monero_payment_messages').children().hide(); + switch(details.status) { + case 'unpaid': + jQuery('.monero_payment_unpaid').show(); + jQuery('.monero_payment_expire_time').html(details.order_expires); + break; + case 'partial': + jQuery('.monero_payment_partial').show(); + jQuery('.monero_payment_expire_time').html(details.order_expires); + break; + case 'paid': + jQuery('.monero_payment_paid').show(); + jQuery('.monero_confirm_time').html(details.time_to_confirm); + jQuery('.button-row button').prop("disabled",true); + break; + case 'confirmed': + jQuery('.monero_payment_confirmed').show(); + jQuery('.button-row button').prop("disabled",true); + break; + case 'expired': + jQuery('.monero_payment_expired').show(); + jQuery('.button-row button').prop("disabled",true); + break; + case 'expired_partial': + jQuery('.monero_payment_expired_partial').show(); + jQuery('.button-row button').prop("disabled",true); + break; + } + + jQuery('#monero_exchange_rate').html('1 XMR = '+details.rate_formatted+' '+details.currency); + jQuery('#monero_total_amount').html(details.amount_total_formatted); + jQuery('#monero_total_paid').html(details.amount_paid_formatted); + jQuery('#monero_total_due').html(details.amount_due_formatted); + + jQuery('#monero_integrated_address').html(details.integrated_address); + + if(monero_show_qr) { + var qr = jQuery('#monero_qr_code').html(''); + new QRCode(qr.get(0), details.qrcode_uri); + } + + if(details.txs.length) { + jQuery('#monero_tx_table').show(); + jQuery('#monero_tx_none').hide(); + jQuery('#monero_tx_table tbody').html(''); + for(var i=0; i < details.txs.length; i++) { + var tx = details.txs[i]; + var height = tx.height == 0 ? 'N/A' : tx.height; + var row = ''+ + ''+ + ''+ + ''+tx.txid+''+ + ''+ + ''+height+''+ + ''+tx.amount_formatted+' Monero'+ + ''; + + jQuery('#monero_tx_table tbody').append(row); + } + } else { + jQuery('#monero_tx_table').hide(); + jQuery('#monero_tx_none').show(); + } + + // Show state change notifications + var new_txs = details.txs; + var old_txs = monero_order_state.txs; + if(new_txs.length != old_txs.length) { + for(var i = 0; i < new_txs.length; i++) { + var is_new_tx = true; + for(var j = 0; j < old_txs.length; j++) { + if(new_txs[i].txid == old_txs[j].txid && new_txs[i].amount == old_txs[j].amount) { + is_new_tx = false; + break; + } + } + if(is_new_tx) { + monero_showNotification('Transaction received for '+new_txs[i].amount_formatted+' Monero'); + } + } + } + + if(details.status != monero_order_state.status) { + switch(details.status) { + case 'paid': + monero_showNotification('Your order has been paid in full'); + break; + case 'confirmed': + monero_showNotification('Your order has been confirmed'); + break; + case 'expired': + case 'expired_partial': + monero_showNotification('Your order has expired', 'error'); + break; + } + } + + monero_order_state = { + status: monero_details.status, + txs: monero_details.txs + }; + +} +jQuery(document).ready(function($) { + if (typeof monero_details !== 'undefined') { + monero_order_state = { + status: monero_details.status, + txs: monero_details.txs + }; + setInterval(monero_fetchDetails, 30000); + monero_updateDetails(); + new ClipboardJS('.clipboard').on('success', function(e) { + e.clearSelection(); + if(e.trigger.disabled) return; + switch(e.trigger.getAttribute('data-clipboard-target')) { + case '#monero_integrated_address': + monero_showNotification('Copied destination address!'); + break; + case '#monero_total_due': + monero_showNotification('Copied total amount due!'); + break; + } + e.clearSelection(); + }); + } +}); \ No newline at end of file diff --git a/assets/js/qrcode.js b/assets/js/qrcode.js new file mode 100644 index 0000000..5507c15 --- /dev/null +++ b/assets/js/qrcode.js @@ -0,0 +1,614 @@ +/** + * @fileoverview + * - Using the 'QRCode for Javascript library' + * - Fixed dataset of 'QRCode for Javascript library' for support full-spec. + * - this library has no dependencies. + * + * @author davidshimjs + * @see http://www.d-project.com/ + * @see http://jeromeetienne.github.com/jquery-qrcode/ + */ +var QRCode; + +(function () { + //--------------------------------------------------------------------- + // QRCode for JavaScript + // + // Copyright (c) 2009 Kazuhiko Arase + // + // URL: http://www.d-project.com/ + // + // Licensed under the MIT license: + // http://www.opensource.org/licenses/mit-license.php + // + // The word "QR Code" is registered trademark of + // DENSO WAVE INCORPORATED + // http://www.denso-wave.com/qrcode/faqpatent-e.html + // + //--------------------------------------------------------------------- + function QR8bitByte(data) { + this.mode = QRMode.MODE_8BIT_BYTE; + this.data = data; + this.parsedData = []; + + // Added to support UTF-8 Characters + for (var i = 0, l = this.data.length; i < l; i++) { + var byteArray = []; + var code = this.data.charCodeAt(i); + + if (code > 0x10000) { + byteArray[0] = 0xF0 | ((code & 0x1C0000) >>> 18); + byteArray[1] = 0x80 | ((code & 0x3F000) >>> 12); + byteArray[2] = 0x80 | ((code & 0xFC0) >>> 6); + byteArray[3] = 0x80 | (code & 0x3F); + } else if (code > 0x800) { + byteArray[0] = 0xE0 | ((code & 0xF000) >>> 12); + byteArray[1] = 0x80 | ((code & 0xFC0) >>> 6); + byteArray[2] = 0x80 | (code & 0x3F); + } else if (code > 0x80) { + byteArray[0] = 0xC0 | ((code & 0x7C0) >>> 6); + byteArray[1] = 0x80 | (code & 0x3F); + } else { + byteArray[0] = code; + } + + this.parsedData.push(byteArray); + } + + this.parsedData = Array.prototype.concat.apply([], this.parsedData); + + if (this.parsedData.length != this.data.length) { + this.parsedData.unshift(191); + this.parsedData.unshift(187); + this.parsedData.unshift(239); + } + } + + QR8bitByte.prototype = { + getLength: function (buffer) { + return this.parsedData.length; + }, + write: function (buffer) { + for (var i = 0, l = this.parsedData.length; i < l; i++) { + buffer.put(this.parsedData[i], 8); + } + } + }; + + function QRCodeModel(typeNumber, errorCorrectLevel) { + this.typeNumber = typeNumber; + this.errorCorrectLevel = errorCorrectLevel; + this.modules = null; + this.moduleCount = 0; + this.dataCache = null; + this.dataList = []; + } + + QRCodeModel.prototype={addData:function(data){var newData=new QR8bitByte(data);this.dataList.push(newData);this.dataCache=null;},isDark:function(row,col){if(row<0||this.moduleCount<=row||col<0||this.moduleCount<=col){throw new Error(row+","+col);} + return this.modules[row][col];},getModuleCount:function(){return this.moduleCount;},make:function(){this.makeImpl(false,this.getBestMaskPattern());},makeImpl:function(test,maskPattern){this.moduleCount=this.typeNumber*4+17;this.modules=new Array(this.moduleCount);for(var row=0;row=7){this.setupTypeNumber(test);} + if(this.dataCache==null){this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList);} + this.mapData(this.dataCache,maskPattern);},setupPositionProbePattern:function(row,col){for(var r=-1;r<=7;r++){if(row+r<=-1||this.moduleCount<=row+r)continue;for(var c=-1;c<=7;c++){if(col+c<=-1||this.moduleCount<=col+c)continue;if((0<=r&&r<=6&&(c==0||c==6))||(0<=c&&c<=6&&(r==0||r==6))||(2<=r&&r<=4&&2<=c&&c<=4)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}},getBestMaskPattern:function(){var minLostPoint=0;var pattern=0;for(var i=0;i<8;i++){this.makeImpl(true,i);var lostPoint=QRUtil.getLostPoint(this);if(i==0||minLostPoint>lostPoint){minLostPoint=lostPoint;pattern=i;}} + return pattern;},createMovieClip:function(target_mc,instance_name,depth){var qr_mc=target_mc.createEmptyMovieClip(instance_name,depth);var cs=1;this.make();for(var row=0;row>i)&1)==1);this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=mod;} + for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[i%3+this.moduleCount-8-3][Math.floor(i/3)]=mod;}},setupTypeInfo:function(test,maskPattern){var data=(this.errorCorrectLevel<<3)|maskPattern;var bits=QRUtil.getBCHTypeInfo(data);for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<6){this.modules[i][8]=mod;}else if(i<8){this.modules[i+1][8]=mod;}else{this.modules[this.moduleCount-15+i][8]=mod;}} + for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<8){this.modules[8][this.moduleCount-i-1]=mod;}else if(i<9){this.modules[8][15-i-1+1]=mod;}else{this.modules[8][15-i-1]=mod;}} + this.modules[this.moduleCount-8][8]=(!test);},mapData:function(data,maskPattern){var inc=-1;var row=this.moduleCount-1;var bitIndex=7;var byteIndex=0;for(var col=this.moduleCount-1;col>0;col-=2){if(col==6)col--;while(true){for(var c=0;c<2;c++){if(this.modules[row][col-c]==null){var dark=false;if(byteIndex>>bitIndex)&1)==1);} + var mask=QRUtil.getMask(maskPattern,row,col-c);if(mask){dark=!dark;} + this.modules[row][col-c]=dark;bitIndex--;if(bitIndex==-1){byteIndex++;bitIndex=7;}}} + row+=inc;if(row<0||this.moduleCount<=row){row-=inc;inc=-inc;break;}}}}};QRCodeModel.PAD0=0xEC;QRCodeModel.PAD1=0x11;QRCodeModel.createData=function(typeNumber,errorCorrectLevel,dataList){var rsBlocks=QRRSBlock.getRSBlocks(typeNumber,errorCorrectLevel);var buffer=new QRBitBuffer();for(var i=0;itotalDataCount*8){throw new Error("code length overflow. (" + +buffer.getLengthInBits() + +">" + +totalDataCount*8 + +")");} + if(buffer.getLengthInBits()+4<=totalDataCount*8){buffer.put(0,4);} + while(buffer.getLengthInBits()%8!=0){buffer.putBit(false);} + while(true){if(buffer.getLengthInBits()>=totalDataCount*8){break;} + buffer.put(QRCodeModel.PAD0,8);if(buffer.getLengthInBits()>=totalDataCount*8){break;} + buffer.put(QRCodeModel.PAD1,8);} + return QRCodeModel.createBytes(buffer,rsBlocks);};QRCodeModel.createBytes=function(buffer,rsBlocks){var offset=0;var maxDcCount=0;var maxEcCount=0;var dcdata=new Array(rsBlocks.length);var ecdata=new Array(rsBlocks.length);for(var r=0;r=0)?modPoly.get(modIndex):0;}} + var totalCodeCount=0;for(var i=0;i=0){d^=(QRUtil.G15<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)));} + return((data<<10)|d)^QRUtil.G15_MASK;},getBCHTypeNumber:function(data){var d=data<<12;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)>=0){d^=(QRUtil.G18<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)));} + return(data<<12)|d;},getBCHDigit:function(data){var digit=0;while(data!=0){digit++;data>>>=1;} + return digit;},getPatternPosition:function(typeNumber){return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1];},getMask:function(maskPattern,i,j){switch(maskPattern){case QRMaskPattern.PATTERN000:return(i+j)%2==0;case QRMaskPattern.PATTERN001:return i%2==0;case QRMaskPattern.PATTERN010:return j%3==0;case QRMaskPattern.PATTERN011:return(i+j)%3==0;case QRMaskPattern.PATTERN100:return(Math.floor(i/2)+Math.floor(j/3))%2==0;case QRMaskPattern.PATTERN101:return(i*j)%2+(i*j)%3==0;case QRMaskPattern.PATTERN110:return((i*j)%2+(i*j)%3)%2==0;case QRMaskPattern.PATTERN111:return((i*j)%3+(i+j)%2)%2==0;default:throw new Error("bad maskPattern:"+maskPattern);}},getErrorCorrectPolynomial:function(errorCorrectLength){var a=new QRPolynomial([1],0);for(var i=0;i5){lostPoint+=(3+sameCount-5);}}} + for(var row=0;row=256){n-=255;} + return QRMath.EXP_TABLE[n];},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)};for(var i=0;i<8;i++){QRMath.EXP_TABLE[i]=1<>>(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i>>(length-i-1))&1)==1);}},getLengthInBits:function(){return this.length;},putBit:function(bit){var bufIndex=Math.floor(this.length/8);if(this.buffer.length<=bufIndex){this.buffer.push(0);} + if(bit){this.buffer[bufIndex]|=(0x80>>>(this.length%8));} + this.length++;}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]]; + + function _isSupportCanvas() { + return typeof CanvasRenderingContext2D != "undefined"; + } + + // android 2.x doesn't support Data-URI spec + function _getAndroid() { + var android = false; + var sAgent = navigator.userAgent; + + if (/android/i.test(sAgent)) { // android + android = true; + var aMat = sAgent.toString().match(/android ([0-9]\.[0-9])/i); + + if (aMat && aMat[1]) { + android = parseFloat(aMat[1]); + } + } + + return android; + } + + var svgDrawer = (function() { + + var Drawing = function (el, htOption) { + this._el = el; + this._htOption = htOption; + }; + + Drawing.prototype.draw = function (oQRCode) { + var _htOption = this._htOption; + var _el = this._el; + var nCount = oQRCode.getModuleCount(); + var nWidth = Math.floor(_htOption.width / nCount); + var nHeight = Math.floor(_htOption.height / nCount); + + this.clear(); + + function makeSVG(tag, attrs) { + var el = document.createElementNS('http://www.w3.org/2000/svg', tag); + for (var k in attrs) + if (attrs.hasOwnProperty(k)) el.setAttribute(k, attrs[k]); + return el; + } + + var svg = makeSVG("svg" , {'viewBox': '0 0 ' + String(nCount) + " " + String(nCount), 'width': '100%', 'height': '100%', 'fill': _htOption.colorLight}); + svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink"); + _el.appendChild(svg); + + svg.appendChild(makeSVG("rect", {"fill": _htOption.colorLight, "width": "100%", "height": "100%"})); + svg.appendChild(makeSVG("rect", {"fill": _htOption.colorDark, "width": "1", "height": "1", "id": "template"})); + + for (var row = 0; row < nCount; row++) { + for (var col = 0; col < nCount; col++) { + if (oQRCode.isDark(row, col)) { + var child = makeSVG("use", {"x": String(col), "y": String(row)}); + child.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#template") + svg.appendChild(child); + } + } + } + }; + Drawing.prototype.clear = function () { + while (this._el.hasChildNodes()) + this._el.removeChild(this._el.lastChild); + }; + return Drawing; + })(); + + var useSVG = document.documentElement.tagName.toLowerCase() === "svg"; + + // Drawing in DOM by using Table tag + var Drawing = useSVG ? svgDrawer : !_isSupportCanvas() ? (function () { + var Drawing = function (el, htOption) { + this._el = el; + this._htOption = htOption; + }; + + /** + * Draw the QRCode + * + * @param {QRCode} oQRCode + */ + Drawing.prototype.draw = function (oQRCode) { + var _htOption = this._htOption; + var _el = this._el; + var nCount = oQRCode.getModuleCount(); + var nWidth = Math.floor(_htOption.width / nCount); + var nHeight = Math.floor(_htOption.height / nCount); + var aHTML = ['']; + + for (var row = 0; row < nCount; row++) { + aHTML.push(''); + + for (var col = 0; col < nCount; col++) { + aHTML.push(''); + } + + aHTML.push(''); + } + + aHTML.push('
'); + _el.innerHTML = aHTML.join(''); + + // Fix the margin values as real size. + var elTable = _el.childNodes[0]; + var nLeftMarginTable = (_htOption.width - elTable.offsetWidth) / 2; + var nTopMarginTable = (_htOption.height - elTable.offsetHeight) / 2; + + if (nLeftMarginTable > 0 && nTopMarginTable > 0) { + elTable.style.margin = nTopMarginTable + "px " + nLeftMarginTable + "px"; + } + }; + + /** + * Clear the QRCode + */ + Drawing.prototype.clear = function () { + this._el.innerHTML = ''; + }; + + return Drawing; + })() : (function () { // Drawing in Canvas + function _onMakeImage() { + this._elImage.src = this._elCanvas.toDataURL("image/png"); + this._elImage.style.display = "block"; + this._elCanvas.style.display = "none"; + } + + // Android 2.1 bug workaround + // http://code.google.com/p/android/issues/detail?id=5141 + if (this._android && this._android <= 2.1) { + var factor = 1 / window.devicePixelRatio; + var drawImage = CanvasRenderingContext2D.prototype.drawImage; + CanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) { + if (("nodeName" in image) && /img/i.test(image.nodeName)) { + for (var i = arguments.length - 1; i >= 1; i--) { + arguments[i] = arguments[i] * factor; + } + } else if (typeof dw == "undefined") { + arguments[1] *= factor; + arguments[2] *= factor; + arguments[3] *= factor; + arguments[4] *= factor; + } + + drawImage.apply(this, arguments); + }; + } + + /** + * Check whether the user's browser supports Data URI or not + * + * @private + * @param {Function} fSuccess Occurs if it supports Data URI + * @param {Function} fFail Occurs if it doesn't support Data URI + */ + function _safeSetDataURI(fSuccess, fFail) { + var self = this; + self._fFail = fFail; + self._fSuccess = fSuccess; + + // Check it just once + if (self._bSupportDataURI === null) { + var el = document.createElement("img"); + var fOnError = function() { + self._bSupportDataURI = false; + + if (self._fFail) { + self._fFail.call(self); + } + }; + var fOnSuccess = function() { + self._bSupportDataURI = true; + + if (self._fSuccess) { + self._fSuccess.call(self); + } + }; + + el.onabort = fOnError; + el.onerror = fOnError; + el.onload = fOnSuccess; + el.src = "data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; // the Image contains 1px data. + return; + } else if (self._bSupportDataURI === true && self._fSuccess) { + self._fSuccess.call(self); + } else if (self._bSupportDataURI === false && self._fFail) { + self._fFail.call(self); + } + }; + + /** + * Drawing QRCode by using canvas + * + * @constructor + * @param {HTMLElement} el + * @param {Object} htOption QRCode Options + */ + var Drawing = function (el, htOption) { + this._bIsPainted = false; + this._android = _getAndroid(); + + this._htOption = htOption; + this._elCanvas = document.createElement("canvas"); + this._elCanvas.width = htOption.width; + this._elCanvas.height = htOption.height; + el.appendChild(this._elCanvas); + this._el = el; + this._oContext = this._elCanvas.getContext("2d"); + this._bIsPainted = false; + this._elImage = document.createElement("img"); + this._elImage.alt = "Scan me!"; + this._elImage.style.display = "none"; + this._el.appendChild(this._elImage); + this._bSupportDataURI = null; + }; + + /** + * Draw the QRCode + * + * @param {QRCode} oQRCode + */ + Drawing.prototype.draw = function (oQRCode) { + var _elImage = this._elImage; + var _oContext = this._oContext; + var _htOption = this._htOption; + + var nCount = oQRCode.getModuleCount(); + var nWidth = _htOption.width / nCount; + var nHeight = _htOption.height / nCount; + var nRoundedWidth = Math.round(nWidth); + var nRoundedHeight = Math.round(nHeight); + + _elImage.style.display = "none"; + this.clear(); + + for (var row = 0; row < nCount; row++) { + for (var col = 0; col < nCount; col++) { + var bIsDark = oQRCode.isDark(row, col); + var nLeft = col * nWidth; + var nTop = row * nHeight; + _oContext.strokeStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight; + _oContext.lineWidth = 1; + _oContext.fillStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight; + _oContext.fillRect(nLeft, nTop, nWidth, nHeight); + + // 안티 앨리어싱 방지 처리 + _oContext.strokeRect( + Math.floor(nLeft) + 0.5, + Math.floor(nTop) + 0.5, + nRoundedWidth, + nRoundedHeight + ); + + _oContext.strokeRect( + Math.ceil(nLeft) - 0.5, + Math.ceil(nTop) - 0.5, + nRoundedWidth, + nRoundedHeight + ); + } + } + + this._bIsPainted = true; + }; + + /** + * Make the image from Canvas if the browser supports Data URI. + */ + Drawing.prototype.makeImage = function () { + if (this._bIsPainted) { + _safeSetDataURI.call(this, _onMakeImage); + } + }; + + /** + * Return whether the QRCode is painted or not + * + * @return {Boolean} + */ + Drawing.prototype.isPainted = function () { + return this._bIsPainted; + }; + + /** + * Clear the QRCode + */ + Drawing.prototype.clear = function () { + this._oContext.clearRect(0, 0, this._elCanvas.width, this._elCanvas.height); + this._bIsPainted = false; + }; + + /** + * @private + * @param {Number} nNumber + */ + Drawing.prototype.round = function (nNumber) { + if (!nNumber) { + return nNumber; + } + + return Math.floor(nNumber * 1000) / 1000; + }; + + return Drawing; + })(); + + /** + * Get the type by string length + * + * @private + * @param {String} sText + * @param {Number} nCorrectLevel + * @return {Number} type + */ + function _getTypeNumber(sText, nCorrectLevel) { + var nType = 1; + var length = _getUTF8Length(sText); + + for (var i = 0, len = QRCodeLimitLength.length; i <= len; i++) { + var nLimit = 0; + + switch (nCorrectLevel) { + case QRErrorCorrectLevel.L : + nLimit = QRCodeLimitLength[i][0]; + break; + case QRErrorCorrectLevel.M : + nLimit = QRCodeLimitLength[i][1]; + break; + case QRErrorCorrectLevel.Q : + nLimit = QRCodeLimitLength[i][2]; + break; + case QRErrorCorrectLevel.H : + nLimit = QRCodeLimitLength[i][3]; + break; + } + + if (length <= nLimit) { + break; + } else { + nType++; + } + } + + if (nType > QRCodeLimitLength.length) { + throw new Error("Too long data"); + } + + return nType; + } + + function _getUTF8Length(sText) { + var replacedText = encodeURI(sText).toString().replace(/\%[0-9a-fA-F]{2}/g, 'a'); + return replacedText.length + (replacedText.length != sText ? 3 : 0); + } + + /** + * @class QRCode + * @constructor + * @example + * new QRCode(document.getElementById("test"), "http://jindo.dev.naver.com/collie"); + * + * @example + * var oQRCode = new QRCode("test", { + * text : "http://naver.com", + * width : 128, + * height : 128 + * }); + * + * oQRCode.clear(); // Clear the QRCode. + * oQRCode.makeCode("http://map.naver.com"); // Re-create the QRCode. + * + * @param {HTMLElement|String} el target element or 'id' attribute of element. + * @param {Object|String} vOption + * @param {String} vOption.text QRCode link data + * @param {Number} [vOption.width=256] + * @param {Number} [vOption.height=256] + * @param {String} [vOption.colorDark="#000000"] + * @param {String} [vOption.colorLight="#ffffff"] + * @param {QRCode.CorrectLevel} [vOption.correctLevel=QRCode.CorrectLevel.H] [L|M|Q|H] + */ + QRCode = function (el, vOption) { + this._htOption = { + width : 256, + height : 256, + typeNumber : 4, + colorDark : "#000000", + colorLight : "#ffffff", + correctLevel : QRErrorCorrectLevel.H + }; + + if (typeof vOption === 'string') { + vOption = { + text : vOption + }; + } + + // Overwrites options + if (vOption) { + for (var i in vOption) { + this._htOption[i] = vOption[i]; + } + } + + if (typeof el == "string") { + el = document.getElementById(el); + } + + if (this._htOption.useSVG) { + Drawing = svgDrawer; + } + + this._android = _getAndroid(); + this._el = el; + this._oQRCode = null; + this._oDrawing = new Drawing(this._el, this._htOption); + + if (this._htOption.text) { + this.makeCode(this._htOption.text); + } + }; + + /** + * Make the QRCode + * + * @param {String} sText link data + */ + QRCode.prototype.makeCode = function (sText) { + this._oQRCode = new QRCodeModel(_getTypeNumber(sText, this._htOption.correctLevel), this._htOption.correctLevel); + this._oQRCode.addData(sText); + this._oQRCode.make(); + this._el.title = sText; + this._oDrawing.draw(this._oQRCode); + this.makeImage(); + }; + + /** + * Make the Image from Canvas element + * - It occurs automatically + * - Android below 3 doesn't support Data-URI spec. + * + * @private + */ + QRCode.prototype.makeImage = function () { + if (typeof this._oDrawing.makeImage == "function" && (!this._android || this._android >= 3)) { + this._oDrawing.makeImage(); + } + }; + + /** + * Clear the QRCode + */ + QRCode.prototype.clear = function () { + this._oDrawing.clear(); + }; + + /** + * @name QRCode.CorrectLevel + */ + QRCode.CorrectLevel = QRErrorCorrectLevel; +})(); diff --git a/assets/js/qrcode.min.js b/assets/js/qrcode.min.js new file mode 100644 index 0000000..993e88f --- /dev/null +++ b/assets/js/qrcode.min.js @@ -0,0 +1 @@ +var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j=0?p.get(q):0}}for(var r=0,m=0;mm;m++)for(var j=0;jm;m++)for(var j=0;j=0;)b^=f.G15<=0;)b^=f.G18<>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;cf;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=[''],h=0;d>h;h++){g.push("");for(var i=0;d>i;i++)g.push('');g.push("")}g.push("
"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}(); \ No newline at end of file diff --git a/assets/systemd-unit-files/monero-wallet-rpc.service b/assets/systemd-unit-files/monero-wallet-rpc.service new file mode 100644 index 0000000..9818c4c --- /dev/null +++ b/assets/systemd-unit-files/monero-wallet-rpc.service @@ -0,0 +1,14 @@ +[Unit] +Description=Monero Wallet RPC +After=network.target monerod.service + +[Service] +User=moneroservices +Group=moneroservices +WorkingDirectory=/opt/monero-wallets +Type=simple +ExecStart=/opt/monero-bin/monero-wallet-rpc --wallet-file /opt/monero-wallets/woocommerce --rpc-bind-port 18080 --password-file /opt/monero-wallets/woocommerce.password --disable-rpc-login --log-file /var/log/monero-wallet.log +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/assets/systemd-unit-files/monerod.service b/assets/systemd-unit-files/monerod.service new file mode 100644 index 0000000..65e56b7 --- /dev/null +++ b/assets/systemd-unit-files/monerod.service @@ -0,0 +1,14 @@ +[Unit] +Description=Monero Full Node +After=network.target + +[Service] +User=moneroservices +Group=moneroservices +WorkingDirectory=/opt/monero-data-dir +Type=simple +LimitNOFILE=65535 +ExecStart=/usr/bin/monerod --log-file /var/log/monerod.log --data-dir /opt/monero-data-dir --non-interactive +Restart=always +[Install] +WantedBy=multi-user.target diff --git a/include/admin/class-monero-admin-interface.php b/include/admin/class-monero-admin-interface.php new file mode 100644 index 0000000..930f84e --- /dev/null +++ b/include/admin/class-monero-admin-interface.php @@ -0,0 +1,133 @@ +prepare_items(); + $payments_list->display(); + } + + /** + * Monero settings page + */ + public function settings_page() { + WC_Admin_Settings::output(); + } + + public function settings_page_init() { + global $current_tab, $current_section; + + $current_section = 'monero_gateway'; + $current_tab = 'checkout'; + + // Include settings pages. + WC_Admin_Settings::get_settings_pages(); + + // Save settings if data has been posted. + if (apply_filters("woocommerce_save_settings_{$current_tab}_{$current_section}", !empty($_POST))) { + WC_Admin_Settings::save(); + } + + // Add any posted messages. + if (!empty($_GET['wc_error'])) { + WC_Admin_Settings::add_error(wp_kses_post(wp_unslash($_GET['wc_error']))); + } + + if (!empty($_GET['wc_message'])) { + WC_Admin_Settings::add_message(wp_kses_post(wp_unslash($_GET['wc_message']))); + } + + do_action('woocommerce_settings_page_init'); + } + +} + +return new Monero_Admin_Interface(); diff --git a/include/admin/class-monero-admin-payments-list.php b/include/admin/class-monero-admin-payments-list.php new file mode 100644 index 0000000..40ea59c --- /dev/null +++ b/include/admin/class-monero-admin-payments-list.php @@ -0,0 +1,276 @@ + 'payment', + 'plural' => 'payments', + 'ajax' => false + )); + } + + function extra_tablenav($which) { + if ($which == "top") { + $hidden_fields = wp_nonce_field() . wp_referer_field(); + $tab_info = array( + 'all' => array(), + 'pending' => array(), + 'paid' => array(), + 'confirmed' => array(), + 'expired' => array(), + ); + foreach($tab_info as $type=>&$info) { + $info['active'] = ''; + $info['count'] = $this->get_item_count($type); + } + if(isset($_GET['type'])) { + switch($_GET['type']) { + case 'all': + $tab_info['all']['active'] = 'class="current" aria-current="page"'; + break; + case 'pending': + $tab_info['pending']['active'] = 'class="current" aria-current="page"'; + break; + case 'paid': + $tab_info['paid']['active'] = 'class="current" aria-current="page"'; + break; + case 'confirmed': + $tab_info['confirmed']['active'] = 'class="current" aria-current="page"'; + break; + case 'expired': + $tab_info['expired']['active'] = 'class="current" aria-current="page"'; + break; + } + } else { + $tab_info['all']['active'] = 'class="current" aria-current="page"'; + } + if(Monero_Gateway::get_confirm_type() == 'monero-wallet-rpc') { + $balance = Monero_Gateway::admin_balance_info(); + $balance_info = << + Wallet height: {$balance['height']}
+ Your balance is: {$balance['balance']}
+ Unlocked balance: {$balance['unlocked_balance']}
+
+ +HTML; + } else { + $balance_info = ''; + } + echo << +

Monero Payments

+ $balance_info +
+ + +

Monero Payments List

+ + + +HTML; + } else if ($which == "bottom") { + echo '
'; + } + } + + /** + * Get column value. + * + * @param mixed $item Item being displayed. + * @param string $column_name Column name. + */ + public function column_default($item, $column_name) { + + switch($column_name) { + case 'col_order_id': + echo $this->get_order_link($item->order_id); + break; + case 'col_payment_id': + echo $item->payment_id; + break; + case 'col_txid': + $url = MONERO_GATEWAY_EXPLORER_URL.'/tx/'.$item->txid; + echo ''.$item->txid.''; + break; + case 'col_height': + echo $item->height; + break; + case 'col_amount': + echo Monero_Gateway::format_monero($item->amount).' Monero'; + break; + } + } + + protected function get_order_link($order_id) { + $order = new WC_Order($order_id); + $buyer = ''; + + if($order->get_billing_first_name() || $order->get_billing_last_name()) { + $buyer = trim(sprintf(_x('%1$s %2$s', 'full name', 'woocommerce'), $order->get_billing_first_name(), $order->get_billing_last_name())); + } else if ($order->get_billing_company()) { + $buyer = trim($order->get_billing_company()); + } else if ($order->get_customer_id()) { + $user = get_user_by('id', $order->get_customer_id()); + $buyer = ucwords($user->display_name); + } + + return '#' . esc_attr( $order->get_order_number() ) . ' ' . esc_html( $buyer ) . ''; + + } + + function get_columns() { + return $columns= array( + 'col_order_id' => __('Order'), + 'col_payment_id' => __('Payment ID'), + 'col_txid' => __('Txid'), + 'col_height' => __('Height'), + 'col_amount' => __('Amount'), + ); + } + + public function get_sortable_columns() { + return array(); + return $sortable = array( + 'col_order_id' => 'col_order_id', + 'col_payment_id' => 'payment_id', + 'col_txid' => 'txid', + 'col_height' => 'height', + 'col_amount' => 'amount', + ); + } + + function prepare_items() { + + $this->_column_headers = array($this->get_columns(), array(), $this->get_sortable_columns()); + $current_page = absint($this->get_pagenum()); + + $per_page = 25; + + $this->get_items($current_page, $per_page); + + } + + public function no_items() { + esc_html_e('No Monero payments found', 'monero_gateway'); + } + + protected function get_filter_vars() { + $type = isset($_GET['type']) ? $_GET['type'] : null; + return (object) array( + 'type' => $type, + ); + } + + protected function get_item_count($type) { + global $wpdb; + $table_name_1 = $wpdb->prefix.'monero_gateway_quotes'; + $table_name_2 = $wpdb->prefix.'monero_gateway_quotes_txids'; + $query_where = ' WHERE 1=1 '.$this->get_clause_type($type); + $query = "SELECT COUNT(*) AS count FROM {$table_name_2} t2 LEFT JOIN $table_name_1 t1 ON t2.payment_id = t1.payment_id {$query_where}"; + $item_count = $wpdb->get_var($query); + if(is_null($item_count)) $item_count = 0; + return $item_count; + } + + protected function get_clause_type($type) { + global $wpdb; + switch($type) { + case 'pending': + $query_where = $wpdb->prepare(' AND pending = 1 AND paid = 0 ', array()); + break; + case 'paid': + $query_where = $wpdb->prepare(' AND paid = 1 AND confirmed = 0 ', array()); + break; + case 'confirmed': + $query_where = $wpdb->prepare(' AND confirmed = 1 ', array()); + break; + case 'expired': + $query_where = $wpdb->prepare(' AND paid = 0 AND pending = 0 ', array()); + break; + case 'all': + default: + $query_where = ' '; + } + return $query_where; + } + + public function get_items($current_page, $per_page) { + global $wpdb; + + $this->items = array(); + $filters = $this->get_filter_vars(); + + $table_name_1 = $wpdb->prefix.'monero_gateway_quotes'; + $table_name_2 = $wpdb->prefix.'monero_gateway_quotes_txids'; + + $query_where = ' WHERE 1=1 '; + + $query_where .= $this->get_clause_type($filters->type); + + $query_order = $wpdb->prepare('ORDER BY id DESC LIMIT %d, %d;', ($current_page-1)*$per_page, $per_page); + + $query = "SELECT t1.order_id, t1.confirmed, t1.paid, t1.pending, t2.* FROM {$table_name_2} t2 LEFT JOIN $table_name_1 t1 ON t2.payment_id = t1.payment_id {$query_where} {$query_order}"; + + $this->items = $wpdb->get_results($query); + + $max_items = $this->get_item_count($filters->type); + + $this->set_pagination_args( + array( + 'total_items' => $max_items, + 'per_page' => $per_page, + 'total_pages' => ceil($max_items/$per_page), + ) + ); + } + +} diff --git a/include/admin/monero-gateway-admin-settings.php b/include/admin/monero-gateway-admin-settings.php new file mode 100644 index 0000000..0a02d40 --- /dev/null +++ b/include/admin/monero-gateway-admin-settings.php @@ -0,0 +1,114 @@ + array( + 'title' => __('Enable / Disable', 'monero_gateway'), + 'label' => __('Enable this payment gateway', 'monero_gateway'), + 'type' => 'checkbox', + 'default' => 'no' + ), + 'title' => array( + 'title' => __('Title', 'monero_gateway'), + 'type' => 'text', + 'desc_tip' => __('Payment title the customer will see during the checkout process.', 'monero_gateway'), + 'default' => __('Monero Gateway', 'monero_gateway') + ), + 'description' => array( + 'title' => __('Description', 'monero_gateway'), + 'type' => 'textarea', + 'desc_tip' => __('Payment description the customer will see during the checkout process.', 'monero_gateway'), + 'default' => __('Pay securely using Monero. You will be provided payment details after checkout.', 'monero_gateway') + ), + 'discount' => array( + 'title' => __('Discount for using Monero', 'monero_gateway'), + 'desc_tip' => __('Provide a discount to your customers for making a private payment with Monero', 'monero_gateway'), + 'description' => __('Enter a percentage discount (i.e. 5 for 5%) or leave this empty if you do not wish to provide a discount', 'monero_gateway'), + 'type' => __('number'), + 'default' => '0' + ), + 'valid_time' => array( + 'title' => __('Order valid time', 'monero_gateway'), + 'desc_tip' => __('Amount of time order is valid before expiring', 'monero_gateway'), + 'description' => __('Enter the number of seconds that the funds must be received in after order is placed. 3600 seconds = 1 hour', 'monero_gateway'), + 'type' => __('number'), + 'default' => '3600' + ), + 'confirms' => array( + 'title' => __('Number of confirmations', 'monero_gateway'), + 'desc_tip' => __('Number of confirms a transaction must have to be valid', 'monero_gateway'), + 'description' => __('Enter the number of confirms that transactions must have. Enter 0 to zero-confim. Each confirm will take approximately four minutes', 'monero_gateway'), + 'type' => __('number'), + 'default' => '5' + ), + 'confirm_type' => array( + 'title' => __('Confirmation Type', 'monero_gateway'), + 'desc_tip' => __('Select the method for confirming transactions', 'monero_gateway'), + 'description' => __('Select the method for confirming transactions', 'monero_gateway'), + 'type' => 'select', + 'options' => array( + 'viewkey' => __('viewkey', 'monero_gateway'), + 'monero-wallet-rpc' => __('monero-wallet-rpc', 'monero_gateway') + ), + 'default' => 'viewkey' + ), + 'monero_address' => array( + 'title' => __('Monero Address', 'monero_gateway'), + 'label' => __('Useful for people that have not a daemon online'), + 'type' => 'text', + 'desc_tip' => __('Monero Wallet Address (MoneroL)', 'monero_gateway') + ), + 'viewkey' => array( + 'title' => __('Secret Viewkey', 'monero_gateway'), + 'label' => __('Secret Viewkey'), + 'type' => 'text', + 'desc_tip' => __('Your secret Viewkey', 'monero_gateway') + ), + 'daemon_host' => array( + 'title' => __('Monero wallet RPC Host/IP', 'monero_gateway'), + 'type' => 'text', + 'desc_tip' => __('This is the Daemon Host/IP to authorize the payment with', 'monero_gateway'), + 'default' => '127.0.0.1', + ), + 'daemon_port' => array( + 'title' => __('Monero wallet RPC port', 'monero_gateway'), + 'type' => __('number'), + 'desc_tip' => __('This is the Wallet RPC port to authorize the payment with', 'monero_gateway'), + 'default' => '18080', + ), + 'testnet' => array( + 'title' => __(' Testnet', 'monero_gateway'), + 'label' => __(' Check this if you are using testnet ', 'monero_gateway'), + 'type' => 'checkbox', + 'description' => __('Advanced usage only', 'monero_gateway'), + 'default' => 'no' + ), + 'onion_service' => array( + 'title' => __(' SSL warnings ', 'monero_gateway'), + 'label' => __(' Check to Silence SSL warnings', 'monero_gateway'), + 'type' => 'checkbox', + 'description' => __('Check this box if you are running on an Onion Service (Suppress SSL errors)', 'monero_gateway'), + 'default' => 'no' + ), + 'show_qr' => array( + 'title' => __('Show QR Code', 'monero_gateway'), + 'label' => __('Show QR Code', 'monero_gateway'), + 'type' => 'checkbox', + 'description' => __('Enable this to show a QR code after checkout with payment details.'), + 'default' => 'no' + ), + 'use_monero_price' => array( + 'title' => __('Show Prices in Monero', 'monero_gateway'), + 'label' => __('Show Prices in Monero', 'monero_gateway'), + 'type' => 'checkbox', + 'description' => __('Enable this to convert ALL prices on the frontend to Monero (experimental)'), + 'default' => 'no' + ), + 'use_monero_price_decimals' => array( + 'title' => __('Display Decimals', 'monero_gateway'), + 'type' => __('number'), + 'description' => __('Number of decimal places to display on frontend. Upon checkout exact price will be displayed.'), + 'default' => 12, + ), +); diff --git a/include/class-monero-base58.php b/include/class-monero-base58.php new file mode 100644 index 0000000..0818996 --- /dev/null +++ b/include/class-monero-base58.php @@ -0,0 +1,354 @@ +hex_to_bin(): Invalid input type (must be a string)'); + } + if (strlen($hex) % 2 != 0) { + throw new Exception('base58->hex_to_bin(): Invalid input length (must be even)'); + } + + $res = array_fill(0, strlen($hex) / 2, 0); + for ($i = 0; $i < strlen($hex) / 2; $i++) { + $res[$i] = intval(substr($hex, $i * 2, $i * 2 + 2 - $i * 2), 16); + } + return $res; + } + + /** + * + * Convert a binary array to a hexadecimal string + * + * @param array $bin A binary array to convert to a hexadecimal string + * @return string + * + */ + private function bin_to_hex($bin) { + if (gettype($bin) != 'array') { + throw new Exception('base58->bin_to_hex(): Invalid input type (must be an array)'); + } + + $res = []; + for ($i = 0; $i < count($bin); $i++) { + $res[] = substr('0'.dechex($bin[$i]), -2); + } + return join($res); + } + + /** + * + * Convert a string to a binary array + * + * @param string $str A string to convert to a binary array + * @return array + * + */ + private function str_to_bin($str) { + if (gettype($str) != 'string') { + throw new Exception('base58->str_to_bin(): Invalid input type (must be a string)'); + } + + $res = array_fill(0, strlen($str), 0); + for ($i = 0; $i < strlen($str); $i++) { + $res[$i] = ord($str[$i]); + } + return $res; + } + + /** + * + * Convert a binary array to a string + * + * @param array $bin A binary array to convert to a string + * @return string + * + */ + private function bin_to_str($bin) { + if (gettype($bin) != 'array') { + throw new Exception('base58->bin_to_str(): Invalid input type (must be an array)'); + } + + $res = array_fill(0, count($bin), 0); + for ($i = 0; $i < count($bin); $i++) { + $res[$i] = chr($bin[$i]); + } + return preg_replace('/[[:^print:]]/', '', join($res)); // preg_replace necessary to strip errant non-ASCII characters eg. '' + } + + /** + * + * Convert a UInt8BE (one unsigned big endian byte) array to UInt64 + * + * @param array $data A UInt8BE array to convert to UInt64 + * @return number + * + */ + private function uint8_be_to_64($data) { + if (gettype($data) != 'array') { + throw new Exception ('base58->uint8_be_to_64(): Invalid input type (must be an array)'); + } + + $res = 0; + $i = 0; + switch (9 - count($data)) { + case 1: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 2: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 3: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 4: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 5: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 6: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 7: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 8: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + break; + default: + throw new Exception('base58->uint8_be_to_64: Invalid input length (1 <= count($data) <= 8)'); + } + return $res; + } + + /** + * + * Convert a UInt64 (unsigned 64 bit integer) to a UInt8BE array + * + * @param number $num A UInt64 number to convert to a UInt8BE array + * @param integer $size Size of array to return + * @return array + * + */ + private function uint64_to_8_be($num, $size) { + if (gettype($num) != ('integer' || 'double')) { + throw new Exception ('base58->uint64_to_8_be(): Invalid input type ($num must be a number)'); + } + if (gettype($size) != 'integer') { + throw new Exception ('base58->uint64_to_8_be(): Invalid input type ($size must be an integer)'); + } + if ($size < 1 || $size > 8) { + throw new Exception ('base58->uint64_to_8_be(): Invalid size (1 <= $size <= 8)'); + } + + $res = array_fill(0, $size, 0); + for ($i = $size - 1; $i >= 0; $i--) { + $res[$i] = bcmod($num, bcpow(2, 8)); + $num = bcdiv($num, bcpow(2, 8)); + } + return $res; + } + + /** + * + * Convert a hexadecimal (Base16) array to a Base58 string + * + * @param array $data + * @param array $buf + * @param number $index + * @return array + * + */ + private function encode_block($data, $buf, $index) { + if (gettype($data) != 'array') { + throw new Exception('base58->encode_block(): Invalid input type ($data must be an array)'); + } + if (gettype($buf) != 'array') { + throw new Exception('base58->encode_block(): Invalid input type ($buf must be an array)'); + } + if (gettype($index) != ('integer' || 'double')) { + throw new Exception('base58->encode_block(): Invalid input type ($index must be a number)'); + } + if (count($data) < 1 or count($data) > self::$full_encoded_block_size) { + throw new Exception('base58->encode_block(): Invalid input length (1 <= count($data) <= 8)'); + } + + $num = self::uint8_be_to_64($data); + $i = self::$encoded_block_sizes[count($data)] - 1; + while ($num > 0) { + $remainder = bcmod($num, 58); + $num = bcdiv($num, 58); + $buf[$index + $i] = ord(self::$alphabet[$remainder]); + $i--; + } + return $buf; + } + + /** + * + * Encode a hexadecimal (Base16) string to Base58 + * + * @param string $hex A hexadecimal (Base16) string to convert to Base58 + * @return string + * + */ + public function encode($hex) { + if (gettype($hex) != 'string') { + throw new Exception ('base58->encode(): Invalid input type (must be a string)'); + } + + $data = self::hex_to_bin($hex); + if (count($data) == 0) { + return ''; + } + + $full_block_count = floor(count($data) / self::$full_block_size); + $last_block_size = count($data) % self::$full_block_size; + $res_size = $full_block_count * self::$full_encoded_block_size + self::$encoded_block_sizes[$last_block_size]; + + $res = array_fill(0, $res_size, 0); + for ($i = 0; $i < $res_size; $i++) { + $res[$i] = self::$alphabet[0]; + } + + for ($i = 0; $i < $full_block_count; $i++) { + $res = self::encode_block(array_slice($data, $i * self::$full_block_size, ($i * self::$full_block_size + self::$full_block_size) - ($i * self::$full_block_size)), $res, $i * self::$full_encoded_block_size); + } + + if ($last_block_size > 0) { + $res = self::encode_block(array_slice($data, $full_block_count * self::$full_block_size, $full_block_count * self::$full_block_size + $last_block_size), $res, $full_block_count * self::$full_encoded_block_size); + } + + return self::bin_to_str($res); + } + + /** + * + * Convert a Base58 input to hexadecimal (Base16) + * + * @param array $data + * @param array $buf + * @param integer $index + * @return array + * + */ + private function decode_block($data, $buf, $index) { + if (gettype($data) != 'array') { + throw new Exception('base58->decode_block(): Invalid input type ($data must be an array)'); + } + if (gettype($buf) != 'array') { + throw new Exception('base58->decode_block(): Invalid input type ($buf must be an array)'); + } + if (gettype($index) != ('integer' || 'double')) { + throw new Exception('base58->decode_block(): Invalid input type ($index must be a number)'); + } + + $res_size = self::index_of(self::$encoded_block_sizes, count($data)); + if ($res_size <= 0) { + throw new Exception('base58->decode_block(): Invalid input length ($data must be a value from base58::$encoded_block_sizes)'); + } + + $res_num = 0; + $order = 1; + for ($i = count($data) - 1; $i >= 0; $i--) { + $digit = strpos(self::$alphabet, chr($data[$i])); + if ($digit < 0) { + throw new Exception("base58->decode_block(): Invalid character ($digit \"{$digit}\" not found in base58::$alphabet)"); + } + + $product = bcadd(bcmul($order, $digit), $res_num); + if ($product > bcpow(2, 64)) { + throw new Exception('base58->decode_block(): Integer overflow ($product exceeds the maximum 64bit integer)'); + } + + $res_num = $product; + $order = bcmul($order, 58); + } + if ($res_size < self::$full_block_size && bcpow(2, 8 * $res_size) <= 0) { + throw new Exception('base58->decode_block(): Integer overflow (bcpow(2, 8 * $res_size) exceeds the maximum 64bit integer)'); + } + + $tmp_buf = self::uint64_to_8_be($res_num, $res_size); + for ($i = 0; $i < count($tmp_buf); $i++) { + $buf[$i + $index] = $tmp_buf[$i]; + } + return $buf; + } + + /** + * + * Decode a Base58 string to hexadecimal (Base16) + * + * @param string $hex A Base58 string to convert to hexadecimal (Base16) + * @return string + * + */ + public function decode($enc) { + if (gettype($enc) != 'string') { + throw new Exception ('base58->decode(): Invalid input type (must be a string)'); + } + + $enc = self::str_to_bin($enc); + if (count($enc) == 0) { + return ''; + } + $full_block_count = floor(bcdiv(count($enc), self::$full_encoded_block_size)); + $last_block_size = bcmod(count($enc), self::$full_encoded_block_size); + $last_block_decoded_size = self::index_of(self::$encoded_block_sizes, $last_block_size); + + $data_size = $full_block_count * self::$full_block_size + $last_block_decoded_size; + + $data = array_fill(0, $data_size, 0); + for ($i = 0; $i < $full_block_count; $i++) { + $data = self::decode_block(array_slice($enc, $i * self::$full_encoded_block_size, ($i * self::$full_encoded_block_size + self::$full_encoded_block_size) - ($i * self::$full_encoded_block_size)), $data, $i * self::$full_block_size); + } + + if ($last_block_size > 0) { + $data = self::decode_block(array_slice($enc, $full_block_count * self::$full_encoded_block_size, $full_block_count * self::$full_encoded_block_size + $last_block_size), $data, $full_block_count * self::$full_block_size); + } + + return self::bin_to_hex($data); + } + + /** + * + * Search an array for a value + * Source: https://stackoverflow.com/a/30994678 + * + * @param array $haystack An array to search + * @param string $needle A string to search for + * @return number The index of the element found (or -1 for no match) + * + */ + private function index_of($haystack, $needle) { + if (gettype($haystack) != 'array') { + throw new Exception ('base58->decode(): Invalid input type ($haystack must be an array)'); + } + // if (gettype($needle) != 'string') { + // throw new Exception ('base58->decode(): Invalid input type ($needle must be a string)'); + // } + + foreach ($haystack as $key => $value) if ($value === $needle) return $key; + return -1; + } +} diff --git a/include/class-monero-cryptonote.php b/include/class-monero-cryptonote.php new file mode 100644 index 0000000..9028c95 --- /dev/null +++ b/include/class-monero-cryptonote.php @@ -0,0 +1,312 @@ +ed25519 = new ed25519(); + $this->base58 = new Monero_base58(); + $this->address_prefix = MONERO_GATEWAY_ADDRESS_PREFIX; + $this->address_prefix_integrated = MONERO_GATEWAY_ADDRESS_PREFIX_INTEGRATED; + } + + /* + * @param string Hex encoded string of the data to hash + * @return string Hex encoded string of the hashed data + * + */ + public function keccak_256($message) + { + $keccak256 = SHA3::init (SHA3::KECCAK_256); + $keccak256->absorb (hex2bin($message)); + return bin2hex ($keccak256->squeeze (32)) ; + } + + /* + * @return string A hex encoded string of 32 random bytes + * + */ + public function gen_new_hex_seed() + { + $bytes = random_bytes(32); + return bin2hex($bytes); + } + + public function sc_reduce($input) + { + $integer = $this->ed25519->decodeint(hex2bin($input)); + + $modulo = bcmod($integer , $this->ed25519->l); + + $result = bin2hex($this->ed25519->encodeint($modulo)); + return $result; + } + + /* + * Hs in the cryptonote white paper + * + * @param string Hex encoded data to hash + * + * @return string A 32 byte encoded integer + */ + public function hash_to_scalar($data) + { + $hash = $this->keccak_256($data); + $scalar = $this->sc_reduce($hash); + return $scalar; + } + + /* + * Derive a deterministic private view key from a private spend key + * @param string A private spend key represented as a 32 byte hex string + * + * @return string A deterministic private view key represented as a 32 byte hex string + */ + public function derive_viewkey($spendkey) + { + return $this->hash_to_scalar($spendkey); + } + + /* + * Generate a pair of random private keys + * + * @param string A hex string to be used as a seed (this should be random) + * + * @return array An array containing a private spend key and a deterministic view key + */ + public function gen_private_keys($seed) + { + $spendkey = $this->sc_reduce($seed); + $viewkey = $this->derive_viewkey($spendkey); + $result = array("spendkey" => $spendkey, + "viewkey" => $viewkey); + + return $result; + } + + /* + * Get a public key from a private key on the ed25519 curve + * + * @param string a 32 byte hex encoded private key + * + * @return string a 32 byte hex encoding of a point on the curve to be used as a public key + */ + public function pk_from_sk($privKey) + { + $keyInt = $this->ed25519->decodeint(hex2bin($privKey)); + $aG = $this->ed25519->scalarmult_base($keyInt); + return bin2hex($this->ed25519->encodepoint($aG)); + } + + /* + * Generate key derivation + * + * @param string a 32 byte hex encoding of a point on the ed25519 curve used as a public key + * @param string a 32 byte hex encoded private key + * + * @return string The hex encoded key derivation + */ + public function gen_key_derivation($public, $private) + { + $point = $this->ed25519->scalarmult($this->ed25519->decodepoint(hex2bin($public)), $this->ed25519->decodeint(hex2bin($private))); + $res = $this->ed25519->scalarmult($point, 8); + return bin2hex($this->ed25519->encodepoint($res)); + } + + public function encode_variant($data) + { + $orig = $data; + + if ($data < 0x80) + { + return bin2hex(pack('C', $data)); + } + + $encodedBytes = []; + while ($data > 0) + { + $encodedBytes[] = 0x80 | ($data & 0x7f); + $data >>= 7; + } + + $encodedBytes[count($encodedBytes)-1] &= 0x7f; + $bytes = call_user_func_array('pack', array_merge(array('C*'), $encodedBytes));; + return bin2hex($bytes); + } + + public function derivation_to_scalar($der, $index) + { + $encoded = $this->encode_variant($index); + $data = $der . $encoded; + return $this->hash_to_scalar($data); + } + + // this is a one way function used for both encrypting and decrypting 8 byte payment IDs + public function stealth_payment_id($payment_id, $tx_pub_key, $viewkey) + { + if(strlen($payment_id) != 16) + { + throw new Exception("Error: Incorrect payment ID size. Should be 8 bytes"); + } + $der = $this->gen_key_derivation($tx_pub_key, $viewkey); + $data = $der . '8d'; + $hash = $this->keccak_256($data); + $key = substr($hash, 0, 16); + $result = bin2hex(pack('H*',$payment_id) ^ pack('H*',$key)); + return $result; + } + + // takes transaction extra field as hex string and returns transaction public key 'R' as hex string + public function txpub_from_extra($extra) + { + $parsed = array_map("hexdec", str_split($extra, 2)); + + if($parsed[0] == 1) + { + return substr($extra, 2, 64); + } + + if($parsed[0] == 2) + { + if($parsed[0] == 2 || $parsed[2] == 1) + { + $offset = (($parsed[1] + 2) *2) + 2; + return substr($extra, (($parsed[1] + 2) *2) + 2, 64); + } + } + } + + public function derive_public_key($der, $index, $pub) + { + $scalar = $this->derivation_to_scalar($der, $index); + $sG = $this->ed25519->scalarmult_base($this->ed25519->decodeint(hex2bin($scalar))); + $pubPoint = $this->ed25519->decodepoint(hex2bin($pub)); + $key = $this->ed25519->encodepoint($this->ed25519->edwards($pubPoint, $sG)); + return bin2hex($key); + } + + /* + * Perform the calculation P = P' as described in the cryptonote whitepaper + * + * @param string 32 byte transaction public key R + * @param string 32 byte reciever private view key a + * @param string 32 byte reciever public spend key B + * @param int output index + * @param string output you want to check against P + */ + public function is_output_mine($txPublic, $privViewkey, $publicSpendkey, $index, $P) + { + $derivation = $this->gen_key_derivation($txPublic, $privViewkey); + $Pprime = $this->derive_public_key($derivation, $index, $publicSpendkey); + + if($P == $Pprime) + { + return true; + } + else + return false; + } + + /* + * Create a valid base58 encoded Monero address from public keys + * + * @param string Public spend key + * @param string Public view key + * + * @return string Base58 encoded Monero address + */ + public function encode_address($pSpendKey, $pViewKey) + { + $data = $this->address_prefix . $pSpendKey . $pViewKey; + $encoded = $this->base58->encode($data); + return $encoded; + } + + public function verify_checksum($address) + { + $decoded = $this->base58->decode($address); + $checksum = substr($decoded, -8); + $checksum_hash = $this->keccak_256(substr($decoded, 0, -8)); + $calculated = substr($checksum_hash, 0, 8); + return $checksum == $calculated; + } + +/* + * Decode a base58 encoded Monero address + * + * @param string A base58 encoded Monero address + * + * @return array An array containing the Address network byte, public spend key, and public view key + */ + public function decode_address($address) + { + $decoded = $this->base58->decode($address); + + if(!$this->verify_checksum($address)){ + throw new Exception("Error: invalid checksum"); + } + + $expected_prefix = $this->encode_variant($this->address_prefix); + $expected_prefix_length = strlen($expected_prefix); + + $network_byte = substr($decoded, 0, $expected_prefix_length); + $public_spendkey = substr($decoded, $expected_prefix_length, 64); + $public_viewkey = substr($decoded, 64+$expected_prefix_length, 64); + + return array( + "networkByte" => $network_byte, + "spendkey" => $public_spendkey, + "viewkey" => $public_viewkey + ); + } + + /* + * Get an integrated address from public keys and a payment id + * + * @param string A 32 byte hex encoded public spend key + * @param string A 32 byte hex encoded public view key + * @param string An 8 byte hex string to use as a payment id + */ + public function integrated_addr_from_keys($public_spendkey, $public_viewkey, $payment_id) + { + $prefix = $this->encode_variant($this->address_prefix_integrated); + $data = $prefix.$public_spendkey.$public_viewkey.$payment_id; + $checksum = substr($this->keccak_256($data), 0, 8); + $result = $this->base58->encode($data.$checksum); + return $result; + } + + /* + * Generate a Monero address from seed + * + * @param string Hex string to use as seed + * + * @return string A base58 encoded Monero address + */ + public function address_from_seed($hex_seed) + { + $private_keys = $this->gen_private_keys($hex_seed); + $private_viewkey = $private_keys["viewkey"]; + $private_spendkey = $private_keys["spendkey"]; + + $public_spendkey = $this->pk_from_sk($private_spendkey); + $public_viewkey = $this->pk_from_sk($private_viewkey); + + $address = $this->encode_address($public_spendkey, $public_viewkey); + return $address; + } +} diff --git a/include/class-monero-explorer-tools.php b/include/class-monero-explorer-tools.php new file mode 100644 index 0000000..e43b00f --- /dev/null +++ b/include/class-monero-explorer-tools.php @@ -0,0 +1,90 @@ +url = $testnet ? MONERO_GATEWAY_TESTNET_EXPLORER_URL : MONERO_GATEWAY_MAINNET_EXPLORER_URL; + } + + private function call_api($endpoint) + { + $curl = curl_init(); + curl_setopt_array($curl, array( + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_URL => $this->url . $endpoint, + )); + $data = curl_exec($curl); + curl_close($curl); + return json_decode($data, true); + } + + public function get_last_block_height() + { + $data = $this->call_api('/api/networkinfo'); + if($data['status'] == 'success') + return $data['data']['height'] - 1; + else + return 0; + } + + public function getheight() + { + return $this->get_last_block_height(); + } + + public function get_txs_from_block($height) + { + $data = $this->call_api("/api/search/$height"); + if($data['status'] == 'success') + return $data['data']['txs']; + else + return []; + } + + public function get_outputs($address, $viewkey) + { + $data = $this->call_api("/api/outputsblocks?address=$address&viewkey=$viewkey&limit=5&mempool=1"); + if($data['status'] == 'success') + return $data['data']['outputs']; + else + return []; + } + + public function check_tx($tx_hash, $address, $viewkey) + { + $data = $this->call_api("/api/outputs?txhash=$tx_hash&address=$address&viewkey=$viewkey&txprove=0"); + if($data['status'] == 'success') { + foreach($data['data']['outputs'] as $output) { + if($output['match']) + return true; + } + } else { + return false; + } + } + + function get_mempool_txs() + { + $data = $this->call_api('/api/mempool'); + if($data['status'] == 'success') + return $data['txs']; + else + return []; + } + +} diff --git a/include/class-monero-gateway.php b/include/class-monero-gateway.php new file mode 100644 index 0000000..7cda884 --- /dev/null +++ b/include/class-monero-gateway.php @@ -0,0 +1,789 @@ +'); + } + + function __construct($add_action=true) + { + $this->id = self::$_id; + $this->method_title = __(self::$_method_title, 'monero_gateway'); + $this->method_description = __(self::$_method_description, 'monero_gateway'); + $this->has_fields = false; + $this->supports = array( + 'products', + 'subscriptions', + 'subscription_cancellation', + 'subscription_suspension', + 'subscription_reactivation', + 'subscription_amount_changes', + 'subscription_date_changes', + 'subscription_payment_method_change' + ); + + $this->enabled = $this->get_option('enabled') == 'yes'; + + $this->init_form_fields(); + $this->init_settings(); + + self::$_title = $this->settings['title']; + $this->title = $this->settings['title']; + $this->description = $this->settings['description']; + self::$discount = $this->settings['discount']; + self::$valid_time = $this->settings['valid_time']; + self::$confirms = $this->settings['confirms']; + self::$confirm_type = $this->settings['confirm_type']; + self::$address = $this->settings['monero_address']; + self::$viewkey = $this->settings['viewkey']; + self::$host = $this->settings['daemon_host']; + self::$port = $this->settings['daemon_port']; + self::$testnet = $this->settings['testnet'] == 'yes'; + self::$onion_service = $this->settings['onion_service'] == 'yes'; + self::$show_qr = $this->settings['show_qr'] == 'yes'; + self::$use_monero_price = $this->settings['use_monero_price'] == 'yes'; + self::$use_monero_price_decimals = $this->settings['use_monero_price_decimals']; + + $explorer_url = self::$testnet ? MONERO_GATEWAY_TESTNET_EXPLORER_URL : MONERO_GATEWAY_MAINNET_EXPLORER_URL; + defined('MONERO_GATEWAY_EXPLORER_URL') || define('MONERO_GATEWAY_EXPLORER_URL', $explorer_url); + + if($add_action) + add_action('woocommerce_update_options_payment_gateways_'.$this->id, array($this, 'process_admin_options')); + + // Initialize helper classes + self::$cryptonote = new Monero_Cryptonote(); + if(self::$confirm_type == 'monero-wallet-rpc') { + require_once('class-monero-wallet-rpc.php'); + self::$monero_wallet_rpc = new Monero_Wallet_Rpc(self::$host, self::$port); + } else { + require_once('class-monero-explorer-tools.php'); + self::$monero_explorer_tools = new Monero_Explorer_Tools(self::$testnet); + } + + self::$log = new WC_Logger(); + } + + public function init_form_fields() + { + $this->form_fields = include 'admin/monero-gateway-admin-settings.php'; + } + + public function validate_monero_address_field($key,$address) + { + if($this->settings['confirm_type'] == 'viewkey') { + if (strlen($address) == 95 && substr($address, 0, 1) == '4') + if(self::$cryptonote->verify_checksum($address)) + return $address; + self::$_errors[] = 'Monero address is invalid'; + } + return $address; + } + + public function validate_viewkey_field($key,$viewkey) + { + if($this->settings['confirm_type'] == 'viewkey') { + if(preg_match('/^[a-z0-9]{64}$/i', $viewkey)) { + return $viewkey; + } else { + self::$_errors[] = 'Viewkey is invalid'; + return ''; + } + } + return $viewkey; + } + + public function validate_confirms_field($key,$confirms) + { + if($confirms >= 0 && $confirms <= 60) + return $confirms; + self::$_errors[] = 'Number of confirms must be between 0 and 60'; + } + + public function validate_valid_time_field($key,$valid_time) + { + if($valid_time >= 600 && $valid_time < 86400*7) + return $valid_time; + self::$_errors[] = 'Order valid time must be between 600 (10 minutes) and 604800 (1 week)'; + } + + public function admin_options() + { + $confirm_type = self::$confirm_type; + if($confirm_type === 'monero-wallet-rpc') + $balance = self::admin_balance_info(); + + $settings_html = $this->generate_settings_html(array(), false); + $errors = array_merge(self::$_errors, $this->admin_php_module_check(), $this->admin_ssl_check()); + include MONERO_GATEWAY_PLUGIN_DIR . '/templates/monero-gateway/admin/settings-page.php'; + } + + public static function admin_balance_info() + { + if(!is_admin()) { + return array( + 'height' => 'Not Available', + 'balance' => 'Not Available', + 'unlocked_balance' => 'Not Available', + ); + } + $wallet_amount = self::$monero_wallet_rpc->getbalance(); + $height = self::$monero_wallet_rpc->getheight(); + if (!isset($wallet_amount)) { + self::$_errors[] = 'Cannot connect to monero-wallet-rpc'; + self::$log->add('Monero_Payments', '[ERROR] Cannot connect to monero-wallet-rpc'); + return array( + 'height' => 'Not Available', + 'balance' => 'Not Available', + 'unlocked_balance' => 'Not Available', + ); + } else { + return array( + 'height' => $height, + 'balance' => self::format_monero($wallet_amount['balance']).' Monero', + 'unlocked_balance' => self::format_monero($wallet_amount['unlocked_balance']).' Monero' + ); + } + } + + protected function admin_ssl_check() + { + $errors = array(); + if ($this->enabled && !self::$onion_service) + if (get_option('woocommerce_force_ssl_checkout') == 'no') + $errors[] = sprintf('%s is enabled and WooCommerce is not forcing the SSL certificate on your checkout page. Please ensure that you have a valid SSL certificate and that you are forcing the checkout pages to be secured.', self::$_method_title, admin_url('admin.php?page=wc-settings&tab=checkout')); + return $errors; + } + + protected function admin_php_module_check() + { + $errors = array(); + if(!extension_loaded('bcmath')) + $errors[] = 'PHP extension bcmath must be installed'; + return $errors; + } + + public function process_payment($order_id) + { + global $wpdb; + $table_name = $wpdb->prefix.'monero_gateway_quotes'; + + $order = wc_get_order($order_id); + + // Generate a unique payment id + do { + $payment_id = bin2hex(openssl_random_pseudo_bytes(8)); + $query = $wpdb->prepare("SELECT COUNT(*) FROM $table_name WHERE payment_id=%s", array($payment_id)); + $payment_id_used = $wpdb->get_var($query); + } while ($payment_id_used); + + $currency = $order->get_currency(); + $rate = self::get_live_rate($currency); + $fiat_amount = $order->get_total(''); + $monero_amount = 1e8 * $fiat_amount / $rate; + + if(self::$discount) + $monero_amount = $monero_amount - $monero_amount * self::$discount / 100; + + $monero_amount = intval($monero_amount * MONERO_GATEWAY_ATOMIC_UNITS_POW); + + $query = $wpdb->prepare("INSERT INTO $table_name (order_id, payment_id, currency, rate, amount) VALUES (%d, %s, %s, %d, %d)", array($order_id, $payment_id, $currency, $rate, $monero_amount)); + $wpdb->query($query); + + $order->update_status('on-hold', __('Awaiting offline payment', 'monero_gateway')); + $order->reduce_order_stock(); // Reduce stock levels + WC()->cart->empty_cart(); // Remove cart + + return array( + 'result' => 'success', + 'redirect' => $this->get_return_url($order) + ); + } + + /* + * function for verifying payments + * This cron runs every 30 seconds + */ + public static function do_update_event() + { + global $wpdb; + + // Get Live Price + $currencies = implode(',', self::$currencies); + $api_link = 'https://min-api.cryptocompare.com/data/price?fsym=XMR&tsyms='.$currencies.'&extraParams=monero_woocommerce'; + $curl = curl_init(); + curl_setopt_array($curl, array( + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_URL => $api_link, + )); + $resp = curl_exec($curl); + curl_close($curl); + $price = json_decode($resp, true); + + if(!isset($price['Response']) || $price['Response'] != 'Error') { + $table_name = $wpdb->prefix.'monero_gateway_live_rates'; + foreach($price as $currency=>$rate) { + // shift decimal eight places for precise int storage + $rate = intval($rate * 1e8); + $query = $wpdb->prepare("INSERT INTO $table_name (currency, rate, updated) VALUES (%s, %d, NOW()) ON DUPLICATE KEY UPDATE rate=%d, updated=NOW()", array($currency, $rate, $rate)); + $wpdb->query($query); + } + } + + // Get current network/wallet height + if(self::$confirm_type == 'monero-wallet-rpc') + $height = self::$monero_wallet_rpc->getheight(); + else + $height = self::$monero_explorer_tools->getheight(); + set_transient('monero_gateway_network_height', $height); + + // Get pending payments + $table_name_1 = $wpdb->prefix.'monero_gateway_quotes'; + $table_name_2 = $wpdb->prefix.'monero_gateway_quotes_txids'; + + $query = $wpdb->prepare("SELECT *, $table_name_1.payment_id AS payment_id, $table_name_1.amount AS amount_total, $table_name_2.amount AS amount_paid, NOW() as now FROM $table_name_1 LEFT JOIN $table_name_2 ON $table_name_1.payment_id = $table_name_2.payment_id WHERE pending=1", array()); + $rows = $wpdb->get_results($query); + + $pending_payments = array(); + + // Group the query into distinct orders by payment_id + foreach($rows as $row) { + if(!isset($pending_payments[$row->payment_id])) + $pending_payments[$row->payment_id] = array( + 'quote' => null, + 'txs' => array() + ); + $pending_payments[$row->payment_id]['quote'] = $row; + if($row->txid) + $pending_payments[$row->payment_id]['txs'][] = $row; + } + + // Loop through each pending payment and check status + foreach($pending_payments as $pending) { + $quote = $pending['quote']; + $old_txs = $pending['txs']; + $order_id = $quote->order_id; + $order = wc_get_order($order_id); + $payment_id = self::sanatize_id($quote->payment_id); + $amount_monero = $quote->amount_total; + + if(self::$confirm_type == 'monero-wallet-rpc') + $new_txs = self::check_payment_rpc($payment_id); + else + $new_txs = self::check_payment_explorer($payment_id); + + foreach($new_txs as $new_tx) { + $is_new_tx = true; + foreach($old_txs as $old_tx) { + if($new_tx['txid'] == $old_tx->txid && $new_tx['amount'] == $old_tx->amount_paid) { + $is_new_tx = false; + break; + } + } + if($is_new_tx) { + $old_txs[] = (object) $new_tx; + } + + $query = $wpdb->prepare("INSERT INTO $table_name_2 (payment_id, txid, amount, height) VALUES (%s, %s, %d, %d) ON DUPLICATE KEY UPDATE height=%d", array($payment_id, $new_tx['txid'], $new_tx['amount'], $new_tx['height'], $new_tx['height'])); + $wpdb->query($query); + } + + $txs = $old_txs; + $heights = array(); + $amount_paid = 0; + foreach($txs as $tx) { + $amount_paid += $tx->amount; + $heights[] = $tx->height; + } + + $paid = $amount_paid > $amount_monero - MONERO_GATEWAY_ATOMIC_UNIT_THRESHOLD; + + if($paid) { + if(self::$confirms == 0) { + $confirmed = true; + } else { + $highest_block = max($heights); + if($height - $highest_block >= self::$confirms && !in_array(0, $heights)) { + $confirmed = true; + } else { + $confirmed = false; + } + } + } else { + $confirmed = false; + } + + if($paid && $confirmed) { + self::$log->add('Monero_Payments', "[SUCCESS] Payment has been confirmed for order id $order_id and payment id $payment_id"); + $query = $wpdb->prepare("UPDATE $table_name_1 SET confirmed=1,paid=1,pending=0 WHERE payment_id=%s", array($payment_id)); + $wpdb->query($query); + + unset(self::$payment_details[$order_id]); + + if(self::is_virtual_in_cart($order_id) == true){ + $order->update_status('completed', __('Payment has been received.', 'monero_gateway')); + } else { + $order->update_status('processing', __('Payment has been received.', 'monero_gateway')); + } + + } else if($paid) { + self::$log->add('Monero_Payments', "[SUCCESS] Payment has been received for order id $order_id and payment id $payment_id"); + $query = $wpdb->prepare("UPDATE $table_name_1 SET paid=1 WHERE payment_id=%s", array($payment_id)); + $wpdb->query($query); + + unset(self::$payment_details[$order_id]); + + } else { + $timestamp_created = new DateTime($quote->created); + $timestamp_now = new DateTime($quote->now); + $order_age_seconds = $timestamp_now->getTimestamp() - $timestamp_created->getTimestamp(); + if($order_age_seconds > self::$valid_time) { + self::$log->add('Monero_Payments', "[FAILED] Payment has expired for order id $order_id and payment id $payment_id"); + $query = $wpdb->prepare("UPDATE $table_name_1 SET pending=0 WHERE payment_id=%s", array($payment_id)); + $wpdb->query($query); + + unset(self::$payment_details[$order_id]); + + $order->update_status('cancelled', __('Payment has expired.', 'monero_gateway')); + } + } + } + } + + protected static function check_payment_rpc($payment_id) + { + $txs = array(); + $payments = self::$monero_wallet_rpc->get_all_payments($payment_id); + foreach($payments as $payment) { + $txs[] = array( + 'amount' => $payment['amount'], + 'txid' => $payment['tx_hash'], + 'height' => $payment['block_height'] + ); + } + return $txs; + } + + public static function check_payment_explorer($payment_id) + { + $txs = array(); + $outputs = self::$monero_explorer_tools->get_outputs(self::$address, self::$viewkey); + foreach($outputs as $payment) { + if($payment['payment_id'] == $payment_id) { + $txs[] = array( + 'amount' => $payment['amount'], + 'txid' => $payment['tx_hash'], + 'height' => $payment['block_no'] + ); + } + } + return $txs; + } + + protected static function get_payment_details($order_id) + { + if(!is_integer($order_id)) + $order_id = $order_id->get_id(); + + if(isset(self::$payment_details[$order_id])) + return self::$payment_details[$order_id]; + + global $wpdb; + $table_name_1 = $wpdb->prefix.'monero_gateway_quotes'; + $table_name_2 = $wpdb->prefix.'monero_gateway_quotes_txids'; + $query = $wpdb->prepare("SELECT *, $table_name_1.payment_id AS payment_id, $table_name_1.amount AS amount_total, $table_name_2.amount AS amount_paid, NOW() as now FROM $table_name_1 LEFT JOIN $table_name_2 ON $table_name_1.payment_id = $table_name_2.payment_id WHERE order_id=%d", array($order_id)); + $details = $wpdb->get_results($query); + if (count($details)) { + $txs = array(); + $heights = array(); + $amount_paid = 0; + foreach($details as $tx) { + if(!isset($tx->txid)) + continue; + $txs[] = array( + 'txid' => $tx->txid, + 'height' => $tx->height, + 'amount' => $tx->amount_paid, + 'amount_formatted' => self::format_monero($tx->amount_paid) + ); + $amount_paid += $tx->amount_paid; + $heights[] = $tx->height; + } + + usort($txs, function($a, $b) { + if($a['height'] == 0) return -1; + return $b['height'] - $a['height']; + }); + + if(count($heights) && !in_array(0, $heights)) { + $height = get_transient('monero_gateway_network_height'); + $highest_block = max($heights); + $confirms = $height - $highest_block; + $blocks_to_confirm = self::$confirms - $confirms; + } else { + $blocks_to_confirm = self::$confirms; + } + $time_to_confirm = self::format_seconds_to_time($blocks_to_confirm * MONERO_GATEWAY_DIFFICULTY_TARGET); + + $amount_total = $details[0]->amount_total; + $amount_due = max(0, $amount_total - $amount_paid); + + $timestamp_created = new DateTime($details[0]->created); + $timestamp_now = new DateTime($details[0]->now); + + $order_age_seconds = $timestamp_now->getTimestamp() - $timestamp_created->getTimestamp(); + $order_expires_seconds = self::$valid_time - $order_age_seconds; + + $address = self::$address; + $payment_id = self::sanatize_id($details[0]->payment_id); + + if(self::$confirm_type == 'monero-wallet-rpc') { + $array_integrated_address = self::$monero_wallet_rpc->make_integrated_address($payment_id); + if (isset($array_integrated_address['integrated_address'])) { + $integrated_addr = $array_integrated_address['integrated_address']; + } else { + self::$log->add('Monero_Gateway', '[ERROR] Unable get integrated address'); + return '[ERROR] Unable get integrated address'; + } + } else { + if ($address) { + $decoded_address = self::$cryptonote->decode_address($address); + $pub_spendkey = $decoded_address['spendkey']; + $pub_viewkey = $decoded_address['viewkey']; + $integrated_addr = self::$cryptonote->integrated_addr_from_keys($pub_spendkey, $pub_viewkey, $payment_id); + } else { + self::$log->add('Monero_Gateway', '[ERROR] Merchant has not set Monero address'); + return '[ERROR] Merchant has not set Monero address'; + } + } + + $status = ''; + $paid = $details[0]->paid == 1; + $confirmed = $details[0]->confirmed == 1; + $pending = $details[0]->pending == 1; + + if($confirmed) { + $status = 'confirmed'; + } else if($paid) { + $status = 'paid'; + } else if($pending && $order_expires_seconds > 0) { + if(count($txs)) { + $status = 'partial'; + } else { + $status = 'unpaid'; + } + } else { + if(count($txs)) { + $status = 'expired_partial'; + } else { + $status = 'expired'; + } + } + + $qrcode_uri = 'monero:'.$address.'?tx_amount='.$amount_due.'&tx_payment_id='.$payment_id; + $my_order_url = wc_get_endpoint_url('view-order', $order_id, wc_get_page_permalink('myaccount')); + + $payment_details = array( + 'order_id' => $order_id, + 'payment_id' => $payment_id, + 'integrated_address' => $integrated_addr, + 'qrcode_uri' => $qrcode_uri, + 'my_order_url' => $my_order_url, + 'rate' => $details[0]->rate, + 'rate_formatted' => sprintf('%.8f', $details[0]->rate / 1e8), + 'currency' => $details[0]->currency, + 'amount_total' => $amount_total, + 'amount_paid' => $amount_paid, + 'amount_due' => $amount_due, + 'amount_total_formatted' => self::format_monero($amount_total), + 'amount_paid_formatted' => self::format_monero($amount_paid), + 'amount_due_formatted' => self::format_monero($amount_due), + 'status' => $status, + 'created' => $details[0]->created, + 'order_age' => $order_age_seconds, + 'order_expires' => self::format_seconds_to_time($order_expires_seconds), + 'blocks_to_confirm' => $blocks_to_confirm, + 'time_to_confirm' => $time_to_confirm, + 'txs' => $txs + ); + self::$payment_details[$order_id] = $payment_details; + return $payment_details; + } else { + return '[ERROR] Quote not found'; + } + + } + + public static function get_payment_details_ajax() { + + $user = wp_get_current_user(); + if($user === 0) + self::ajax_output(array('error' => '[ERROR] User not logged in')); + + $order_id = preg_replace("/[^0-9]+/", "", $_GET['order_id']); + $order = wc_get_order( $order_id ); + + if($order->user_id != $user->ID) + self::ajax_output(array('error' => '[ERROR] Order does not belong to this user')); + + if($order->get_payment_method() != self::$_id) + self::ajax_output(array('error' => '[ERROR] Order not paid for with Monero')); + + $details = self::get_payment_details($order); + if(!is_array($details)) + self::ajax_output(array('error' => $details)); + + self::ajax_output($details); + + } + public static function ajax_output($response) { + ob_clean(); + header('Content-type: application/json'); + echo json_encode($response); + wp_die(); + } + + public static function admin_order_page($post) + { + $order = wc_get_order($post->ID); + if($order->get_payment_method() != self::$_id) + return; + + $method_title = self::$_title; + $details = self::get_payment_details($order); + if(!is_array($details)) { + $error = $details; + include MONERO_GATEWAY_PLUGIN_DIR . '/templates/monero-gateway/admin/order-history-error-page.php'; + return; + } + include MONERO_GATEWAY_PLUGIN_DIR . '/templates/monero-gateway/admin/order-history-page.php'; + } + + public static function customer_order_page($order) + { + if(is_integer($order)) { + $order_id = $order; + $order = wc_get_order($order_id); + } else { + $order_id = $order->get_id(); + } + + if($order->get_payment_method() != self::$_id) + return; + + $method_title = self::$_title; + $details = self::get_payment_details($order_id); + if(!is_array($details)) { + $error = $details; + include MONERO_GATEWAY_PLUGIN_DIR . '/templates/monero-gateway/customer/order-error-page.php'; + return; + } + $show_qr = self::$show_qr; + $details_json = json_encode($details); + $ajax_url = WC_AJAX::get_endpoint('monero_gateway_payment_details'); + include MONERO_GATEWAY_PLUGIN_DIR . '/templates/monero-gateway/customer/order-page.php'; + } + + public static function customer_order_email($order) + { + if(is_integer($order)) { + $order_id = $order; + $order = wc_get_order($order_id); + } else { + $order_id = $order->get_id(); + } + + if($order->get_payment_method() != self::$_id) + return; + + $method_title = self::$_title; + $details = self::get_payment_details($order_id); + if(!is_array($details)) { + include MONERO_GATEWAY_PLUGIN_DIR . '/templates/monero-gateway/customer/order-email-error-block.php'; + return; + } + include MONERO_GATEWAY_PLUGIN_DIR . '/templates/monero-gateway/customer/order-email-block.php'; + } + + public static function get_id() + { + return self::$_id; + } + + public static function get_confirm_type() + { + return self::$confirm_type; + } + + public static function use_qr_code() + { + return self::$show_qr; + } + + public static function use_monero_price() + { + return self::$use_monero_price; + } + + + public static function convert_wc_price($price, $currency) + { + $rate = self::get_live_rate($currency); + $monero_amount = intval(MONERO_GATEWAY_ATOMIC_UNITS_POW * 1e8 * $price / $rate) / MONERO_GATEWAY_ATOMIC_UNITS_POW; + $monero_amount_formatted = sprintf('%.'.self::$use_monero_price_decimals.'f', $monero_amount); + + return << + $monero_amount_formatted + XMR + + +HTML; + } + + public static function convert_wc_price_order($price_html, $order) + { + if($order->get_payment_method() != self::$_id) + return $price_html; + + $order_id = $order->get_id(); + $payment_details = self::get_payment_details($order_id); + if(!is_array($payment_details)) + return $price_html; + + // Experimental regex, may fail with other custom price formatters + $match_ok = preg_match('/data-price="([^"]*)"/', $price_html, $matches); + if($match_ok !== 1) // regex failed + return $price_html; + + $price = array_pop($matches); + $currency = $payment_details['currency']; + $rate = $payment_details['rate']; + $monero_amount = intval(MONERO_GATEWAY_ATOMIC_UNITS_POW * 1e8 * $price / $rate) / MONERO_GATEWAY_ATOMIC_UNITS_POW; + $monero_amount_formatted = sprintf('%.'.MONERO_GATEWAY_ATOMIC_UNITS.'f', $monero_amount); + + return << + $monero_amount_formatted + XMR + + +HTML; + } + + public static function get_live_rate($currency) + { + if(isset(self::$rates[$currency])) + return self::$rates[$currency]; + + global $wpdb; + $table_name = $wpdb->prefix.'monero_gateway_live_rates'; + $query = $wpdb->prepare("SELECT rate FROM $table_name WHERE currency=%s", array($currency)); + + $rate = $wpdb->get_row($query)->rate; + self::$rates[$currency] = $rate; + + return $rate; + } + + protected static function sanatize_id($payment_id) + { + // Limit payment id to alphanumeric characters + $sanatized_id = preg_replace("/[^a-zA-Z0-9]+/", "", $payment_id); + return $sanatized_id; + } + + protected static function is_virtual_in_cart($order_id) + { + $order = wc_get_order($order_id); + $items = $order->get_items(); + $cart_size = count($items); + $virtual_items = 0; + + foreach ( $items as $item ) { + $product = new WC_Product( $item['product_id'] ); + if ($product->is_virtual()) { + $virtual_items += 1; + } + } + return $virtual_items == $cart_size; + } + + public static function format_monero($atomic_units) { + return sprintf(MONERO_GATEWAY_ATOMIC_UNITS_SPRINTF, $atomic_units / MONERO_GATEWAY_ATOMIC_UNITS_POW); + } + + public static function format_seconds_to_time($seconds) + { + $units = array(); + + $dtF = new \DateTime('@0'); + $dtT = new \DateTime("@$seconds"); + $diff = $dtF->diff($dtT); + + $d = $diff->format('%a'); + $h = $diff->format('%h'); + $m = $diff->format('%i'); + + if($d == 1) + $units[] = "$d day"; + else if($d > 1) + $units[] = "$d days"; + + if($h == 0 && $d != 0) + $units[] = "$h hours"; + else if($h == 1) + $units[] = "$h hour"; + else if($h > 0) + $units[] = "$h hours"; + + if($m == 1) + $units[] = "$m minute"; + else + $units[] = "$m minutes"; + + return implode(', ', $units) . ($seconds < 0 ? ' ago' : ''); + } + +} diff --git a/monero/library.php b/include/class-monero-wallet-rpc.php similarity index 67% rename from monero/library.php rename to include/class-monero-wallet-rpc.php index 8a75108..5940dda 100644 --- a/monero/library.php +++ b/include/class-monero-wallet-rpc.php @@ -1,7 +1,6 @@ * http://implix.com * Modified to work with monero-rpc wallet by Serhack and cryptochangements + * Modified to work with monero-wallet-rpc wallet by mosu-forge */ -class Monero_Library + +defined( 'ABSPATH' ) || exit; + +class Monero_Wallet_Rpc { - protected $url = null, $is_debug = false, $parameters_structure = 'array'; + protected $url = null, $is_debug = false; protected $curl_options = array( CURLOPT_CONNECTTIMEOUT => 8, CURLOPT_TIMEOUT => 8 @@ -36,7 +39,7 @@ class Monero_Library { $this->validate(false === extension_loaded('curl'), 'The curl extension must be loaded to use this class!'); $this->validate(false === extension_loaded('json'), 'The json extension must be loaded to use this class!'); - + $this->host = $pHost; $this->port = $pPort; $this->url = $pHost . ':' . $pPort . '/json_rpc'; @@ -45,7 +48,7 @@ class Monero_Library public function validate($pFailed, $pErrMsg) { if ($pFailed) { - echo $pErrMsg; + if(is_admin()) echo $pErrMsg; } } @@ -55,25 +58,12 @@ class Monero_Library return $this; } - /* public function setParametersStructure($pParametersStructure) - { - if (in_array($pParametersStructure, array('array', 'object'))) - { - $this->parameters_structure = $pParametersStructure; - } - else - { - throw new UnexpectedValueException('Invalid parameters structure type.'); - } - return $this; - } */ - public function setCurlOptions($pOptionsArray) { if (is_array($pOptionsArray)) { $this->curl_options = $pOptionsArray + $this->curl_options; } else { - echo 'Invalid options type.'; + if(is_admin()) echo 'Invalid options type.'; } return $this; } @@ -81,13 +71,7 @@ class Monero_Library public function _print($json) { $json_encoded = json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); - echo $json_encoded; - } - - public function address() - { - $address = $this->_run('getaddress'); - return $address; + if(is_admin()) echo $json_encoded; } public function _run($method, $params = null) @@ -99,23 +83,29 @@ class Monero_Library private function request($pMethod, $pParams) { static $requestId = 0; + // generating uniuqe id per process $requestId++; + // check if given params are correct $this->validate(false === is_scalar($pMethod), 'Method name has no scalar value'); - // $this->validate(false === is_array($pParams), 'Params must be given as array'); - // send params as an object or an array - //$pParams = ($this->parameters_structure == 'object') ? $pParams[0] : array_values($pParams); + // Request (method invocation) $request = json_encode(array('jsonrpc' => '2.0', 'method' => $pMethod, 'params' => $pParams, 'id' => $requestId)); + // if is_debug mode is true then add url and request to is_debug $this->debug('Url: ' . $this->url . "\r\n", false); $this->debug('Request: ' . $request . "\r\n", false); + + // Response (method invocation) $responseMessage = $this->getResponse($request); + // if is_debug mode is true then add response to is_debug and display it $this->debug('Response: ' . $responseMessage . "\r\n", true); + // decode and create array ( can be object, just set to false ) $responseDecoded = json_decode($responseMessage, true); + // check if decoding json generated any errors $jsonErrorMsg = $this->getJsonLastErrorMsg(); $this->validate(!is_null($jsonErrorMsg), $jsonErrorMsg . ': ' . $responseMessage); @@ -149,7 +139,7 @@ class Monero_Library $endTime = array_sum(explode(' ', microtime())); // performance summary $debug .= 'Request time: ' . round($endTime - $startTime, 3) . ' s Memory usage: ' . round(memory_get_usage() / 1024) . " kb\r\n"; - echo nl2br($debug); + if(is_admin()) echo nl2br($debug); // send output immediately flush(); // clean static @@ -177,14 +167,18 @@ class Monero_Library } // send the request $response = curl_exec($ch); + // check http status code $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if (isset($this->httpErrors[$httpCode])) { - echo 'Response Http Error - ' . $this->httpErrors[$httpCode]; + if(is_admin()) + echo 'Response Http Error - ' . $this->httpErrors[$httpCode]; } + // check for curl error if (0 < curl_errno($ch)) { - echo '[ERROR] Failed to connect to monero-wallet-rpc at ' . $this->host . ' port '. $this->port .'
'; + if(is_admin()) + echo '[ERROR] Failed to connect to monero-wallet-rpc at ' . $this->host . ' port '. $this->port .'
'; } // close the connection curl_close($ch); @@ -219,12 +213,18 @@ class Monero_Library } } - /* + /* * The following functions can all be called to interact with the Monero RPC wallet * They will majority of them will return the result as an array * Example: $daemon->address(); where $daemon is an instance of this class, will return the wallet address as string within an array */ + public function address() + { + $address = $this->_run('getaddress'); + return $address; + } + public function getbalance() { $balance = $this->_run('getbalance'); @@ -234,7 +234,7 @@ class Monero_Library public function getheight() { $height = $this->_run('getheight'); - return $height; + return $height['height']; } public function incoming_transfer($type) @@ -271,7 +271,7 @@ class Monero_Library public function split_integrated_address($integrated_address) { if (!isset($integrated_address)) { - echo "Error: Integrated_Address mustn't be null"; + if(is_admin()) echo "Error: Integrated_Address must not be null"; } else { $split_params = array('integrated_address' => $integrated_address); $split_methods = $this->_run('split_integrated_address', $split_params); @@ -281,8 +281,8 @@ class Monero_Library public function make_uri($address, $amount, $recipient_name = null, $description = null) { - // If I pass 1, it will be 0.0000001 xmr. Then - $new_amount = $amount * 100000000; + // Convert to atomic units + $new_amount = $amount * MONERO_GATEWAY_ATOMIC_UNITS_POW; $uri_params = array('address' => $address, 'amount' => $new_amount, 'payment_id' => '', 'recipient_name' => $recipient_name, 'tx_description' => $description); $uri = $this->_run('make_uri', $uri_params); @@ -296,9 +296,9 @@ class Monero_Library return $parsed_uri; } - public function transfer($amount, $address, $mixin = 4) + public function transfer($amount, $address, $mixin = 12) { - $new_amount = $amount * 1000000000000; + $new_amount = $amount * MONERO_GATEWAY_ATOMIC_UNITS_POW; $destinations = array('amount' => $new_amount, 'address' => $address); $transfer_parameters = array('destinations' => array($destinations), 'mixin' => $mixin, 'get_tx_key' => true, 'unlock_time' => 0, 'payment_id' => ''); $transfer_method = $this->_run('transfer', $transfer_parameters); @@ -309,7 +309,38 @@ class Monero_Library { $get_payments_parameters = array('payment_id' => $payment_id); $get_payments = $this->_run('get_payments', $get_payments_parameters); - return $get_payments; + if(isset($get_payments['payments'])) + return $get_payments['payments']; + else + return array(); + } + + public function get_pool_payments($payment_id) + { + $get_payments_parameters = array('pool' => true); + $get_payments = $this->_run('get_transfers', $get_payments_parameters); + + if(!isset($get_payments['pool'])) + return array(); + + $payments = array(); + foreach($get_payments['pool'] as $payment) { + if($payment['double_spend_seen'])continue; + if($payment['payment_id'] == $payment_id) { + $payment['tx_hash'] = $payment['txid']; + $payment['block_height'] = $payment['height']; + $payments[] = $payment; + } + } + + return $payments; + } + + public function get_all_payments($payment_id) + { + $confirmed_payments = $this->get_payments($payment_id); + $pool_payments = $this->get_pool_payments($payment_id); + return array_merge($pool_payments, $confirmed_payments); } public function get_bulk_payments($payment_id, $min_block_height) @@ -319,118 +350,3 @@ class Monero_Library return $get_bulk_payments; } } - -class NodeTools -{ - private $url; - public function __construct($testnet = false) - { - if(!testnet) - { - $this->url = 'https://xmrchain.net'; - } - if(testnet) - { - $this->url = 'https://testnet.xmrchain.net'; - } - } - - public function get_last_block_height() - { - $curl = curl_init(); - - curl_setopt_array($curl, array( - CURLOPT_RETURNTRANSFER => 1, - CURLOPT_URL => $this->url . 'api/networkinfo', - )); - $resp = curl_exec($curl); - curl_close($curl); - - $array = json_decode($resp, true); - return $array['data']['height'] - 1; - } - - public function get_txs_from_block($height) - { - $curl = curl_init(); - - curl_setopt_array($curl, array( - CURLOPT_RETURNTRANSFER => 1, - CURLOPT_URL => $this->url . '/api/search/' . $height, - )); - $resp = curl_exec($curl); - curl_close($curl); - - $array = json_decode($resp, true); - - return $array['data']['txs']; - } - - public function get_outputs($address, $viewkey, $zero_conf = false) - { - $curl = curl_init(); - - if(!$zero_conf) - { - curl_setopt_array($curl, array( - CURLOPT_RETURNTRANSFER => 1, - CURLOPT_URL => $this->url . '/api/outputsblocks?address=' . $address . '&viewkey=' . $viewkey . '&limit=5&mempool=0', - )); - } - - // also look in mempool if accepting zero confirmation transactions - if($zero_conf) - { - curl_setopt_array($curl, array( - CURLOPT_RETURNTRANSFER => 1, - CURLOPT_URL => $this->url . '/api/outputsblocks?address=' . $address . '&viewkey=' . $viewkey . '&limit=5&mempool=1', - )); - } - - $resp = curl_exec($curl); - curl_close($curl); - - $array = json_decode($resp, true); - - return $array['data']['outputs']; - } - - public function check_tx($tx_hash, $address, $viewKey) - { - $curl = curl_init(); - curl_setopt_array($curl, array( - CURLOPT_RETURNTRANSFER => 1, - CURLOPT_URL => $this-url . '/api/outputs?txhash=' .$tx_hash . '&address='. $address . '&viewkey='. $viewKey .'&txprove=0', - )); - $resp = curl_exec($curl); - curl_close($curl); - $array = json_decode($resp, true); - $output_count = count($array['data']['outputs']); - $i = 0; - while($i < $output_count) - { - if($array['data']['outputs'][$i]['match']) - { - return $array['data']['outputs'][$i]; - } - - $i++; - } - - } - - function get_mempool_txs() - { - $curl = curl_init(); - - curl_setopt_array($curl, array( - CURLOPT_RETURNTRANSFER => 1, - CURLOPT_URL => $this->url . '/api/mempool', - )); - $resp = curl_exec($curl); - curl_close($curl); - $array = json_decode($resp, true); - return $array; - } - -} diff --git a/monero/include/SHA3.php b/include/crypto/SHA3.php similarity index 99% rename from monero/include/SHA3.php rename to include/crypto/SHA3.php index 385561f..aa3b263 100644 --- a/monero/include/SHA3.php +++ b/include/crypto/SHA3.php @@ -23,7 +23,6 @@ vim: ts=4 noet ai */ @file */ - /** SHA-3 (FIPS-202) for PHP strings (byte arrays) (PHP 5.2.1+) PHP 7.0 computes SHA-3 about 4 times faster than PHP 5.2 - 5.6 (on x86_64) @@ -34,6 +33,9 @@ vim: ts=4 noet ai */ This uses PHP's native byte strings. Supports 32-bit as well as 64-bit systems. Also for LE vs. BE systems. */ + +defined( 'ABSPATH' ) || exit; + class SHA3 { const SHA3_224 = 1; const SHA3_256 = 2; diff --git a/monero/include/ed25519.php b/include/crypto/ed25519.php similarity index 99% rename from monero/include/ed25519.php rename to include/crypto/ed25519.php index e6d443b..0a270f7 100644 --- a/monero/include/ed25519.php +++ b/include/crypto/ed25519.php @@ -30,6 +30,9 @@ * * @link http://ed25519.cr.yp.to/software.html Other ED25519 implementations this is referenced from */ + +defined( 'ABSPATH' ) || exit; + class ed25519 { public $b; diff --git a/monero-woocommerce-gateway.php b/monero-woocommerce-gateway.php new file mode 100644 index 0000000..10accac --- /dev/null +++ b/monero-woocommerce-gateway.php @@ -0,0 +1,249 @@ +'.__('Settings', 'monero_gateway').'' + ); + return array_merge($plugin_links, $links); + } + + add_filter('cron_schedules', 'monero_cron_add_one_minute'); + function monero_cron_add_one_minute($schedules) { + $schedules['one_minute'] = array( + 'interval' => 60, + 'display' => __('Once every minute', 'monero_gateway') + ); + return $schedules; + } + + add_action('wp', 'monero_activate_cron'); + function monero_activate_cron() { + if(!wp_next_scheduled('monero_update_event')) { + wp_schedule_event(time(), 'one_minute', 'monero_update_event'); + } + } + + add_action('monero_update_event', 'monero_update_event'); + function monero_update_event() { + Monero_Gateway::do_update_event(); + } + + add_action('woocommerce_thankyou_'.Monero_Gateway::get_id(), 'monero_order_confirm_page'); + add_action('woocommerce_order_details_after_order_table', 'monero_order_page'); + add_action('woocommerce_email_after_order_table', 'monero_order_email'); + + function monero_order_confirm_page($order_id) { + Monero_Gateway::customer_order_page($order_id); + } + function monero_order_page($order) { + if(!is_wc_endpoint_url('order-received')) + Monero_Gateway::customer_order_page($order); + } + function monero_order_email($order) { + Monero_Gateway::customer_order_email($order); + } + + add_action('wc_ajax_monero_gateway_payment_details', 'monero_get_payment_details_ajax'); + function monero_get_payment_details_ajax() { + Monero_Gateway::get_payment_details_ajax(); + } + + add_filter('woocommerce_currencies', 'monero_add_currency'); + function monero_add_currency($currencies) { + $currencies['Monero'] = __('Monero', 'monero_gateway'); + return $currencies; + } + + add_filter('woocommerce_currency_symbol', 'monero_add_currency_symbol', 10, 2); + function monero_add_currency_symbol($currency_symbol, $currency) { + switch ($currency) { + case 'Monero': + $currency_symbol = 'XMR'; + break; + } + return $currency_symbol; + } + + if(Monero_Gateway::use_monero_price()) { + + // This filter will replace all prices with amount in Monero (live rates) + add_filter('wc_price', 'monero_live_price_format', 10, 3); + function monero_live_price_format($price_html, $price_float, $args) { + if(!isset($args['currency']) || !$args['currency']) { + global $woocommerce; + $currency = strtoupper(get_woocommerce_currency()); + } else { + $currency = strtoupper($args['currency']); + } + return Monero_Gateway::convert_wc_price($price_float, $currency); + } + + // These filters will replace the live rate with the exchange rate locked in for the order + // We must be careful to hit all the hooks for price displays associated with an order, + // else the exchange rate can change dynamically (which it should for an order) + add_filter('woocommerce_order_formatted_line_subtotal', 'monero_order_item_price_format', 10, 3); + function monero_order_item_price_format($price_html, $item, $order) { + return Monero_Gateway::convert_wc_price_order($price_html, $order); + } + + add_filter('woocommerce_get_formatted_order_total', 'monero_order_total_price_format', 10, 2); + function monero_order_total_price_format($price_html, $order) { + return Monero_Gateway::convert_wc_price_order($price_html, $order); + } + + add_filter('woocommerce_get_order_item_totals', 'monero_order_totals_price_format', 10, 3); + function monero_order_totals_price_format($total_rows, $order, $tax_display) { + foreach($total_rows as &$row) { + $price_html = $row['value']; + $row['value'] = Monero_Gateway::convert_wc_price_order($price_html, $order); + } + return $total_rows; + } + + } + + add_action('wp_enqueue_scripts', 'monero_enqueue_scripts'); + function monero_enqueue_scripts() { + if(Monero_Gateway::use_monero_price()) + wp_dequeue_script('wc-cart-fragments'); + if(Monero_Gateway::use_qr_code()) + wp_enqueue_script('monero-qr-code', MONERO_GATEWAY_PLUGIN_URL.'assets/js/qrcode.min.js'); + + wp_enqueue_script('monero-clipboard-js', MONERO_GATEWAY_PLUGIN_URL.'assets/js/clipboard.min.js'); + wp_enqueue_script('monero-gateway', MONERO_GATEWAY_PLUGIN_URL.'assets/js/monero-gateway-order-page.js'); + wp_enqueue_style('monero-gateway', MONERO_GATEWAY_PLUGIN_URL.'assets/css/monero-gateway-order-page.css'); + } + + // [monero-price currency="USD"] + // currency: BTC, GBP, etc + // if no none, then default store currency + function monero_price_func( $atts ) { + global $woocommerce; + $a = shortcode_atts( array( + 'currency' => get_woocommerce_currency() + ), $atts ); + + $currency = strtoupper($a['currency']); + $rate = Monero_Gateway::get_live_rate($currency); + if($currency == 'BTC') + $rate_formatted = sprintf('%.8f', $rate / 1e8); + else + $rate_formatted = sprintf('%.5f', $rate / 1e8); + + return "1 XMR = $rate_formatted $currency"; + } + add_shortcode('monero-price', 'monero_price_func'); + + + // [monero-accepted-here] + function monero_accepted_func() { + return ''; + } + add_shortcode('monero-accepted-here', 'monero_accepted_func'); + +} + +register_deactivation_hook(__FILE__, 'monero_deactivate'); +function monero_deactivate() { + $timestamp = wp_next_scheduled('monero_update_event'); + wp_unschedule_event($timestamp, 'monero_update_event'); +} + +register_activation_hook(__FILE__, 'monero_install'); +function monero_install() { + global $wpdb; + require_once( ABSPATH . '/wp-admin/includes/upgrade.php' ); + $charset_collate = $wpdb->get_charset_collate(); + + $table_name = $wpdb->prefix . "monero_gateway_quotes"; + if($wpdb->get_var("show tables like '$table_name'") != $table_name) { + $sql = "CREATE TABLE $table_name ( + order_id BIGINT(20) UNSIGNED NOT NULL, + payment_id VARCHAR(16) DEFAULT '' NOT NULL, + currency VARCHAR(6) DEFAULT '' NOT NULL, + rate BIGINT UNSIGNED DEFAULT 0 NOT NULL, + amount BIGINT UNSIGNED DEFAULT 0 NOT NULL, + paid TINYINT NOT NULL DEFAULT 0, + confirmed TINYINT NOT NULL DEFAULT 0, + pending TINYINT NOT NULL DEFAULT 1, + created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (order_id) + ) $charset_collate;"; + dbDelta($sql); + } + + $table_name = $wpdb->prefix . "monero_gateway_quotes_txids"; + if($wpdb->get_var("show tables like '$table_name'") != $table_name) { + $sql = "CREATE TABLE $table_name ( + id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + payment_id VARCHAR(16) DEFAULT '' NOT NULL, + txid VARCHAR(64) DEFAULT '' NOT NULL, + amount BIGINT UNSIGNED DEFAULT 0 NOT NULL, + height MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (id), + UNIQUE KEY (payment_id, txid, amount) + ) $charset_collate;"; + dbDelta($sql); + } + + $table_name = $wpdb->prefix . "monero_gateway_live_rates"; + if($wpdb->get_var("show tables like '$table_name'") != $table_name) { + $sql = "CREATE TABLE $table_name ( + currency VARCHAR(6) DEFAULT '' NOT NULL, + rate BIGINT UNSIGNED DEFAULT 0 NOT NULL, + updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (currency) + ) $charset_collate;"; + dbDelta($sql); + } +} diff --git a/monero/include/base58.php b/monero/include/base58.php deleted file mode 100644 index e0c1604..0000000 --- a/monero/include/base58.php +++ /dev/null @@ -1,354 +0,0 @@ -hex_to_bin(): Invalid input type (must be a string)'); - } - if (strlen($hex) % 2 != 0) { - throw new Exception('base58->hex_to_bin(): Invalid input length (must be even)'); - } - - $res = array_fill(0, strlen($hex) / 2, 0); - for ($i = 0; $i < strlen($hex) / 2; $i++) { - $res[$i] = intval(substr($hex, $i * 2, $i * 2 + 2 - $i * 2), 16); - } - return $res; - } - - /** - * - * Convert a binary array to a hexadecimal string - * - * @param array $bin A binary array to convert to a hexadecimal string - * @return string - * - */ - private function bin_to_hex($bin) { - if (gettype($bin) != 'array') { - throw new Exception('base58->bin_to_hex(): Invalid input type (must be an array)'); - } - - $res = []; - for ($i = 0; $i < count($bin); $i++) { - $res[] = substr('0'.dechex($bin[$i]), -2); - } - return join($res); - } - - /** - * - * Convert a string to a binary array - * - * @param string $str A string to convert to a binary array - * @return array - * - */ - private function str_to_bin($str) { - if (gettype($str) != 'string') { - throw new Exception('base58->str_to_bin(): Invalid input type (must be a string)'); - } - - $res = array_fill(0, strlen($str), 0); - for ($i = 0; $i < strlen($str); $i++) { - $res[$i] = ord($str[$i]); - } - return $res; - } - - /** - * - * Convert a binary array to a string - * - * @param array $bin A binary array to convert to a string - * @return string - * - */ - private function bin_to_str($bin) { - if (gettype($bin) != 'array') { - throw new Exception('base58->bin_to_str(): Invalid input type (must be an array)'); - } - - $res = array_fill(0, count($bin), 0); - for ($i = 0; $i < count($bin); $i++) { - $res[$i] = chr($bin[$i]); - } - return preg_replace('/[[:^print:]]/', '', join($res)); // preg_replace necessary to strip errant non-ASCII characters eg. '' - } - - /** - * - * Convert a UInt8BE (one unsigned big endian byte) array to UInt64 - * - * @param array $data A UInt8BE array to convert to UInt64 - * @return number - * - */ - private function uint8_be_to_64($data) { - if (gettype($data) != 'array') { - throw new Exception ('base58->uint8_be_to_64(): Invalid input type (must be an array)'); - } - - $res = 0; - $i = 0; - switch (9 - count($data)) { - case 1: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 2: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 3: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 4: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 5: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 6: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 7: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 8: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - break; - default: - throw new Exception('base58->uint8_be_to_64: Invalid input length (1 <= count($data) <= 8)'); - } - return $res; - } - - /** - * - * Convert a UInt64 (unsigned 64 bit integer) to a UInt8BE array - * - * @param number $num A UInt64 number to convert to a UInt8BE array - * @param integer $size Size of array to return - * @return array - * - */ - private function uint64_to_8_be($num, $size) { - if (gettype($num) != ('integer' || 'double')) { - throw new Exception ('base58->uint64_to_8_be(): Invalid input type ($num must be a number)'); - } - if (gettype($size) != 'integer') { - throw new Exception ('base58->uint64_to_8_be(): Invalid input type ($size must be an integer)'); - } - if ($size < 1 || $size > 8) { - throw new Exception ('base58->uint64_to_8_be(): Invalid size (1 <= $size <= 8)'); - } - - $res = array_fill(0, $size, 0); - for ($i = $size - 1; $i >= 0; $i--) { - $res[$i] = bcmod($num, bcpow(2, 8)); - $num = bcdiv($num, bcpow(2, 8)); - } - return $res; - } - - /** - * - * Convert a hexadecimal (Base16) array to a Base58 string - * - * @param array $data - * @param array $buf - * @param number $index - * @return array - * - */ - private function encode_block($data, $buf, $index) { - if (gettype($data) != 'array') { - throw new Exception('base58->encode_block(): Invalid input type ($data must be an array)'); - } - if (gettype($buf) != 'array') { - throw new Exception('base58->encode_block(): Invalid input type ($buf must be an array)'); - } - if (gettype($index) != ('integer' || 'double')) { - throw new Exception('base58->encode_block(): Invalid input type ($index must be a number)'); - } - if (count($data) < 1 or count($data) > self::$full_encoded_block_size) { - throw new Exception('base58->encode_block(): Invalid input length (1 <= count($data) <= 8)'); - } - - $num = self::uint8_be_to_64($data); - $i = self::$encoded_block_sizes[count($data)] - 1; - while ($num > 0) { - $remainder = bcmod($num, 58); - $num = bcdiv($num, 58); - $buf[$index + $i] = ord(self::$alphabet[$remainder]); - $i--; - } - return $buf; - } - - /** - * - * Encode a hexadecimal (Base16) string to Base58 - * - * @param string $hex A hexadecimal (Base16) string to convert to Base58 - * @return string - * - */ - public function encode($hex) { - if (gettype($hex) != 'string') { - throw new Exception ('base58->encode(): Invalid input type (must be a string)'); - } - - $data = self::hex_to_bin($hex); - if (count($data) == 0) { - return ''; - } - - $full_block_count = floor(count($data) / self::$full_block_size); - $last_block_size = count($data) % self::$full_block_size; - $res_size = $full_block_count * self::$full_encoded_block_size + self::$encoded_block_sizes[$last_block_size]; - - $res = array_fill(0, $res_size, 0); - for ($i = 0; $i < $res_size; $i++) { - $res[$i] = self::$alphabet[0]; - } - - for ($i = 0; $i < $full_block_count; $i++) { - $res = self::encode_block(array_slice($data, $i * self::$full_block_size, ($i * self::$full_block_size + self::$full_block_size) - ($i * self::$full_block_size)), $res, $i * self::$full_encoded_block_size); - } - - if ($last_block_size > 0) { - $res = self::encode_block(array_slice($data, $full_block_count * self::$full_block_size, $full_block_count * self::$full_block_size + $last_block_size), $res, $full_block_count * self::$full_encoded_block_size); - } - - return self::bin_to_str($res); - } - - /** - * - * Convert a Base58 input to hexadecimal (Base16) - * - * @param array $data - * @param array $buf - * @param integer $index - * @return array - * - */ - private function decode_block($data, $buf, $index) { - if (gettype($data) != 'array') { - throw new Exception('base58->decode_block(): Invalid input type ($data must be an array)'); - } - if (gettype($buf) != 'array') { - throw new Exception('base58->decode_block(): Invalid input type ($buf must be an array)'); - } - if (gettype($index) != ('integer' || 'double')) { - throw new Exception('base58->decode_block(): Invalid input type ($index must be a number)'); - } - - $res_size = self::index_of(self::$encoded_block_sizes, count($data)); - if ($res_size <= 0) { - throw new Exception('base58->decode_block(): Invalid input length ($data must be a value from base58::$encoded_block_sizes)'); - } - - $res_num = 0; - $order = 1; - for ($i = count($data) - 1; $i >= 0; $i--) { - $digit = strpos(self::$alphabet, chr($data[$i])); - if ($digit < 0) { - throw new Exception("base58->decode_block(): Invalid character ($digit \"{$digit}\" not found in base58::$alphabet)"); - } - - $product = bcadd(bcmul($order, $digit), $res_num); - if ($product > bcpow(2, 64)) { - throw new Exception('base58->decode_block(): Integer overflow ($product exceeds the maximum 64bit integer)'); - } - - $res_num = $product; - $order = bcmul($order, 58); - } - if ($res_size < self::$full_block_size && bcpow(2, 8 * $res_size) <= 0) { - throw new Exception('base58->decode_block(): Integer overflow (bcpow(2, 8 * $res_size) exceeds the maximum 64bit integer)'); - } - - $tmp_buf = self::uint64_to_8_be($res_num, $res_size); - for ($i = 0; $i < count($tmp_buf); $i++) { - $buf[$i + $index] = $tmp_buf[$i]; - } - return $buf; - } - - /** - * - * Decode a Base58 string to hexadecimal (Base16) - * - * @param string $hex A Base58 string to convert to hexadecimal (Base16) - * @return string - * - */ - public function decode($enc) { - if (gettype($enc) != 'string') { - throw new Exception ('base58->decode(): Invalid input type (must be a string)'); - } - - $enc = self::str_to_bin($enc); - if (count($enc) == 0) { - return ''; - } - $full_block_count = floor(bcdiv(count($enc), self::$full_encoded_block_size)); - $last_block_size = bcmod(count($enc), self::$full_encoded_block_size); - $last_block_decoded_size = self::index_of(self::$encoded_block_sizes, $last_block_size); - - $data_size = $full_block_count * self::$full_block_size + $last_block_decoded_size; - - $data = array_fill(0, $data_size, 0); - for ($i = 0; $i <= $full_block_count; $i++) { - $data = self::decode_block(array_slice($enc, $i * self::$full_encoded_block_size, ($i * self::$full_encoded_block_size + self::$full_encoded_block_size) - ($i * self::$full_encoded_block_size)), $data, $i * self::$full_block_size); - } - - if ($last_block_size > 0) { - $data = self::decode_block(array_slice($enc, $full_block_count * self::$full_encoded_block_size, $full_block_count * self::$full_encoded_block_size + $last_block_size), $data, $full_block_count * self::$full_block_size); - } - - return self::bin_to_hex($data); - } - - /** - * - * Search an array for a value - * Source: https://stackoverflow.com/a/30994678 - * - * @param array $haystack An array to search - * @param string $needle A string to search for - * @return number The index of the element found (or -1 for no match) - * - */ - private function index_of($haystack, $needle) { - if (gettype($haystack) != 'array') { - throw new Exception ('base58->decode(): Invalid input type ($haystack must be an array)'); - } - // if (gettype($needle) != 'string') { - // throw new Exception ('base58->decode(): Invalid input type ($needle must be a string)'); - // } - - foreach ($haystack as $key => $value) if ($value === $needle) return $key; - return -1; - } -} - -?> diff --git a/monero/include/cryptonote.php b/monero/include/cryptonote.php deleted file mode 100644 index a6dafaf..0000000 --- a/monero/include/cryptonote.php +++ /dev/null @@ -1,305 +0,0 @@ -ed25519 = new ed25519(); - $this->base58 = new base58(); - } - - /* - * @param string Hex encoded string of the data to hash - * @return string Hex encoded string of the hashed data - * - */ - public function keccak_256($message) - { - $keccak256 = SHA3::init (SHA3::KECCAK_256); - $keccak256->absorb (hex2bin($message)); - return bin2hex ($keccak256->squeeze (32)) ; - } - - /* - * @return string A hex encoded string of 32 random bytes - * - */ - public function gen_new_hex_seed() - { - $bytes = random_bytes(32); - return bin2hex($bytes); - } - - public function sc_reduce($input) - { - $integer = $this->ed25519->decodeint(hex2bin($input)); - - $modulo = bcmod($integer , $this->ed25519->l); - - $result = bin2hex($this->ed25519->encodeint($modulo)); - return $result; - } - - /* - * Hs in the cryptonote white paper - * - * @param string Hex encoded data to hash - * - * @return string A 32 byte encoded integer - */ - public function hash_to_scalar($data) - { - $hash = $this->keccak_256($data); - $scalar = $this->sc_reduce($hash); - return $scalar; - } - - /* - * Derive a deterministic private view key from a private spend key - * @param string A private spend key represented as a 32 byte hex string - * - * @return string A deterministic private view key represented as a 32 byte hex string - */ - public function derive_viewKey($spendKey) - { - return $this->hash_to_scalar($spendkey); - } - - /* - * Generate a pair of random private keys - * - * @param string A hex string to be used as a seed (this should be random) - * - * @return array An array containing a private spend key and a deterministic view key - */ - public function gen_private_keys($seed) - { - $spendKey = $this->sc_reduce($seed); - $viewKey = $this->derive_viewKey($spendKey); - $result = array("spendKey" => $spendKey, - "viewKey" => $viewKey); - - return $result; - } - - /* - * Get a public key from a private key on the ed25519 curve - * - * @param string a 32 byte hex encoded private key - * - * @return string a 32 byte hex encoding of a point on the curve to be used as a public key - */ - public function pk_from_sk($privKey) - { - $keyInt = $this->ed25519->decodeint(hex2bin($privKey)); - $aG = $this->ed25519->scalarmult_base($keyInt); - return bin2hex($this->ed25519->encodepoint($aG)); - } - - /* - * Generate key derivation - * - * @param string a 32 byte hex encoding of a point on the ed25519 curve used as a public key - * @param string a 32 byte hex encoded private key - * - * @return string The hex encoded key derivation - */ - public function gen_key_derivation($public, $private) - { - $point = $this->ed25519->scalarmult($this->ed25519->decodepoint(hex2bin($public)), $this->ed25519->decodeint(hex2bin($private))); - $res = $this->ed25519->scalarmult($point, 8); - return bin2hex($this->ed25519->encodepoint($res)); - } - - public function encode_varint($data) - { - $orig = $data; - - if ($data < 0x80) - { - return bin2hex(pack('C', $data)); - } - - $encodedBytes = []; - while ($data > 0) - { - $encodedBytes[] = 0x80 | ($data & 0x7f); - $data >>= 7; - } - - $encodedBytes[count($encodedBytes)-1] &= 0x7f; - $bytes = call_user_func_array('pack', array_merge(array('C*'), $encodedBytes));; - return bin2hex($bytes); - } - - public function derivation_to_scalar($der, $index) - { - $encoded = $this->encode_varint($index); - $data = $der . $encoded; - return $this->hash_to_scalar($data); - } - - // this is a one way function used for both encrypting and decrypting 8 byte payment IDs - public function stealth_payment_id($payment_id, $tx_pub_key, $viewkey) - { - if(strlen($payment_id) != 16) - { - throw new Exception("Error: Incorrect payment ID size. Should be 8 bytes"); - } - $der = $this->gen_key_derivation($tx_pub_key, $viewkey); - $data = $der . '8d'; - $hash = $this->keccak_256($data); - $key = substr($hash, 0, 16); - $result = bin2hex(pack('H*',$payment_id) ^ pack('H*',$key)); - return $result; - } - - // takes transaction extra field as hex string and returns transaction public key 'R' as hex string - public function txpub_from_extra($extra) - { - $parsed = array_map("hexdec", str_split($extra, 2)); - - if($parsed[0] == 1) - { - return substr($extra, 2, 64); - } - - if($parsed[0] == 2) - { - if($parsed[0] == 2 || $parsed[2] == 1) - { - $offset = (($parsed[1] + 2) *2) + 2; - return substr($extra, (($parsed[1] + 2) *2) + 2, 64); - } - } - } - - public function derive_public_key($der, $index, $pub) - { - $scalar = $this->derivation_to_scalar($der, $index); - $sG = $this->ed25519->scalarmult_base($this->ed25519->decodeint(hex2bin($scalar))); - $pubPoint = $this->ed25519->decodepoint(hex2bin($pub)); - $key = $this->ed25519->encodepoint($this->ed25519->edwards($pubPoint, $sG)); - return bin2hex($key); - } - - /* - * Perform the calculation P = P' as described in the cryptonote whitepaper - * - * @param string 32 byte transaction public key R - * @param string 32 byte reciever private view key a - * @param string 32 byte reciever public spend key B - * @param int output index - * @param string output you want to check against P - */ - public function is_output_mine($txPublic, $privViewkey, $publicSpendkey, $index, $P) - { - $derivation = $this->gen_key_derivation($txPublic, $privViewkey); - $Pprime = $this->derive_public_key($derivation, $index, $publicSpendkey); - - if($P == $Pprime) - { - return true; - } - else - return false; - } - - /* - * Create a valid base58 encoded Monero address from public keys - * - * @param string Public spend key - * @param string Public view key - * - * @return string Base58 encoded Monero address - */ - public function encode_address($pSpendKey, $pViewKey) - { - // mainnet network byte is 18 (0x12) - $data = "12" . $pSpendKey . $pViewKey; - $encoded = $this->base58->encode($data); - return $encoded; - } - - public function verify_checksum($address) - { - $decoded = $this->base58->decode($address); - $checksum = substr($decoded, -8); - $checksum_hash = $this->keccak_256(substr($decoded, 0, 130)); - $calculated = substr($checksum_hash, 0, 8); - if($checksum == $calculated){ - return true; - } - else - return false; - } - - /* - * Decode a base58 encoded Monero address - * - * @param string A base58 encoded Monero address - * - * @return array An array containing the Address network byte, public spend key, and public view key - */ - public function decode_address($address) - { - $decoded = $this->base58->decode($address); - - if(!$this->verify_checksum($address)){ - throw new Exception("Error: invalid checksum"); - } - - $network_byte = substr($decoded, 0, 2); - $public_spendKey = substr($decoded, 2, 64); - $public_viewKey = substr($decoded, 66, 64); - - $result = array("networkByte" => $network_byte, - "spendKey" => $public_spendKey, - "viewKey" => $public_viewKey); - return $result; - } - - /* - * Get an integrated address from public keys and a payment id - * - * @param string A 32 byte hex encoded public spend key - * @param string A 32 byte hex encoded public view key - * @param string An 8 byte hex string to use as a payment id - */ - public function integrated_addr_from_keys($public_spendkey, $public_viewkey, $payment_id) - { - // 0x13 is the mainnet network byte for integrated addresses - $data = "13".$public_spendkey.$public_viewkey.$payment_id; - $checksum = substr($this->keccak_256($data), 0, 8); - $result = $this->base58->encode($data.$checksum); - return $result; - } - - /* - * Generate a Monero address from seed - * - * @param string Hex string to use as seed - * - * @return string A base58 encoded Monero address - */ - public function address_from_seed($hex_seed) - { - $private_keys = $this->gen_private_keys($hex_seed); - $private_viewKey = $private_keys["viewKey"]; - $private_spendKey = $private_keys["spendKey"]; - - $public_spendKey = $this->pk_from_sk($private_spendKey); - $public_viewKey = $this->pk_from_sk($private_viewKey); - - $address = $this->encode_address($public_spendKey, $public_viewKey); - return $address; - } - } - diff --git a/monero/include/monero_payments.php b/monero/include/monero_payments.php deleted file mode 100644 index 1e44cec..0000000 --- a/monero/include/monero_payments.php +++ /dev/null @@ -1,748 +0,0 @@ -id = "monero_gateway"; - $this->method_title = __("Monero GateWay", 'monero_gateway'); - $this->method_description = __("Monero Payment Gateway Plug-in for WooCommerce. You can find more information about this payment gateway on our website. You'll need a daemon online for your address.", 'monero_gateway'); - $this->title = __("Monero Gateway", 'monero_gateway'); - $this->version = "2.0"; - // - $this->icon = apply_filters('woocommerce_offline_icon', ''); - $this->has_fields = false; - - $this->log = new WC_Logger(); - - $this->init_form_fields(); - $this->host = $this->get_option('daemon_host'); - $this->port = $this->get_option('daemon_port'); - $this->address = $this->get_option('monero_address'); - $this->viewKey = $this->get_option('viewKey'); - $this->discount = $this->get_option('discount'); - $this->accept_zero_conf = $this->get_option('zero_conf'); - - $this->use_viewKey = $this->get_option('use_viewKey'); - $this->use_rpc = $this->get_option('use_rpc'); - - $env = $this->get_option('environment'); - - if($this->use_viewKey == 'yes') - { - $this->non_rpc = true; - } - if($this->use_rpc == 'yes') - { - $this->non_rpc = false; - } - if($this->accept_zero_conf == 'yes') - { - $this->zero_confirm = true; - } - - if($env == 'yes') - { - $this->testnet = true; - } - - // After init_settings() is called, you can get the settings and load them into variables, e.g: - // $this->title = $this->get_option('title' ); - $this->init_settings(); - - // Turn these settings into variables we can use - foreach ($this->settings as $setting_key => $value) { - $this->$setting_key = $value; - } - - add_action('admin_notices', array($this, 'do_ssl_check')); - add_action('admin_notices', array($this, 'validate_fields')); - add_action('woocommerce_thankyou_' . $this->id, array($this, 'instruction')); - if (is_admin()) { - /* Save Settings */ - add_action('woocommerce_update_options_payment_gateways_' . $this->id, array($this, 'process_admin_options')); - add_filter('woocommerce_currencies', array($this,'add_my_currency')); - add_filter('woocommerce_currency_symbol', array($this,'add_my_currency_symbol'), 10, 2); - add_action('woocommerce_email_before_order_table', array($this, 'email_instructions'), 10, 2); - } - $this->monero_daemon = new Monero_Library($this->host, $this->port); - $this->cryptonote = new Cryptonote(); - - $this->supports = array( 'subscriptions', 'products' ); - $this->supports = array( - 'products', - 'subscriptions', - 'subscription_cancellation', - 'subscription_suspension', - 'subscription_reactivation', - 'subscription_amount_changes', - 'subscription_date_changes', - 'subscription_payment_method_change' - ); - } - - public function get_icon() - { - return apply_filters('woocommerce_gateway_icon', ""); - } - - public function init_form_fields() - { - $this->form_fields = array( - 'enabled' => array( - 'title' => __('Enable / Disable', 'monero_gateway'), - 'label' => __('Enable this payment gateway', 'monero_gateway'), - 'type' => 'checkbox', - 'default' => 'no' - ), - - 'title' => array( - 'title' => __('Title', 'monero_gateway'), - 'type' => 'text', - 'desc_tip' => __('Payment title the customer will see during the checkout process.', 'monero_gateway'), - 'default' => __('Monero XMR Payment', 'monero_gateway') - ), - 'description' => array( - 'title' => __('Description', 'monero_gateway'), - 'type' => 'textarea', - 'desc_tip' => __('Payment description the customer will see during the checkout process.', 'monero_gateway'), - 'default' => __('Pay securely using XMR.', 'monero_gateway') - - ), - 'use_viewKey' => array( - 'title' => __('Use ViewKey', 'monero_gateway'), - 'label' => __(' Verify Transaction with ViewKey ', 'monero_gateway'), - 'type' => 'checkbox', - 'description' => __('Fill in the Address and ViewKey fields to verify transactions with your ViewKey', 'monero_gateway'), - 'default' => 'no' - ), - 'monero_address' => array( - 'title' => __('Monero Address', 'monero_gateway'), - 'label' => __('Useful for people that have not a daemon online'), - 'type' => 'text', - 'desc_tip' => __('Monero Wallet Address', 'monero_gateway') - ), - 'viewKey' => array( - 'title' => __('Secret ViewKey', 'monero_gateway'), - 'label' => __('Secret ViewKey'), - 'type' => 'text', - 'desc_tip' => __('Your secret ViewKey', 'monero_gateway') - ), - 'use_rpc' => array( - 'title' => __('Use monero-wallet-rpc', 'monero_gateway'), - 'label' => __(' Verify transactions with the monero-wallet-rpc ', 'monero_gateway'), - 'type' => 'checkbox', - 'description' => __('This must be setup seperately', 'monero_gateway'), - 'default' => 'no' - ), - 'daemon_host' => array( - 'title' => __('Monero wallet RPC Host/ IP', 'monero_gateway'), - 'type' => 'text', - 'desc_tip' => __('This is the Daemon Host/IP to authorize the payment with port', 'monero_gateway'), - 'default' => 'localhost', - ), - 'daemon_port' => array( - 'title' => __('Monero wallet RPC port', 'monero_gateway'), - 'type' => 'text', - 'desc_tip' => __('This is the Daemon Host/IP to authorize the payment with port', 'monero_gateway'), - 'default' => '18080', - ), - 'discount' => array( - 'title' => __('% discount for using XMR', 'monero_gateway'), - - 'desc_tip' => __('Provide a discount to your customers for making a private payment with XMR!', 'monero_gateway'), - 'description' => __('Do you want to spread the word about Monero? Offer a small discount! Leave this empty if you do not wish to provide a discount', 'monero_gateway'), - 'type' => __('number'), - 'default' => '5' - - ), - 'environment' => array( - 'title' => __(' Testnet', 'monero_gateway'), - 'label' => __(' Check this if you are using testnet ', 'monero_gateway'), - 'type' => 'checkbox', - 'description' => __('Check this box if you are using testnet', 'monero_gateway'), - 'default' => 'no' - ), - 'zero_conf' => array( - 'title' => __(' Accept 0 conf txs', 'monero_gateway'), - 'label' => __(' Accept 0-confirmation transactions ', 'monero_gateway'), - 'type' => 'checkbox', - 'description' => __('This is faster but less secure', 'monero_gateway'), - 'default' => 'no' - ), - 'onion_service' => array( - 'title' => __(' SSL warnings ', 'monero_gateway'), - 'label' => __(' Check to Silence SSL warnings', 'monero_gateway'), - 'type' => 'checkbox', - 'description' => __('Check this box if you are running on an Onion Service (Suppress SSL errors)', 'monero_gateway'), - 'default' => 'no' - ), - ); - } - - public function add_my_currency($currencies) - { - $currencies['XMR'] = __('Monero', 'woocommerce'); - return $currencies; - } - - public function add_my_currency_symbol($currency_symbol, $currency) - { - switch ($currency) { - case 'XMR': - $currency_symbol = 'XMR'; - break; - } - return $currency_symbol; - } - - public function admin_options() - { - $this->log->add('Monero_gateway', '[SUCCESS] Monero Settings OK'); - echo "

Monero Payment Gateway

"; - - echo "

Welcome to Monero Extension for WooCommerce. Getting started: Make a connection with daemon Contact Me"; - echo "

"; - - if(!$this->non_rpc) // only try to get balance data if using wallet-rpc - $this->getamountinfo(); - - echo "
"; - echo "
"; - $this->generate_settings_html(); - echo "
"; - echo "

Learn more about using monero-wallet-rpc here and viewkeys here

"; - } - - public function getamountinfo() - { - $wallet_amount = $this->monero_daemon->getbalance(); - if (!isset($wallet_amount)) { - $this->log->add('Monero_gateway', '[ERROR] Cannot connect to monero-wallet-rpc'); - echo "
Your balance is: Not Available
"; - echo "Unlocked balance: Not Available"; - } - else - { - $real_wallet_amount = $wallet_amount['balance'] / 1000000000000; - $real_amount_rounded = round($real_wallet_amount, 6); - - $unlocked_wallet_amount = $wallet_amount['unlocked_balance'] / 1000000000000; - $unlocked_amount_rounded = round($unlocked_wallet_amount, 6); - - echo "Your balance is: " . $real_amount_rounded . " XMR
"; - echo "Unlocked balance: " . $unlocked_amount_rounded . " XMR
"; - } - } - - public function process_payment($order_id) - { - $order = wc_get_order($order_id); - $order->update_status('on-hold', __('Awaiting offline payment', 'monero_gateway')); - // Reduce stock levels - $order->reduce_order_stock(); - - // Remove cart - WC()->cart->empty_cart(); - - // Return thank you redirect - return array( - 'result' => 'success', - 'redirect' => $this->get_return_url($order) - ); - - } - - // Submit payment and handle response - - public function validate_fields() - { - if ($this->check_monero() != TRUE) { - echo "

Your Monero Address doesn't look valid. Have you checked it?

"; - } - if(!$this->check_viewKey()) - { - echo "

Your ViewKey doesn't look valid. Have you checked it?

"; - } - if($this->check_checkedBoxes()) - { - echo "

You must choose to either use monero-wallet-rpc or a ViewKey, not both

"; - } - - } - - - // Validate fields - - public function check_monero() - { - $monero_address = $this->settings['monero_address']; - if (strlen($monero_address) == 95 && substr($monero_address, 1)) - { - if($this->cryptonote->verify_checksum($monero_address)) - { - return true; - } - } - return false; - } - public function check_viewKey() - { - if($this->use_viewKey == 'yes') - { - if (strlen($this->viewKey) == 64) { - return true; - } - return false; - } - return true; - } - public function check_checkedBoxes() - { - if($this->use_viewKey == 'yes') - { - if($this->use_rpc == 'yes') - { - return true; - } - } - else - return false; - } - - public function is_virtual_in_cart($order_id) - { - $order = wc_get_order( $order_id ); - $items = $order->get_items(); - $cart_size = count($items); - $virtual_items = 0; - - foreach ( $items as $item ) { - $product = new WC_Product( $item['product_id'] ); - if ( $product->is_virtual() ) { - $virtual_items += 1; - } - } - if($virtual_items == $cart_size) - { - return true; - } - else{ - return false; - } - } - - public function instruction($order_id) - { - if($this->non_rpc) - { - echo ""; - $order = wc_get_order($order_id); - $amount = floatval(preg_replace('#[^\d.]#', '', $order->get_total())); - $payment_id = $this->set_paymentid_cookie(8); - $currency = $order->get_currency(); - $amount_xmr2 = $this->changeto($amount, $currency, $payment_id); - $address = $this->address; - - $order->update_meta_data( "Payment ID", $payment_id); - $order->update_meta_data( "Amount requested (XMR)", $amount_xmr2); - $order->save(); - - if (!isset($address)) { - // If there isn't address (merchant missed that field!), $address will be the Monero address for donating :) - $address = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; - } - - - - $decoded_address = $this->cryptonote->decode_address($address); - $pub_spendKey = $decoded_address['spendKey']; - $pub_viewKey = $decoded_address['viewKey']; - - $integrated_addr = $this->cryptonote->integrated_addr_from_keys($pub_spendKey, $pub_viewKey, $payment_id); - - $uri = urlencode("monero:".$address."?tx_amount=".$amount_xmr2."&tx_payment_id=".$payment_id); - $this->verify_non_rpc($payment_id, $amount_xmr2, $order_id, $this->zero_confirm); - if($this->confirmed == false) - { - echo "

We are waiting for your transaction to be confirmed

"; - } - if($this->confirmed) - { - echo "

Your transaction has been successfully confirmed!

"; - } - - echo " - - - - - - - - - - -
- -
- -
- -

MONERO PAYMENT

-
- - -
-
- Send: -
".$amount_xmr2."
-
-
- To this address: -
".$integrated_addr."
-
-
- Or scan QR: -
-
-
-
- - - - -
- -
- - - "; - - echo " - "; - } - else - { - $order = wc_get_order($order_id); - $amount = floatval(preg_replace('#[^\d.]#', '', $order->get_total())); - $payment_id = $this->set_paymentid_cookie(8); - $currency = $order->get_currency(); - $amount_xmr2 = $this->changeto($amount, $currency, $payment_id); - - $order->update_meta_data( "Payment ID", $payment_id); - $order->update_meta_data( "Amount requested (XMR)", $amount_xmr2); - $order->save(); - - $uri = urlencode("monero:".$address."?tx_amount=".$amount_xmr2."&tx_payment_id=".$payment_id); - $array_integrated_address = $this->monero_daemon->make_integrated_address($payment_id); - if (!isset($array_integrated_address)) { - $this->log->add('Monero_Gateway', '[ERROR] Unable get integrated address'); - // Seems that we can't connect with daemon, then set array_integrated_address, little hack - $array_integrated_address["integrated_address"] = $address; - } - $message = $this->verify_payment($payment_id, $amount_xmr2, $order); - if ($this->confirmed) { - $color = "006400"; - } else { - $color = "DC143C"; - } - echo "

" . $message . "

"; - - echo " - - - - - - - - - - -
- -
- -
- -

MONERO PAYMENT

-
- - -
-
- Send: -
".$amount_xmr2."
-
-
- To this address: -
".$array_integrated_address['integrated_address']."
-
-
- Or scan QR: -
-
-
-
- - - - -
- -
- - - "; - - echo " - "; - } - } - - private function set_paymentid_cookie($size) - { - if (!isset($_COOKIE['payment_id'])) { - $payment_id = bin2hex(openssl_random_pseudo_bytes($size)); - setcookie('payment_id', $payment_id, time() + 2700); - } - else{ - $payment_id = $this->sanatize_id($_COOKIE['payment_id']); - } - return $payment_id; - } - - public function sanatize_id($payment_id) - { - // Limit payment id to alphanumeric characters - $sanatized_id = preg_replace("/[^a-zA-Z0-9]+/", "", $payment_id); - return $sanatized_id; - } - - public function changeto($amount, $currency, $payment_id) - { - global $wpdb; - // This will create a table named whatever the payment id is inside the database "WordPress" - $create_table = "CREATE TABLE IF NOT EXISTS $payment_id ( - rate INT - )"; - $wpdb->query($create_table); - $rows_num = $wpdb->get_results("SELECT count(*) as count FROM $payment_id"); - if ($rows_num[0]->count > 0) // Checks if the row has already been created or not - { - $stored_rate = $wpdb->get_results("SELECT rate FROM $payment_id"); - - $stored_rate_transformed = $stored_rate[0]->rate / 100; //this will turn the stored rate back into a decimaled number - - if (isset($this->discount)) { - $sanatized_discount = preg_replace('/[^0-9]/', '', $this->discount); - $discount_decimal = $sanatized_discount / 100; - $new_amount = $amount / $stored_rate_transformed; - $discount = $new_amount * $discount_decimal; - $final_amount = $new_amount - $discount; - $rounded_amount = round($final_amount, 12); - } else { - $new_amount = $amount / $stored_rate_transformed; - $rounded_amount = round($new_amount, 12); //the Monero wallet can't handle decimals smaller than 0.000000000001 - } - } else // If the row has not been created then the live exchange rate will be grabbed and stored - { - $xmr_live_price = $this->retriveprice($currency); - $live_for_storing = $xmr_live_price * 100; //This will remove the decimal so that it can easily be stored as an integer - - $wpdb->query("INSERT INTO $payment_id (rate) VALUES ($live_for_storing)"); - if(isset($this->discount)) - { - $new_amount = $amount / $xmr_live_price; - $discount = $new_amount * $this->discount / 100; - $discounted_price = $new_amount - $discount; - $rounded_amount = round($discounted_price, 12); - } - else - { - $new_amount = $amount / $xmr_live_price; - $rounded_amount = round($new_amount, 12); - } - } - - return $rounded_amount; - } - - - // Check if we are forcing SSL on checkout pages - // Custom function not required by the Gateway - - public function retriveprice($currency) - { - $api_link = 'https://min-api.cryptocompare.com/data/price?fsym=XMR&tsyms=BTC,USD,EUR,CAD,INR,GBP,COP,SGD' . ',' . $currency . '&extraParams=monero_woocommerce'; - $xmr_price = file_get_contents($api_link); - $price = json_decode($xmr_price, TRUE); - if (!isset($price)) { - $this->log->add('Monero_Gateway', '[ERROR] Unable to get the price of Monero'); - } - switch ($currency) { - case 'USD': - return $price['USD']; - case 'EUR': - return $price['EUR']; - case 'CAD': - return $price['CAD']; - case 'GBP': - return $price['GBP']; - case 'INR': - return $price['INR']; - case 'COP': - return $price['COP']; - case 'SGD': - return $price['SGD']; - case $currency: - return $price[$currency]; - case 'XMR': - $price = '1'; - return $price; - } - } - - private function on_verified($payment_id, $amount_atomic_units, $order_id) - { - $message = "Payment has been received and confirmed. Thanks!"; - $this->log->add('Monero_gateway', '[SUCCESS] Payment has been recorded. Congratulations!'); - $this->confirmed = true; - $order = wc_get_order($order_id); - - if($this->is_virtual_in_cart($order_id) == true){ - $order->update_status('completed', __('Payment has been received.', 'monero_gateway')); - } - else{ - $order->update_status('processing', __('Payment has been received.', 'monero_gateway')); // Show payment id used for order - } - global $wpdb; - $wpdb->query("DROP TABLE $payment_id"); // Drop the table from database after payment has been confirmed as it is no longer needed - - $this->reloadTime = 3000000000000; // Greatly increase the reload time as it is no longer needed - return $message; - } - - public function verify_payment($payment_id, $amount, $order_id) - { - /* - * function for verifying payments - * Check if a payment has been made with this payment id then notify the merchant - */ - $message = "We are waiting for your payment to be confirmed"; - $amount_atomic_units = $amount * 1000000000000; - $get_payments_method = $this->monero_daemon->get_payments($payment_id); - if (isset($get_payments_method["payments"][0]["amount"])) { - if ($get_payments_method["payments"][0]["amount"] >= $amount_atomic_units) - { - $message = $this->on_verified($payment_id, $amount_atomic_units, $order_id); - } - if ($get_payments_method["payments"][0]["amount"] < $amount_atomic_units) - { - $totalPayed = $get_payments_method["payments"][0]["amount"]; - $outputs_count = count($get_payments_method["payments"]); // number of outputs recieved with this payment id - $output_counter = 1; - - while($output_counter < $outputs_count) - { - $totalPayed += $get_payments_method["payments"][$output_counter]["amount"]; - $output_counter++; - } - if($totalPayed >= $amount_atomic_units) - { - $message = $this->on_verified($payment_id, $amount_atomic_units, $order_id); - } - } - } - return $message; - } - public function last_block_seen($height) // sometimes 2 blocks are mined within a few seconds of each other. Make sure we don't miss one - { - if (!isset($_COOKIE['last_seen_block'])) - { - setcookie('last_seen_block', $height, time() + 2700); - return 0; - } - else{ - $cookie_block = $_COOKIE['last_seen_block']; - $difference = $height - $cookie_block; - setcookie('last_seen_block', $height, time() + 2700); - return $difference; - } - } - - public function verify_non_rpc($payment_id, $amount, $order_id, $accept_zero_conf = false) - { - $tools = new NodeTools($this->testnet); - - $amount_atomic_units = $amount * 1000000000000; - - $outputs = $tools->get_outputs($this->address, $this->viewKey, $accept_zero_conf); - $outs_count = count($outputs); - - $i = 0; - $tx_hash; - if($outs_count != 0) - { - while($i < $outs_count ) - { - if($outputs[$i]['payment_id'] == $payment_id) - { - if($outputs[$i]['amount'] >= $amount_atomic_units) - { - $this->on_verified($payment_id, $amount_atomic_units, $order_id); - return true; - } - } - $i++; - } - } - return false; - } - - public function do_ssl_check() - { - if ($this->enabled == "yes" && !$this->get_option('onion_service')) { - if (get_option('woocommerce_force_ssl_checkout') == "no") { - echo "

" . sprintf(__("%s is enabled and WooCommerce is not forcing the SSL certificate on your checkout page. Please ensure that you have a valid SSL certificate and that you are forcing the checkout pages to be secured."), $this->method_title, admin_url('admin.php?page=wc-settings&tab=checkout')) . "

"; - } - } - } - - public function connect_daemon() - { - $host = $this->settings['daemon_host']; - $port = $this->settings['daemon_port']; - $monero_library = new Monero($host, $port); - if ($monero_library->works() == true) { - echo "

Everything works! Congratulations and welcome to Monero.

"; - - } else { - $this->log->add('Monero_gateway', '[ERROR] Plugin cannot reach wallet RPC.'); - echo "

Error with connection of daemon, see documentation!

"; - } - } -} diff --git a/monero/monero_gateway.php b/monero/monero_gateway.php deleted file mode 100644 index 35e3a04..0000000 --- a/monero/monero_gateway.php +++ /dev/null @@ -1,65 +0,0 @@ -' . __('Settings', 'monero_payment') . '', - ); - - return array_merge($plugin_links, $links); -} - -add_action('admin_menu', 'monero_create_menu'); -function monero_create_menu() -{ - add_menu_page( - __('Monero', 'textdomain'), - 'Monero', - 'manage_options', - 'admin.php?page=wc-settings&tab=checkout§ion=monero_gateway', - '', - plugins_url('monero/assets/monero_icon.png'), - 56 // Position on menu, woocommerce has 55.5, products has 55.6 - - ); -} - - diff --git a/readme.txt b/readme.txt index 2c71579..ee5ac56 100644 --- a/readme.txt +++ b/readme.txt @@ -1,9 +1,9 @@ === Monero WooCommerce Extension === -Contributors: serhack +Contributors: serhack, mosu-forge Donate link: http://monerointegrations.com/donate.html Tags: monero, woocommerce, integration, payment, merchant, cryptocurrency, accept monero, monero woocommerce Requires at least: 4.0 -Tested up to: 4.8 +Tested up to: 4.9.8 Stable tag: trunk License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -50,6 +50,9 @@ An extension to WooCommerce for accepting Monero as payment in your store. = 0.2 = * Bug fixes += 0.3 = +* Complete rewrite of how the plugin handles payments + == Upgrade Notice == soon diff --git a/templates/monero-gateway/admin/order-history-error-page.php b/templates/monero-gateway/admin/order-history-error-page.php new file mode 100644 index 0000000..0017f52 --- /dev/null +++ b/templates/monero-gateway/admin/order-history-error-page.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/monero-gateway/admin/order-history-page.php b/templates/monero-gateway/admin/order-history-page.php new file mode 100644 index 0000000..cfdb47a --- /dev/null +++ b/templates/monero-gateway/admin/order-history-page.php @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Exchange rate1 XMR =
Total amount XMR
Total paid XMR
Total due XMR
Order age ago
Order exipires + +
Status + Confirmed'; + break; + case 'paid': + echo 'Paid, waiting confirmation'; + break; + case 'partial': + echo 'Partial payment made'; + break; + case 'unpaid': + echo 'Pending payment'; + break; + case 'expired_partial': + echo 'Expired, partial payment made'; + break; + case 'expired': + echo 'Expired'; + break; + } + ?> +
Payment id
Integrated address
+ + + + + + + + + + + + + + + +
TransactionsHeightAmount
+ + XMR
+ diff --git a/templates/monero-gateway/admin/settings-page.php b/templates/monero-gateway/admin/settings-page.php new file mode 100644 index 0000000..698234a --- /dev/null +++ b/templates/monero-gateway/admin/settings-page.php @@ -0,0 +1,54 @@ + +

Monero Gateway Error:

+ + +

Monero Gateway Settings

+ + +
+ '; + echo 'Your balance is: ' . $balance['balance'] . '
'; + echo 'Unlocked balance: ' . $balance['unlocked_balance'] . '
'; + ?> +
+ + + + +
+ +

Learn more about using the Monero payment gateway

+ + + + \ No newline at end of file diff --git a/templates/monero-gateway/customer/order-email-block.php b/templates/monero-gateway/customer/order-email-block.php new file mode 100644 index 0000000..57b1e85 --- /dev/null +++ b/templates/monero-gateway/customer/order-email-block.php @@ -0,0 +1,56 @@ + + +

+ +

+ +

Your order has been confirmed. Thank you for paying with Monero!

+ + + +

+ +

+ +

Your order has expired. Please place another order to complete your purchase.

+ + + +

+ +

+ +

Please pay the amount due to complete your transactions. Your order will expire in if payment is not received.

+ +
+ + + + + + + + + + + + +
+ PAY TO:
+ + + +
+ TOTAL DUE:
+ + XMR + +
+ EXCHANGE RATE:
+ + 1 XMR = + +
+
+ + \ No newline at end of file diff --git a/templates/monero-gateway/customer/order-email-error-block.php b/templates/monero-gateway/customer/order-email-error-block.php new file mode 100644 index 0000000..f07b613 --- /dev/null +++ b/templates/monero-gateway/customer/order-email-error-block.php @@ -0,0 +1,5 @@ +

+ +

+ +

Payment method not available, please contact the store owner for manual payment

diff --git a/templates/monero-gateway/customer/order-error-page.php b/templates/monero-gateway/customer/order-error-page.php new file mode 100644 index 0000000..2d62da2 --- /dev/null +++ b/templates/monero-gateway/customer/order-error-page.php @@ -0,0 +1,4 @@ +
+

+

Payment method not available, please contact the store owner for manual payment

+
\ No newline at end of file diff --git a/templates/monero-gateway/customer/order-page.php b/templates/monero-gateway/customer/order-page.php new file mode 100644 index 0000000..8e809a2 --- /dev/null +++ b/templates/monero-gateway/customer/order-page.php @@ -0,0 +1,100 @@ +
+

+ + + + + Please pay the amount due to complete your transactions. Your order will expire in if payment is not received. + + We have received partial payment. Please pay the remaining amount to complete your transactions. Your order will expire in if payment is not received. + + We have received your payment in full. Please wait while amount is confirmed. Approximate confirm time is .
You can check your payment status anytime in your account dashboard.
+ + Your order has been confirmed. Thank you for paying with Monero! + + Your order has expired. Please place another order to complete your purchase. + + Your order has expired. Please contact the store owner to receive refund on your partial payment. + +
+ +
    +
  • + Pay to: + + + + + + + + + +
  • +
  • + Total due: + + + XMR + + + + + +
  • +
  • + Total order amount: + + XMR + +
  • +
  • + Total paid: + + XMR + +
  • +
  • + Exchange rate: +
  • +
+ + + + + + + + + + + + + + + + + + +
+ +
+ + From 1b2c66a75b5721615744dcd732cac3a4122568eb Mon Sep 17 00:00:00 2001 From: mosu forge Date: Sat, 22 Sep 2018 08:35:41 -0700 Subject: [PATCH 55/87] removed donation address --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index aea0a99..f714348 100644 --- a/README.md +++ b/README.md @@ -103,5 +103,3 @@ This will display a badge showing that you accept Monero-currency. ## Donations monero-integrations: 44krVcL6TPkANjpFwS2GWvg1kJhTrN7y9heVeQiDJ3rP8iGbCd5GeA4f3c2NKYHC1R4mCgnW7dsUUUae2m9GiNBGT4T8s2X - -mosu-forge: 4A6BQp7do5MTxpCguq1kAS27yMLpbHcf89Ha2a8Shayt2vXkCr6QRpAXr1gLYRV5esfzoK3vLJTm5bDWk5gKmNrT6s6xZep From a17af45bd87a9c6682e4c49ffedf2ac49db2b3fb Mon Sep 17 00:00:00 2001 From: SerHack <27734319+serhack@users.noreply.github.com> Date: Sat, 24 Nov 2018 21:47:27 +0100 Subject: [PATCH 56/87] Update LICENSE --- LICENSE | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 1dba848..528fd46 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,6 @@ MIT License -Copyright (c) 2018, Ryo Currency Project -Portions Copyright (c) 2017-2018, Monero Integrations +Copyright (c) 2017-2018, Monero Integrations Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 53463e9111312b42b842ab4d3939757d54eafbd4 Mon Sep 17 00:00:00 2001 From: SerHack <27734319+serhack@users.noreply.github.com> Date: Sat, 24 Nov 2018 21:51:01 +0100 Subject: [PATCH 57/87] Update readme.txt --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index ee5ac56..7fdcf7b 100644 --- a/readme.txt +++ b/readme.txt @@ -1,5 +1,5 @@ === Monero WooCommerce Extension === -Contributors: serhack, mosu-forge +Contributors: Monero Integrations Team Donate link: http://monerointegrations.com/donate.html Tags: monero, woocommerce, integration, payment, merchant, cryptocurrency, accept monero, monero woocommerce Requires at least: 4.0 From ccb387095d3ed7da45323aa5e411e202e687a646 Mon Sep 17 00:00:00 2001 From: SerHack <27734319+serhack@users.noreply.github.com> Date: Sun, 25 Nov 2018 18:22:09 +0100 Subject: [PATCH 58/87] Update class-monero-gateway.php --- include/class-monero-gateway.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/class-monero-gateway.php b/include/class-monero-gateway.php index 7cda884..56f9819 100644 --- a/include/class-monero-gateway.php +++ b/include/class-monero-gateway.php @@ -519,7 +519,8 @@ class Monero_Gateway extends WC_Payment_Gateway } } - $qrcode_uri = 'monero:'.$address.'?tx_amount='.$amount_due.'&tx_payment_id='.$payment_id; + $amount_formatted = self::format_monero($amount_due); + $qrcode_uri = 'monero:'.$address.'?tx_amount='.$amount_formatted.'&tx_payment_id='.$payment_id; $my_order_url = wc_get_endpoint_url('view-order', $order_id, wc_get_page_permalink('myaccount')); $payment_details = array( From cd06d0303b98ff6b3099e077ebc9c3df533293ba Mon Sep 17 00:00:00 2001 From: mosu forge Date: Wed, 28 Nov 2018 04:06:19 -0800 Subject: [PATCH 59/87] Re-added Ryo-currency copyright, contributer, and donation address --- LICENSE | 1 + README.md | 2 ++ readme.txt | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 528fd46..d4ba96f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2017-2018, Monero Integrations +Copyright (c) 2018, Ryo Currency Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index f714348..8a2fcc1 100644 --- a/README.md +++ b/README.md @@ -103,3 +103,5 @@ This will display a badge showing that you accept Monero-currency. ## Donations monero-integrations: 44krVcL6TPkANjpFwS2GWvg1kJhTrN7y9heVeQiDJ3rP8iGbCd5GeA4f3c2NKYHC1R4mCgnW7dsUUUae2m9GiNBGT4T8s2X + +ryo-currency: 4A6BQp7do5MTxpCguq1kAS27yMLpbHcf89Ha2a8Shayt2vXkCr6QRpAXr1gLYRV5esfzoK3vLJTm5bDWk5gKmNrT6s6xZep diff --git a/readme.txt b/readme.txt index 7fdcf7b..0338811 100644 --- a/readme.txt +++ b/readme.txt @@ -1,5 +1,5 @@ === Monero WooCommerce Extension === -Contributors: Monero Integrations Team +Contributors: Monero Integrations Team, Ryo Currency Project Donate link: http://monerointegrations.com/donate.html Tags: monero, woocommerce, integration, payment, merchant, cryptocurrency, accept monero, monero woocommerce Requires at least: 4.0 From f7fa0b758e013f74ad08f6f9c6e7e6fea5500652 Mon Sep 17 00:00:00 2001 From: mosu forge Date: Wed, 28 Nov 2018 05:34:47 -0800 Subject: [PATCH 60/87] Updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f714348..7dc3607 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Note on security: using this option, while the most secure, requires you to run * `Monero wallet RPC port` (if confirmation type is `monero-wallet-rpc`) - Port the wallet rpc is bound to with the `--rpc-bind-port` argument. (Default 18080) * `Testnet` - Check this to change the blockchain explorer links to the testnet explorer. (Default: unchecked) * `SSL warnings` - Check this to silence SSL warnings. (Default: unchecked) -* `Show QR Code` - Show payment QR codes. There is no Monero software that can read QR codes at this time (Default: unchecked) +* `Show QR Code` - Show payment QR codes. (Default: unchecked) * `Show Prices in Monero` - Convert all prices on the frontend to Monero. Experimental feature, only use if you do not accept any other payment option. (Default: unchecked) * `Display Decimals` (if show prices in Monero is enabled) - Number of decimals to round prices to on the frontend. The final order amount will not be rounded and will be displayed down to the nanoMonero. (Default: 12) From ad0d9ef004f58b587b6ac2f06da7f78d5415bd70 Mon Sep 17 00:00:00 2001 From: mosu forge Date: Wed, 28 Nov 2018 07:37:38 -0800 Subject: [PATCH 61/87] modified woocommerce_gateway_icon filter to use two arguments --- include/class-monero-gateway.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/class-monero-gateway.php b/include/class-monero-gateway.php index 56f9819..6f3a45b 100644 --- a/include/class-monero-gateway.php +++ b/include/class-monero-gateway.php @@ -42,7 +42,7 @@ class Monero_Gateway extends WC_Payment_Gateway public function get_icon() { - return apply_filters('woocommerce_gateway_icon', ''); + return apply_filters('woocommerce_gateway_icon', '', $this->id); } function __construct($add_action=true) From 80098195921f45a7455767ab881aaa2aba418393 Mon Sep 17 00:00:00 2001 From: itssteven Date: Fri, 11 Jan 2019 16:12:58 +0000 Subject: [PATCH 62/87] Subaddresses Generate subaddress instead of payment id and don't bother creating an integrated address if we're using the rpc. --- include/class-monero-gateway.php | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/include/class-monero-gateway.php b/include/class-monero-gateway.php index 6f3a45b..41a976d 100644 --- a/include/class-monero-gateway.php +++ b/include/class-monero-gateway.php @@ -209,12 +209,24 @@ class Monero_Gateway extends WC_Payment_Gateway $order = wc_get_order($order_id); - // Generate a unique payment id - do { - $payment_id = bin2hex(openssl_random_pseudo_bytes(8)); - $query = $wpdb->prepare("SELECT COUNT(*) FROM $table_name WHERE payment_id=%s", array($payment_id)); - $payment_id_used = $wpdb->get_var($query); - } while ($payment_id_used); + if(self::$confirm_type != 'wownero-wallet-rpc') { + // Generate a unique payment id + do { + $payment_id = bin2hex(openssl_random_pseudo_bytes(8)); + $query = $wpdb->prepare("SELECT COUNT(*) FROM $table_name WHERE payment_id=%s", array($payment_id)); + $payment_id_used = $wpdb->get_var($query); + } while ($payment_id_used); + } + else { + // Generate subaddress + $payment_id = self::$wownero_wallet_rpc->create_address(0, 'Order: ' . $order_id); + if(isset($payment_id['address'])) { + $payment_id = $payment_id['address']; + } + else { + $this->log->add('Wownero_Gateway', 'Couldn\'t create subaddress for order ' . $order_id); + } + } $currency = $order->get_currency(); $rate = self::get_live_rate($currency); @@ -477,13 +489,7 @@ class Monero_Gateway extends WC_Payment_Gateway $payment_id = self::sanatize_id($details[0]->payment_id); if(self::$confirm_type == 'monero-wallet-rpc') { - $array_integrated_address = self::$monero_wallet_rpc->make_integrated_address($payment_id); - if (isset($array_integrated_address['integrated_address'])) { - $integrated_addr = $array_integrated_address['integrated_address']; - } else { - self::$log->add('Monero_Gateway', '[ERROR] Unable get integrated address'); - return '[ERROR] Unable get integrated address'; - } + $integrated_addr = $payment_id; } else { if ($address) { $decoded_address = self::$cryptonote->decode_address($address); From 184e740452f0081abf32a012e8c14e76898f369d Mon Sep 17 00:00:00 2001 From: itssteven Date: Fri, 11 Jan 2019 16:49:08 +0000 Subject: [PATCH 63/87] Subaddresses `get_address_index` for getting the index number of a specific subaddress, `get_transfers` to get in&pool transfers for that subaddresss index number, `store` required in create_address and create_address required to create new subaddresses --- include/class-monero-wallet-rpc.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/include/class-monero-wallet-rpc.php b/include/class-monero-wallet-rpc.php index 5940dda..4ff7f58 100644 --- a/include/class-monero-wallet-rpc.php +++ b/include/class-monero-wallet-rpc.php @@ -349,4 +349,30 @@ class Monero_Wallet_Rpc $get_bulk_payments = $this->_run('get_bulk_payments', $get_bulk_payments_parameters); return $get_bulk_payments; } + + public function get_transfers($arr) + { + $get_parameters = $arr; + $get_transfers = $this->_run('get_transfers', $get_parameters); + return $get_transfers; + } + + public function get_address_index($subaddress) + { + $params = array('address' => $subaddress); + return $this->_run('get_address_index', $params); + } + + public function store() + { + return $this->_run('store'); + } + + public function create_address($account_index = 0, $label = '') + { + $params = array('account_index' => $account_index, 'label' => $label); + $create_address_method = $this->_run('create_address', $params); + $save = $this->store(); // Save wallet state after subaddress creation + return $create_address_method; + } } From f7d1f9b968398be311867650e6b4fa095bd0dc51 Mon Sep 17 00:00:00 2001 From: itssteven Date: Fri, 11 Jan 2019 16:49:38 +0000 Subject: [PATCH 64/87] Update class-monero-wallet-rpc.php --- include/class-monero-wallet-rpc.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/include/class-monero-wallet-rpc.php b/include/class-monero-wallet-rpc.php index 4ff7f58..2f3bb57 100644 --- a/include/class-monero-wallet-rpc.php +++ b/include/class-monero-wallet-rpc.php @@ -244,13 +244,6 @@ class Monero_Wallet_Rpc return $incoming_transfers; } - public function get_transfers($input_type, $input_value) - { - $get_parameters = array($input_type => $input_value); - $get_transfers = $this->_run('get_transfers', $get_parameters); - return $get_transfers; - } - public function view_key() { $query_key = array('key_type' => 'view_key'); From ba395d507ee671dede3dff2e96561b2dbf9688c5 Mon Sep 17 00:00:00 2001 From: itssteven Date: Fri, 11 Jan 2019 16:56:33 +0000 Subject: [PATCH 65/87] Update class-monero-gateway.php --- include/class-monero-gateway.php | 33 +++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/include/class-monero-gateway.php b/include/class-monero-gateway.php index 41a976d..ca9fbe8 100644 --- a/include/class-monero-gateway.php +++ b/include/class-monero-gateway.php @@ -404,13 +404,32 @@ class Monero_Gateway extends WC_Payment_Gateway protected static function check_payment_rpc($payment_id) { $txs = array(); - $payments = self::$monero_wallet_rpc->get_all_payments($payment_id); - foreach($payments as $payment) { - $txs[] = array( - 'amount' => $payment['amount'], - 'txid' => $payment['tx_hash'], - 'height' => $payment['block_height'] - ); + $address_index = self::$wownero_wallet_rpc->get_address_index($payment_id); + if(isset($address_index['index']['minor'])){ + $address_index = $address_index['index']['minor']; + } + else { + self::$log->add('Wownero_Gateway', '[ERROR] Couldn\'t get address index of subaddress: ' . $payment_id); + return $txs; + } + $payments = self::$wownero_wallet_rpc->get_transfers(array( 'in' => true, 'pool' => true, 'subaddr_indices' => array($address_index))); + if(isset($payments['in'])) { + foreach($payments['in'] as $payment) { + $txs[] = array( + 'amount' => $payment['amount'], + 'txid' => $payment['txid'], + 'height' => $payment['height'] + ); + } + } + if(isset($payments['pool'])) { + foreach($payments['pool'] as $payment) { + $txs[] = array( + 'amount' => $payment['amount'], + 'txid' => $payment['txid'], + 'height' => $payment['height'] + ); + } } return $txs; } From 7e485215706c061cc7b1695b98790f49c499ccad Mon Sep 17 00:00:00 2001 From: itssteven Date: Fri, 11 Jan 2019 17:00:42 +0000 Subject: [PATCH 66/87] Subaddresses `payment_id` column extended to fit a subaddress over a 16 char payment id --- monero-woocommerce-gateway.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monero-woocommerce-gateway.php b/monero-woocommerce-gateway.php index 10accac..1ef25bb 100644 --- a/monero-woocommerce-gateway.php +++ b/monero-woocommerce-gateway.php @@ -209,7 +209,7 @@ function monero_install() { if($wpdb->get_var("show tables like '$table_name'") != $table_name) { $sql = "CREATE TABLE $table_name ( order_id BIGINT(20) UNSIGNED NOT NULL, - payment_id VARCHAR(16) DEFAULT '' NOT NULL, + payment_id VARCHAR(100) DEFAULT '' NOT NULL, currency VARCHAR(6) DEFAULT '' NOT NULL, rate BIGINT UNSIGNED DEFAULT 0 NOT NULL, amount BIGINT UNSIGNED DEFAULT 0 NOT NULL, @@ -226,7 +226,7 @@ function monero_install() { if($wpdb->get_var("show tables like '$table_name'") != $table_name) { $sql = "CREATE TABLE $table_name ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, - payment_id VARCHAR(16) DEFAULT '' NOT NULL, + payment_id VARCHAR(100) DEFAULT '' NOT NULL, txid VARCHAR(64) DEFAULT '' NOT NULL, amount BIGINT UNSIGNED DEFAULT 0 NOT NULL, height MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, From 8e7d258b5b37604d2dc7db8075c3704fcbf53314 Mon Sep 17 00:00:00 2001 From: itssteven Date: Fri, 11 Jan 2019 18:13:04 +0000 Subject: [PATCH 67/87] Update class-monero-gateway.php --- include/class-monero-gateway.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/class-monero-gateway.php b/include/class-monero-gateway.php index ca9fbe8..4ba94b0 100644 --- a/include/class-monero-gateway.php +++ b/include/class-monero-gateway.php @@ -209,7 +209,7 @@ class Monero_Gateway extends WC_Payment_Gateway $order = wc_get_order($order_id); - if(self::$confirm_type != 'wownero-wallet-rpc') { + if(self::$confirm_type != 'monero-wallet-rpc') { // Generate a unique payment id do { $payment_id = bin2hex(openssl_random_pseudo_bytes(8)); @@ -219,12 +219,12 @@ class Monero_Gateway extends WC_Payment_Gateway } else { // Generate subaddress - $payment_id = self::$wownero_wallet_rpc->create_address(0, 'Order: ' . $order_id); + $payment_id = self::$monero_wallet_rpc->create_address(0, 'Order: ' . $order_id); if(isset($payment_id['address'])) { $payment_id = $payment_id['address']; } else { - $this->log->add('Wownero_Gateway', 'Couldn\'t create subaddress for order ' . $order_id); + $this->log->add('Monero_Gateway', 'Couldn\'t create subaddress for order ' . $order_id); } } @@ -404,15 +404,15 @@ class Monero_Gateway extends WC_Payment_Gateway protected static function check_payment_rpc($payment_id) { $txs = array(); - $address_index = self::$wownero_wallet_rpc->get_address_index($payment_id); + $address_index = self::$monero_wallet_rpc->get_address_index($payment_id); if(isset($address_index['index']['minor'])){ $address_index = $address_index['index']['minor']; } else { - self::$log->add('Wownero_Gateway', '[ERROR] Couldn\'t get address index of subaddress: ' . $payment_id); + self::$log->add('Monero_Gateway', '[ERROR] Couldn\'t get address index of subaddress: ' . $payment_id); return $txs; } - $payments = self::$wownero_wallet_rpc->get_transfers(array( 'in' => true, 'pool' => true, 'subaddr_indices' => array($address_index))); + $payments = self::$monero_wallet_rpc->get_transfers(array( 'in' => true, 'pool' => true, 'subaddr_indices' => array($address_index))); if(isset($payments['in'])) { foreach($payments['in'] as $payment) { $txs[] = array( From 21df86e4b8eea305a882eb350d5cb688805d3f77 Mon Sep 17 00:00:00 2001 From: itssteven Date: Sat, 12 Jan 2019 08:57:50 +0000 Subject: [PATCH 68/87] Update class-monero-gateway.php --- include/class-monero-gateway.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/class-monero-gateway.php b/include/class-monero-gateway.php index 4ba94b0..03ae5ff 100644 --- a/include/class-monero-gateway.php +++ b/include/class-monero-gateway.php @@ -401,15 +401,15 @@ class Monero_Gateway extends WC_Payment_Gateway } } - protected static function check_payment_rpc($payment_id) + protected static function check_payment_rpc($subaddress) { $txs = array(); - $address_index = self::$monero_wallet_rpc->get_address_index($payment_id); + $address_index = self::$monero_wallet_rpc->get_address_index($subaddress); if(isset($address_index['index']['minor'])){ $address_index = $address_index['index']['minor']; } else { - self::$log->add('Monero_Gateway', '[ERROR] Couldn\'t get address index of subaddress: ' . $payment_id); + self::$log->add('Monero_Gateway', '[ERROR] Couldn\'t get address index of subaddress: ' . $subaddress); return $txs; } $payments = self::$monero_wallet_rpc->get_transfers(array( 'in' => true, 'pool' => true, 'subaddr_indices' => array($address_index))); From 5fa97a828270ce17091613c31321336fbcabe2f6 Mon Sep 17 00:00:00 2001 From: itssteven Date: Sat, 12 Jan 2019 09:21:44 +0000 Subject: [PATCH 69/87] Update monero-woocommerce-gateway.php --- monero-woocommerce-gateway.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monero-woocommerce-gateway.php b/monero-woocommerce-gateway.php index 1ef25bb..95429af 100644 --- a/monero-woocommerce-gateway.php +++ b/monero-woocommerce-gateway.php @@ -209,7 +209,7 @@ function monero_install() { if($wpdb->get_var("show tables like '$table_name'") != $table_name) { $sql = "CREATE TABLE $table_name ( order_id BIGINT(20) UNSIGNED NOT NULL, - payment_id VARCHAR(100) DEFAULT '' NOT NULL, + payment_id VARCHAR(94) DEFAULT '' NOT NULL, currency VARCHAR(6) DEFAULT '' NOT NULL, rate BIGINT UNSIGNED DEFAULT 0 NOT NULL, amount BIGINT UNSIGNED DEFAULT 0 NOT NULL, @@ -226,7 +226,7 @@ function monero_install() { if($wpdb->get_var("show tables like '$table_name'") != $table_name) { $sql = "CREATE TABLE $table_name ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, - payment_id VARCHAR(100) DEFAULT '' NOT NULL, + payment_id VARCHAR(94) DEFAULT '' NOT NULL, txid VARCHAR(64) DEFAULT '' NOT NULL, amount BIGINT UNSIGNED DEFAULT 0 NOT NULL, height MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, From 646bcd671ae883cf0a66e4812b38b8226be67210 Mon Sep 17 00:00:00 2001 From: SerHack <27734319+serhack@users.noreply.github.com> Date: Sun, 3 Mar 2019 13:51:48 +0100 Subject: [PATCH 70/87] Update class-monero-gateway.php --- include/class-monero-gateway.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/class-monero-gateway.php b/include/class-monero-gateway.php index 03ae5ff..8322f53 100644 --- a/include/class-monero-gateway.php +++ b/include/class-monero-gateway.php @@ -588,7 +588,7 @@ class Monero_Gateway extends WC_Payment_Gateway $order_id = preg_replace("/[^0-9]+/", "", $_GET['order_id']); $order = wc_get_order( $order_id ); - if($order->user_id != $user->ID) + if($order->user_id() != $user->ID) self::ajax_output(array('error' => '[ERROR] Order does not belong to this user')); if($order->get_payment_method() != self::$_id) From 596a1e29ad0862820eac11013e7646f195793112 Mon Sep 17 00:00:00 2001 From: SerHack <27734319+serhack@users.noreply.github.com> Date: Sun, 16 Jun 2019 10:00:50 +0200 Subject: [PATCH 71/87] Update readme.txt --- readme.txt | 52 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/readme.txt b/readme.txt index 0338811..87a1e64 100644 --- a/readme.txt +++ b/readme.txt @@ -1,18 +1,19 @@ === Monero WooCommerce Extension === -Contributors: Monero Integrations Team, Ryo Currency Project +Contributors: SerHack, mosu-forge Donate link: http://monerointegrations.com/donate.html Tags: monero, woocommerce, integration, payment, merchant, cryptocurrency, accept monero, monero woocommerce Requires at least: 4.0 -Tested up to: 4.9.8 +Tested up to: 5.0.1 Stable tag: trunk -License: GPLv2 or later -License URI: http://www.gnu.org/licenses/gpl-2.0.html +License: MIT license +License URI: https://github.com/monero-integrations/monerowp/blob/master/LICENSE -Monero WooCommerce Extension is a Wordpress plugin that allows to accept bitcoins at WooCommerce-powered online stores. +Monero WooCommerce Extension is a Wordpress plugin that allows to accept monero at WooCommerce-powered online stores. == Description == -An extension to WooCommerce for accepting Monero as payment in your store. +Your online store must use WooCommerce platform (free wordpress plugin). +Once you installed and activated WooCommerce, you may install and activate Monero WooCommerce Extension. = Benefits = @@ -21,17 +22,15 @@ An extension to WooCommerce for accepting Monero as payment in your store. * Add monero payments option to your existing online store with alternative main currency. * Flexible exchange rate calculations fully managed via administrative settings. * Zero fees and no commissions for monero payments processing from any third party. -* Automatic conversion to Monero via real time exchange rate feed and calculations. +* Automatic conversion to Monero via realtime exchange rate feed and calculations. * Ability to set exchange rate calculation multiplier to compensate for any possible losses due to bank conversions and funds transfer fees. == Installation == -1. Install "Monero WooCommerce extension" WordPress plugin just like any other WordPress plugin. +1. Install "Monero WooCommerce extension" wordpress plugin just like any other Wordpress plugin. 2. Activate -3. Setup your monero-wallet-rpc with a view-only wallet -4. Add your monero-wallet-rpc host address and Monero address in the settings panel -5. Click “Enable this payment gateway” -6. Enjoy it! +3. Configure it with your wallet rpc address, (username or password not requested), your monero address +4. Enjoy it! == Remove plugin == @@ -47,11 +46,32 @@ An extension to WooCommerce for accepting Monero as payment in your store. = 0.1 = * First version ! Yay! -= 0.2 = -* Bug fixes += 1.0 = +* Added the view key option + += 2.1 = +* Verify transactions without monero-wallet-rpc +* Optionally accept zero confirmation transactions +* bug fixing + += 2.2 = +* Fix some bugs + += 2.3 = +* Bug fixing + += 3.0.0 = +Huge shoutout to mosu-forge who contributes a lot to make 3.0 possible. +* Ability to set number of confirms: 0 for zero conf, up to 60. +* Amount owed in XMR gets locked in after the order for a configurable amount of time after which the order is invalid, default 60 minutes. +* Shows transactions received along with the number of confirms right on the order success page, auto-updates through AJAX. +* QR code generation is done with Javascript instead of sending payment details to a 3rd party. +* Admin page for showing all transactions made to the wallet. +* Logic is done via cron, instead of the user having to stay on the order page until payment is confirmed. +* Payment details (along with the txid) are always visible on the customer's account dashboard on the my orders section. +* Live prices are also run via cron, shortcodes for showing exchange rates. +* Properly hooks into order confirmation email page. -= 0.3 = -* Complete rewrite of how the plugin handles payments == Upgrade Notice == From fea43715ea55b4d4aedb64db4d75c962f694d9dd Mon Sep 17 00:00:00 2001 From: SerHack <27734319+serhack@users.noreply.github.com> Date: Sun, 16 Jun 2019 10:09:43 +0200 Subject: [PATCH 72/87] Update base_58 library. --- include/class-monero-base58.php | 677 ++++++++++++++++---------------- 1 file changed, 340 insertions(+), 337 deletions(-) diff --git a/include/class-monero-base58.php b/include/class-monero-base58.php index 0818996..795f73c 100644 --- a/include/class-monero-base58.php +++ b/include/class-monero-base58.php @@ -1,354 +1,357 @@ (https://github.com/monero-integrations) + * @copyright 2018 + * @license MIT + * + * ============================================================================ + * + * // Initialize class + * $base58 = new base58(); + * + * // Encode a hexadecimal (base16) string as base58 + * $encoded = $base58->encode('0137F8F06C971B168745F562AA107B4D172F336271BC0F9D3B510C14D3460DFB27D8CEBE561E73AC1E11833D5EA40200EB3C82E9C66ACAF1AB1A6BB53C40537C0B7A22160B0E'); + * + * // Decode + * $decoded = $base58->decode('479cG5opa54beQWSyqNoWw5tna9sHUNmMTtiFqLPaUhDevpJ2YLwXAggSx5ePdeFrYF8cdbmVRSmp1Kn3t4Y9kFu7rZ7pFw'); * */ - -defined( 'ABSPATH' ) || exit; - -class Monero_base58 { - /** - * @var string - */ - static $alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; - static $encoded_block_sizes = [0, 2, 3, 5, 6, 7, 9, 10, 11]; - static $full_block_size = 8; - static $full_encoded_block_size = 11; - - /** - * - * Convert a hexadecimal string to a binary array - * - * @param string $hex A hexadecimal string to convert to a binary array - * @return array - * - */ - private function hex_to_bin($hex) { - if (gettype($hex) != 'string') { - throw new Exception('base58->hex_to_bin(): Invalid input type (must be a string)'); - } - if (strlen($hex) % 2 != 0) { - throw new Exception('base58->hex_to_bin(): Invalid input length (must be even)'); - } - - $res = array_fill(0, strlen($hex) / 2, 0); - for ($i = 0; $i < strlen($hex) / 2; $i++) { - $res[$i] = intval(substr($hex, $i * 2, $i * 2 + 2 - $i * 2), 16); - } - return $res; +use Exception; +class Monero_base58 +{ + static $alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; + static $encoded_block_sizes = [0, 2, 3, 5, 6, 7, 9, 10, 11]; + static $full_block_size = 8; + static $full_encoded_block_size = 11; + /** + * + * Convert a hexadecimal string to a binary array + * + * @param string $hex A hexadecimal string to convert to a binary array + * + * @return array + * + */ + private function hex_to_bin($hex) + { + if (gettype($hex) != 'string') { + throw new Exception('base58->hex_to_bin(): Invalid input type (must be a string)'); } - - /** - * - * Convert a binary array to a hexadecimal string - * - * @param array $bin A binary array to convert to a hexadecimal string - * @return string - * - */ - private function bin_to_hex($bin) { - if (gettype($bin) != 'array') { - throw new Exception('base58->bin_to_hex(): Invalid input type (must be an array)'); - } - - $res = []; - for ($i = 0; $i < count($bin); $i++) { - $res[] = substr('0'.dechex($bin[$i]), -2); - } - return join($res); + if (strlen($hex) % 2 != 0) { + throw new Exception('base58->hex_to_bin(): Invalid input length (must be even)'); } - - /** - * - * Convert a string to a binary array - * - * @param string $str A string to convert to a binary array - * @return array - * - */ - private function str_to_bin($str) { - if (gettype($str) != 'string') { - throw new Exception('base58->str_to_bin(): Invalid input type (must be a string)'); - } - - $res = array_fill(0, strlen($str), 0); - for ($i = 0; $i < strlen($str); $i++) { - $res[$i] = ord($str[$i]); - } - return $res; + $res = array_fill(0, strlen($hex) / 2, 0); + for ($i = 0; $i < strlen($hex) / 2; $i++) { + $res[$i] = intval(substr($hex, $i * 2, $i * 2 + 2 - $i * 2), 16); } - - /** - * - * Convert a binary array to a string - * - * @param array $bin A binary array to convert to a string - * @return string - * - */ - private function bin_to_str($bin) { - if (gettype($bin) != 'array') { - throw new Exception('base58->bin_to_str(): Invalid input type (must be an array)'); - } - - $res = array_fill(0, count($bin), 0); - for ($i = 0; $i < count($bin); $i++) { - $res[$i] = chr($bin[$i]); - } - return preg_replace('/[[:^print:]]/', '', join($res)); // preg_replace necessary to strip errant non-ASCII characters eg. '' + return $res; + } + /** + * + * Convert a binary array to a hexadecimal string + * + * @param array $bin A binary array to convert to a hexadecimal string + * + * @return string + * + */ + private function bin_to_hex($bin) + { + if (gettype($bin) != 'array') { + throw new Exception('base58->bin_to_hex(): Invalid input type (must be an array)'); } - - /** - * - * Convert a UInt8BE (one unsigned big endian byte) array to UInt64 - * - * @param array $data A UInt8BE array to convert to UInt64 - * @return number - * - */ - private function uint8_be_to_64($data) { - if (gettype($data) != 'array') { - throw new Exception ('base58->uint8_be_to_64(): Invalid input type (must be an array)'); - } - - $res = 0; - $i = 0; - switch (9 - count($data)) { - case 1: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 2: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 3: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 4: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 5: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 6: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 7: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 8: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - break; - default: - throw new Exception('base58->uint8_be_to_64: Invalid input length (1 <= count($data) <= 8)'); - } - return $res; + $res = []; + for ($i = 0; $i < count($bin); $i++) { + $res[] = substr('0'.dechex($bin[$i]), -2); } - - /** - * - * Convert a UInt64 (unsigned 64 bit integer) to a UInt8BE array - * - * @param number $num A UInt64 number to convert to a UInt8BE array - * @param integer $size Size of array to return - * @return array - * - */ - private function uint64_to_8_be($num, $size) { - if (gettype($num) != ('integer' || 'double')) { - throw new Exception ('base58->uint64_to_8_be(): Invalid input type ($num must be a number)'); - } - if (gettype($size) != 'integer') { - throw new Exception ('base58->uint64_to_8_be(): Invalid input type ($size must be an integer)'); - } - if ($size < 1 || $size > 8) { - throw new Exception ('base58->uint64_to_8_be(): Invalid size (1 <= $size <= 8)'); - } - - $res = array_fill(0, $size, 0); - for ($i = $size - 1; $i >= 0; $i--) { - $res[$i] = bcmod($num, bcpow(2, 8)); - $num = bcdiv($num, bcpow(2, 8)); - } - return $res; + return join($res); + } + /** + * + * Convert a string to a binary array + * + * @param string $str A string to convert to a binary array + * + * @return array + * + */ + private function str_to_bin($str) + { + if (gettype($str) != 'string') { + throw new Exception('base58->str_to_bin(): Invalid input type (must be a string)'); } - - /** - * - * Convert a hexadecimal (Base16) array to a Base58 string - * - * @param array $data - * @param array $buf - * @param number $index - * @return array - * - */ - private function encode_block($data, $buf, $index) { - if (gettype($data) != 'array') { - throw new Exception('base58->encode_block(): Invalid input type ($data must be an array)'); - } - if (gettype($buf) != 'array') { - throw new Exception('base58->encode_block(): Invalid input type ($buf must be an array)'); - } - if (gettype($index) != ('integer' || 'double')) { - throw new Exception('base58->encode_block(): Invalid input type ($index must be a number)'); - } - if (count($data) < 1 or count($data) > self::$full_encoded_block_size) { - throw new Exception('base58->encode_block(): Invalid input length (1 <= count($data) <= 8)'); - } - - $num = self::uint8_be_to_64($data); - $i = self::$encoded_block_sizes[count($data)] - 1; - while ($num > 0) { - $remainder = bcmod($num, 58); - $num = bcdiv($num, 58); - $buf[$index + $i] = ord(self::$alphabet[$remainder]); - $i--; - } - return $buf; + $res = array_fill(0, strlen($str), 0); + for ($i = 0; $i < strlen($str); $i++) { + $res[$i] = ord($str[$i]); } - - /** - * - * Encode a hexadecimal (Base16) string to Base58 - * - * @param string $hex A hexadecimal (Base16) string to convert to Base58 - * @return string - * - */ - public function encode($hex) { - if (gettype($hex) != 'string') { - throw new Exception ('base58->encode(): Invalid input type (must be a string)'); - } - - $data = self::hex_to_bin($hex); - if (count($data) == 0) { - return ''; - } - - $full_block_count = floor(count($data) / self::$full_block_size); - $last_block_size = count($data) % self::$full_block_size; - $res_size = $full_block_count * self::$full_encoded_block_size + self::$encoded_block_sizes[$last_block_size]; - - $res = array_fill(0, $res_size, 0); - for ($i = 0; $i < $res_size; $i++) { - $res[$i] = self::$alphabet[0]; - } - - for ($i = 0; $i < $full_block_count; $i++) { - $res = self::encode_block(array_slice($data, $i * self::$full_block_size, ($i * self::$full_block_size + self::$full_block_size) - ($i * self::$full_block_size)), $res, $i * self::$full_encoded_block_size); - } - - if ($last_block_size > 0) { - $res = self::encode_block(array_slice($data, $full_block_count * self::$full_block_size, $full_block_count * self::$full_block_size + $last_block_size), $res, $full_block_count * self::$full_encoded_block_size); - } - - return self::bin_to_str($res); + return $res; + } + /** + * + * Convert a binary array to a string + * + * @param array $bin A binary array to convert to a string + * + * @return string + * + */ + private function bin_to_str($bin) + { + if (gettype($bin) != 'array') { + throw new Exception('base58->bin_to_str(): Invalid input type (must be an array)'); } - - /** - * - * Convert a Base58 input to hexadecimal (Base16) - * - * @param array $data - * @param array $buf - * @param integer $index - * @return array - * - */ - private function decode_block($data, $buf, $index) { - if (gettype($data) != 'array') { - throw new Exception('base58->decode_block(): Invalid input type ($data must be an array)'); - } - if (gettype($buf) != 'array') { - throw new Exception('base58->decode_block(): Invalid input type ($buf must be an array)'); - } - if (gettype($index) != ('integer' || 'double')) { - throw new Exception('base58->decode_block(): Invalid input type ($index must be a number)'); - } - - $res_size = self::index_of(self::$encoded_block_sizes, count($data)); - if ($res_size <= 0) { - throw new Exception('base58->decode_block(): Invalid input length ($data must be a value from base58::$encoded_block_sizes)'); - } - - $res_num = 0; - $order = 1; - for ($i = count($data) - 1; $i >= 0; $i--) { - $digit = strpos(self::$alphabet, chr($data[$i])); - if ($digit < 0) { - throw new Exception("base58->decode_block(): Invalid character ($digit \"{$digit}\" not found in base58::$alphabet)"); - } - - $product = bcadd(bcmul($order, $digit), $res_num); - if ($product > bcpow(2, 64)) { - throw new Exception('base58->decode_block(): Integer overflow ($product exceeds the maximum 64bit integer)'); - } - - $res_num = $product; - $order = bcmul($order, 58); - } - if ($res_size < self::$full_block_size && bcpow(2, 8 * $res_size) <= 0) { - throw new Exception('base58->decode_block(): Integer overflow (bcpow(2, 8 * $res_size) exceeds the maximum 64bit integer)'); - } - - $tmp_buf = self::uint64_to_8_be($res_num, $res_size); - for ($i = 0; $i < count($tmp_buf); $i++) { - $buf[$i + $index] = $tmp_buf[$i]; - } - return $buf; + $res = array_fill(0, count($bin), 0); + for ($i = 0; $i < count($bin); $i++) { + $res[$i] = chr($bin[$i]); } - - /** - * - * Decode a Base58 string to hexadecimal (Base16) - * - * @param string $hex A Base58 string to convert to hexadecimal (Base16) - * @return string - * - */ - public function decode($enc) { - if (gettype($enc) != 'string') { - throw new Exception ('base58->decode(): Invalid input type (must be a string)'); - } - - $enc = self::str_to_bin($enc); - if (count($enc) == 0) { - return ''; - } - $full_block_count = floor(bcdiv(count($enc), self::$full_encoded_block_size)); - $last_block_size = bcmod(count($enc), self::$full_encoded_block_size); - $last_block_decoded_size = self::index_of(self::$encoded_block_sizes, $last_block_size); - - $data_size = $full_block_count * self::$full_block_size + $last_block_decoded_size; - - $data = array_fill(0, $data_size, 0); - for ($i = 0; $i < $full_block_count; $i++) { - $data = self::decode_block(array_slice($enc, $i * self::$full_encoded_block_size, ($i * self::$full_encoded_block_size + self::$full_encoded_block_size) - ($i * self::$full_encoded_block_size)), $data, $i * self::$full_block_size); - } - - if ($last_block_size > 0) { - $data = self::decode_block(array_slice($enc, $full_block_count * self::$full_encoded_block_size, $full_block_count * self::$full_encoded_block_size + $last_block_size), $data, $full_block_count * self::$full_block_size); - } - - return self::bin_to_hex($data); + return preg_replace('/[[:^print:]]/', '', join($res)); // preg_replace necessary to strip errant non-ASCII characters eg. '' + } + /** + * + * Convert a UInt8BE (one unsigned big endian byte) array to UInt64 + * + * @param array $data A UInt8BE array to convert to UInt64 + * + * @return number + * + */ + private function uint8_be_to_64($data) + { + if (gettype($data) != 'array') { + throw new Exception ('base58->uint8_be_to_64(): Invalid input type (must be an array)'); } - - /** - * - * Search an array for a value - * Source: https://stackoverflow.com/a/30994678 - * - * @param array $haystack An array to search - * @param string $needle A string to search for - * @return number The index of the element found (or -1 for no match) - * - */ - private function index_of($haystack, $needle) { - if (gettype($haystack) != 'array') { - throw new Exception ('base58->decode(): Invalid input type ($haystack must be an array)'); - } - // if (gettype($needle) != 'string') { - // throw new Exception ('base58->decode(): Invalid input type ($needle must be a string)'); - // } - - foreach ($haystack as $key => $value) if ($value === $needle) return $key; - return -1; + $res = 0; + $i = 0; + switch (9 - count($data)) { + case 1: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 2: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 3: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 4: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 5: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 6: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 7: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 8: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + break; + default: + throw new Exception('base58->uint8_be_to_64: Invalid input length (1 <= count($data) <= 8)'); + } + return $res; + } + /** + * + * Convert a UInt64 (unsigned 64 bit integer) to a UInt8BE array + * + * @param number $num A UInt64 number to convert to a UInt8BE array + * @param integer $size Size of array to return + * + * @return array + * + */ + private function uint64_to_8_be($num, $size) + { + if (gettype($num) != ('integer' || 'double')) { + throw new Exception ('base58->uint64_to_8_be(): Invalid input type ($num must be a number)'); } + if (gettype($size) != 'integer') { + throw new Exception ('base58->uint64_to_8_be(): Invalid input type ($size must be an integer)'); + } + if ($size < 1 || $size > 8) { + throw new Exception ('base58->uint64_to_8_be(): Invalid size (1 <= $size <= 8)'); + } + $res = array_fill(0, $size, 0); + for ($i = $size - 1; $i >= 0; $i--) { + $res[$i] = bcmod($num, bcpow(2, 8)); + $num = bcdiv($num, bcpow(2, 8)); + } + return $res; + } + /** + * + * Convert a hexadecimal (Base16) array to a Base58 string + * + * @param array $data + * @param array $buf + * @param number $index + * + * @return array + * + */ + private function encode_block($data, $buf, $index) + { + if (gettype($data) != 'array') { + throw new Exception('base58->encode_block(): Invalid input type ($data must be an array)'); + } + if (gettype($buf) != 'array') { + throw new Exception('base58->encode_block(): Invalid input type ($buf must be an array)'); + } + if (gettype($index) != ('integer' || 'double')) { + throw new Exception('base58->encode_block(): Invalid input type ($index must be a number)'); + } + if (count($data) < 1 or count($data) > self::$full_encoded_block_size) { + throw new Exception('base58->encode_block(): Invalid input length (1 <= count($data) <= 8)'); + } + $num = self::uint8_be_to_64($data); + $i = self::$encoded_block_sizes[count($data)] - 1; + while ($num > 0) { + $remainder = bcmod($num, 58); + $num = bcdiv($num, 58); + $buf[$index + $i] = ord(self::$alphabet[$remainder]); + $i--; + } + return $buf; + } + /** + * + * Encode a hexadecimal (Base16) string to Base58 + * + * @param string $hex A hexadecimal (Base16) string to convert to Base58 + * + * @return string + * + */ + public function encode($hex) + { + if (gettype($hex) != 'string') { + throw new Exception ('base58->encode(): Invalid input type (must be a string)'); + } + $data = self::hex_to_bin($hex); + if (count($data) == 0) { + return ''; + } + $full_block_count = floor(count($data) / self::$full_block_size); + $last_block_size = count($data) % self::$full_block_size; + $res_size = $full_block_count * self::$full_encoded_block_size + self::$encoded_block_sizes[$last_block_size]; + $res = array_fill(0, $res_size, ord(self::$alphabet[0])); + for ($i = 0; $i < $full_block_count; $i++) { + $res = self::encode_block(array_slice($data, $i * self::$full_block_size, ($i * self::$full_block_size + self::$full_block_size) - ($i * self::$full_block_size)), $res, $i * self::$full_encoded_block_size); + } + if ($last_block_size > 0) { + $res = self::encode_block(array_slice($data, $full_block_count * self::$full_block_size, $full_block_count * self::$full_block_size + $last_block_size), $res, $full_block_count * self::$full_encoded_block_size); + } + return self::bin_to_str($res); + } + /** + * + * Convert a Base58 input to hexadecimal (Base16) + * + * @param array $data + * @param array $buf + * @param integer $index + * + * @return array + * + */ + private function decode_block($data, $buf, $index) + { + if (gettype($data) != 'array') { + throw new Exception('base58->decode_block(): Invalid input type ($data must be an array)'); + } + if (gettype($buf) != 'array') { + throw new Exception('base58->decode_block(): Invalid input type ($buf must be an array)'); + } + if (gettype($index) != ('integer' || 'double')) { + throw new Exception('base58->decode_block(): Invalid input type ($index must be a number)'); + } + $res_size = self::index_of(self::$encoded_block_sizes, count($data)); + if ($res_size <= 0) { + throw new Exception('base58->decode_block(): Invalid input length ($data must be a value from base58::$encoded_block_sizes)'); + } + $res_num = 0; + $order = 1; + for ($i = count($data) - 1; $i >= 0; $i--) { + $digit = strpos(self::$alphabet, chr($data[$i])); + if ($digit < 0) { + throw new Exception("base58->decode_block(): Invalid character ($digit \"{$digit}\" not found in base58::$alphabet)"); + } + $product = bcadd(bcmul($order, $digit), $res_num); + if ($product > bcpow(2, 64)) { + throw new Exception('base58->decode_block(): Integer overflow ($product exceeds the maximum 64bit integer)'); + } + $res_num = $product; + $order = bcmul($order, 58); + } + if ($res_size < self::$full_block_size && bcpow(2, 8 * $res_size) <= 0) { + throw new Exception('base58->decode_block(): Integer overflow (bcpow(2, 8 * $res_size) exceeds the maximum 64bit integer)'); + } + + $tmp_buf = self::uint64_to_8_be($res_num, $res_size); + for ($i = 0; $i < count($tmp_buf); $i++) { + $buf[$i + $index] = $tmp_buf[$i]; + } + return $buf; + } + /** + * + * Decode a Base58 string to hexadecimal (Base16) + * + * @param string $hex A Base58 string to convert to hexadecimal (Base16) + * + * @return string + * + */ + public function decode($enc) + { + if (gettype($enc) != 'string') { + throw new Exception ('base58->decode(): Invalid input type (must be a string)'); + } + $enc = self::str_to_bin($enc); + if (count($enc) == 0) { + return ''; + } + $full_block_count = floor(bcdiv(count($enc), self::$full_encoded_block_size)); + $last_block_size = bcmod(count($enc), self::$full_encoded_block_size); + $last_block_decoded_size = self::index_of(self::$encoded_block_sizes, $last_block_size); + $data_size = $full_block_count * self::$full_block_size + $last_block_decoded_size; + if ($data_size == -1) { + return ''; + } + $data = array_fill(0, $data_size, 0); + for ($i = 0; $i <= $full_block_count; $i++) { + $data = self::decode_block(array_slice($enc, $i * self::$full_encoded_block_size, ($i * self::$full_encoded_block_size + self::$full_encoded_block_size) - ($i * self::$full_encoded_block_size)), $data, $i * self::$full_block_size); + } + if ($last_block_size > 0) { + $data = self::decode_block(array_slice($enc, $full_block_count * self::$full_encoded_block_size, $full_block_count * self::$full_encoded_block_size + $last_block_size), $data, $full_block_count * self::$full_block_size); + } + return self::bin_to_hex($data); + } + /** + * + * Search an array for a value + * Source: https://stackoverflow.com/a/30994678 + * + * @param array $haystack An array to search + * @param string $needle A string to search for + *) + * @return number The index of the element found (or -1 for no match) + * + */ + private function index_of($haystack, $needle) + { + if (gettype($haystack) != 'array') { + throw new Exception ('base58->decode(): Invalid input type ($haystack must be an array)'); + } + // if (gettype($needle) != 'string') { + // throw new Exception ('base58->decode(): Invalid input type ($needle must be a string)'); + // } + foreach ($haystack as $key => $value) if ($value === $needle) return $key; + return -1; + } } From a29b4d00b61c937ad972702eb3436de14e45f268 Mon Sep 17 00:00:00 2001 From: SerHack <27734319+serhack@users.noreply.github.com> Date: Wed, 19 Jun 2019 21:30:50 +0200 Subject: [PATCH 73/87] Update class-monero-base58.php --- include/class-monero-base58.php | 1 - 1 file changed, 1 deletion(-) diff --git a/include/class-monero-base58.php b/include/class-monero-base58.php index 795f73c..574d337 100644 --- a/include/class-monero-base58.php +++ b/include/class-monero-base58.php @@ -26,7 +26,6 @@ * $decoded = $base58->decode('479cG5opa54beQWSyqNoWw5tna9sHUNmMTtiFqLPaUhDevpJ2YLwXAggSx5ePdeFrYF8cdbmVRSmp1Kn3t4Y9kFu7rZ7pFw'); * */ -use Exception; class Monero_base58 { static $alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; From 9af67ae75df499143dd849927b6cbdd394640923 Mon Sep 17 00:00:00 2001 From: SerHack <27734319+serhack@users.noreply.github.com> Date: Wed, 19 Jun 2019 21:31:50 +0200 Subject: [PATCH 74/87] Update monero-woocommerce-gateway.php --- monero-woocommerce-gateway.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monero-woocommerce-gateway.php b/monero-woocommerce-gateway.php index 95429af..1c7e3b5 100644 --- a/monero-woocommerce-gateway.php +++ b/monero-woocommerce-gateway.php @@ -3,7 +3,7 @@ Plugin Name: Monero Woocommerce Gateway Plugin URI: https://github.com/monero-integrations/monerowp Description: Extends WooCommerce by adding a Monero Gateway -Version: 3.0.0 +Version: 3.0.1 Tested up to: 4.9.8 Author: mosu-forge, SerHack Author URI: https://monerointegrations.com/ From a3e10ab36f7ca17cb511efe77b758374b2a2a36e Mon Sep 17 00:00:00 2001 From: SerHack <27734319+serhack@users.noreply.github.com> Date: Sun, 30 Jun 2019 10:43:03 +0200 Subject: [PATCH 75/87] Add a error message if it is unable to fetch prices --- include/class-monero-gateway.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/class-monero-gateway.php b/include/class-monero-gateway.php index 8322f53..c95e281 100644 --- a/include/class-monero-gateway.php +++ b/include/class-monero-gateway.php @@ -1,7 +1,7 @@ query($query); } } + else{ + self::$log->add('Monero_Payments', "[ERROR] Unable to fetch prices from cryptocompare.com."); + } // Get current network/wallet height if(self::$confirm_type == 'monero-wallet-rpc') From 25416473eb84ae2d9085b2cf88b51ed8437519ac Mon Sep 17 00:00:00 2001 From: SerHack <27734319+serhack@users.noreply.github.com> Date: Wed, 3 Jul 2019 10:40:26 +0200 Subject: [PATCH 76/87] Update class-monero-gateway.php --- include/class-monero-gateway.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/class-monero-gateway.php b/include/class-monero-gateway.php index c95e281..6d0f68d 100644 --- a/include/class-monero-gateway.php +++ b/include/class-monero-gateway.php @@ -87,6 +87,10 @@ class Monero_Gateway extends WC_Payment_Gateway $explorer_url = self::$testnet ? MONERO_GATEWAY_TESTNET_EXPLORER_URL : MONERO_GATEWAY_MAINNET_EXPLORER_URL; defined('MONERO_GATEWAY_EXPLORER_URL') || define('MONERO_GATEWAY_EXPLORER_URL', $explorer_url); + // Add the currency of the shop to $currencies array. Needed for do_update_event() function + $currency_shop = get_woocommerce_currency(); + array_push(self::$currencies, $currency_shop); + if($add_action) add_action('woocommerce_update_options_payment_gateways_'.$this->id, array($this, 'process_admin_options')); From 009f1d0594f5380eff994002c7744d24afc5b982 Mon Sep 17 00:00:00 2001 From: SerHack <27734319+serhack@users.noreply.github.com> Date: Sun, 5 Jan 2020 22:26:35 +0100 Subject: [PATCH 77/87] Update monero-gateway-admin-settings.php --- include/admin/monero-gateway-admin-settings.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/admin/monero-gateway-admin-settings.php b/include/admin/monero-gateway-admin-settings.php index 0a02d40..b0eac2f 100644 --- a/include/admin/monero-gateway-admin-settings.php +++ b/include/admin/monero-gateway-admin-settings.php @@ -84,6 +84,12 @@ return array( 'description' => __('Advanced usage only', 'monero_gateway'), 'default' => 'no' ), + 'javascript' => array( + 'title' => __(' Javascript', 'monero_gateway'), + 'label' => __(' Check this to ENABLE Javascript in Checkout page ', 'monero_gateway'), + 'type' => 'checkbox', + 'default' => 'no' + ), 'onion_service' => array( 'title' => __(' SSL warnings ', 'monero_gateway'), 'label' => __(' Check to Silence SSL warnings', 'monero_gateway'), From b45b9625fb44084f50ca8fb5c4414b6ee1c325d6 Mon Sep 17 00:00:00 2001 From: SerHack <27734319+serhack@users.noreply.github.com> Date: Wed, 18 Mar 2020 10:44:17 +0100 Subject: [PATCH 78/87] Update monero-woocommerce-gateway.php --- monero-woocommerce-gateway.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monero-woocommerce-gateway.php b/monero-woocommerce-gateway.php index 1c7e3b5..1b10f4d 100644 --- a/monero-woocommerce-gateway.php +++ b/monero-woocommerce-gateway.php @@ -209,7 +209,7 @@ function monero_install() { if($wpdb->get_var("show tables like '$table_name'") != $table_name) { $sql = "CREATE TABLE $table_name ( order_id BIGINT(20) UNSIGNED NOT NULL, - payment_id VARCHAR(94) DEFAULT '' NOT NULL, + payment_id VARCHAR(95) DEFAULT '' NOT NULL, currency VARCHAR(6) DEFAULT '' NOT NULL, rate BIGINT UNSIGNED DEFAULT 0 NOT NULL, amount BIGINT UNSIGNED DEFAULT 0 NOT NULL, @@ -226,7 +226,7 @@ function monero_install() { if($wpdb->get_var("show tables like '$table_name'") != $table_name) { $sql = "CREATE TABLE $table_name ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, - payment_id VARCHAR(94) DEFAULT '' NOT NULL, + payment_id VARCHAR(95) DEFAULT '' NOT NULL, txid VARCHAR(64) DEFAULT '' NOT NULL, amount BIGINT UNSIGNED DEFAULT 0 NOT NULL, height MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, From 954eb92785cef79aa74e4c63614ca17ba3729cd1 Mon Sep 17 00:00:00 2001 From: xiphon Date: Fri, 19 Jun 2020 17:21:54 +0000 Subject: [PATCH 79/87] monero-explorer-tools: explorer url - strip trailing slashes --- include/class-monero-explorer-tools.php | 1 + 1 file changed, 1 insertion(+) diff --git a/include/class-monero-explorer-tools.php b/include/class-monero-explorer-tools.php index e43b00f..8f8d2c1 100644 --- a/include/class-monero-explorer-tools.php +++ b/include/class-monero-explorer-tools.php @@ -19,6 +19,7 @@ class Monero_Explorer_Tools public function __construct($testnet = false) { $this->url = $testnet ? MONERO_GATEWAY_TESTNET_EXPLORER_URL : MONERO_GATEWAY_MAINNET_EXPLORER_URL; + $this->url = preg_replace("/\/+$/", "", $this->url); } private function call_api($endpoint) From 90e058baeb6883f50d9bdabc7c5c9421a5961981 Mon Sep 17 00:00:00 2001 From: xiphon Date: Sat, 20 Jun 2020 23:55:13 +0000 Subject: [PATCH 80/87] monero-gateway: fix AJAX status updates - use get_customer_id() --- include/class-monero-gateway.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/class-monero-gateway.php b/include/class-monero-gateway.php index 6d0f68d..2187d4a 100644 --- a/include/class-monero-gateway.php +++ b/include/class-monero-gateway.php @@ -595,7 +595,7 @@ class Monero_Gateway extends WC_Payment_Gateway $order_id = preg_replace("/[^0-9]+/", "", $_GET['order_id']); $order = wc_get_order( $order_id ); - if($order->user_id() != $user->ID) + if($order->get_customer_id() != $user->ID) self::ajax_output(array('error' => '[ERROR] Order does not belong to this user')); if($order->get_payment_method() != self::$_id) From fcedf21b3046ea409d54dd5609c591ee6a643209 Mon Sep 17 00:00:00 2001 From: Cactii1 <37589158+Cactii1@users.noreply.github.com> Date: Fri, 18 Dec 2020 05:44:11 +0100 Subject: [PATCH 81/87] Remove Extra / in URI Fix extra / in URI that makes link to tx fail on block explorer. --- templates/monero-gateway/admin/order-history-page.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/monero-gateway/admin/order-history-page.php b/templates/monero-gateway/admin/order-history-page.php index cfdb47a..c57719e 100644 --- a/templates/monero-gateway/admin/order-history-page.php +++ b/templates/monero-gateway/admin/order-history-page.php @@ -72,7 +72,7 @@ - + XMR From acfdc094da0dd9a2c8aa3e04048455d19efbeef8 Mon Sep 17 00:00:00 2001 From: Cactii1 <37589158+Cactii1@users.noreply.github.com> Date: Tue, 2 Mar 2021 18:20:19 +0100 Subject: [PATCH 82/87] Show Proper Address in QR Code Show integrated address in QR Code rather than base address. --- include/class-monero-gateway.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/class-monero-gateway.php b/include/class-monero-gateway.php index 2187d4a..d4dbb44 100644 --- a/include/class-monero-gateway.php +++ b/include/class-monero-gateway.php @@ -552,7 +552,7 @@ class Monero_Gateway extends WC_Payment_Gateway } $amount_formatted = self::format_monero($amount_due); - $qrcode_uri = 'monero:'.$address.'?tx_amount='.$amount_formatted.'&tx_payment_id='.$payment_id; + $qrcode_uri = 'monero:'.$integrated_addr.'?tx_amount='.$amount_formatted.'&tx_payment_id='.$payment_id; $my_order_url = wc_get_endpoint_url('view-order', $order_id, wc_get_page_permalink('myaccount')); $payment_details = array( From 2e6ec376a250d702217baee15bdd1d879afa45ed Mon Sep 17 00:00:00 2001 From: SerHack <27734319+serhack@users.noreply.github.com> Date: Sun, 25 Apr 2021 10:38:32 +0200 Subject: [PATCH 83/87] Update class-monero-gateway.php --- include/class-monero-gateway.php | 63 ++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/include/class-monero-gateway.php b/include/class-monero-gateway.php index d4dbb44..084271a 100644 --- a/include/class-monero-gateway.php +++ b/include/class-monero-gateway.php @@ -228,15 +228,25 @@ class Monero_Gateway extends WC_Payment_Gateway $payment_id = $payment_id['address']; } else { - $this->log->add('Monero_Gateway', 'Couldn\'t create subaddress for order ' . $order_id); + self::$log->add('Monero_Gateway', 'Couldn\'t create subaddress for order ' . $order_id); } } $currency = $order->get_currency(); $rate = self::get_live_rate($currency); $fiat_amount = $order->get_total(''); - $monero_amount = 1e8 * $fiat_amount / $rate; - + + if($rate != 0) + $monero_amount = 1e8 * $fiat_amount / $rate; + else{ + // Critical, the price has not been retrivied. + $monero_amount = -1; + $error_message = "The price for Monero could not be retrieved. Please contact the merchant."; + self::$log->add('Monero_Payments', "[ERROR] Impossible to retrieve price for order: ".$order_id); + wc_add_notice( __('Payment error:', 'woothemes') . $error_message, 'error' ); + return; + } + if(self::$discount) $monero_amount = $monero_amount - $monero_amount * self::$discount / 100; @@ -246,7 +256,7 @@ class Monero_Gateway extends WC_Payment_Gateway $wpdb->query($query); $order->update_status('on-hold', __('Awaiting offline payment', 'monero_gateway')); - $order->reduce_order_stock(); // Reduce stock levels + wc_reduce_stock_levels( $order_id ); WC()->cart->empty_cart(); // Remove cart return array( @@ -280,8 +290,12 @@ class Monero_Gateway extends WC_Payment_Gateway foreach($price as $currency=>$rate) { // shift decimal eight places for precise int storage $rate = intval($rate * 1e8); - $query = $wpdb->prepare("INSERT INTO $table_name (currency, rate, updated) VALUES (%s, %d, NOW()) ON DUPLICATE KEY UPDATE rate=%d, updated=NOW()", array($currency, $rate, $rate)); - $wpdb->query($query); + $query = $wpdb->prepare("INSERT INTO `$table_name` (currency, rate, updated) VALUES (%s, %d, NOW()) ON DUPLICATE KEY UPDATE rate=%d, updated=NOW()", array( $currency, $rate, $rate)); + $result = $wpdb->query($query); + if(!$result){ + self::$log->add('Monero_Payments', "[ERROR] Impossible to write DB. Please check your DB connection or enable Debugging."); + } + } } else{ @@ -552,7 +566,7 @@ class Monero_Gateway extends WC_Payment_Gateway } $amount_formatted = self::format_monero($amount_due); - $qrcode_uri = 'monero:'.$integrated_addr.'?tx_amount='.$amount_formatted.'&tx_payment_id='.$payment_id; + $qrcode_uri = 'monero:'.$integrated_address.'?tx_amount='.$amount_formatted.'&tx_payment_id='.$payment_id; $my_order_url = wc_get_endpoint_url('view-order', $order_id, wc_get_page_permalink('myaccount')); $payment_details = array( @@ -591,26 +605,29 @@ class Monero_Gateway extends WC_Payment_Gateway $user = wp_get_current_user(); if($user === 0) self::ajax_output(array('error' => '[ERROR] User not logged in')); + + if(isset($_GET['order_id'])){ + $order_id = preg_replace("/[^0-9]+/", "", $_GET['order_id']); + $order = wc_get_order($order_id); + + if($order->get_customer_id() != $user->ID) + self::ajax_output(array('error' => '[ERROR] Order does not belong to this user')); - $order_id = preg_replace("/[^0-9]+/", "", $_GET['order_id']); - $order = wc_get_order( $order_id ); - - if($order->get_customer_id() != $user->ID) - self::ajax_output(array('error' => '[ERROR] Order does not belong to this user')); - - if($order->get_payment_method() != self::$_id) - self::ajax_output(array('error' => '[ERROR] Order not paid for with Monero')); - - $details = self::get_payment_details($order); - if(!is_array($details)) - self::ajax_output(array('error' => $details)); - - self::ajax_output($details); - + if($order->get_payment_method() != self::$_id) + self::ajax_output(array('error' => '[ERROR] Order not paid for with Monero')); + + $details = self::get_payment_details($order); + if(!is_array($details)) + self::ajax_output(array('error' => $details)); + + self::ajax_output($details); + } } public static function ajax_output($response) { - ob_clean(); header('Content-type: application/json'); + if (ob_get_length() > 0){ + ob_clean(); + } echo json_encode($response); wp_die(); } From 9f0a8581e63be3519aea413f26b5545019995b43 Mon Sep 17 00:00:00 2001 From: SerHack <27734319+serhack@users.noreply.github.com> Date: Sun, 25 Apr 2021 10:41:07 +0200 Subject: [PATCH 84/87] Update class-monero-gateway.php --- include/class-monero-gateway.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/class-monero-gateway.php b/include/class-monero-gateway.php index 084271a..1d74554 100644 --- a/include/class-monero-gateway.php +++ b/include/class-monero-gateway.php @@ -566,7 +566,7 @@ class Monero_Gateway extends WC_Payment_Gateway } $amount_formatted = self::format_monero($amount_due); - $qrcode_uri = 'monero:'.$integrated_address.'?tx_amount='.$amount_formatted.'&tx_payment_id='.$payment_id; + $qrcode_uri = 'monero:'.$integrated_addr.'?tx_amount='.$amount_formatted.'&tx_payment_id='.$payment_id; $my_order_url = wc_get_endpoint_url('view-order', $order_id, wc_get_page_permalink('myaccount')); $payment_details = array( From 572f04dfba66e66d6fcb625f603f3e7a3fc0ec93 Mon Sep 17 00:00:00 2001 From: tidux <1330562+tidux@users.noreply.github.com> Date: Sat, 8 May 2021 18:56:32 -0700 Subject: [PATCH 85/87] add automated install info to readme --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index cfd88b5..ca2d0f7 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,12 @@ ## Installing the plugin +### Automatic Method + +In the "Add Plugins" section of the WordPress admin UI, search for "monero" and click the Install Now button next to "Monero WooCommerce Extension" by mosu-forge, SerHack. This will enable auto-updates, but only for official releases, so if you need to work from git master or your local fork, please use the manual method below. + +### Manual Method + * Download the plugin from the [releases page](https://github.com/monero-integrations/monerowp) or clone with `git clone https://github.com/monero-integrations/monerowp` * Unzip or place the `monero-woocommerce-gateway` folder in the `wp-content/plugins` directory. * Activate "Monero Woocommerce Gateway" in your WordPress admin dashboard. From b9e5fcc96eaaa418bbc3bf1bacc0a2026cb1384a Mon Sep 17 00:00:00 2001 From: wowario Date: Thu, 13 May 2021 12:13:02 +0300 Subject: [PATCH 86/87] such wow --- LICENSE | 2 +- README.md | 4 +- assets/images/monero-accepted-here.png | Bin 4393 -> 0 bytes assets/images/monero-icon-admin.png | Bin 413 -> 0 bytes assets/images/monero-icon.png | Bin 2835 -> 0 bytes assets/images/wownero-accepted-here.png | Bin 0 -> 9671 bytes assets/images/wownero-icon-admin.png | Bin 0 -> 941 bytes assets/images/wownero-icon.png | Bin 0 -> 4180 bytes assets/js/monero-gateway-order-page.js | 8 +- .../admin/class-monero-admin-interface.php | 33 +++--- .../class-monero-admin-payments-list.php | 23 ++--- .../admin/monero-gateway-admin-settings.php | 94 +++++++++--------- include/class-monero-explorer-tools.php | 1 - include/class-monero-gateway.php | 71 ++++++------- include/class-monero-wallet-rpc.php | 3 +- monero-woocommerce-gateway.php | 32 +++--- readme.txt | 4 +- .../admin/order-history-page.php | 10 +- .../monero-gateway/admin/settings-page.php | 10 +- .../customer/order-email-block.php | 6 +- .../monero-gateway/customer/order-page.php | 8 +- 21 files changed, 152 insertions(+), 157 deletions(-) delete mode 100644 assets/images/monero-accepted-here.png delete mode 100644 assets/images/monero-icon-admin.png delete mode 100644 assets/images/monero-icon.png create mode 100644 assets/images/wownero-accepted-here.png create mode 100644 assets/images/wownero-icon-admin.png create mode 100644 assets/images/wownero-icon.png diff --git a/LICENSE b/LICENSE index d4ba96f..73f6c24 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ MIT License Copyright (c) 2017-2018, Monero Integrations -Copyright (c) 2018, Ryo Currency Project +Copyright (c) 2018, Fireice Trust Fund Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ca2d0f7..a973881 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ ### Automatic Method -In the "Add Plugins" section of the WordPress admin UI, search for "monero" and click the Install Now button next to "Monero WooCommerce Extension" by mosu-forge, SerHack. This will enable auto-updates, but only for official releases, so if you need to work from git master or your local fork, please use the manual method below. +In the "Add Plugins" section of the WordPress admin UI, search for "monero" and click the Install Now button next to "Monero WooCommerce Extension" by SerHack. This will enable auto-updates, but only for official releases, so if you need to work from git master or your local fork, please use the manual method below. ### Manual Method @@ -109,5 +109,3 @@ This will display a badge showing that you accept Monero-currency. ## Donations monero-integrations: 44krVcL6TPkANjpFwS2GWvg1kJhTrN7y9heVeQiDJ3rP8iGbCd5GeA4f3c2NKYHC1R4mCgnW7dsUUUae2m9GiNBGT4T8s2X - -ryo-currency: 4A6BQp7do5MTxpCguq1kAS27yMLpbHcf89Ha2a8Shayt2vXkCr6QRpAXr1gLYRV5esfzoK3vLJTm5bDWk5gKmNrT6s6xZep diff --git a/assets/images/monero-accepted-here.png b/assets/images/monero-accepted-here.png deleted file mode 100644 index 27ba0c37b37dd0ab13c308b0b66f65c6ad5649a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4393 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 diff --git a/assets/images/monero-icon-admin.png b/assets/images/monero-icon-admin.png deleted file mode 100644 index 2f67892a4d50180a38ad3681123887ed53341982..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 413 zcmV;O0b>4%P)gDg`I#2}09S~1%N-CO}0}lRy zR3+`1*=qn?I^Yc$^$Z*kYSImoi9cz=r5>Jk?2r%FI9bO||VH-I0 z_YROkNP7}kkW@>qkO5l2NRq)$C}b&`#>`y&2y96@N&xQ;SOcbjwxp_=IiL!B0wrK5 zhzr1G0PF^rK6IRzI5Gl!$sxYS#Xd5?A1j;L8L$)rgMfeC#s}a<_tB8Fnp*i5nC{cc z_ip=+m6Lw(Mp^6uX#mT=nkf~S*#vL`%;nno8rYEZl@0$&Gnf00000NkvXX Hu0mjfE6S() diff --git a/assets/images/monero-icon.png b/assets/images/monero-icon.png deleted file mode 100644 index 65240e11fbf8199caa7a4caf00093a90e1c522d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2835 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 Ys2un6WJq1N=H_pz1JQ->E8aiuKO3v3Pyhe` diff --git a/assets/images/wownero-accepted-here.png b/assets/images/wownero-accepted-here.png new file mode 100644 index 0000000000000000000000000000000000000000..639b69ae3fdd869e70fe322d6f5a8bc0e25393f7 GIT binary patch literal 9671 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 literal 0 HcmV?d00001 diff --git a/assets/images/wownero-icon.png b/assets/images/wownero-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3388fe37d56d60cdd3f28c723dd2e41f76b261df GIT binary patch literal 4180 zcmV-a5UcNrP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 ?w^;0K7 zX9Nv~XF+8EK5Fk}sZ2?9-P8&1w;kV;Bua@pRL5mx54zp^_kh7REF1zOvSi->ul+9V z?jn?W8@ zuD6tm?yW97UJrHg*Rj-!o&(kOXv#^-R!p^okK3zx%0DM?O%eR4_|Q=vNP2s$q^EZ( zMAvJIo|Giq5Y! z!-~cTj+T?;y+}6#x|lHlTRlUY`zR0Z*L>J6W#nDul7t+OWAOt8t`XhtDi>WpY=?*U zd$hU_6Hv<&NPC_c?)|KA*RdJ$i0elgku{g4C&{K_iwlqJhn3Blm!ZcRyrp1fAD)2{sVGZnxF2`2CN`UXnVlzE1UxK3_qTHUt{K1MDh4SPFZSYvVBtZSPc9bOn z=JXu#_w(}P+Nx%FWWPsU{3&d8C;Bjt%lArxA`eI3L$3J?t}D*wUk3@}`{Po#ztRi` z;>mLZ#|pToq0EM>3T1bCuq|%+MZf`?L26}$Sg7Q zY56eD-&5Qdhd(vKE6pwk;(x(ZOMuUl?JLyA)6!x){MY6b`8fI~C^e7KS*I|g5H87w z3cpToa^b6pl&@b#Bz0|aWkts2&>CJTNzAhiIrcI%026XxN=TNn5=UY7#o({4;_o}6 zo6!1EEVUw$DnrA|ZB}>;sY%%xL0}vZKWijKuHrO58hO3ujs%L;*sVI)=i<4=H7=^26pqNR% zIf*gZK_&;`j3A88f>KLHHxudfpx%*PqAxmNZ3k@c4Abnc2VTNdtBw@-5fcD?zC2xk zt0&`eF^caIG{#bF6*I)PILVd~F0o{PJ~6^EE!Ad&Bh_6^9BSin8l5IOysc' + message + ''); @@ -62,7 +62,7 @@ function monero_updateDetails() { break; } - jQuery('#monero_exchange_rate').html('1 XMR = '+details.rate_formatted+' '+details.currency); + jQuery('#monero_exchange_rate').html('1 WOW = '+details.rate_formatted+' '+details.currency); jQuery('#monero_total_amount').html(details.amount_total_formatted); jQuery('#monero_total_paid').html(details.amount_paid_formatted); jQuery('#monero_total_due').html(details.amount_due_formatted); @@ -87,7 +87,7 @@ function monero_updateDetails() { ''+tx.txid+''+ ''+ ''+height+''+ - ''+tx.amount_formatted+' Monero'+ + ''+tx.amount_formatted+' Wownero'+ ''; jQuery('#monero_tx_table tbody').append(row); @@ -110,7 +110,7 @@ function monero_updateDetails() { } } if(is_new_tx) { - monero_showNotification('Transaction received for '+new_txs[i].amount_formatted+' Monero'); + monero_showNotification('Transaction received for '+new_txs[i].amount_formatted+' Wownero'); } } } diff --git a/include/admin/class-monero-admin-interface.php b/include/admin/class-monero-admin-interface.php index 930f84e..dba52ef 100644 --- a/include/admin/class-monero-admin-interface.php +++ b/include/admin/class-monero-admin-interface.php @@ -1,8 +1,7 @@ @@ -69,32 +68,32 @@ HTML; } echo << -

Monero Payments

+

Wownero Payments

$balance_info
  • - + All ({$tab_info['all']['count']}) |
  • - + Pending ({$tab_info['pending']['count']}) |
  • - + Received ({$tab_info['paid']['count']}) |
  • - + Confirmed ({$tab_info['confirmed']['count']}) |
  • - + Expired ({$tab_info['expired']['count']})
  • @@ -107,7 +106,7 @@ HTML;

    $hidden_fields -

    Monero Payments List

    +

    Wownero Payments List