mirror of
https://git.wownero.com/wownero/wownerowp.git
synced 2024-08-15 01:03:16 +00:00
Compare commits
112 commits
Author | SHA1 | Date | |
---|---|---|---|
|
8122cdf8ef | ||
|
b9e5fcc96e | ||
|
572f04dfba | ||
|
9f0a8581e6 | ||
|
2e6ec376a2 | ||
|
acfdc094da | ||
|
fcedf21b30 | ||
|
90e058baeb | ||
|
954eb92785 | ||
|
b45b9625fb | ||
|
009f1d0594 | ||
|
25416473eb | ||
|
a3e10ab36f | ||
|
9af67ae75d | ||
|
a29b4d00b6 | ||
|
fea43715ea | ||
|
596a1e29ad | ||
|
646bcd671a | ||
|
5fa97a8282 | ||
|
21df86e4b8 | ||
|
8e7d258b5b | ||
|
7e48521570 | ||
|
ba395d507e | ||
|
f7d1f9b968 | ||
|
184e740452 | ||
|
8009819592 | ||
|
5064b95040 | ||
|
ad0d9ef004 | ||
|
17a379a2aa | ||
|
903432ac7a | ||
|
f7fa0b758e | ||
|
cd06d0303b | ||
|
ccb387095d | ||
|
53463e9111 | ||
|
a17af45bd8 | ||
|
ad6fe196a6 | ||
|
1b2c66a75b | ||
|
4def1b771e | ||
|
4c091a13f7 | ||
|
9e1d4b50c7 | ||
|
0c64b9ce5f | ||
|
0b4d7edf33 | ||
|
d97311b2a4 | ||
|
8ec095d9d6 | ||
|
55356ace73 | ||
|
4cbcde5e7b | ||
|
b30ee78993 | ||
|
8ff6da2d52 | ||
|
34eac09175 | ||
|
d5dd832b91 | ||
|
bdd2c1813d | ||
|
8def077ba4 | ||
|
01c33481f8 | ||
|
903bd8b2d7 | ||
|
9e924a1a61 | ||
|
fa9d6b7162 | ||
|
380cd24527 | ||
|
9f8fee17eb | ||
|
df6adb4ab8 | ||
|
7c1cde1e6a | ||
|
ab8ef9fa2f | ||
|
99b5063b2c | ||
|
ecc19adb83 | ||
|
f74a52d56b | ||
|
ee6fa74f02 | ||
|
91d3f12f46 | ||
|
35c9ef14db | ||
|
1afdaf0631 | ||
|
235ae5e442 | ||
|
4b192df223 | ||
|
d0071b12a9 | ||
|
552792625d | ||
|
fc38605772 | ||
|
39361a247b | ||
|
acc6b0d91c | ||
|
ad1edf568c | ||
|
309b141477 | ||
|
dba3598069 | ||
|
b519522899 | ||
|
0b15e31672 | ||
|
460f4f6c75 | ||
|
10996fe71d | ||
|
acd3ca568c | ||
|
8afbecb0bc | ||
|
19a2b2cfe1 | ||
|
33a982107f | ||
|
5cb6d1756b | ||
|
27fa98b31f | ||
|
dcbc682660 | ||
|
566a003122 | ||
|
7259659dad | ||
|
fa49096c15 | ||
|
3e9b4acd48 | ||
|
6b43b53cfe | ||
|
590779c263 | ||
|
6326652a19 | ||
|
5da38e31ca | ||
|
f4822f237e | ||
|
9f1fb367e2 | ||
|
598b7085ed | ||
|
2e2c5b1b4a | ||
|
05695f766d | ||
|
09ae386805 | ||
|
f44378e349 | ||
|
1ca11855fe | ||
|
d3aa4f0410 | ||
|
fbd51c0593 | ||
|
76602ebc3a | ||
|
f561d4badf | ||
|
c453febd79 | ||
|
51cc463e7d | ||
|
20b7b15192 |
36 changed files with 5616 additions and 608 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,3 +1,8 @@
|
||||||
|
|
||||||
*.xml
|
*.xml
|
||||||
*.iml
|
*.iml
|
||||||
|
|
||||||
|
# Emacs
|
||||||
|
*~
|
||||||
|
\#*
|
||||||
|
.\#*
|
3
LICENSE
3
LICENSE
|
@ -1,6 +1,7 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2017 Monero Integrations
|
Copyright (c) 2017-2018, Monero Integrations
|
||||||
|
Copyright (c) 2018, Fireice Trust Fund
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
120
README.md
120
README.md
|
@ -1,65 +1,111 @@
|
||||||
# MoneroWP
|
# Monero Gateway for WooCommerce
|
||||||
A WooCommerce extension for accepting Monero
|
|
||||||
|
|
||||||
## Dependancies
|
## Features
|
||||||
This plugin is rather simple but there are a few things that need to be set up before hand.
|
|
||||||
|
|
||||||
* 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.
|
||||||
|
|
||||||
* The Monero wallet-cli and Monero wallet-rpc tools found [here](https://getmonero.org/downloads/)
|
## Requirements
|
||||||
|
|
||||||
* [WordPress](https://wordpress.org)
|
* 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/)
|
||||||
Wordpress is the backend tool that is needed to use WooCommerce and this Monero plugin
|
* [BCMath](http://php.net/manual/en/book.bc.php) - A PHP extension used for arbitrary precision maths
|
||||||
|
|
||||||
* [WooCommerce](https://woocommerce.com)
|
## Installing the plugin
|
||||||
This Monero plugin is an extension of WooCommerce, which works with WordPress
|
|
||||||
|
|
||||||
## Step 1: Activating the plugin
|
### Automatic Method
|
||||||
* 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.
|
|
||||||
|
|
||||||
* Unzip the file monerowp_release.zip if you downloaded the zip from the releases page [here](https://github.com/monero-integrations/monerowp/releases).
|
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.
|
||||||
|
|
||||||
* 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`
|
### Manual Method
|
||||||
|
|
||||||
* 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"
|
* 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 2: Get a monero daemon to connect to
|
## Option 1: Use your wallet address and viewkey
|
||||||
|
|
||||||
### Option 1: Running a full node yourself
|
This is the easiest way to start accepting Monero on your website. You'll need:
|
||||||
|
|
||||||
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.
|
* Your Monero wallet address starting with `4`
|
||||||
|
* Your wallet's secret viewkey
|
||||||
|
|
||||||
### Option 2: Connecting to a remote node
|
Then simply select the `viewkey` option in the settings page and paste your address and viewkey. You're all set!
|
||||||
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
|
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.
|
||||||
|
|
||||||
* 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)
|
## Option 2: Using `monero-wallet-rpc`
|
||||||
|
|
||||||
* 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.
|
The most secure way to accept Monero on your website. You'll need:
|
||||||
|
|
||||||
## Step 4: Setup Monero Gateway in WooCommerce
|
* Root access to your webserver
|
||||||
|
* Latest [Monero-currency binaries](https://github.com/monero-project/monero/releases)
|
||||||
|
|
||||||
* Navigate to the "settings" panel in the WooCommerce widget in the WordPress admin panel.
|
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.
|
||||||
|
|
||||||
* Click on "Checkout"
|
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.
|
||||||
|
|
||||||
* Select "Monero GateWay"
|
## Configuration
|
||||||
|
|
||||||
* Check the box labeled "Enable this payment gateway"
|
* `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. (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)
|
||||||
|
|
||||||
* 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
|
## Shortcodes
|
||||||
|
|
||||||
* Enter the IP address of your server in the box labeled "Monero wallet rpc Host/IP"
|
This plugin makes available two shortcodes that you can use in your theme.
|
||||||
|
|
||||||
* 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).
|
#### Live price shortcode
|
||||||
|
|
||||||
* Enter the username and password that you want to use in their respective feilds
|
This will display the price of Monero in the selected currency. If no currency is provided, the store's default currency will be used.
|
||||||
|
|
||||||
* Click on "Save changes"
|
```
|
||||||
|
[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
|
||||||
|
```
|
||||||
|
|
||||||
## 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
|
#### Monero accepted here badge
|
||||||
XMR Address : `44krVcL6TPkANjpFwS2GWvg1kJhTrN7y9heVeQiDJ3rP8iGbCd5GeA4f3c2NKYHC1R4mCgnW7dsUUUae2m9GiNBGT4T8s2X`
|
|
||||||
|
This will display a badge showing that you accept Monero-currency.
|
||||||
|
|
||||||
|
`[monero-accepted-here]`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Donations
|
||||||
|
|
||||||
|
monero-integrations: 44krVcL6TPkANjpFwS2GWvg1kJhTrN7y9heVeQiDJ3rP8iGbCd5GeA4f3c2NKYHC1R4mCgnW7dsUUUae2m9GiNBGT4T8s2X
|
||||||
|
|
78
assets/css/monero-gateway-order-page.css
Normal file
78
assets/css/monero-gateway-order-page.css
Normal file
|
@ -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);
|
||||||
|
}
|
BIN
assets/images/wownero-accepted-here.png
Normal file
BIN
assets/images/wownero-accepted-here.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
BIN
assets/images/wownero-icon-admin.png
Normal file
BIN
assets/images/wownero-icon-admin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 941 B |
BIN
assets/images/wownero-icon.png
Normal file
BIN
assets/images/wownero-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.1 KiB |
939
assets/js/clipboard.js
Normal file
939
assets/js/clipboard.js
Normal file
|
@ -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;
|
||||||
|
|
||||||
|
|
||||||
|
/***/ })
|
||||||
|
/******/ ]);
|
||||||
|
});
|
7
assets/js/clipboard.min.js
vendored
Normal file
7
assets/js/clipboard.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
161
assets/js/monero-gateway-order-page.js
Normal file
161
assets/js/monero-gateway-order-page.js
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Fireice Trust Fund
|
||||||
|
*/
|
||||||
|
function monero_showNotification(message, type='success') {
|
||||||
|
var toast = jQuery('<div class="' + type + '"><span>' + message + '</span></div>');
|
||||||
|
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 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);
|
||||||
|
|
||||||
|
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 = ''+
|
||||||
|
'<tr>'+
|
||||||
|
'<td style="word-break: break-all">'+
|
||||||
|
'<a href="'+monero_explorer_url+'/tx/'+tx.txid+'" target="_blank">'+tx.txid+'</a>'+
|
||||||
|
'</td>'+
|
||||||
|
'<td>'+height+'</td>'+
|
||||||
|
'<td>'+tx.amount_formatted+' Wownero</td>'+
|
||||||
|
'</tr>';
|
||||||
|
|
||||||
|
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+' Wownero');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
614
assets/js/qrcode.js
Normal file
614
assets/js/qrcode.js
Normal file
|
@ -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 <a href="http://www.d-project.com/" target="_blank">http://www.d-project.com/</a>
|
||||||
|
* @see <a href="http://jeromeetienne.github.com/jquery-qrcode/" target="_blank">http://jeromeetienne.github.com/jquery-qrcode/</a>
|
||||||
|
*/
|
||||||
|
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<this.moduleCount;row++){this.modules[row]=new Array(this.moduleCount);for(var col=0;col<this.moduleCount;col++){this.modules[row][col]=null;}}
|
||||||
|
this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(test,maskPattern);if(this.typeNumber>=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<this.modules.length;row++){var y=row*cs;for(var col=0;col<this.modules[row].length;col++){var x=col*cs;var dark=this.modules[row][col];if(dark){qr_mc.beginFill(0,100);qr_mc.moveTo(x,y);qr_mc.lineTo(x+cs,y);qr_mc.lineTo(x+cs,y+cs);qr_mc.lineTo(x,y+cs);qr_mc.endFill();}}}
|
||||||
|
return qr_mc;},setupTimingPattern:function(){for(var r=8;r<this.moduleCount-8;r++){if(this.modules[r][6]!=null){continue;}
|
||||||
|
this.modules[r][6]=(r%2==0);}
|
||||||
|
for(var c=8;c<this.moduleCount-8;c++){if(this.modules[6][c]!=null){continue;}
|
||||||
|
this.modules[6][c]=(c%2==0);}},setupPositionAdjustPattern:function(){var pos=QRUtil.getPatternPosition(this.typeNumber);for(var i=0;i<pos.length;i++){for(var j=0;j<pos.length;j++){var row=pos[i];var col=pos[j];if(this.modules[row][col]!=null){continue;}
|
||||||
|
for(var r=-2;r<=2;r++){for(var c=-2;c<=2;c++){if(r==-2||r==2||c==-2||c==2||(r==0&&c==0)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}}}},setupTypeNumber:function(test){var bits=QRUtil.getBCHTypeNumber(this.typeNumber);for(var i=0;i<18;i++){var mod=(!test&&((bits>>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<data.length){dark=(((data[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;i<dataList.length;i++){var data=dataList[i];buffer.put(data.mode,4);buffer.put(data.getLength(),QRUtil.getLengthInBits(data.mode,typeNumber));data.write(buffer);}
|
||||||
|
var totalDataCount=0;for(var i=0;i<rsBlocks.length;i++){totalDataCount+=rsBlocks[i].dataCount;}
|
||||||
|
if(buffer.getLengthInBits()>totalDataCount*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<rsBlocks.length;r++){var dcCount=rsBlocks[r].dataCount;var ecCount=rsBlocks[r].totalCount-dcCount;maxDcCount=Math.max(maxDcCount,dcCount);maxEcCount=Math.max(maxEcCount,ecCount);dcdata[r]=new Array(dcCount);for(var i=0;i<dcdata[r].length;i++){dcdata[r][i]=0xff&buffer.buffer[i+offset];}
|
||||||
|
offset+=dcCount;var rsPoly=QRUtil.getErrorCorrectPolynomial(ecCount);var rawPoly=new QRPolynomial(dcdata[r],rsPoly.getLength()-1);var modPoly=rawPoly.mod(rsPoly);ecdata[r]=new Array(rsPoly.getLength()-1);for(var i=0;i<ecdata[r].length;i++){var modIndex=i+modPoly.getLength()-ecdata[r].length;ecdata[r][i]=(modIndex>=0)?modPoly.get(modIndex):0;}}
|
||||||
|
var totalCodeCount=0;for(var i=0;i<rsBlocks.length;i++){totalCodeCount+=rsBlocks[i].totalCount;}
|
||||||
|
var data=new Array(totalCodeCount);var index=0;for(var i=0;i<maxDcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<dcdata[r].length){data[index++]=dcdata[r][i];}}}
|
||||||
|
for(var i=0;i<maxEcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<ecdata[r].length){data[index++]=ecdata[r][i];}}}
|
||||||
|
return data;};var QRMode={MODE_NUMBER:1<<0,MODE_ALPHA_NUM:1<<1,MODE_8BIT_BYTE:1<<2,MODE_KANJI:1<<3};var QRErrorCorrectLevel={L:1,M:0,Q:3,H:2};var QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var QRUtil={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:(1<<10)|(1<<8)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0),G18:(1<<12)|(1<<11)|(1<<10)|(1<<9)|(1<<8)|(1<<5)|(1<<2)|(1<<0),G15_MASK:(1<<14)|(1<<12)|(1<<10)|(1<<4)|(1<<1),getBCHTypeInfo:function(data){var d=data<<10;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)>=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;i<errorCorrectLength;i++){a=a.multiply(new QRPolynomial([1,QRMath.gexp(i)],0));}
|
||||||
|
return a;},getLengthInBits:function(mode,type){if(1<=type&&type<10){switch(mode){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:return 8;case QRMode.MODE_KANJI:return 8;default:throw new Error("mode:"+mode);}}else if(type<27){switch(mode){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw new Error("mode:"+mode);}}else if(type<41){switch(mode){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw new Error("mode:"+mode);}}else{throw new Error("type:"+type);}},getLostPoint:function(qrCode){var moduleCount=qrCode.getModuleCount();var lostPoint=0;for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount;col++){var sameCount=0;var dark=qrCode.isDark(row,col);for(var r=-1;r<=1;r++){if(row+r<0||moduleCount<=row+r){continue;}
|
||||||
|
for(var c=-1;c<=1;c++){if(col+c<0||moduleCount<=col+c){continue;}
|
||||||
|
if(r==0&&c==0){continue;}
|
||||||
|
if(dark==qrCode.isDark(row+r,col+c)){sameCount++;}}}
|
||||||
|
if(sameCount>5){lostPoint+=(3+sameCount-5);}}}
|
||||||
|
for(var row=0;row<moduleCount-1;row++){for(var col=0;col<moduleCount-1;col++){var count=0;if(qrCode.isDark(row,col))count++;if(qrCode.isDark(row+1,col))count++;if(qrCode.isDark(row,col+1))count++;if(qrCode.isDark(row+1,col+1))count++;if(count==0||count==4){lostPoint+=3;}}}
|
||||||
|
for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount-6;col++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row,col+1)&&qrCode.isDark(row,col+2)&&qrCode.isDark(row,col+3)&&qrCode.isDark(row,col+4)&&!qrCode.isDark(row,col+5)&&qrCode.isDark(row,col+6)){lostPoint+=40;}}}
|
||||||
|
for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount-6;row++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row+1,col)&&qrCode.isDark(row+2,col)&&qrCode.isDark(row+3,col)&&qrCode.isDark(row+4,col)&&!qrCode.isDark(row+5,col)&&qrCode.isDark(row+6,col)){lostPoint+=40;}}}
|
||||||
|
var darkCount=0;for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount;row++){if(qrCode.isDark(row,col)){darkCount++;}}}
|
||||||
|
var ratio=Math.abs(100*darkCount/moduleCount/moduleCount-50)/5;lostPoint+=ratio*10;return lostPoint;}};var QRMath={glog:function(n){if(n<1){throw new Error("glog("+n+")");}
|
||||||
|
return QRMath.LOG_TABLE[n];},gexp:function(n){while(n<0){n+=255;}
|
||||||
|
while(n>=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<<i;}
|
||||||
|
for(var i=8;i<256;i++){QRMath.EXP_TABLE[i]=QRMath.EXP_TABLE[i-4]^QRMath.EXP_TABLE[i-5]^QRMath.EXP_TABLE[i-6]^QRMath.EXP_TABLE[i-8];}
|
||||||
|
for(var i=0;i<255;i++){QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;}
|
||||||
|
function QRPolynomial(num,shift){if(num.length==undefined){throw new Error(num.length+"/"+shift);}
|
||||||
|
var offset=0;while(offset<num.length&&num[offset]==0){offset++;}
|
||||||
|
this.num=new Array(num.length-offset+shift);for(var i=0;i<num.length-offset;i++){this.num[i]=num[i+offset];}}
|
||||||
|
QRPolynomial.prototype={get:function(index){return this.num[index];},getLength:function(){return this.num.length;},multiply:function(e){var num=new Array(this.getLength()+e.getLength()-1);for(var i=0;i<this.getLength();i++){for(var j=0;j<e.getLength();j++){num[i+j]^=QRMath.gexp(QRMath.glog(this.get(i))+QRMath.glog(e.get(j)));}}
|
||||||
|
return new QRPolynomial(num,0);},mod:function(e){if(this.getLength()-e.getLength()<0){return this;}
|
||||||
|
var ratio=QRMath.glog(this.get(0))-QRMath.glog(e.get(0));var num=new Array(this.getLength());for(var i=0;i<this.getLength();i++){num[i]=this.get(i);}
|
||||||
|
for(var i=0;i<e.getLength();i++){num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);}
|
||||||
|
return new QRPolynomial(num,0).mod(e);}};function QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this.dataCount=dataCount;}
|
||||||
|
QRRSBlock.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];QRRSBlock.getRSBlocks=function(typeNumber,errorCorrectLevel){var rsBlock=QRRSBlock.getRsBlockTable(typeNumber,errorCorrectLevel);if(rsBlock==undefined){throw new Error("bad rs block @ typeNumber:"+typeNumber+"/errorCorrectLevel:"+errorCorrectLevel);}
|
||||||
|
var length=rsBlock.length/3;var list=[];for(var i=0;i<length;i++){var count=rsBlock[i*3+0];var totalCount=rsBlock[i*3+1];var dataCount=rsBlock[i*3+2];for(var j=0;j<count;j++){list.push(new QRRSBlock(totalCount,dataCount));}}
|
||||||
|
return list;};QRRSBlock.getRsBlockTable=function(typeNumber,errorCorrectLevel){switch(errorCorrectLevel){case QRErrorCorrectLevel.L:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+0];case QRErrorCorrectLevel.M:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+1];case QRErrorCorrectLevel.Q:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+2];case QRErrorCorrectLevel.H:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+3];default:return undefined;}};function QRBitBuffer(){this.buffer=[];this.length=0;}
|
||||||
|
QRBitBuffer.prototype={get:function(index){var bufIndex=Math.floor(index/8);return((this.buffer[bufIndex]>>>(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i<length;i++){this.putBit(((num>>>(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 = ['<table style="border:0;border-collapse:collapse;">'];
|
||||||
|
|
||||||
|
for (var row = 0; row < nCount; row++) {
|
||||||
|
aHTML.push('<tr>');
|
||||||
|
|
||||||
|
for (var col = 0; col < nCount; col++) {
|
||||||
|
aHTML.push('<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:' + nWidth + 'px;height:' + nHeight + 'px;background-color:' + (oQRCode.isDark(row, col) ? _htOption.colorDark : _htOption.colorLight) + ';"></td>');
|
||||||
|
}
|
||||||
|
|
||||||
|
aHTML.push('</tr>');
|
||||||
|
}
|
||||||
|
|
||||||
|
aHTML.push('</table>');
|
||||||
|
_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;
|
||||||
|
})();
|
1
assets/js/qrcode.min.js
vendored
Normal file
1
assets/js/qrcode.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
14
assets/systemd-unit-files/monero-wallet-rpc.service
Normal file
14
assets/systemd-unit-files/monero-wallet-rpc.service
Normal file
|
@ -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
|
14
assets/systemd-unit-files/monerod.service
Normal file
14
assets/systemd-unit-files/monerod.service
Normal file
|
@ -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
|
132
include/admin/class-monero-admin-interface.php
Normal file
132
include/admin/class-monero-admin-interface.php
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Fireice Trust Fund
|
||||||
|
* Admin interface for Monero gateway
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
require_once('class-monero-admin-payments-list.php');
|
||||||
|
|
||||||
|
if (class_exists('Monero_Admin_Interface', false)) {
|
||||||
|
return new Monero_Admin_Interface();
|
||||||
|
}
|
||||||
|
|
||||||
|
class Monero_Admin_Interface {
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
add_action('add_meta_boxes', array($this, 'meta_boxes'));
|
||||||
|
add_action('admin_menu', array($this, 'admin_menu'));
|
||||||
|
add_action('admin_head', array( $this, 'admin_menu_update'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add meta boxes.
|
||||||
|
*/
|
||||||
|
public function meta_boxes() {
|
||||||
|
add_meta_box(
|
||||||
|
'monero_admin_order_details',
|
||||||
|
__('Wownero Gateway','wownero_gateway'),
|
||||||
|
array($this, 'meta_box_order_details'),
|
||||||
|
'shop_order',
|
||||||
|
'normal',
|
||||||
|
'high'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Meta box for order page
|
||||||
|
*/
|
||||||
|
public function meta_box_order_details($order) {
|
||||||
|
Monero_Gateway::admin_order_page($order);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add menu items.
|
||||||
|
*/
|
||||||
|
public function admin_menu() {
|
||||||
|
add_menu_page(
|
||||||
|
__('Wownero', 'wownero_gateway'),
|
||||||
|
__('Wownero', 'wownero_gateway'),
|
||||||
|
'manage_woocommerce',
|
||||||
|
'wownero_gateway',
|
||||||
|
array($this, 'orders_page'),
|
||||||
|
MONERO_GATEWAY_PLUGIN_URL.'/assets/images/wownero-icon-admin.png',
|
||||||
|
56 // Position on menu, woocommerce has 55.5, products has 55.6
|
||||||
|
);
|
||||||
|
|
||||||
|
add_submenu_page(
|
||||||
|
'wownero_gateway',
|
||||||
|
__('Payments', 'wownero_gateway'),
|
||||||
|
__('Payments', 'wownero_gateway'),
|
||||||
|
'manage_woocommerce',
|
||||||
|
'wownero_gateway_payments',
|
||||||
|
array($this, 'payments_page')
|
||||||
|
);
|
||||||
|
|
||||||
|
$settings_page = add_submenu_page(
|
||||||
|
'wownero_gateway',
|
||||||
|
__('Settings', 'wownero_gateway'),
|
||||||
|
__('Settings', 'wownero_gateway'),
|
||||||
|
'manage_options',
|
||||||
|
'monero_gateway_settings',
|
||||||
|
array($this, 'settings_page')
|
||||||
|
);
|
||||||
|
add_action('load-'.$settings_page, array($this, 'settings_page_init'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove duplicate sub-menu item
|
||||||
|
*/
|
||||||
|
public function admin_menu_update() {
|
||||||
|
global $submenu;
|
||||||
|
if (isset($submenu['wownero_gateway'])) {
|
||||||
|
unset($submenu['wownero_gateway'][0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monero payments page
|
||||||
|
*/
|
||||||
|
public function payments_page() {
|
||||||
|
$payments_list = new Monero_Admin_Payments_List();
|
||||||
|
$payments_list->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 = 'wownero_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();
|
275
include/admin/class-monero-admin-payments-list.php
Normal file
275
include/admin/class-monero-admin-payments-list.php
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Fireice Trust Fund
|
||||||
|
* Admin interface for Monero gateway
|
||||||
|
*/
|
||||||
|
|
||||||
|
if(!class_exists('WP_List_Table')) {
|
||||||
|
require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php');
|
||||||
|
}
|
||||||
|
|
||||||
|
class Monero_Admin_Payments_List extends WP_List_Table {
|
||||||
|
|
||||||
|
function __construct() {
|
||||||
|
parent::__construct(array(
|
||||||
|
'singular'=> '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() == 'wownero-wallet-rpc') {
|
||||||
|
$balance = Monero_Gateway::admin_balance_info();
|
||||||
|
$balance_info = <<<HTML
|
||||||
|
<div style="border:1px solid #ddd;padding:5px 10px;">
|
||||||
|
Wallet height: {$balance['height']}</br>
|
||||||
|
Your balance is: {$balance['balance']}</br>
|
||||||
|
Unlocked balance: {$balance['unlocked_balance']}</br>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
HTML;
|
||||||
|
} else {
|
||||||
|
$balance_info = '';
|
||||||
|
}
|
||||||
|
echo <<<HTML
|
||||||
|
<div class="wrap">
|
||||||
|
<h1 class="wp-heading-inline">Wownero Payments</h1>
|
||||||
|
$balance_info
|
||||||
|
<hr class="wp-header-end">
|
||||||
|
<ul class="subsubsub">
|
||||||
|
<li>
|
||||||
|
<a href="?page=wownero_gateway_payments&type=all" {$tab_info['all']['active']}>
|
||||||
|
All <span class="count">({$tab_info['all']['count']})</span>
|
||||||
|
</a> |
|
||||||
|
</li>
|
||||||
|
<li style="display:none">
|
||||||
|
<a href="?page=wownero_gateway_payments&type=pending" {$tab_info['pending']['active']}>
|
||||||
|
Pending <span class="count">({$tab_info['pending']['count']})</span>
|
||||||
|
</a> |
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="?page=wownero_gateway_payments&type=paid" {$tab_info['paid']['active']}>
|
||||||
|
Received <span class="count">({$tab_info['paid']['count']})</span>
|
||||||
|
</a> |
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="?page=wownero_gateway_payments&type=confirmed" {$tab_info['confirmed']['active']}>
|
||||||
|
Confirmed <span class="count">({$tab_info['confirmed']['count']})</span>
|
||||||
|
</a> |
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="?page=wownero_gateway_payments&type=expired" {$tab_info['expired']['active']}>
|
||||||
|
Expired <span class="count">({$tab_info['expired']['count']})</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<form id="monero-payments-filter" method="get" style="display:none">
|
||||||
|
<p class="search-box">
|
||||||
|
<label class="screen-reader-text" for="post-search-input">Search payments:</label>
|
||||||
|
<input type="search" id="post-search-input" name="s" value="">
|
||||||
|
<input type="submit" id="search-submit" class="button" value="Search payments">
|
||||||
|
</p>
|
||||||
|
$hidden_fields
|
||||||
|
</form>
|
||||||
|
<h2 class="screen-reader-text">Wownero Payments List</h2>
|
||||||
|
<style>
|
||||||
|
#col_order_id { width: 150px; }
|
||||||
|
#col_payment_id { width: 150px; }
|
||||||
|
#col_height { width: 100px; }
|
||||||
|
#col_amount { width: 150px; }
|
||||||
|
</style>
|
||||||
|
<table class="wp-list-table widefat fixed striped posts">
|
||||||
|
|
||||||
|
HTML;
|
||||||
|
} else if ($which == "bottom") {
|
||||||
|
echo '<br class="clear"></div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 '<a href="'.$url.'" target="_blank">'.$item->txid.'</a>';
|
||||||
|
break;
|
||||||
|
case 'col_height':
|
||||||
|
echo $item->height;
|
||||||
|
break;
|
||||||
|
case 'col_amount':
|
||||||
|
echo Monero_Gateway::format_monero($item->amount).' Wownero';
|
||||||
|
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 '<a href="' . esc_url( admin_url( 'post.php?post=' . absint( $order->get_id() ) ) . '&action=edit' ) . '" class="order-view"><strong>#' . esc_attr( $order->get_order_number() ) . ' ' . esc_html( $buyer ) . '</strong></a>';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Wownero payments found', 'wownero_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),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
120
include/admin/monero-gateway-admin-settings.php
Normal file
120
include/admin/monero-gateway-admin-settings.php
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'enabled' => array(
|
||||||
|
'title' => __('Enable / Disable', 'wownero_gateway'),
|
||||||
|
'label' => __('Enable this payment gateway', 'wownero_gateway'),
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'default' => 'no'
|
||||||
|
),
|
||||||
|
'title' => array(
|
||||||
|
'title' => __('Title', 'wownero_gateway'),
|
||||||
|
'type' => 'text',
|
||||||
|
'desc_tip' => __('Payment title the customer will see during the checkout process.', 'wownero_gateway'),
|
||||||
|
'default' => __('Wownero Gateway', 'wownero_gateway')
|
||||||
|
),
|
||||||
|
'description' => array(
|
||||||
|
'title' => __('Description', 'wownero_gateway'),
|
||||||
|
'type' => 'textarea',
|
||||||
|
'desc_tip' => __('Payment description the customer will see during the checkout process.', 'wownero_gateway'),
|
||||||
|
'default' => __('Pay securely using Wownero. You will be provided payment details after checkout.', 'wownero_gateway')
|
||||||
|
),
|
||||||
|
'discount' => array(
|
||||||
|
'title' => __('Discount for using Wownero', 'wownero_gateway'),
|
||||||
|
'desc_tip' => __('Provide a discount to your customers for making a private payment with Wownero', 'wownero_gateway'),
|
||||||
|
'description' => __('Enter a percentage discount (i.e. 5 for 5%) or leave this empty if you do not wish to provide a discount', 'wownero_gateway'),
|
||||||
|
'type' => __('number'),
|
||||||
|
'default' => '0'
|
||||||
|
),
|
||||||
|
'valid_time' => array(
|
||||||
|
'title' => __('Order valid time', 'wownero_gateway'),
|
||||||
|
'desc_tip' => __('Amount of time order is valid before expiring', 'wownero_gateway'),
|
||||||
|
'description' => __('Enter the number of seconds that the funds must be received in after order is placed. 3600 seconds = 1 hour', 'wownero_gateway'),
|
||||||
|
'type' => __('number'),
|
||||||
|
'default' => '3600'
|
||||||
|
),
|
||||||
|
'confirms' => array(
|
||||||
|
'title' => __('Number of confirmations', 'wownero_gateway'),
|
||||||
|
'desc_tip' => __('Number of confirms a transaction must have to be valid', 'wownero_gateway'),
|
||||||
|
'description' => __('Enter the number of confirms that transactions must have. Enter 0 to zero-confim. Each confirm will take approximately four minutes', 'wownero_gateway'),
|
||||||
|
'type' => __('number'),
|
||||||
|
'default' => '4'
|
||||||
|
),
|
||||||
|
'confirm_type' => array(
|
||||||
|
'title' => __('Confirmation Type', 'wownero_gateway'),
|
||||||
|
'desc_tip' => __('Select the method for confirming transactions', 'wownero_gateway'),
|
||||||
|
'description' => __('Select the method for confirming transactions', 'wownero_gateway'),
|
||||||
|
'type' => 'select',
|
||||||
|
'options' => array(
|
||||||
|
'viewkey' => __('viewkey', 'wownero_gateway'),
|
||||||
|
'wownero-wallet-rpc' => __('wownero-wallet-rpc', 'wownero_gateway')
|
||||||
|
),
|
||||||
|
'default' => 'viewkey'
|
||||||
|
),
|
||||||
|
'monero_address' => array(
|
||||||
|
'title' => __('Wownero Address', 'wownero_gateway'),
|
||||||
|
'label' => __('Useful for people that have not a daemon online'),
|
||||||
|
'type' => 'text',
|
||||||
|
'desc_tip' => __('Wownero Wallet Address (MoneroL)', 'wownero_gateway')
|
||||||
|
),
|
||||||
|
'viewkey' => array(
|
||||||
|
'title' => __('Secret Viewkey', 'wownero_gateway'),
|
||||||
|
'label' => __('Secret Viewkey'),
|
||||||
|
'type' => 'text',
|
||||||
|
'desc_tip' => __('Your secret Viewkey', 'wownero_gateway')
|
||||||
|
),
|
||||||
|
'daemon_host' => array(
|
||||||
|
'title' => __('Wownero wallet RPC Host/IP', 'wownero_gateway'),
|
||||||
|
'type' => 'text',
|
||||||
|
'desc_tip' => __('This is the Daemon Host/IP to authorize the payment with', 'wownero_gateway'),
|
||||||
|
'default' => '127.0.0.1',
|
||||||
|
),
|
||||||
|
'daemon_port' => array(
|
||||||
|
'title' => __('Wownero wallet RPC port', 'wownero_gateway'),
|
||||||
|
'type' => __('number'),
|
||||||
|
'desc_tip' => __('This is the Wallet RPC port to authorize the payment with', 'wownero_gateway'),
|
||||||
|
'default' => '34567',
|
||||||
|
),
|
||||||
|
'testnet' => array(
|
||||||
|
'title' => __(' Testnet', 'wownero_gateway'),
|
||||||
|
'label' => __(' Check this if you are using testnet ', 'wownero_gateway'),
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'description' => __('Advanced usage only', 'wownero_gateway'),
|
||||||
|
'default' => 'no'
|
||||||
|
),
|
||||||
|
'javascript' => array(
|
||||||
|
'title' => __(' Javascript', 'wownero_gateway'),
|
||||||
|
'label' => __(' Check this to ENABLE Javascript in Checkout page ', 'wownero_gateway'),
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'default' => 'no'
|
||||||
|
),
|
||||||
|
'onion_service' => array(
|
||||||
|
'title' => __(' SSL warnings ', 'wownero_gateway'),
|
||||||
|
'label' => __(' Check to Silence SSL warnings', 'wownero_gateway'),
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'description' => __('Check this box if you are running on an Onion Service (Suppress SSL errors)', 'wownero_gateway'),
|
||||||
|
'default' => 'no'
|
||||||
|
),
|
||||||
|
'show_qr' => array(
|
||||||
|
'title' => __('Show QR Code', 'wownero_gateway'),
|
||||||
|
'label' => __('Show QR Code', 'wownero_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 Wownero', 'wownero_gateway'),
|
||||||
|
'label' => __('Show Prices in Wownero', 'wownero_gateway'),
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'description' => __('Enable this to convert ALL prices on the frontend to Wownero (experimental)'),
|
||||||
|
'default' => 'no'
|
||||||
|
),
|
||||||
|
'use_monero_price_decimals' => array(
|
||||||
|
'title' => __('Display Decimals', 'wownero_gateway'),
|
||||||
|
'type' => __('number'),
|
||||||
|
'description' => __('Number of decimal places to display on frontend. Upon checkout exact price will be displayed.'),
|
||||||
|
'default' => 11,
|
||||||
|
),
|
||||||
|
);
|
356
include/class-monero-base58.php
Normal file
356
include/class-monero-base58.php
Normal file
|
@ -0,0 +1,356 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* monerophp/base58
|
||||||
|
*
|
||||||
|
* A PHP Base58 codec
|
||||||
|
* https://github.com/monero-integrations/monerophp
|
||||||
|
*
|
||||||
|
* Using work from
|
||||||
|
* bigreddmachine [MoneroPy] (https://github.com/bigreddmachine)
|
||||||
|
* Paul Shapiro [mymonero-core-js] (https://github.com/paulshapiro)
|
||||||
|
*
|
||||||
|
* @author Monero Integrations Team <support@monerointegrations.com> (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');
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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)');
|
||||||
|
}
|
||||||
|
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, 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;
|
||||||
|
}
|
||||||
|
}
|
312
include/class-monero-cryptonote.php
Normal file
312
include/class-monero-cryptonote.php
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
Copyright (c) 2018 Monero-Integrations
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
if(!class_exists('SHA3'))
|
||||||
|
require_once('crypto/SHA3.php');
|
||||||
|
|
||||||
|
if(!class_exists('ed25519'))
|
||||||
|
require_once('crypto/ed25519.php');
|
||||||
|
|
||||||
|
require_once('class-monero-base58.php');
|
||||||
|
|
||||||
|
class Monero_Cryptonote
|
||||||
|
{
|
||||||
|
protected $ed25519;
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
90
include/class-monero-explorer-tools.php
Normal file
90
include/class-monero-explorer-tools.php
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* monero_explorer_tools.php
|
||||||
|
*
|
||||||
|
* Uses CURL to call API functions from the block explorer
|
||||||
|
* https://xmrchain.net/
|
||||||
|
*
|
||||||
|
* @author Serhack
|
||||||
|
* @author cryptochangements
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
class Monero_Explorer_Tools
|
||||||
|
{
|
||||||
|
private $url;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
$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 [];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
840
include/class-monero-gateway.php
Normal file
840
include/class-monero-gateway.php
Normal file
|
@ -0,0 +1,840 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Main Gateway of Monero using either a local daemon or the explorer
|
||||||
|
* Authors: SerHack, cryptochangements
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
require_once('class-monero-cryptonote.php');
|
||||||
|
|
||||||
|
class Monero_Gateway extends WC_Payment_Gateway
|
||||||
|
{
|
||||||
|
private static $_id = 'wownero_gateway';
|
||||||
|
private static $_title = 'Wownero Gateway';
|
||||||
|
private static $_method_title = 'Wownero Gateway';
|
||||||
|
private static $_method_description = 'Wownero Gateway Plug-in for WooCommerce.';
|
||||||
|
private static $_errors = [];
|
||||||
|
|
||||||
|
private static $discount = false;
|
||||||
|
private static $valid_time = null;
|
||||||
|
private static $confirms = null;
|
||||||
|
private static $confirm_type = null;
|
||||||
|
private static $address = null;
|
||||||
|
private static $viewkey = null;
|
||||||
|
private static $host = null;
|
||||||
|
private static $port = null;
|
||||||
|
private static $testnet = false;
|
||||||
|
private static $onion_service = false;
|
||||||
|
private static $show_qr = false;
|
||||||
|
private static $use_monero_price = false;
|
||||||
|
private static $use_monero_price_decimals = MONERO_GATEWAY_ATOMIC_UNITS;
|
||||||
|
|
||||||
|
private static $cryptonote;
|
||||||
|
private static $monero_wallet_rpc;
|
||||||
|
private static $monero_explorer_tools;
|
||||||
|
private static $log;
|
||||||
|
|
||||||
|
private static $currencies = 'USD';
|
||||||
|
private static $rates = array();
|
||||||
|
|
||||||
|
private static $payment_details = array();
|
||||||
|
|
||||||
|
public function get_icon()
|
||||||
|
{
|
||||||
|
return apply_filters('woocommerce_gateway_icon', '<img src="'.MONERO_GATEWAY_PLUGIN_URL.'assets/images/wownero-icon.png"/>', $this->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __construct($add_action=true)
|
||||||
|
{
|
||||||
|
$this->id = self::$_id;
|
||||||
|
$this->method_title = __(self::$_method_title, 'wownero_gateway');
|
||||||
|
$this->method_description = __(self::$_method_description, 'wownero_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);
|
||||||
|
|
||||||
|
// 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'));
|
||||||
|
|
||||||
|
// Initialize helper classes
|
||||||
|
self::$cryptonote = new Monero_Cryptonote();
|
||||||
|
if(self::$confirm_type == 'wownero-wallet-rpc') {
|
||||||
|
require_once('class-wownero-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) == 97)
|
||||||
|
if(self::$cryptonote->verify_checksum($address))
|
||||||
|
return $address;
|
||||||
|
self::$_errors[] = 'Wownero 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 === 'wownero-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 wownero-wallet-rpc';
|
||||||
|
self::$log->add('Monero_Payments', '[ERROR] Cannot connect to wownero-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']).' Wownero',
|
||||||
|
'unlocked_balance' => self::format_monero($wallet_amount['unlocked_balance']).' Wownero'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 <a href="%s">forcing the checkout pages to be secured.</a>', 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);
|
||||||
|
|
||||||
|
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::$monero_wallet_rpc->create_address(0, 'Order: ' . $order_id);
|
||||||
|
if(isset($payment_id['address'])) {
|
||||||
|
$payment_id = $payment_id['address'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
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('');
|
||||||
|
|
||||||
|
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 Wownero 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;
|
||||||
|
|
||||||
|
$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', 'wownero_gateway'));
|
||||||
|
wc_reduce_stock_levels( $order_id );
|
||||||
|
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://api.coingecko.com/api/v3/coins/wownero/tickers?page=1';
|
||||||
|
$curl = curl_init();
|
||||||
|
curl_setopt_array($curl, array(
|
||||||
|
CURLOPT_RETURNTRANSFER => 1,
|
||||||
|
CURLOPT_URL => $api_link,
|
||||||
|
));
|
||||||
|
$resp = curl_exec($curl);
|
||||||
|
curl_close($curl);
|
||||||
|
$json = json_decode($resp, true);
|
||||||
|
$price = $json['tickers'][0]['converted_last']['usd'];
|
||||||
|
|
||||||
|
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));
|
||||||
|
$result = $wpdb->query($query);
|
||||||
|
if(!$result){
|
||||||
|
self::$log->add('Wownero_Payments', "[ERROR] Impossible to write DB. Please check your DB connection or enable Debugging.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
self::$log->add('Monero_Payments', "[ERROR] Unable to fetch prices from coingecko.com.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current network/wallet height
|
||||||
|
if(self::$confirm_type == 'wownero-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 == 'wownero-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.', 'wownero_gateway'));
|
||||||
|
} else {
|
||||||
|
$order->update_status('processing', __('Payment has been received.', 'wownero_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.', 'wownero_gateway'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function check_payment_rpc($subaddress)
|
||||||
|
{
|
||||||
|
$txs = array();
|
||||||
|
$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: ' . $subaddress);
|
||||||
|
return $txs;
|
||||||
|
}
|
||||||
|
$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(
|
||||||
|
'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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 == 'wownero-wallet-rpc') {
|
||||||
|
$integrated_addr = $payment_id;
|
||||||
|
} 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('Wownero_Gateway', '[ERROR] Merchant has not set Wownero address');
|
||||||
|
return '[ERROR] Merchant has not set Wownero 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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$amount_formatted = self::format_monero($amount_due);
|
||||||
|
$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(
|
||||||
|
'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'));
|
||||||
|
|
||||||
|
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'));
|
||||||
|
|
||||||
|
if($order->get_payment_method() != self::$_id)
|
||||||
|
self::ajax_output(array('error' => '[ERROR] Order not paid for with Wownero'));
|
||||||
|
|
||||||
|
$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) {
|
||||||
|
header('Content-type: application/json');
|
||||||
|
if (ob_get_length() > 0){
|
||||||
|
ob_clean();
|
||||||
|
}
|
||||||
|
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 <<<HTML
|
||||||
|
<span class="woocommerce-Price-amount amount" data-price="$price" data-currency="$currency"
|
||||||
|
data-rate="$rate" data-rate-type="live">
|
||||||
|
$monero_amount_formatted
|
||||||
|
<span class="woocommerce-Price-currencySymbol">WOW</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
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 <<<HTML
|
||||||
|
<span class="woocommerce-Price-amount amount" data-price="$price" data-currency="$currency"
|
||||||
|
data-rate="$rate" data-rate-type="fixed">
|
||||||
|
$monero_amount_formatted
|
||||||
|
<span class="woocommerce-Price-currencySymbol">WOW</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
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' : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* library.php
|
* monero_wallet_rpc
|
||||||
*
|
*
|
||||||
* Written using the JSON RPC specification -
|
* Written using the JSON RPC specification -
|
||||||
* http://json-rpc.org/wiki/specification
|
* http://json-rpc.org/wiki/specification
|
||||||
|
@ -9,17 +8,19 @@
|
||||||
* @author Kacper Rowinski <krowinski@implix.com>
|
* @author Kacper Rowinski <krowinski@implix.com>
|
||||||
* http://implix.com
|
* http://implix.com
|
||||||
* Modified to work with monero-rpc wallet by Serhack and cryptochangements
|
* 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
|
|
||||||
|
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(
|
protected $curl_options = array(
|
||||||
CURLOPT_CONNECTTIMEOUT => 8,
|
CURLOPT_CONNECTTIMEOUT => 8,
|
||||||
CURLOPT_TIMEOUT => 8
|
CURLOPT_TIMEOUT => 8
|
||||||
);
|
);
|
||||||
private $username;
|
protected $host;
|
||||||
private $password;
|
protected $port;
|
||||||
private $httpErrors = array(
|
private $httpErrors = array(
|
||||||
400 => '400 Bad Request',
|
400 => '400 Bad Request',
|
||||||
401 => '401 Unauthorized',
|
401 => '401 Unauthorized',
|
||||||
|
@ -33,20 +34,20 @@ class Monero_Library
|
||||||
503 => '503 Service Unavailable'
|
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('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->validate(false === extension_loaded('json'), 'The json extension must be loaded to use this class!');
|
||||||
|
|
||||||
$this->url = $pUrl;
|
$this->host = $pHost;
|
||||||
$this->username = $pUser;
|
$this->port = $pPort;
|
||||||
$this->password = $pPass;
|
$this->url = $pHost . ':' . $pPort . '/json_rpc';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validate($pFailed, $pErrMsg)
|
public function validate($pFailed, $pErrMsg)
|
||||||
{
|
{
|
||||||
if ($pFailed) {
|
if ($pFailed) {
|
||||||
echo $pErrMsg;
|
if(is_admin()) echo $pErrMsg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,25 +57,12 @@ class Monero_Library
|
||||||
return $this;
|
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)
|
public function setCurlOptions($pOptionsArray)
|
||||||
{
|
{
|
||||||
if (is_array($pOptionsArray)) {
|
if (is_array($pOptionsArray)) {
|
||||||
$this->curl_options = $pOptionsArray + $this->curl_options;
|
$this->curl_options = $pOptionsArray + $this->curl_options;
|
||||||
} else {
|
} else {
|
||||||
echo 'Invalid options type.';
|
if(is_admin()) echo 'Invalid options type.';
|
||||||
}
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -82,13 +70,7 @@ class Monero_Library
|
||||||
public function _print($json)
|
public function _print($json)
|
||||||
{
|
{
|
||||||
$json_encoded = json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
$json_encoded = json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
echo $json_encoded;
|
if(is_admin()) echo $json_encoded;
|
||||||
}
|
|
||||||
|
|
||||||
public function address()
|
|
||||||
{
|
|
||||||
$address = $this->_run('getaddress');
|
|
||||||
return $address;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function _run($method, $params = null)
|
public function _run($method, $params = null)
|
||||||
|
@ -100,23 +82,29 @@ class Monero_Library
|
||||||
private function request($pMethod, $pParams)
|
private function request($pMethod, $pParams)
|
||||||
{
|
{
|
||||||
static $requestId = 0;
|
static $requestId = 0;
|
||||||
|
|
||||||
// generating uniuqe id per process
|
// generating uniuqe id per process
|
||||||
$requestId++;
|
$requestId++;
|
||||||
|
|
||||||
// check if given params are correct
|
// check if given params are correct
|
||||||
$this->validate(false === is_scalar($pMethod), 'Method name has no scalar value');
|
$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 (method invocation)
|
||||||
$request = json_encode(array('jsonrpc' => '2.0', 'method' => $pMethod, 'params' => $pParams, 'id' => $requestId));
|
$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
|
// if is_debug mode is true then add url and request to is_debug
|
||||||
$this->debug('Url: ' . $this->url . "\r\n", false);
|
$this->debug('Url: ' . $this->url . "\r\n", false);
|
||||||
$this->debug('Request: ' . $request . "\r\n", false);
|
$this->debug('Request: ' . $request . "\r\n", false);
|
||||||
|
|
||||||
|
// Response (method invocation)
|
||||||
$responseMessage = $this->getResponse($request);
|
$responseMessage = $this->getResponse($request);
|
||||||
|
|
||||||
// if is_debug mode is true then add response to is_debug and display it
|
// if is_debug mode is true then add response to is_debug and display it
|
||||||
$this->debug('Response: ' . $responseMessage . "\r\n", true);
|
$this->debug('Response: ' . $responseMessage . "\r\n", true);
|
||||||
|
|
||||||
// decode and create array ( can be object, just set to false )
|
// decode and create array ( can be object, just set to false )
|
||||||
$responseDecoded = json_decode($responseMessage, true);
|
$responseDecoded = json_decode($responseMessage, true);
|
||||||
|
|
||||||
// check if decoding json generated any errors
|
// check if decoding json generated any errors
|
||||||
$jsonErrorMsg = $this->getJsonLastErrorMsg();
|
$jsonErrorMsg = $this->getJsonLastErrorMsg();
|
||||||
$this->validate(!is_null($jsonErrorMsg), $jsonErrorMsg . ': ' . $responseMessage);
|
$this->validate(!is_null($jsonErrorMsg), $jsonErrorMsg . ': ' . $responseMessage);
|
||||||
|
@ -150,7 +138,7 @@ class Monero_Library
|
||||||
$endTime = array_sum(explode(' ', microtime()));
|
$endTime = array_sum(explode(' ', microtime()));
|
||||||
// performance summary
|
// performance summary
|
||||||
$debug .= 'Request time: ' . round($endTime - $startTime, 3) . ' s Memory usage: ' . round(memory_get_usage() / 1024) . " kb\r\n";
|
$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
|
// send output immediately
|
||||||
flush();
|
flush();
|
||||||
// clean static
|
// clean static
|
||||||
|
@ -166,8 +154,6 @@ class Monero_Library
|
||||||
throw new RuntimeException('Could\'t initialize a cURL session');
|
throw new RuntimeException('Could\'t initialize a cURL session');
|
||||||
}
|
}
|
||||||
curl_setopt($ch, CURLOPT_URL, $this->url);
|
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_POST, 1);
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $pRequest);
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $pRequest);
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
|
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
|
||||||
|
@ -180,14 +166,18 @@ class Monero_Library
|
||||||
}
|
}
|
||||||
// send the request
|
// send the request
|
||||||
$response = curl_exec($ch);
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
// check http status code
|
// check http status code
|
||||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
if (isset($this->httpErrors[$httpCode])) {
|
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
|
// check for curl error
|
||||||
if (0 < curl_errno($ch)) {
|
if (0 < curl_errno($ch)) {
|
||||||
echo 'Unable to connect to ' . $this->url . ' Error: ' . curl_error($ch);
|
if(is_admin())
|
||||||
|
echo '[ERROR] Failed to connect to wownero-wallet-rpc at ' . $this->host . ' port '. $this->port .'</br>';
|
||||||
}
|
}
|
||||||
// close the connection
|
// close the connection
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
|
@ -222,12 +212,18 @@ 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
|
* 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
|
* 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()
|
public function getbalance()
|
||||||
{
|
{
|
||||||
$balance = $this->_run('getbalance');
|
$balance = $this->_run('getbalance');
|
||||||
|
@ -237,7 +233,7 @@ class Monero_Library
|
||||||
public function getheight()
|
public function getheight()
|
||||||
{
|
{
|
||||||
$height = $this->_run('getheight');
|
$height = $this->_run('getheight');
|
||||||
return $height;
|
return $height['height'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function incoming_transfer($type)
|
public function incoming_transfer($type)
|
||||||
|
@ -247,13 +243,6 @@ class Monero_Library
|
||||||
return $incoming_transfers;
|
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()
|
public function view_key()
|
||||||
{
|
{
|
||||||
$query_key = array('key_type' => 'view_key');
|
$query_key = array('key_type' => 'view_key');
|
||||||
|
@ -269,12 +258,12 @@ class Monero_Library
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A payment id can be passed as a string
|
/* 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)
|
public function split_integrated_address($integrated_address)
|
||||||
{
|
{
|
||||||
if (!isset($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 {
|
} else {
|
||||||
$split_params = array('integrated_address' => $integrated_address);
|
$split_params = array('integrated_address' => $integrated_address);
|
||||||
$split_methods = $this->_run('split_integrated_address', $split_params);
|
$split_methods = $this->_run('split_integrated_address', $split_params);
|
||||||
|
@ -284,8 +273,8 @@ class Monero_Library
|
||||||
|
|
||||||
public function make_uri($address, $amount, $recipient_name = null, $description = null)
|
public function make_uri($address, $amount, $recipient_name = null, $description = null)
|
||||||
{
|
{
|
||||||
// If I pass 1, it will be 0.0000001 xmr. Then
|
// Convert to atomic units
|
||||||
$new_amount = $amount * 100000000;
|
$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_params = array('address' => $address, 'amount' => $new_amount, 'payment_id' => '', 'recipient_name' => $recipient_name, 'tx_description' => $description);
|
||||||
$uri = $this->_run('make_uri', $uri_params);
|
$uri = $this->_run('make_uri', $uri_params);
|
||||||
|
@ -299,9 +288,9 @@ class Monero_Library
|
||||||
return $parsed_uri;
|
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);
|
$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_parameters = array('destinations' => array($destinations), 'mixin' => $mixin, 'get_tx_key' => true, 'unlock_time' => 0, 'payment_id' => '');
|
||||||
$transfer_method = $this->_run('transfer', $transfer_parameters);
|
$transfer_method = $this->_run('transfer', $transfer_parameters);
|
||||||
|
@ -312,7 +301,38 @@ class Monero_Library
|
||||||
{
|
{
|
||||||
$get_payments_parameters = array('payment_id' => $payment_id);
|
$get_payments_parameters = array('payment_id' => $payment_id);
|
||||||
$get_payments = $this->_run('get_payments', $get_payments_parameters);
|
$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)
|
public function get_bulk_payments($payment_id, $min_block_height)
|
||||||
|
@ -321,4 +341,30 @@ class Monero_Library
|
||||||
$get_bulk_payments = $this->_run('get_bulk_payments', $get_bulk_payments_parameters);
|
$get_bulk_payments = $this->_run('get_bulk_payments', $get_bulk_payments_parameters);
|
||||||
return $get_bulk_payments;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
324
include/crypto/SHA3.php
Normal file
324
include/crypto/SHA3.php
Normal file
|
@ -0,0 +1,324 @@
|
||||||
|
<?php /* -*- coding: utf-8; indent-tabs-mode: t; tab-width: 4 -*-
|
||||||
|
vim: ts=4 noet ai */
|
||||||
|
|
||||||
|
/**
|
||||||
|
Streamable SHA-3 for PHP 5.2+, with no lib/ext dependencies!
|
||||||
|
|
||||||
|
Copyright © 2018 Desktopd Developers
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
@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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
501
include/crypto/ed25519.php
Normal file
501
include/crypto/ed25519.php
Normal file
|
@ -0,0 +1,501 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013 John Judy
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//ini_set('xdebug.max_nesting_level', 0);
|
||||||
|
/**
|
||||||
|
* A PHP implementation of the Python ED25519 library
|
||||||
|
*
|
||||||
|
* @author johnj
|
||||||
|
*
|
||||||
|
* @link http://ed25519.cr.yp.to/software.html Other ED25519 implementations this is referenced from
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
class ed25519
|
||||||
|
{
|
||||||
|
public $b;
|
||||||
|
|
||||||
|
public $q;
|
||||||
|
|
||||||
|
public $l;
|
||||||
|
|
||||||
|
public $d;
|
||||||
|
|
||||||
|
public $I;
|
||||||
|
|
||||||
|
public $By;
|
||||||
|
|
||||||
|
public $Bx;
|
||||||
|
|
||||||
|
public $B;
|
||||||
|
|
||||||
|
private $gmp; // Is the GMP extension available?
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
249
monero-woocommerce-gateway.php
Normal file
249
monero-woocommerce-gateway.php
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
Plugin Name: Monero Woocommerce Gateway
|
||||||
|
Plugin URI: https://github.com/monero-integrations/monerowp
|
||||||
|
Description: Extends WooCommerce by adding a Wownero Gateway
|
||||||
|
Version: 3.0.1
|
||||||
|
Tested up to: 4.9.8
|
||||||
|
Author: SerHack
|
||||||
|
Author URI: https://monerointegrations.com/
|
||||||
|
*/
|
||||||
|
// This code is for Dark Net Markets
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
// Constants, you can edit these if you fork this repo
|
||||||
|
define('MONERO_GATEWAY_MAINNET_EXPLORER_URL', 'https://explore.wownero.com/');
|
||||||
|
define('MONERO_GATEWAY_TESTNET_EXPLORER_URL', 'https://testnet.xmrchain.com/');
|
||||||
|
define('MONERO_GATEWAY_ADDRESS_PREFIX', 0x1032);
|
||||||
|
define('MONERO_GATEWAY_ADDRESS_PREFIX_INTEGRATED', 0x1A9A);
|
||||||
|
define('MONERO_GATEWAY_ATOMIC_UNITS', 11);
|
||||||
|
define('MONERO_GATEWAY_ATOMIC_UNIT_THRESHOLD', 10); // Amount under in atomic units payment is valid
|
||||||
|
define('MONERO_GATEWAY_DIFFICULTY_TARGET', 300);
|
||||||
|
|
||||||
|
// Do not edit these constants
|
||||||
|
define('MONERO_GATEWAY_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||||
|
define('MONERO_GATEWAY_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||||
|
define('MONERO_GATEWAY_ATOMIC_UNITS_POW', pow(10, MONERO_GATEWAY_ATOMIC_UNITS));
|
||||||
|
define('MONERO_GATEWAY_ATOMIC_UNITS_SPRINTF', '%.'.MONERO_GATEWAY_ATOMIC_UNITS.'f');
|
||||||
|
|
||||||
|
// Include our Gateway Class and register Payment Gateway with WooCommerce
|
||||||
|
add_action('plugins_loaded', 'monero_init', 1);
|
||||||
|
function monero_init() {
|
||||||
|
|
||||||
|
// If the class doesn't exist (== WooCommerce isn't installed), return NULL
|
||||||
|
if (!class_exists('WC_Payment_Gateway')) return;
|
||||||
|
|
||||||
|
// If we made it this far, then include our Gateway Class
|
||||||
|
require_once('include/class-monero-gateway.php');
|
||||||
|
|
||||||
|
// Create a new instance of the gateway so we have static variables set up
|
||||||
|
new Monero_Gateway($add_action=false);
|
||||||
|
|
||||||
|
// Include our Admin interface class
|
||||||
|
require_once('include/admin/class-monero-admin-interface.php');
|
||||||
|
|
||||||
|
add_filter('woocommerce_payment_gateways', 'wownero_gateway');
|
||||||
|
function wownero_gateway($methods) {
|
||||||
|
$methods[] = 'Monero_Gateway';
|
||||||
|
return $methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'monero_payment');
|
||||||
|
function monero_payment($links) {
|
||||||
|
$plugin_links = array(
|
||||||
|
'<a href="'.admin_url('admin.php?page=monero_gateway_settings').'">'.__('Settings', 'wownero_gateway').'</a>'
|
||||||
|
);
|
||||||
|
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', 'wownero_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['Wownero'] = __('Wownero', 'wownero_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 'Wownero':
|
||||||
|
$currency_symbol = 'WOW';
|
||||||
|
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 "<span class=\"monero-price\">1 WOW = $rate_formatted $currency</span>";
|
||||||
|
}
|
||||||
|
add_shortcode('monero-price', 'monero_price_func');
|
||||||
|
|
||||||
|
|
||||||
|
// [monero-accepted-here]
|
||||||
|
function monero_accepted_func() {
|
||||||
|
return '<img src="'.MONERO_GATEWAY_PLUGIN_URL.'assets/images/monero-accepted-here.png" />';
|
||||||
|
}
|
||||||
|
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(95) 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(95) 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);
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Before Width: | Height: | Size: 413 B |
|
@ -1,447 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Main Gateway of Monero using a daemon online
|
|
||||||
* Authors: Serhack and cryptochangements
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
class Monero_Gateway extends WC_Payment_Gateway
|
|
||||||
{
|
|
||||||
private $reloadTime = 30000;
|
|
||||||
private $discount;
|
|
||||||
private $confirmed = false;
|
|
||||||
private $monero_daemon;
|
|
||||||
|
|
||||||
function __construct()
|
|
||||||
{
|
|
||||||
$this->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 = "0.3";
|
|
||||||
//
|
|
||||||
$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->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:
|
|
||||||
// $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', 'add_my_currency');
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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')
|
|
||||||
|
|
||||||
),
|
|
||||||
'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')
|
|
||||||
),
|
|
||||||
'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',
|
|
||||||
),
|
|
||||||
'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'),
|
|
||||||
|
|
||||||
'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%'
|
|
||||||
|
|
||||||
),
|
|
||||||
'environment' => array(
|
|
||||||
'title' => __(' Test Mode', 'monero_gateway'),
|
|
||||||
'label' => __('Enable Test Mode', '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'),
|
|
||||||
'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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 "<h1>Monero Payment Gateway</h1>";
|
|
||||||
|
|
||||||
echo "<p>Welcome to Monero Extension for WooCommerce. Getting started: Make a connection with daemon <a href='https://reddit.com/u/serhack'>Contact Me</a>";
|
|
||||||
echo "<div style='border:1px solid #DDD;padding:5px 10px;font-weight:bold;color:#223079;background-color:#9ddff3;'>";
|
|
||||||
$this->getamountinfo();
|
|
||||||
echo "</div>";
|
|
||||||
echo "<table class='form-table'>";
|
|
||||||
$this->generate_settings_html();
|
|
||||||
echo "</table>";
|
|
||||||
echo "<h4>Learn more about using a password with the monero wallet-rpc <a href=\"https://github.com/cryptochangements34/monerowp/blob/master/README.md\">here</a></h4>";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getamountinfo()
|
|
||||||
{
|
|
||||||
$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";
|
|
||||||
}
|
|
||||||
$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 </br>";
|
|
||||||
echo "Unlocked balance: " . $unlocked_amount_rounded . " XMR </br>";
|
|
||||||
}
|
|
||||||
|
|
||||||
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 "<div class=\"error\"><p>Your Monero Address doesn't seem valid. Have you checked it?</p></div>";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Validate fields
|
|
||||||
|
|
||||||
public function check_monero()
|
|
||||||
{
|
|
||||||
$monero_address = $this->settings['monero_address'];
|
|
||||||
if (strlen($monero_address) == 95 && substr($monero_address, 1)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
$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');
|
|
||||||
// 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 "<h4><font color=$color>" . $message . "</font></h4>";
|
|
||||||
echo "
|
|
||||||
<head>
|
|
||||||
<!--Import Google Icon Font-->
|
|
||||||
<link href='https://fonts.googleapis.com/icon?family=Material+Icons' rel='stylesheet'>
|
|
||||||
<link href='https://fonts.googleapis.com/css?family=Montserrat:400,800' rel='stylesheet'>
|
|
||||||
<link href='http://cdn.monerointegrations.com/style.css' rel='stylesheet'>
|
|
||||||
<!--Let browser know website is optimized for mobile-->
|
|
||||||
<meta name='viewport' content='width=device-width, initial-scale=1.0'/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- page container -->
|
|
||||||
<div class='page-container'>
|
|
||||||
<!-- monero container payment box -->
|
|
||||||
<div class='container-xmr-payment'>
|
|
||||||
<!-- header -->
|
|
||||||
<div class='header-xmr-payment'>
|
|
||||||
<span class='logo-xmr'><img src='http://cdn.monerointegrations.com/logomonero.png' /></span>
|
|
||||||
<span class='xmr-payment-text-header'><h2>MONERO PAYMENT</h2></span>
|
|
||||||
</div>
|
|
||||||
<!-- end header -->
|
|
||||||
<!-- xmr content box -->
|
|
||||||
<div class='content-xmr-payment'>
|
|
||||||
<div class='xmr-amount-send'>
|
|
||||||
<span class='xmr-label'>Send:</span>
|
|
||||||
<div class='xmr-amount-box'>".$amount_xmr2."</div><div class='xmr-box'>XMR</div>
|
|
||||||
</div>
|
|
||||||
<div class='xmr-address'>
|
|
||||||
<span class='xmr-label'>To this address:</span>
|
|
||||||
<div class='xmr-address-box'>".$array_integrated_address['integrated_address']."</div>
|
|
||||||
</div>
|
|
||||||
<div class='xmr-qr-code'>
|
|
||||||
<span class='xmr-label'>Or scan QR:</span>
|
|
||||||
<div class='xmr-qr-code-box'><img src='https://api.qrserver.com/v1/create-qr-code/? size=200x200&data=".$uri."' /></div>
|
|
||||||
</div>
|
|
||||||
<div class='clear'></div>
|
|
||||||
</div>
|
|
||||||
<!-- end content box -->
|
|
||||||
<!-- footer xmr payment -->
|
|
||||||
<div class='footer-xmr-payment'>
|
|
||||||
<a href='https://getmonero.org' target='_blank'>Help</a> | <a href='https://getmonero.org' target='_blank'>About Monero</a>
|
|
||||||
</div>
|
|
||||||
<!-- end footer xmr payment -->
|
|
||||||
</div>
|
|
||||||
<!-- end monero container payment box -->
|
|
||||||
</div>
|
|
||||||
<!-- end page container -->
|
|
||||||
</body>
|
|
||||||
";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
echo "
|
|
||||||
<script type='text/javascript'>setTimeout(function () { location.reload(true); }, $this->reloadTime);</script>";
|
|
||||||
}
|
|
||||||
|
|
||||||
private function set_paymentid_cookie()
|
|
||||||
{
|
|
||||||
if (!isset($_COOKIE['payment_id'])) {
|
|
||||||
$payment_id = bin2hex(openssl_random_pseudo_bytes(8));
|
|
||||||
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)) {
|
|
||||||
$discount_decimal = $this->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 moneo 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
|
|
||||||
$new_amount = $amount / $xmr_live_price;
|
|
||||||
$rounded_amount = round($new_amount, 12);
|
|
||||||
|
|
||||||
$wpdb->query("INSERT INTO $payment_id (rate)
|
|
||||||
VALUES ($live_for_storing)");
|
|
||||||
}
|
|
||||||
|
|
||||||
return $rounded_amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Check if we are forcing SSL on checkout pages
|
|
||||||
// Custom function not required by the 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');
|
|
||||||
$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 'XMR':
|
|
||||||
$price = '1';
|
|
||||||
return $price;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = "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 do_ssl_check()
|
|
||||||
{
|
|
||||||
if ($this->enabled == "yes" && !$this->get_option('onion_service')) {
|
|
||||||
if (get_option('woocommerce_force_ssl_checkout') == "no") {
|
|
||||||
echo "<div class=\"error\"><p>" . sprintf(__("<strong>%s</strong> 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 <a href=\"%s\">forcing the checkout pages to be secured.</a>"), $this->method_title, admin_url('admin.php?page=wc-settings&tab=checkout')) . "</p></div>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 "<div class=\"notice notice-success is-dismissible\"><p>Everything works! Congratulations and welcome to Monero. <button type=\"button\" class=\"notice-dismiss\">
|
|
||||||
<span class=\"screen-reader-text\">Dismiss this notice.</span>
|
|
||||||
</button></p></div>";
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$this->log->add('Monero_gateway', '[ERROR] Plugin can not reach wallet rpc.');
|
|
||||||
echo "<div class=\" notice notice-error\"><p>Error with connection of daemon, see documentation!</p></div>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
<?php
|
|
||||||
/*
|
|
||||||
Plugin Name: Monero - WooCommerce Gateway
|
|
||||||
Plugin URI: http://monerointegrations.com
|
|
||||||
Description: Extends WooCommerce by Adding the Monero Gateway
|
|
||||||
Version: 1.0
|
|
||||||
Author: SerHack
|
|
||||||
Author URI: http://monerointegrations.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
// This code isn't for Dark Net Markets, please report them to Authority!
|
|
||||||
if (!defined('ABSPATH')) {
|
|
||||||
exit; // Exit if accessed directly
|
|
||||||
}
|
|
||||||
// Include our Gateway Class and register Payment Gateway with WooCommerce
|
|
||||||
add_action('plugins_loaded', 'monero_init', 0);
|
|
||||||
function monero_init()
|
|
||||||
{
|
|
||||||
/* If the class doesn't exist (== WooCommerce isn't installed), return NULL */
|
|
||||||
if (!class_exists('WC_Payment_Gateway')) return;
|
|
||||||
|
|
||||||
|
|
||||||
/* If we made it this far, then include our Gateway Class */
|
|
||||||
include_once('include/monero_payments.php');
|
|
||||||
require_once('library.php');
|
|
||||||
|
|
||||||
// Lets add it too WooCommerce
|
|
||||||
add_filter('woocommerce_payment_gateways', 'monero_gateway');
|
|
||||||
function monero_gateway($methods)
|
|
||||||
{
|
|
||||||
$methods[] = 'Monero_Gateway';
|
|
||||||
return $methods;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Add custom link
|
|
||||||
* The url will be http://yourworpress/wp-admin/admin.php?=wc-settings&tab=checkout
|
|
||||||
*/
|
|
||||||
add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'monero_payment');
|
|
||||||
function monero_payment($links)
|
|
||||||
{
|
|
||||||
$plugin_links = array(
|
|
||||||
'<a href="' . admin_url('admin.php?page=wc-settings&tab=checkout') . '">' . __('Settings', 'monero_payment') . '</a>',
|
|
||||||
);
|
|
||||||
|
|
||||||
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/icon.png'),
|
|
||||||
56 // Position on menu, woocommerce has 55.5, products has 55.6
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
93
readme.txt
Normal file
93
readme.txt
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
=== 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: 5.0.1
|
||||||
|
Stable tag: trunk
|
||||||
|
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 monero at WooCommerce-powered online stores.
|
||||||
|
|
||||||
|
== Description ==
|
||||||
|
|
||||||
|
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 =
|
||||||
|
|
||||||
|
* 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. Configure it with your wallet rpc address, (username or password not requested), your monero address
|
||||||
|
4. 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!
|
||||||
|
|
||||||
|
= 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 =
|
||||||
|
Fuck you mosu-forge
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
|
||||||
|
== 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.
|
|
@ -0,0 +1 @@
|
||||||
|
<?php echo $error; ?>
|
82
templates/monero-gateway/admin/order-history-page.php
Normal file
82
templates/monero-gateway/admin/order-history-page.php
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<table class="striped" style="width:100%" cellspacing="0" cellpadding="5">
|
||||||
|
<tr>
|
||||||
|
<td>Exchange rate</td>
|
||||||
|
<td>1 WOW = <?php echo $details['rate_formatted'].' '.$details['currency']; ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Total amount</td>
|
||||||
|
<td><?php echo $details['amount_total_formatted']; ?> WOW</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Total paid</td>
|
||||||
|
<td><?php echo $details['amount_paid_formatted']; ?> WOW</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Total due</td>
|
||||||
|
<td><?php echo $details['amount_due_formatted']; ?> WOW</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Order age</td>
|
||||||
|
<td><?php echo Monero_Gateway::format_seconds_to_time($details['order_age']) ?> ago</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Order exipires</td>
|
||||||
|
<td>
|
||||||
|
<?php echo $details['order_expires'] ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Status</td>
|
||||||
|
<td>
|
||||||
|
<?php
|
||||||
|
switch($details['status']) {
|
||||||
|
case 'confirmed':
|
||||||
|
echo '<span style="color:#006400">Confirmed</span>';
|
||||||
|
break;
|
||||||
|
case 'paid':
|
||||||
|
echo '<span style="color:#006400">Paid, waiting confirmation</span>';
|
||||||
|
break;
|
||||||
|
case 'partial':
|
||||||
|
echo '<span style="color:#ffae42">Partial payment made</span>';
|
||||||
|
break;
|
||||||
|
case 'unpaid':
|
||||||
|
echo '<span style="color:#ffae42">Pending payment</span>';
|
||||||
|
break;
|
||||||
|
case 'expired_partial':
|
||||||
|
echo '<span style="color:#dc143c">Expired, partial payment made</span>';
|
||||||
|
break;
|
||||||
|
case 'expired':
|
||||||
|
echo '<span style="color:#dc143c">Expired</span>';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Payment id</td>
|
||||||
|
<td><?php echo $details['payment_id'] ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Integrated address</td>
|
||||||
|
<td style="word-break: break-all;"><?php echo $details['integrated_address'] ?></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<?php if(count($details['txs'])): ?>
|
||||||
|
<table class="striped" style="width:100%" cellspacing="0" cellpadding="5">
|
||||||
|
<tr>
|
||||||
|
<td>Transactions</td>
|
||||||
|
<td>Height</td>
|
||||||
|
<td>Amount</td>
|
||||||
|
</tr>
|
||||||
|
<?php foreach($details['txs'] as $tx): ?>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="<?php echo MONERO_GATEWAY_EXPLORER_URL.'tx/'.$tx['txid']; ?>" target="_blank"><?php echo $tx['txid']; ?></a>
|
||||||
|
</td>
|
||||||
|
<td><?php echo $tx['height']; ?></td>
|
||||||
|
<td><?php echo sprintf(MONERO_GATEWAY_ATOMIC_UNITS_SPRINTF, $tx['amount'] / MONERO_GATEWAY_ATOMIC_UNITS_POW); ?> WOW</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</table>
|
||||||
|
<?php endif; ?>
|
54
templates/monero-gateway/admin/settings-page.php
Normal file
54
templates/monero-gateway/admin/settings-page.php
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<?php foreach($errors as $error): ?>
|
||||||
|
<div class="error"><p><strong>Wownero Gateway Error</strong>: <?php echo $error; ?></p></div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<h1>Wownero Gateway Settings</h1>
|
||||||
|
|
||||||
|
<?php if($confirm_type === 'wownero-wallet-rpc'): ?>
|
||||||
|
<div style="border:1px solid #ddd;padding:5px 10px;">
|
||||||
|
<?php
|
||||||
|
echo 'Wallet height: ' . $balance['height'] . '</br>';
|
||||||
|
echo 'Your balance is: ' . $balance['balance'] . '</br>';
|
||||||
|
echo 'Unlocked balance: ' . $balance['unlocked_balance'] . '</br>';
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<table class="form-table">
|
||||||
|
<?php echo $settings_html ?>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h4><a href="https://github.com/monero-integrations/monerowp">Learn more about using the Wownero payment gateway</a></h4>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function moneroUpdateFields() {
|
||||||
|
var confirmType = jQuery("#woocommerce_monero_gateway_confirm_type").val();
|
||||||
|
if(confirmType == "wownero-wallet-rpc") {
|
||||||
|
jQuery("#woocommerce_monero_gateway_monero_address").closest("tr").hide();
|
||||||
|
jQuery("#woocommerce_monero_gateway_viewkey").closest("tr").hide();
|
||||||
|
jQuery("#woocommerce_monero_gateway_daemon_host").closest("tr").show();
|
||||||
|
jQuery("#woocommerce_monero_gateway_daemon_port").closest("tr").show();
|
||||||
|
} else {
|
||||||
|
jQuery("#woocommerce_monero_gateway_monero_address").closest("tr").show();
|
||||||
|
jQuery("#woocommerce_monero_gateway_viewkey").closest("tr").show();
|
||||||
|
jQuery("#woocommerce_monero_gateway_daemon_host").closest("tr").hide();
|
||||||
|
jQuery("#woocommerce_monero_gateway_daemon_port").closest("tr").hide();
|
||||||
|
}
|
||||||
|
var useMoneroPrices = jQuery("#woocommerce_monero_gateway_use_monero_price").is(":checked");
|
||||||
|
if(useMoneroPrices) {
|
||||||
|
jQuery("#woocommerce_monero_gateway_use_monero_price_decimals").closest("tr").show();
|
||||||
|
} else {
|
||||||
|
jQuery("#woocommerce_monero_gateway_use_monero_price_decimals").closest("tr").hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
moneroUpdateFields();
|
||||||
|
jQuery("#woocommerce_monero_gateway_confirm_type").change(moneroUpdateFields);
|
||||||
|
jQuery("#woocommerce_monero_gateway_use_monero_price").change(moneroUpdateFields);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#woocommerce_monero_gateway_monero_address,
|
||||||
|
#woocommerce_monero_gateway_viewkey {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
56
templates/monero-gateway/customer/order-email-block.php
Normal file
56
templates/monero-gateway/customer/order-email-block.php
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<?php if($details['status'] == 'confirmed'): ?>
|
||||||
|
|
||||||
|
<h2 style="color: #96588a; display: block; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; font-size: 18px; font-weight: bold; line-height: 130%; margin: 0 0 18px; text-align: left;">
|
||||||
|
<?php echo $method_title ?>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p style="margin: 0 0 16px;">Your order has been confirmed. Thank you for paying with Wownero!</p>
|
||||||
|
|
||||||
|
<?php elseif($details['status'] == 'expired' || $details['status'] == 'expired_partial'): ?>
|
||||||
|
|
||||||
|
<h2 style="color: #96588a; display: block; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; font-size: 18px; font-weight: bold; line-height: 130%; margin: 0 0 18px; text-align: left;">
|
||||||
|
<?php echo $method_title ?>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p style="margin: 0 0 16px;">Your order has expired. Please place another order to complete your purchase.</p>
|
||||||
|
|
||||||
|
<?php elseif($details['status'] == 'unpaid'): ?>
|
||||||
|
|
||||||
|
<h2 style="color: #96588a; display: block; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; font-size: 18px; font-weight: bold; line-height: 130%; margin: 0 0 18px; text-align: left;">
|
||||||
|
<?php echo $method_title ?>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p style="margin: 0 0 16px;">Please pay the amount due to complete your transactions. Your order will expire in <?php echo $details['order_expires']; ?> if payment is not received.</p>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 40px;">
|
||||||
|
<table class="td" cellspacing="0" cellpadding="6" style="width: 100%; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; color: #636363; border: 1px solid #e5e5e5; vertical-align: middle;" border="1">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="td" style="text-align: left; vertical-align: middle; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; word-wrap: break-word; color: #636363; border: 1px solid #e5e5e5; padding: 12px;">
|
||||||
|
PAY TO: <br/>
|
||||||
|
<strong>
|
||||||
|
<?php echo $details['integrated_address']; ?>
|
||||||
|
</strong>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="td" style="text-align: left; vertical-align: middle; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; word-wrap: break-word; color: #636363; border: 1px solid #e5e5e5; padding: 12px;">
|
||||||
|
TOTAL DUE: <br/>
|
||||||
|
<strong>
|
||||||
|
<?php echo $details['amount_total_formatted']; ?> WOW
|
||||||
|
</strong>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="td" style="text-align: left; vertical-align: middle; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; word-wrap: break-word; color: #636363; border: 1px solid #e5e5e5; padding: 12px;">
|
||||||
|
EXCHANGE RATE: <br/>
|
||||||
|
<strong>
|
||||||
|
1 WOW = <?php echo $details['rate_formatted'] . ' ' . $details['currency']; ?>
|
||||||
|
</strong>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<h2 style="color: #96588a; display: block; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; font-size: 18px; font-weight: bold; line-height: 130%; margin: 0 0 18px; text-align: left;">
|
||||||
|
<?php echo $method_title ?>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p style="margin: 0 0 16px;">Payment method not available, please contact the store owner for manual payment</p>
|
4
templates/monero-gateway/customer/order-error-page.php
Normal file
4
templates/monero-gateway/customer/order-error-page.php
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<section class="woocommerce-order-details">
|
||||||
|
<h2 class="woocommerce-order-details__title"><?php echo $method_title ?></h2>
|
||||||
|
<p>Payment method not available, please contact the store owner for manual payment</p>
|
||||||
|
</section>
|
100
templates/monero-gateway/customer/order-page.php
Normal file
100
templates/monero-gateway/customer/order-page.php
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
<section class="woocommerce-order-details">
|
||||||
|
<h2 class="woocommerce-order-details__title"><?php echo $method_title ?></h2>
|
||||||
|
<noscript><h1>You must enable javascript in order to confirm your order</h1></noscript>
|
||||||
|
|
||||||
|
<strong id="monero_payment_messages">
|
||||||
|
|
||||||
|
<span class="monero_payment_unpaid">Please pay the amount due to complete your transactions. Your order will expire in <span class="monero_payment_expire_time"></span> if payment is not received.</span>
|
||||||
|
|
||||||
|
<span class="monero_payment_partial">We have received partial payment. Please pay the remaining amount to complete your transactions. Your order will expire in <span class="monero_payment_expire_time"></span> if payment is not received.</span>
|
||||||
|
|
||||||
|
<span class="monero_payment_paid">We have received your payment in full. Please wait while amount is confirmed. Approximate confirm time is <span class="monero_confirm_time"></span>. <?php if(is_wc_endpoint_url('order-received')): ?><br/>You can <a href="<?php echo $details['my_order_url']; ?>">check your payment status</a> anytime in your account dashboard.<?php endif; ?></span>
|
||||||
|
|
||||||
|
<span class="monero_payment_confirmed">Your order has been confirmed. Thank you for paying with Wownero!</span>
|
||||||
|
|
||||||
|
<span class="monero_payment_expired">Your order has expired. Please place another order to complete your purchase.</span>
|
||||||
|
|
||||||
|
<span class="monero_payment_expired_partial">Your order has expired. Please contact the store owner to receive refund on your partial payment.</span>
|
||||||
|
|
||||||
|
</strong>
|
||||||
|
|
||||||
|
<ul class="order_details" style="margin-top:30px">
|
||||||
|
<li>
|
||||||
|
Pay to:
|
||||||
|
<strong class="monero_details_row">
|
||||||
|
<span class="monero_details_main" id="monero_integrated_address"></span>
|
||||||
|
<span class="monero_details_right button-row">
|
||||||
|
<?php if($show_qr): ?>
|
||||||
|
<button href="#" class="button" title="Show QR Code" onclick="monero_showQR()">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 512 512" version="1"><path d="M0 512h233V279H0zm47-186h139v139H47z"/><path d="M93 372h47v47H93zm279 93h47v47h-47zm93 0h47v47h-47z"/><path d="M465 326h-46v-47H279v233h47V372h46v47h140V279h-47zM0 233h233V0H0zM47 47h139v139H47z"/><path d="M93 93h47v47H93zM279 0v233h233V0zm186 186H326V47h139z"/><path d="M372 93h47v47h-47z"/></svg>
|
||||||
|
</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
<button href="#" class="button clipboard" title="Copy Address"
|
||||||
|
data-clipboard-target="#monero_integrated_address">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 512 512" version="1"><path d="M504 118c-6-6-12-8-20-8H365c-11 0-23 3-36 11V27c0-7-3-14-8-19s-12-8-20-8H183c-8 0-16 2-25 6-10 4-17 8-22 13L19 136c-5 5-9 12-13 22-4 9-6 17-6 25v192c0 7 3 14 8 19s12 8 19 8h156v82c0 8 2 14 8 20 5 5 12 8 19 8h274c8 0 14-3 20-8 5-6 8-12 8-20V137c0-8-3-14-8-19zm-175 52v86h-85l85-86zM146 61v85H61l85-85zm56 185c-5 5-10 12-14 21-3 9-5 18-5 25v73H37V183h118c8 0 14-3 20-8 5-6 8-12 8-20V37h109v118l-90 91zm273 229H219V292h119c8 0 14-2 19-8 6-5 8-11 8-19V146h110v329z"/></svg>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</strong>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Total due:
|
||||||
|
<strong class="monero_details_row">
|
||||||
|
<span class="monero_details_main">
|
||||||
|
<span id="monero_total_due"></span> WOW
|
||||||
|
</span>
|
||||||
|
<span class="monero_details_right button-row">
|
||||||
|
<button href="#" class="button clipboard" title="Copy Amount"
|
||||||
|
data-clipboard-target="#monero_total_due">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 512 512" version="1"><path d="M504 118c-6-6-12-8-20-8H365c-11 0-23 3-36 11V27c0-7-3-14-8-19s-12-8-20-8H183c-8 0-16 2-25 6-10 4-17 8-22 13L19 136c-5 5-9 12-13 22-4 9-6 17-6 25v192c0 7 3 14 8 19s12 8 19 8h156v82c0 8 2 14 8 20 5 5 12 8 19 8h274c8 0 14-3 20-8 5-6 8-12 8-20V137c0-8-3-14-8-19zm-175 52v86h-85l85-86zM146 61v85H61l85-85zm56 185c-5 5-10 12-14 21-3 9-5 18-5 25v73H37V183h118c8 0 14-3 20-8 5-6 8-12 8-20V37h109v118l-90 91zm273 229H219V292h119c8 0 14-2 19-8 6-5 8-11 8-19V146h110v329z"/></svg>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</strong>
|
||||||
|
</li>
|
||||||
|
<li style="display:none">
|
||||||
|
Total order amount:
|
||||||
|
<strong>
|
||||||
|
<span id="monero_total_amount"></span> WOW
|
||||||
|
</strong>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Total paid:
|
||||||
|
<strong>
|
||||||
|
<span id="monero_total_paid"></span> WOW
|
||||||
|
</strong>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Exchange rate:<strong id="monero_exchange_rate"></strong>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<table id="monero_tx_table" style="display:none;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Transaction id</th>
|
||||||
|
<th>Height</th>
|
||||||
|
<th>Amount</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div id="monero_tx_none" style="display:none;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="monero_qr_code_container" style="display:none;" onclick="monero_showQR(false)">
|
||||||
|
<div id="monero_qr_code">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div id="monero_toast"></div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var monero_show_qr = <?php echo $show_qr ? 'true' : 'false'; ?>;
|
||||||
|
var monero_ajax_url = '<?php echo $ajax_url; ?>';
|
||||||
|
var monero_explorer_url = '<?php echo MONERO_GATEWAY_EXPLORER_URL; ?>';
|
||||||
|
var monero_details = <?php echo $details_json; ?>;
|
||||||
|
</script>
|
Loading…
Add table
Add a link
Reference in a new issue