Rewrite plugin to v3.0

monerowp
mosu forge 6 years ago
parent 4c091a13f7
commit 4def1b771e

5
.gitignore vendored

@ -1,3 +1,8 @@
*.xml
*.iml
# Emacs
*~
\#*
.\#*

@ -1,6 +1,7 @@
MIT License
Copyright (c) 2017-2018 Monero Integrations
Copyright (c) 2018, Ryo Currency Project
Portions Copyright (c) 2017-2018, Monero Integrations
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,84 +1,107 @@
# MoneroWP
A WooCommerce extension for accepting Monero
# Monero Gateway for WooCommerce
## Dependencies
This plugin is rather simple but there are a few things that need to be set up beforehand.
## Features
* A web server! Ideally with the most recent versions of PHP and mysql
* Payment validation done through either `monero-wallet-rpc` or the [xmrchain.net blockchain explorer](https://xmrchain.net/).
* Validates payments with `cron`, so does not require users to stay on the order confirmation page for their order to validate.
* Order status updates are done through AJAX instead of Javascript page reloads.
* Customers can pay with multiple transactions and are notified as soon as transactions hit the mempool.
* Configurable block confirmations, from `0` for zero confirm to `60` for high ticket purchases.
* Live price updates every minute; total amount due is locked in after the order is placed for a configurable amount of time (default 60 minutes) so the price does not change after order has been made.
* Hooks into emails, order confirmation page, customer order history page, and admin order details page.
* View all payments received to your wallet with links to the blockchain explorer and associated orders.
* Optionally display all prices on your store in terms of Monero.
* Shortcodes! Display exchange rates in numerous currencies.
* A Monero wallet. You can find the official wallet [here](https://getmonero.org/downloads/)
## Requirements
* [WordPress](https://wordpress.org)
WordPress is the backend tool that is needed to use WooCommerce and this Monero plugin
* Monero wallet to receive payments - [GUI](https://github.com/monero-project/monero-gui/releases) - [CLI](https://github.com/monero-project/monero/releases) - [Paper](https://moneroaddress.org/)
* [BCMath](http://php.net/manual/en/book.bc.php) - A PHP extension used for arbitrary precision maths
* [WooCommerce](https://woocommerce.com)
This Monero plugin is an extension of WooCommerce, which works with WordPress
## Installing the plugin
* [BCMath](http://php.net/manual/en/book.bc.php)
A PHP extension used for arbitrary precision maths
* Download the plugin from the [releases page](https://github.com/monero-integrations/monerowp) or clone with `git clone https://github.com/monero-integrations/monerowp`
* Unzip or place the `monero-woocommerce-gateway` folder in the `wp-content/plugins` directory.
* Activate "Monero Woocommerce Gateway" in your WordPress admin dashboard.
* It is highly recommended that you use native cronjobs instead of WordPress's "Poor Man's Cron" by adding `define('DISABLE_WP_CRON', true);` into your `wp-config.php` file and adding `* * * * * wget -q -O - https://yourstore.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1` to your crontab.
## Step 1: Activating the plugin
* Downloading: First of all, you will need to download the plugin. You can download the latest release as a .zip file from https://github.com/monero-integrations/monerowp/releases If you wish, you can also download the latest source code from GitHub. This can be done with the command `git clone https://github.com/monero-integrations/monerowp.git` or can be downloaded as a zip file from the GitHub web page.
## Option 1: Use your wallet address and viewkey
* Unzip the file monerowp_release.zip if you downloaded the zip from the releases page [here](https://github.com/monero-integrations/monerowp/releases).
This is the easiest way to start accepting Monero on your website. You'll need:
* Put the plugin in the correct directory: You will need to put the folder named `monero` from this repo/unzipped release into the WordPress plugins directory. This can be found at `path/to/wordpress/folder/wp-content/plugins`
* Your Monero wallet address starting with `4`
* Your wallet's secret viewkey
* Activate the plugin from the WordPress admin panel: Once you login to the admin panel in WordPress, click on "Installed Plugins" under "Plugins". Then simply click "Activate" where it says "Monero - WooCommerce Gateway"
Then simply select the `viewkey` option in the settings page and paste your address and viewkey. You're all set!
## Step 2 Option 1: Use your wallet address and viewkey
Note on privacy: when you validate transactions with your private viewkey, your viewkey is sent to (but not stored on) xmrchain.net over HTTPS. This could potentially allow an attacker to see your incoming, but not outgoing, transactions if they were to get his hands on your viewkey. Even if this were to happen, your funds would still be safe and it would be impossible for somebody to steal your money. For maximum privacy use your own `monero-wallet-rpc` instance.
* Get your Monero wallet address starting with '4'
* Get your wallet secret viewkey from your wallet
## Option 2: Using `monero-wallet-rpc`
A note on privacy: When you validate transactions with your private viewkey, your viewkey is sent to (but not stored on) xmrchain.net over HTTPS. This could potentially allow an attacker to see your incoming, but not outgoing, transactions if he were to get his hands on your viewkey. Even if this were to happen, your funds would still be safe and it would be impossible for somebody to steal your money. For maximum privacy use your own monero-wallet-rpc instance.
The most secure way to accept Monero on your website. You'll need:
## Step 2 Option 2: Get a Monero daemon to connect to
* Root access to your webserver
* Latest [Monero-currency binaries](https://github.com/monero-project/monero/releases)
### Option 1: Running a full node yourself
After downloading (or compiling) the Monero binaries on your server, install the [systemd unit files](https://github.com/monero-integrations/monerowp/tree/master/assets/systemd-unit-files) or run `monerod` and `monero-wallet-rpc` with `screen` or `tmux`. You can skip running `monerod` by using a remote node with `monero-wallet-rpc` by adding `--daemon-address node.moneroworld.com:18089` to the `monero-wallet-rpc.service` file.
To do this: start the Monero daemon on your server and leave it running in the background. This can be accomplished by running `./monerod` inside your Monero downloads folder. The first time that you start your node, the Monero daemon will download and sync the entire Monero blockchain. This can take several hours and is best done on a machine with at least 4GB of ram, an SSD hard drive (with at least 40GB of free space), and a high speed internet connection.
Note on security: using this option, while the most secure, requires you to run the Monero wallet RPC program on your server. Best practice for this is to use a view-only wallet since otherwise your server would be running a hot-wallet and a security breach could allow hackers to empty your funds.
### Option 2: Connecting to a remote node
The easiest way to find a remote node to connect to is to visit [moneroworld.com](https://moneroworld.com/#nodes) and use one of the nodes offered. It is probably easiest to use node.moneroworld.com:18089 which will automatically connect you to a random node.
## Configuration
### Setup your Monero wallet-rpc
* `Enable / Disable` - Turn on or off Monero gateway. (Default: Disable)
* `Title` - Name of the payment gateway as displayed to the customer. (Default: Monero Gateway)
* `Discount for using Monero` - Percentage discount applied to orders for paying with Monero. Can also be negative to apply a surcharge. (Default: 0)
* `Order valid time` - Number of seconds after order is placed that the transaction must be seen in the mempool. (Default: 3600 [1 hour])
* `Number of confirmations` - Number of confirmations the transaction must recieve before the order is marked as complete. Use `0` for nearly instant confirmation. (Default: 5)
* `Confirmation Type` - Confirm transactions with either your viewkey, or by using `monero-wallet-rpc`. (Default: viewkey)
* `Monero Address` (if confirmation type is viewkey) - Your public Monero address starting with 4. (No default)
* `Secret Viewkey` (if confirmation type is viewkey) - Your *private* viewkey (No default)
* `Monero wallet RPC Host/IP` (if confirmation type is `monero-wallet-rpc`) - IP address where the wallet rpc is running. It is highly discouraged to run the wallet anywhere other than the local server! (Default: 127.0.0.1)
* `Monero wallet RPC port` (if confirmation type is `monero-wallet-rpc`) - Port the wallet rpc is bound to with the `--rpc-bind-port` argument. (Default 18080)
* `Testnet` - Check this to change the blockchain explorer links to the testnet explorer. (Default: unchecked)
* `SSL warnings` - Check this to silence SSL warnings. (Default: unchecked)
* `Show QR Code` - Show payment QR codes. There is no Monero software that can read QR codes at this time (Default: unchecked)
* `Show Prices in Monero` - Convert all prices on the frontend to Monero. Experimental feature, only use if you do not accept any other payment option. (Default: unchecked)
* `Display Decimals` (if show prices in Monero is enabled) - Number of decimals to round prices to on the frontend. The final order amount will not be rounded and will be displayed down to the nanoMonero. (Default: 12)
* Setup a Monero wallet using the monero-wallet-cli tool. If you do not know how to do this you can learn about it at [getmonero.org](https://getmonero.org/resources/user-guides/monero-wallet-cli.html)
## Shortcodes
* [Create a view-only wallet from that wallet for security.](https://monero.stackexchange.com/questions/3178/how-to-create-a-view-only-wallet-for-the-gui/4582#4582)
This plugin makes available two shortcodes that you can use in your theme.
* Start the Wallet RPC and leave it running in the background. This can be accomplished by running `./monero-wallet-rpc --rpc-bind-port 18082 --disable-rpc-login --log-level 2 --wallet-file /path/viewOnlyWalletFile` where "/path/viewOnlyWalletFile" is the wallet file for your view-only wallet. If you wish to use a remote node you can add the `--daemon-address` flag followed by the address of the node. `--daemon-address node.moneroworld.com:18089` for example.
#### Live price shortcode
## Step 4: Setup Monero Gateway in WooCommerce
This will display the price of Monero in the selected currency. If no currency is provided, the store's default currency will be used.
* Navigate to the "settings" panel in the WooCommerce widget in the WordPress admin panel.
```
[monero-price]
[monero-price currency="BTC"]
[monero-price currency="USD"]
[monero-price currency="CAD"]
[monero-price currency="EUR"]
[monero-price currency="GBP"]
```
Will display:
```
1 XMR = 123.68000 USD
1 XMR = 0.01827000 BTC
1 XMR = 123.68000 USD
1 XMR = 168.43000 CAD
1 XMR = 105.54000 EUR
1 XMR = 94.84000 GBP
```
* Click on "Checkout"
* Select "Monero GateWay"
#### Monero accepted here badge
* Check the box labeled "Enable this payment gateway"
This will display a badge showing that you accept Monero-currency.
* Check either "Use ViewKey" or "Use monero-wallet-rpc"
`[monero-accepted-here]`
If You chose to use viewkey:
![Monero Accepted Here](/assets/images/monero-accepted-here.png?raw=true "Monero Accepted Here")
* Enter your Monero wallet address in the box labeled "Monero Address". If you do not know your address, you can run the `address` command in your Monero wallet
## Donations
* Enter your secret viewkey in the box labeled "ViewKey"
monero-integrations: 44krVcL6TPkANjpFwS2GWvg1kJhTrN7y9heVeQiDJ3rP8iGbCd5GeA4f3c2NKYHC1R4mCgnW7dsUUUae2m9GiNBGT4T8s2X
If you chose to use monero-wallet-rpc:
* Enter your Monero wallet address in the box labeled "Monero Address". If you do not know your address, you can run the `address` command in your Monero wallet
* Enter the IP address of your server in the box labeled "Monero wallet RPC Host/IP"
* Enter the port number of the Wallet RPC in the box labeled "Monero wallet RPC port" (will be `18082` if you used the above example).
Finally:
* Click on "Save changes"
## Donating to the Devs :)
XMR Address : `44krVcL6TPkANjpFwS2GWvg1kJhTrN7y9heVeQiDJ3rP8iGbCd5GeA4f3c2NKYHC1R4mCgnW7dsUUUae2m9GiNBGT4T8s2X`
mosu-forge: 4A6BQp7do5MTxpCguq1kAS27yMLpbHcf89Ha2a8Shayt2vXkCr6QRpAXr1gLYRV5esfzoK3vLJTm5bDWk5gKmNrT6s6xZep

@ -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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Before

Width:  |  Height:  |  Size: 413 B

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

@ -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;
/***/ })
/******/ ]);
});

File diff suppressed because one or more lines are too long

@ -0,0 +1,161 @@
/*
* Copyright (c) 2018, Ryo Currency Project
*/
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 XMR = '+details.rate_formatted+' '+details.currency);
jQuery('#monero_total_amount').html(details.amount_total_formatted);
jQuery('#monero_total_paid').html(details.amount_paid_formatted);
jQuery('#monero_total_due').html(details.amount_due_formatted);
jQuery('#monero_integrated_address').html(details.integrated_address);
if(monero_show_qr) {
var qr = jQuery('#monero_qr_code').html('');
new QRCode(qr.get(0), details.qrcode_uri);
}
if(details.txs.length) {
jQuery('#monero_tx_table').show();
jQuery('#monero_tx_none').hide();
jQuery('#monero_tx_table tbody').html('');
for(var i=0; i < details.txs.length; i++) {
var tx = details.txs[i];
var height = tx.height == 0 ? 'N/A' : tx.height;
var row = ''+
'<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+' Monero</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+' Monero');
}
}
}
if(details.status != monero_order_state.status) {
switch(details.status) {
case 'paid':
monero_showNotification('Your order has been paid in full');
break;
case 'confirmed':
monero_showNotification('Your order has been confirmed');
break;
case 'expired':
case 'expired_partial':
monero_showNotification('Your order has expired', 'error');
break;
}
}
monero_order_state = {
status: monero_details.status,
txs: monero_details.txs
};
}
jQuery(document).ready(function($) {
if (typeof monero_details !== 'undefined') {
monero_order_state = {
status: monero_details.status,
txs: monero_details.txs
};
setInterval(monero_fetchDetails, 30000);
monero_updateDetails();
new ClipboardJS('.clipboard').on('success', function(e) {
e.clearSelection();
if(e.trigger.disabled) return;
switch(e.trigger.getAttribute('data-clipboard-target')) {
case '#monero_integrated_address':
monero_showNotification('Copied destination address!');
break;
case '#monero_total_due':
monero_showNotification('Copied total amount due!');
break;
}
e.clearSelection();
});
}
});

@ -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;
})();

File diff suppressed because one or more lines are too long

@ -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

@ -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

@ -0,0 +1,133 @@
<?php
/*
* Copyright (c) 2018, Ryo Currency Project
* Admin interface for Monero gateway
* Authors: mosu-forge
*/
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',
__('Monero Gateway','monero_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(
__('Monero', 'monero_gateway'),
__('Monero', 'monero_gateway'),
'manage_woocommerce',
'monero_gateway',
array($this, 'orders_page'),
MONERO_GATEWAY_PLUGIN_URL.'/assets/images/monero-icon-admin.png',
56 // Position on menu, woocommerce has 55.5, products has 55.6
);
add_submenu_page(
'monero_gateway',
__('Payments', 'monero_gateway'),
__('Payments', 'monero_gateway'),
'manage_woocommerce',
'monero_gateway_payments',
array($this, 'payments_page')
);
$settings_page = add_submenu_page(
'monero_gateway',
__('Settings', 'monero_gateway'),
__('Settings', 'monero_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['monero_gateway'])) {
unset($submenu['monero_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 = 'monero_gateway';
$current_tab = 'checkout';
// Include settings pages.
WC_Admin_Settings::get_settings_pages();
// Save settings if data has been posted.
if (apply_filters("woocommerce_save_settings_{$current_tab}_{$current_section}", !empty($_POST))) {
WC_Admin_Settings::save();
}
// Add any posted messages.
if (!empty($_GET['wc_error'])) {
WC_Admin_Settings::add_error(wp_kses_post(wp_unslash($_GET['wc_error'])));
}
if (!empty($_GET['wc_message'])) {
WC_Admin_Settings::add_message(wp_kses_post(wp_unslash($_GET['wc_message'])));
}
do_action('woocommerce_settings_page_init');
}
}
return new Monero_Admin_Interface();

@ -0,0 +1,276 @@
<?php
/*
* Copyright (c) 2018, Ryo Currency Project
* Admin interface for Monero gateway
* Authors: mosu-forge
*/
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() == 'monero-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">Monero Payments</h1>
$balance_info
<hr class="wp-header-end">
<ul class="subsubsub">
<li>
<a href="?page=monero_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=monero_gateway_payments&type=pending" {$tab_info['pending']['active']}>
Pending <span class="count">({$tab_info['pending']['count']})</span>
</a> |
</li>
<li>
<a href="?page=monero_gateway_payments&type=paid" {$tab_info['paid']['active']}>
Received <span class="count">({$tab_info['paid']['count']})</span>
</a> |
</li>
<li>
<a href="?page=monero_gateway_payments&type=confirmed" {$tab_info['confirmed']['active']}>
Confirmed <span class="count">({$tab_info['confirmed']['count']})</span>
</a> |
</li>
<li>
<a href="?page=monero_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">Monero 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).' Monero';
break;
}
}
protected function get_order_link($order_id) {
$order = new WC_Order($order_id);
$buyer = '';
if($order->get_billing_first_name() || $order->get_billing_last_name()) {
$buyer = trim(sprintf(_x('%1$s %2$s', 'full name', 'woocommerce'), $order->get_billing_first_name(), $order->get_billing_last_name()));
} else if ($order->get_billing_company()) {
$buyer = trim($order->get_billing_company());
} else if ($order->get_customer_id()) {
$user = get_user_by('id', $order->get_customer_id());
$buyer = ucwords($user->display_name);
}
return '<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 Monero payments found', 'monero_gateway');
}
protected function get_filter_vars() {
$type = isset($_GET['type']) ? $_GET['type'] : null;
return (object) array(
'type' => $type,
);
}
protected function get_item_count($type) {
global $wpdb;
$table_name_1 = $wpdb->prefix.'monero_gateway_quotes';
$table_name_2 = $wpdb->prefix.'monero_gateway_quotes_txids';
$query_where = ' WHERE 1=1 '.$this->get_clause_type($type);
$query = "SELECT COUNT(*) AS count FROM {$table_name_2} t2 LEFT JOIN $table_name_1 t1 ON t2.payment_id = t1.payment_id {$query_where}";
$item_count = $wpdb->get_var($query);
if(is_null($item_count)) $item_count = 0;
return $item_count;
}
protected function get_clause_type($type) {
global $wpdb;
switch($type) {
case 'pending':
$query_where = $wpdb->prepare(' AND pending = 1 AND paid = 0 ', array());
break;
case 'paid':
$query_where = $wpdb->prepare(' AND paid = 1 AND confirmed = 0 ', array());
break;
case 'confirmed':
$query_where = $wpdb->prepare(' AND confirmed = 1 ', array());
break;
case 'expired':
$query_where = $wpdb->prepare(' AND paid = 0 AND pending = 0 ', array());
break;
case 'all':
default:
$query_where = ' ';
}
return $query_where;
}
public function get_items($current_page, $per_page) {
global $wpdb;
$this->items = array();
$filters = $this->get_filter_vars();
$table_name_1 = $wpdb->prefix.'monero_gateway_quotes';
$table_name_2 = $wpdb->prefix.'monero_gateway_quotes_txids';
$query_where = ' WHERE 1=1 ';
$query_where .= $this->get_clause_type($filters->type);
$query_order = $wpdb->prepare('ORDER BY id DESC LIMIT %d, %d;', ($current_page-1)*$per_page, $per_page);
$query = "SELECT t1.order_id, t1.confirmed, t1.paid, t1.pending, t2.* FROM {$table_name_2} t2 LEFT JOIN $table_name_1 t1 ON t2.payment_id = t1.payment_id {$query_where} {$query_order}";
$this->items = $wpdb->get_results($query);
$max_items = $this->get_item_count($filters->type);
$this->set_pagination_args(
array(
'total_items' => $max_items,
'per_page' => $per_page,
'total_pages' => ceil($max_items/$per_page),
)
);
}
}

@ -0,0 +1,114 @@
<?php
defined( 'ABSPATH' ) || exit;
return 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 Gateway', 'monero_gateway')
),
'description' => array(
'title' => __('Description', 'monero_gateway'),
'type' => 'textarea',
'desc_tip' => __('Payment description the customer will see during the checkout process.', 'monero_gateway'),
'default' => __('Pay securely using Monero. You will be provided payment details after checkout.', 'monero_gateway')
),
'discount' => array(
'title' => __('Discount for using Monero', 'monero_gateway'),
'desc_tip' => __('Provide a discount to your customers for making a private payment with Monero', 'monero_gateway'),
'description' => __('Enter a percentage discount (i.e. 5 for 5%) or leave this empty if you do not wish to provide a discount', 'monero_gateway'),
'type' => __('number'),
'default' => '0'
),
'valid_time' => array(
'title' => __('Order valid time', 'monero_gateway'),
'desc_tip' => __('Amount of time order is valid before expiring', 'monero_gateway'),
'description' => __('Enter the number of seconds that the funds must be received in after order is placed. 3600 seconds = 1 hour', 'monero_gateway'),
'type' => __('number'),
'default' => '3600'
),
'confirms' => array(
'title' => __('Number of confirmations', 'monero_gateway'),
'desc_tip' => __('Number of confirms a transaction must have to be valid', 'monero_gateway'),
'description' => __('Enter the number of confirms that transactions must have. Enter 0 to zero-confim. Each confirm will take approximately four minutes', 'monero_gateway'),
'type' => __('number'),
'default' => '5'
),
'confirm_type' => array(
'title' => __('Confirmation Type', 'monero_gateway'),
'desc_tip' => __('Select the method for confirming transactions', 'monero_gateway'),
'description' => __('Select the method for confirming transactions', 'monero_gateway'),
'type' => 'select',
'options' => array(
'viewkey' => __('viewkey', 'monero_gateway'),
'monero-wallet-rpc' => __('monero-wallet-rpc', 'monero_gateway')
),
'default' => 'viewkey'
),
'monero_address' => array(
'title' => __('Monero Address', 'monero_gateway'),
'label' => __('Useful for people that have not a daemon online'),
'type' => 'text',
'desc_tip' => __('Monero Wallet Address (MoneroL)', 'monero_gateway')
),
'viewkey' => array(
'title' => __('Secret Viewkey', 'monero_gateway'),
'label' => __('Secret Viewkey'),
'type' => 'text',
'desc_tip' => __('Your secret Viewkey', 'monero_gateway')
),
'daemon_host' => array(
'title' => __('Monero wallet RPC Host/IP', 'monero_gateway'),
'type' => 'text',
'desc_tip' => __('This is the Daemon Host/IP to authorize the payment with', 'monero_gateway'),
'default' => '127.0.0.1',
),
'daemon_port' => array(
'title' => __('Monero wallet RPC port', 'monero_gateway'),
'type' => __('number'),
'desc_tip' => __('This is the Wallet RPC port to authorize the payment with', 'monero_gateway'),
'default' => '18080',
),
'testnet' => array(
'title' => __(' Testnet', 'monero_gateway'),
'label' => __(' Check this if you are using testnet ', 'monero_gateway'),
'type' => 'checkbox',
'description' => __('Advanced usage only', 'monero_gateway'),
'default' => 'no'
),
'onion_service' => array(
'title' => __(' SSL warnings ', 'monero_gateway'),
'label' => __(' Check to Silence SSL warnings', 'monero_gateway'),
'type' => 'checkbox',
'description' => __('Check this box if you are running on an Onion Service (Suppress SSL errors)', 'monero_gateway'),
'default' => 'no'
),
'show_qr' => array(
'title' => __('Show QR Code', 'monero_gateway'),
'label' => __('Show QR Code', 'monero_gateway'),
'type' => 'checkbox',
'description' => __('Enable this to show a QR code after checkout with payment details.'),
'default' => 'no'
),
'use_monero_price' => array(
'title' => __('Show Prices in Monero', 'monero_gateway'),
'label' => __('Show Prices in Monero', 'monero_gateway'),
'type' => 'checkbox',
'description' => __('Enable this to convert ALL prices on the frontend to Monero (experimental)'),
'default' => 'no'
),
'use_monero_price_decimals' => array(
'title' => __('Display Decimals', 'monero_gateway'),
'type' => __('number'),
'description' => __('Number of decimal places to display on frontend. Upon checkout exact price will be displayed.'),
'default' => 12,
),
);

@ -0,0 +1,354 @@
<?php
/*
* base58.php
*
* PHP Base58 codec
*
* Based on https://github.com/MoneroPy/moneropy/base58.py and https://github.com/mymonero/mymonero-core-js/cryptonote_utils/cryptonote_base58.js
*
*/
defined( 'ABSPATH' ) || exit;
class Monero_base58 {
/**
* @var string
*/
static $alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
static $encoded_block_sizes = [0, 2, 3, 5, 6, 7, 9, 10, 11];
static $full_block_size = 8;
static $full_encoded_block_size = 11;
/**
*
* Convert a hexadecimal string to a binary array
*
* @param string $hex A hexadecimal string to convert to a binary array
* @return array
*
*/
private function hex_to_bin($hex) {
if (gettype($hex) != 'string') {
throw new Exception('base58->hex_to_bin(): Invalid input type (must be a string)');
}
if (strlen($hex) % 2 != 0) {
throw new Exception('base58->hex_to_bin(): Invalid input length (must be even)');
}
$res = array_fill(0, strlen($hex) / 2, 0);
for ($i = 0; $i < strlen($hex) / 2; $i++) {
$res[$i] = intval(substr($hex, $i * 2, $i * 2 + 2 - $i * 2), 16);
}
return $res;
}
/**
*
* Convert a binary array to a hexadecimal string
*
* @param array $bin A binary array to convert to a hexadecimal string
* @return string
*
*/
private function bin_to_hex($bin) {
if (gettype($bin) != 'array') {
throw new Exception('base58->bin_to_hex(): Invalid input type (must be an array)');
}
$res = [];
for ($i = 0; $i < count($bin); $i++) {
$res[] = substr('0'.dechex($bin[$i]), -2);
}
return join($res);
}
/**
*
* Convert a string to a binary array
*
* @param string $str A string to convert to a binary array
* @return array
*
*/
private function str_to_bin($str) {
if (gettype($str) != 'string') {
throw new Exception('base58->str_to_bin(): Invalid input type (must be a string)');
}
$res = array_fill(0, strlen($str), 0);
for ($i = 0; $i < strlen($str); $i++) {
$res[$i] = ord($str[$i]);
}
return $res;
}
/**
*
* Convert a binary array to a string
*
* @param array $bin A binary array to convert to a string
* @return string
*
*/
private function bin_to_str($bin) {
if (gettype($bin) != 'array') {
throw new Exception('base58->bin_to_str(): Invalid input type (must be an array)');
}
$res = array_fill(0, count($bin), 0);
for ($i = 0; $i < count($bin); $i++) {
$res[$i] = chr($bin[$i]);
}
return preg_replace('/[[:^print:]]/', '', join($res)); // preg_replace necessary to strip errant non-ASCII characters eg. ''
}
/**
*
* Convert a UInt8BE (one unsigned big endian byte) array to UInt64
*
* @param array $data A UInt8BE array to convert to UInt64
* @return number
*
*/
private function uint8_be_to_64($data) {
if (gettype($data) != 'array') {
throw new Exception ('base58->uint8_be_to_64(): Invalid input type (must be an array)');
}
$res = 0;
$i = 0;
switch (9 - count($data)) {
case 1:
$res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]);
case 2:
$res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]);
case 3:
$res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]);
case 4:
$res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]);
case 5:
$res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]);
case 6:
$res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]);
case 7:
$res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]);
case 8:
$res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]);
break;
default:
throw new Exception('base58->uint8_be_to_64: Invalid input length (1 <= count($data) <= 8)');
}
return $res;
}
/**
*
* Convert a UInt64 (unsigned 64 bit integer) to a UInt8BE array
*
* @param number $num A UInt64 number to convert to a UInt8BE array
* @param integer $size Size of array to return
* @return array
*
*/
private function uint64_to_8_be($num, $size) {
if (gettype($num) != ('integer' || 'double')) {
throw new Exception ('base58->uint64_to_8_be(): Invalid input type ($num must be a number)');
}
if (gettype($size) != 'integer') {
throw new Exception ('base58->uint64_to_8_be(): Invalid input type ($size must be an integer)');
}
if ($size < 1 || $size > 8) {
throw new Exception ('base58->uint64_to_8_be(): Invalid size (1 <= $size <= 8)');
}
$res = array_fill(0, $size, 0);
for ($i = $size - 1; $i >= 0; $i--) {
$res[$i] = bcmod($num, bcpow(2, 8));
$num = bcdiv($num, bcpow(2, 8));
}
return $res;
}
/**
*
* Convert a hexadecimal (Base16) array to a Base58 string
*
* @param array $data
* @param array $buf
* @param number $index
* @return array
*
*/
private function encode_block($data, $buf, $index) {
if (gettype($data) != 'array') {
throw new Exception('base58->encode_block(): Invalid input type ($data must be an array)');
}
if (gettype($buf) != 'array') {
throw new Exception('base58->encode_block(): Invalid input type ($buf must be an array)');
}
if (gettype($index) != ('integer' || 'double')) {
throw new Exception('base58->encode_block(): Invalid input type ($index must be a number)');
}
if (count($data) < 1 or count($data) > self::$full_encoded_block_size) {
throw new Exception('base58->encode_block(): Invalid input length (1 <= count($data) <= 8)');
}
$num = self::uint8_be_to_64($data);
$i = self::$encoded_block_sizes[count($data)] - 1;
while ($num > 0) {
$remainder = bcmod($num, 58);
$num = bcdiv($num, 58);
$buf[$index + $i] = ord(self::$alphabet[$remainder]);
$i--;
}
return $buf;
}
/**
*
* Encode a hexadecimal (Base16) string to Base58
*
* @param string $hex A hexadecimal (Base16) string to convert to Base58
* @return string
*
*/
public function encode($hex) {
if (gettype($hex) != 'string') {
throw new Exception ('base58->encode(): Invalid input type (must be a string)');
}
$data = self::hex_to_bin($hex);
if (count($data) == 0) {
return '';
}
$full_block_count = floor(count($data) / self::$full_block_size);
$last_block_size = count($data) % self::$full_block_size;
$res_size = $full_block_count * self::$full_encoded_block_size + self::$encoded_block_sizes[$last_block_size];
$res = array_fill(0, $res_size, 0);
for ($i = 0; $i < $res_size; $i++) {
$res[$i] = self::$alphabet[0];
}
for ($i = 0; $i < $full_block_count; $i++) {
$res = self::encode_block(array_slice($data, $i * self::$full_block_size, ($i * self::$full_block_size + self::$full_block_size) - ($i * self::$full_block_size)), $res, $i * self::$full_encoded_block_size);
}
if ($last_block_size > 0) {
$res = self::encode_block(array_slice($data, $full_block_count * self::$full_block_size, $full_block_count * self::$full_block_size + $last_block_size), $res, $full_block_count * self::$full_encoded_block_size);
}
return self::bin_to_str($res);
}
/**
*
* Convert a Base58 input to hexadecimal (Base16)
*
* @param array $data
* @param array $buf
* @param integer $index
* @return array
*
*/
private function decode_block($data, $buf, $index) {
if (gettype($data) != 'array') {
throw new Exception('base58->decode_block(): Invalid input type ($data must be an array)');
}
if (gettype($buf) != 'array') {
throw new Exception('base58->decode_block(): Invalid input type ($buf must be an array)');
}
if (gettype($index) != ('integer' || 'double')) {
throw new Exception('base58->decode_block(): Invalid input type ($index must be a number)');
}
$res_size = self::index_of(self::$encoded_block_sizes, count($data));
if ($res_size <= 0) {
throw new Exception('base58->decode_block(): Invalid input length ($data must be a value from base58::$encoded_block_sizes)');
}
$res_num = 0;
$order = 1;
for ($i = count($data) - 1; $i >= 0; $i--) {
$digit = strpos(self::$alphabet, chr($data[$i]));
if ($digit < 0) {
throw new Exception("base58->decode_block(): Invalid character ($digit \"{$digit}\" not found in base58::$alphabet)");
}
$product = bcadd(bcmul($order, $digit), $res_num);
if ($product > bcpow(2, 64)) {
throw new Exception('base58->decode_block(): Integer overflow ($product exceeds the maximum 64bit integer)');
}
$res_num = $product;
$order = bcmul($order, 58);
}
if ($res_size < self::$full_block_size && bcpow(2, 8 * $res_size) <= 0) {
throw new Exception('base58->decode_block(): Integer overflow (bcpow(2, 8 * $res_size) exceeds the maximum 64bit integer)');
}
$tmp_buf = self::uint64_to_8_be($res_num, $res_size);
for ($i = 0; $i < count($tmp_buf); $i++) {
$buf[$i + $index] = $tmp_buf[$i];
}
return $buf;
}
/**
*
* Decode a Base58 string to hexadecimal (Base16)
*
* @param string $hex A Base58 string to convert to hexadecimal (Base16)
* @return string
*
*/
public function decode($enc) {
if (gettype($enc) != 'string') {
throw new Exception ('base58->decode(): Invalid input type (must be a string)');
}
$enc = self::str_to_bin($enc);
if (count($enc) == 0) {
return '';
}
$full_block_count = floor(bcdiv(count($enc), self::$full_encoded_block_size));
$last_block_size = bcmod(count($enc), self::$full_encoded_block_size);
$last_block_decoded_size = self::index_of(self::$encoded_block_sizes, $last_block_size);
$data_size = $full_block_count * self::$full_block_size + $last_block_decoded_size;
$data = array_fill(0, $data_size, 0);
for ($i = 0; $i < $full_block_count; $i++) {
$data = self::decode_block(array_slice($enc, $i * self::$full_encoded_block_size, ($i * self::$full_encoded_block_size + self::$full_encoded_block_size) - ($i * self::$full_encoded_block_size)), $data, $i * self::$full_block_size);
}
if ($last_block_size > 0) {
$data = self::decode_block(array_slice($enc, $full_block_count * self::$full_encoded_block_size, $full_block_count * self::$full_encoded_block_size + $last_block_size), $data, $full_block_count * self::$full_block_size);
}
return self::bin_to_hex($data);
}
/**
*
* Search an array for a value
* Source: https://stackoverflow.com/a/30994678
*
* @param array $haystack An array to search
* @param string $needle A string to search for
* @return number The index of the element found (or -1 for no match)
*
*/
private function index_of($haystack, $needle) {
if (gettype($haystack) != 'array') {
throw new Exception ('base58->decode(): Invalid input type ($haystack must be an array)');
}
// if (gettype($needle) != 'string') {
// throw new Exception ('base58->decode(): Invalid input type ($needle must be a string)');
// }
foreach ($haystack as $key => $value) if ($value === $needle) return $key;
return -1;
}
}

@ -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;
}
}

@ -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
* @author mosu-forge
*
*/
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;
}
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 [];
}
}

@ -0,0 +1,789 @@
<?php
/*
* Main Gateway of Monero using either a local daemon or the explorer
* Authors: Serhack, cryptochangements, mosu-forge
*/
defined( 'ABSPATH' ) || exit;
require_once('class-monero-cryptonote.php');
class Monero_Gateway extends WC_Payment_Gateway
{
private static $_id = 'monero_gateway';
private static $_title = 'Monero Gateway';
private static $_method_title = 'Monero Gateway';
private static $_method_description = 'Monero 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 = array('BTC','USD','EUR','CAD','INR','GBP','COP','SGD','JPY');
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/monero-icon.png"/>');
}
function __construct($add_action=true)
{
$this->id = self::$_id;
$this->method_title = __(self::$_method_title, 'monero_gateway');
$this->method_description = __(self::$_method_description, 'monero_gateway');
$this->has_fields = false;
$this->supports = array(
'products',
'subscriptions',
'subscription_cancellation',
'subscription_suspension',
'subscription_reactivation',
'subscription_amount_changes',
'subscription_date_changes',
'subscription_payment_method_change'
);
$this->enabled = $this->get_option('enabled') == 'yes';
$this->init_form_fields();
$this->init_settings();
self::$_title = $this->settings['title'];
$this->title = $this->settings['title'];
$this->description = $this->settings['description'];
self::$discount = $this->settings['discount'];
self::$valid_time = $this->settings['valid_time'];
self::$confirms = $this->settings['confirms'];
self::$confirm_type = $this->settings['confirm_type'];
self::$address = $this->settings['monero_address'];
self::$viewkey = $this->settings['viewkey'];
self::$host = $this->settings['daemon_host'];
self::$port = $this->settings['daemon_port'];
self::$testnet = $this->settings['testnet'] == 'yes';
self::$onion_service = $this->settings['onion_service'] == 'yes';
self::$show_qr = $this->settings['show_qr'] == 'yes';
self::$use_monero_price = $this->settings['use_monero_price'] == 'yes';
self::$use_monero_price_decimals = $this->settings['use_monero_price_decimals'];
$explorer_url = self::$testnet ? MONERO_GATEWAY_TESTNET_EXPLORER_URL : MONERO_GATEWAY_MAINNET_EXPLORER_URL;
defined('MONERO_GATEWAY_EXPLORER_URL') || define('MONERO_GATEWAY_EXPLORER_URL', $explorer_url);
if($add_action)
add_action('woocommerce_update_options_payment_gateways_'.$this->id, array($this, 'process_admin_options'));
// Initialize helper classes
self::$cryptonote = new Monero_Cryptonote();
if(self::$confirm_type == 'monero-wallet-rpc') {
require_once('class-monero-wallet-rpc.php');
self::$monero_wallet_rpc = new Monero_Wallet_Rpc(self::$host, self::$port);
} else {
require_once('class-monero-explorer-tools.php');
self::$monero_explorer_tools = new Monero_Explorer_Tools(self::$testnet);
}
self::$log = new WC_Logger();
}
public function init_form_fields()
{
$this->form_fields = include 'admin/monero-gateway-admin-settings.php';
}
public function validate_monero_address_field($key,$address)
{
if($this->settings['confirm_type'] == 'viewkey') {
if (strlen($address) == 95 && substr($address, 0, 1) == '4')
if(self::$cryptonote->verify_checksum($address))
return $address;
self::$_errors[] = 'Monero address is invalid';
}
return $address;
}
public function validate_viewkey_field($key,$viewkey)
{
if($this->settings['confirm_type'] == 'viewkey') {
if(preg_match('/^[a-z0-9]{64}$/i', $viewkey)) {
return $viewkey;
} else {
self::$_errors[] = 'Viewkey is invalid';
return '';
}
}
return $viewkey;
}
public function validate_confirms_field($key,$confirms)
{
if($confirms >= 0 && $confirms <= 60)
return $confirms;
self::$_errors[] = 'Number of confirms must be between 0 and 60';
}
public function validate_valid_time_field($key,$valid_time)
{
if($valid_time >= 600 && $valid_time < 86400*7)
return $valid_time;
self::$_errors[] = 'Order valid time must be between 600 (10 minutes) and 604800 (1 week)';
}
public function admin_options()
{
$confirm_type = self::$confirm_type;
if($confirm_type === 'monero-wallet-rpc')
$balance = self::admin_balance_info();
$settings_html = $this->generate_settings_html(array(), false);
$errors = array_merge(self::$_errors, $this->admin_php_module_check(), $this->admin_ssl_check());
include MONERO_GATEWAY_PLUGIN_DIR . '/templates/monero-gateway/admin/settings-page.php';
}
public static function admin_balance_info()
{
if(!is_admin()) {
return array(
'height' => 'Not Available',
'balance' => 'Not Available',
'unlocked_balance' => 'Not Available',
);
}
$wallet_amount = self::$monero_wallet_rpc->getbalance();
$height = self::$monero_wallet_rpc->getheight();
if (!isset($wallet_amount)) {
self::$_errors[] = 'Cannot connect to monero-wallet-rpc';
self::$log->add('Monero_Payments', '[ERROR] Cannot connect to monero-wallet-rpc');
return array(
'height' => 'Not Available',
'balance' => 'Not Available',
'unlocked_balance' => 'Not Available',
);
} else {
return array(
'height' => $height,
'balance' => self::format_monero($wallet_amount['balance']).' Monero',
'unlocked_balance' => self::format_monero($wallet_amount['unlocked_balance']).' Monero'
);
}
}
protected function admin_ssl_check()
{
$errors = array();
if ($this->enabled && !self::$onion_service)
if (get_option('woocommerce_force_ssl_checkout') == 'no')
$errors[] = sprintf('%s is enabled and WooCommerce is not forcing the SSL certificate on your checkout page. Please ensure that you have a valid SSL certificate and that you are <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);
// Generate a unique payment id
do {
$payment_id = bin2hex(openssl_random_pseudo_bytes(8));
$query = $wpdb->prepare("SELECT COUNT(*) FROM $table_name WHERE payment_id=%s", array($payment_id));
$payment_id_used = $wpdb->get_var($query);
} while ($payment_id_used);
$currency = $order->get_currency();
$rate = self::get_live_rate($currency);
$fiat_amount = $order->get_total('');
$monero_amount = 1e8 * $fiat_amount / $rate;
if(self::$discount)
$monero_amount = $monero_amount - $monero_amount * self::$discount / 100;
$monero_amount = intval($monero_amount * MONERO_GATEWAY_ATOMIC_UNITS_POW);
$query = $wpdb->prepare("INSERT INTO $table_name (order_id, payment_id, currency, rate, amount) VALUES (%d, %s, %s, %d, %d)", array($order_id, $payment_id, $currency, $rate, $monero_amount));
$wpdb->query($query);
$order->update_status('on-hold', __('Awaiting offline payment', 'monero_gateway'));
$order->reduce_order_stock(); // Reduce stock levels
WC()->cart->empty_cart(); // Remove cart
return array(
'result' => 'success',
'redirect' => $this->get_return_url($order)
);
}
/*
* function for verifying payments
* This cron runs every 30 seconds
*/
public static function do_update_event()
{
global $wpdb;
// Get Live Price
$currencies = implode(',', self::$currencies);
$api_link = 'https://min-api.cryptocompare.com/data/price?fsym=XMR&tsyms='.$currencies.'&extraParams=monero_woocommerce';
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => $api_link,
));
$resp = curl_exec($curl);
curl_close($curl);
$price = json_decode($resp, true);
if(!isset($price['Response']) || $price['Response'] != 'Error') {
$table_name = $wpdb->prefix.'monero_gateway_live_rates';
foreach($price as $currency=>$rate) {
// shift decimal eight places for precise int storage
$rate = intval($rate * 1e8);
$query = $wpdb->prepare("INSERT INTO $table_name (currency, rate, updated) VALUES (%s, %d, NOW()) ON DUPLICATE KEY UPDATE rate=%d, updated=NOW()", array($currency, $rate, $rate));
$wpdb->query($query);
}
}
// Get current network/wallet height
if(self::$confirm_type == 'monero-wallet-rpc')
$height = self::$monero_wallet_rpc->getheight();
else
$height = self::$monero_explorer_tools->getheight();
set_transient('monero_gateway_network_height', $height);
// Get pending payments
$table_name_1 = $wpdb->prefix.'monero_gateway_quotes';
$table_name_2 = $wpdb->prefix.'monero_gateway_quotes_txids';
$query = $wpdb->prepare("SELECT *, $table_name_1.payment_id AS payment_id, $table_name_1.amount AS amount_total, $table_name_2.amount AS amount_paid, NOW() as now FROM $table_name_1 LEFT JOIN $table_name_2 ON $table_name_1.payment_id = $table_name_2.payment_id WHERE pending=1", array());
$rows = $wpdb->get_results($query);
$pending_payments = array();
// Group the query into distinct orders by payment_id
foreach($rows as $row) {
if(!isset($pending_payments[$row->payment_id]))
$pending_payments[$row->payment_id] = array(
'quote' => null,
'txs' => array()
);
$pending_payments[$row->payment_id]['quote'] = $row;
if($row->txid)
$pending_payments[$row->payment_id]['txs'][] = $row;
}
// Loop through each pending payment and check status
foreach($pending_payments as $pending) {
$quote = $pending['quote'];
$old_txs = $pending['txs'];
$order_id = $quote->order_id;
$order = wc_get_order($order_id);
$payment_id = self::sanatize_id($quote->payment_id);
$amount_monero = $quote->amount_total;
if(self::$confirm_type == 'monero-wallet-rpc')
$new_txs = self::check_payment_rpc($payment_id);
else
$new_txs = self::check_payment_explorer($payment_id);
foreach($new_txs as $new_tx) {
$is_new_tx = true;
foreach($old_txs as $old_tx) {
if($new_tx['txid'] == $old_tx->txid && $new_tx['amount'] == $old_tx->amount_paid) {
$is_new_tx = false;
break;
}
}
if($is_new_tx) {
$old_txs[] = (object) $new_tx;
}
$query = $wpdb->prepare("INSERT INTO $table_name_2 (payment_id, txid, amount, height) VALUES (%s, %s, %d, %d) ON DUPLICATE KEY UPDATE height=%d", array($payment_id, $new_tx['txid'], $new_tx['amount'], $new_tx['height'], $new_tx['height']));
$wpdb->query($query);
}
$txs = $old_txs;
$heights = array();
$amount_paid = 0;
foreach($txs as $tx) {
$amount_paid += $tx->amount;
$heights[] = $tx->height;
}
$paid = $amount_paid > $amount_monero - MONERO_GATEWAY_ATOMIC_UNIT_THRESHOLD;
if($paid) {
if(self::$confirms == 0) {
$confirmed = true;
} else {
$highest_block = max($heights);
if($height - $highest_block >= self::$confirms && !in_array(0, $heights)) {
$confirmed = true;
} else {
$confirmed = false;
}
}
} else {
$confirmed = false;
}
if($paid && $confirmed) {
self::$log->add('Monero_Payments', "[SUCCESS] Payment has been confirmed for order id $order_id and payment id $payment_id");
$query = $wpdb->prepare("UPDATE $table_name_1 SET confirmed=1,paid=1,pending=0 WHERE payment_id=%s", array($payment_id));
$wpdb->query($query);
unset(self::$payment_details[$order_id]);
if(self::is_virtual_in_cart($order_id) == true){
$order->update_status('completed', __('Payment has been received.', 'monero_gateway'));
} else {
$order->update_status('processing', __('Payment has been received.', 'monero_gateway'));
}
} else if($paid) {
self::$log->add('Monero_Payments', "[SUCCESS] Payment has been received for order id $order_id and payment id $payment_id");
$query = $wpdb->prepare("UPDATE $table_name_1 SET paid=1 WHERE payment_id=%s", array($payment_id));
$wpdb->query($query);
unset(self::$payment_details[$order_id]);
} else {
$timestamp_created = new DateTime($quote->created);
$timestamp_now = new DateTime($quote->now);
$order_age_seconds = $timestamp_now->getTimestamp() - $timestamp_created->getTimestamp();
if($order_age_seconds > self::$valid_time) {
self::$log->add('Monero_Payments', "[FAILED] Payment has expired for order id $order_id and payment id $payment_id");
$query = $wpdb->prepare("UPDATE $table_name_1 SET pending=0 WHERE payment_id=%s", array($payment_id));
$wpdb->query($query);
unset(self::$payment_details[$order_id]);
$order->update_status('cancelled', __('Payment has expired.', 'monero_gateway'));
}
}
}
}
protected static function check_payment_rpc($payment_id)
{
$txs = array();
$payments = self::$monero_wallet_rpc->get_all_payments($payment_id);
foreach($payments as $payment) {
$txs[] = array(
'amount' => $payment['amount'],
'txid' => $payment['tx_hash'],
'height' => $payment['block_height']
);
}
return $txs;
}
public static function check_payment_explorer($payment_id)
{
$txs = array();
$outputs = self::$monero_explorer_tools->get_outputs(self::$address, self::$viewkey);
foreach($outputs as $payment) {
if($payment['payment_id'] == $payment_id) {
$txs[] = array(
'amount' => $payment['amount'],
'txid' => $payment['tx_hash'],
'height' => $payment['block_no']
);
}
}
return $txs;
}
protected static function get_payment_details($order_id)
{
if(!is_integer($order_id))
$order_id = $order_id->get_id();
if(isset(self::$payment_details[$order_id]))
return self::$payment_details[$order_id];
global $wpdb;
$table_name_1 = $wpdb->prefix.'monero_gateway_quotes';
$table_name_2 = $wpdb->prefix.'monero_gateway_quotes_txids';
$query = $wpdb->prepare("SELECT *, $table_name_1.payment_id AS payment_id, $table_name_1.amount AS amount_total, $table_name_2.amount AS amount_paid, NOW() as now FROM $table_name_1 LEFT JOIN $table_name_2 ON $table_name_1.payment_id = $table_name_2.payment_id WHERE order_id=%d", array($order_id));
$details = $wpdb->get_results($query);
if (count($details)) {
$txs = array();
$heights = array();
$amount_paid = 0;
foreach($details as $tx) {
if(!isset($tx->txid))
continue;
$txs[] = array(
'txid' => $tx->txid,
'height' => $tx->height,
'amount' => $tx->amount_paid,
'amount_formatted' => self::format_monero($tx->amount_paid)
);
$amount_paid += $tx->amount_paid;
$heights[] = $tx->height;
}
usort($txs, function($a, $b) {
if($a['height'] == 0) return -1;
return $b['height'] - $a['height'];
});
if(count($heights) && !in_array(0, $heights)) {
$height = get_transient('monero_gateway_network_height');
$highest_block = max($heights);
$confirms = $height - $highest_block;
$blocks_to_confirm = self::$confirms - $confirms;
} else {
$blocks_to_confirm = self::$confirms;
}
$time_to_confirm = self::format_seconds_to_time($blocks_to_confirm * MONERO_GATEWAY_DIFFICULTY_TARGET);
$amount_total = $details[0]->amount_total;
$amount_due = max(0, $amount_total - $amount_paid);
$timestamp_created = new DateTime($details[0]->created);
$timestamp_now = new DateTime($details[0]->now);
$order_age_seconds = $timestamp_now->getTimestamp() - $timestamp_created->getTimestamp();
$order_expires_seconds = self::$valid_time - $order_age_seconds;
$address = self::$address;
$payment_id = self::sanatize_id($details[0]->payment_id);
if(self::$confirm_type == 'monero-wallet-rpc') {
$array_integrated_address = self::$monero_wallet_rpc->make_integrated_address($payment_id);
if (isset($array_integrated_address['integrated_address'])) {
$integrated_addr = $array_integrated_address['integrated_address'];
} else {
self::$log->add('Monero_Gateway', '[ERROR] Unable get integrated address');
return '[ERROR] Unable get integrated address';
}
} else {
if ($address) {
$decoded_address = self::$cryptonote->decode_address($address);
$pub_spendkey = $decoded_address['spendkey'];
$pub_viewkey = $decoded_address['viewkey'];
$integrated_addr = self::$cryptonote->integrated_addr_from_keys($pub_spendkey, $pub_viewkey, $payment_id);
} else {
self::$log->add('Monero_Gateway', '[ERROR] Merchant has not set Monero address');
return '[ERROR] Merchant has not set Monero address';
}
}
$status = '';
$paid = $details[0]->paid == 1;
$confirmed = $details[0]->confirmed == 1;
$pending = $details[0]->pending == 1;
if($confirmed) {
$status = 'confirmed';
} else if($paid) {
$status = 'paid';
} else if($pending && $order_expires_seconds > 0) {
if(count($txs)) {
$status = 'partial';
} else {
$status = 'unpaid';
}
} else {
if(count($txs)) {
$status = 'expired_partial';
} else {
$status = 'expired';
}
}
$qrcode_uri = 'monero:'.$address.'?tx_amount='.$amount_due.'&tx_payment_id='.$payment_id;
$my_order_url = wc_get_endpoint_url('view-order', $order_id, wc_get_page_permalink('myaccount'));
$payment_details = array(
'order_id' => $order_id,
'payment_id' => $payment_id,
'integrated_address' => $integrated_addr,
'qrcode_uri' => $qrcode_uri,
'my_order_url' => $my_order_url,
'rate' => $details[0]->rate,
'rate_formatted' => sprintf('%.8f', $details[0]->rate / 1e8),
'currency' => $details[0]->currency,
'amount_total' => $amount_total,
'amount_paid' => $amount_paid,
'amount_due' => $amount_due,
'amount_total_formatted' => self::format_monero($amount_total),
'amount_paid_formatted' => self::format_monero($amount_paid),
'amount_due_formatted' => self::format_monero($amount_due),
'status' => $status,
'created' => $details[0]->created,
'order_age' => $order_age_seconds,
'order_expires' => self::format_seconds_to_time($order_expires_seconds),
'blocks_to_confirm' => $blocks_to_confirm,
'time_to_confirm' => $time_to_confirm,
'txs' => $txs
);
self::$payment_details[$order_id] = $payment_details;
return $payment_details;
} else {
return '[ERROR] Quote not found';
}
}
public static function get_payment_details_ajax() {
$user = wp_get_current_user();
if($user === 0)
self::ajax_output(array('error' => '[ERROR] User not logged in'));
$order_id = preg_replace("/[^0-9]+/", "", $_GET['order_id']);
$order = wc_get_order( $order_id );
if($order->user_id != $user->ID)
self::ajax_output(array('error' => '[ERROR] Order does not belong to this user'));
if($order->get_payment_method() != self::$_id)
self::ajax_output(array('error' => '[ERROR] Order not paid for with Monero'));
$details = self::get_payment_details($order);
if(!is_array($details))
self::ajax_output(array('error' => $details));
self::ajax_output($details);
}
public static function ajax_output($response) {
ob_clean();
header('Content-type: application/json');
echo json_encode($response);
wp_die();
}
public static function admin_order_page($post)
{
$order = wc_get_order($post->ID);
if($order->get_payment_method() != self::$_id)
return;
$method_title = self::$_title;
$details = self::get_payment_details($order);
if(!is_array($details)) {
$error = $details;
include MONERO_GATEWAY_PLUGIN_DIR . '/templates/monero-gateway/admin/order-history-error-page.php';
return;
}
include MONERO_GATEWAY_PLUGIN_DIR . '/templates/monero-gateway/admin/order-history-page.php';
}
public static function customer_order_page($order)
{
if(is_integer($order)) {
$order_id = $order;
$order = wc_get_order($order_id);
} else {
$order_id = $order->get_id();
}
if($order->get_payment_method() != self::$_id)
return;
$method_title = self::$_title;
$details = self::get_payment_details($order_id);
if(!is_array($details)) {
$error = $details;
include MONERO_GATEWAY_PLUGIN_DIR . '/templates/monero-gateway/customer/order-error-page.php';
return;
}
$show_qr = self::$show_qr;
$details_json = json_encode($details);
$ajax_url = WC_AJAX::get_endpoint('monero_gateway_payment_details');
include MONERO_GATEWAY_PLUGIN_DIR . '/templates/monero-gateway/customer/order-page.php';
}
public static function customer_order_email($order)
{
if(is_integer($order)) {
$order_id = $order;
$order = wc_get_order($order_id);
} else {
$order_id = $order->get_id();
}
if($order->get_payment_method() != self::$_id)
return;
$method_title = self::$_title;
$details = self::get_payment_details($order_id);
if(!is_array($details)) {
include MONERO_GATEWAY_PLUGIN_DIR . '/templates/monero-gateway/customer/order-email-error-block.php';
return;
}
include MONERO_GATEWAY_PLUGIN_DIR . '/templates/monero-gateway/customer/order-email-block.php';
}
public static function get_id()
{
return self::$_id;
}
public static function get_confirm_type()
{
return self::$confirm_type;
}
public static function use_qr_code()
{
return self::$show_qr;
}
public static function use_monero_price()
{
return self::$use_monero_price;
}
public static function convert_wc_price($price, $currency)
{
$rate = self::get_live_rate($currency);
$monero_amount = intval(MONERO_GATEWAY_ATOMIC_UNITS_POW * 1e8 * $price / $rate) / MONERO_GATEWAY_ATOMIC_UNITS_POW;
$monero_amount_formatted = sprintf('%.'.self::$use_monero_price_decimals.'f', $monero_amount);
return <<<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">XMR</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">XMR</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
/**
* library.php
* monero_wallet_rpc
*
* Written using the JSON RPC specification -
* http://json-rpc.org/wiki/specification
@ -9,10 +8,14 @@
* @author Kacper Rowinski <krowinski@implix.com>
* http://implix.com
* Modified to work with monero-rpc wallet by Serhack and cryptochangements
* Modified to work with monero-wallet-rpc wallet by mosu-forge
*/
class Monero_Library
defined( 'ABSPATH' ) || exit;
class Monero_Wallet_Rpc
{
protected $url = null, $is_debug = false, $parameters_structure = 'array';
protected $url = null, $is_debug = false;
protected $curl_options = array(
CURLOPT_CONNECTTIMEOUT => 8,
CURLOPT_TIMEOUT => 8
@ -36,7 +39,7 @@ class Monero_Library
{
$this->validate(false === extension_loaded('curl'), 'The curl extension must be loaded to use this class!');
$this->validate(false === extension_loaded('json'), 'The json extension must be loaded to use this class!');
$this->host = $pHost;
$this->port = $pPort;
$this->url = $pHost . ':' . $pPort . '/json_rpc';
@ -45,7 +48,7 @@ class Monero_Library
public function validate($pFailed, $pErrMsg)
{
if ($pFailed) {
echo $pErrMsg;
if(is_admin()) echo $pErrMsg;
}
}
@ -55,25 +58,12 @@ class Monero_Library
return $this;
}
/* public function setParametersStructure($pParametersStructure)
{
if (in_array($pParametersStructure, array('array', 'object')))
{
$this->parameters_structure = $pParametersStructure;
}
else
{
throw new UnexpectedValueException('Invalid parameters structure type.');
}
return $this;
} */
public function setCurlOptions($pOptionsArray)
{
if (is_array($pOptionsArray)) {
$this->curl_options = $pOptionsArray + $this->curl_options;
} else {
echo 'Invalid options type.';
if(is_admin()) echo 'Invalid options type.';
}
return $this;
}
@ -81,13 +71,7 @@ class Monero_Library
public function _print($json)
{
$json_encoded = json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
echo $json_encoded;
}
public function address()
{
$address = $this->_run('getaddress');
return $address;
if(is_admin()) echo $json_encoded;
}
public function _run($method, $params = null)
@ -99,23 +83,29 @@ class Monero_Library
private function request($pMethod, $pParams)
{
static $requestId = 0;
// generating uniuqe id per process
$requestId++;
// check if given params are correct
$this->validate(false === is_scalar($pMethod), 'Method name has no scalar value');
// $this->validate(false === is_array($pParams), 'Params must be given as array');
// send params as an object or an array
//$pParams = ($this->parameters_structure == 'object') ? $pParams[0] : array_values($pParams);
// Request (method invocation)
$request = json_encode(array('jsonrpc' => '2.0', 'method' => $pMethod, 'params' => $pParams, 'id' => $requestId));
// if is_debug mode is true then add url and request to is_debug
$this->debug('Url: ' . $this->url . "\r\n", false);
$this->debug('Request: ' . $request . "\r\n", false);
// Response (method invocation)
$responseMessage = $this->getResponse($request);
// if is_debug mode is true then add response to is_debug and display it
$this->debug('Response: ' . $responseMessage . "\r\n", true);
// decode and create array ( can be object, just set to false )
$responseDecoded = json_decode($responseMessage, true);
// check if decoding json generated any errors
$jsonErrorMsg = $this->getJsonLastErrorMsg();
$this->validate(!is_null($jsonErrorMsg), $jsonErrorMsg . ': ' . $responseMessage);
@ -149,7 +139,7 @@ class Monero_Library
$endTime = array_sum(explode(' ', microtime()));
// performance summary
$debug .= 'Request time: ' . round($endTime - $startTime, 3) . ' s Memory usage: ' . round(memory_get_usage() / 1024) . " kb\r\n";
echo nl2br($debug);
if(is_admin()) echo nl2br($debug);
// send output immediately
flush();
// clean static
@ -177,14 +167,18 @@ class Monero_Library
}
// send the request
$response = curl_exec($ch);
// check http status code
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (isset($this->httpErrors[$httpCode])) {
echo 'Response Http Error - ' . $this->httpErrors[$httpCode];
if(is_admin())
echo 'Response Http Error - ' . $this->httpErrors[$httpCode];
}
// check for curl error
if (0 < curl_errno($ch)) {
echo '[ERROR] Failed to connect to monero-wallet-rpc at ' . $this->host . ' port '. $this->port .'</br>';
if(is_admin())
echo '[ERROR] Failed to connect to monero-wallet-rpc at ' . $this->host . ' port '. $this->port .'</br>';
}
// close the connection
curl_close($ch);
@ -219,12 +213,18 @@ class Monero_Library
}
}
/*
/*
* The following functions can all be called to interact with the Monero RPC wallet
* They will majority of them will return the result as an array
* Example: $daemon->address(); where $daemon is an instance of this class, will return the wallet address as string within an array
*/
public function address()
{
$address = $this->_run('getaddress');
return $address;
}
public function getbalance()
{
$balance = $this->_run('getbalance');
@ -234,7 +234,7 @@ class Monero_Library
public function getheight()
{
$height = $this->_run('getheight');
return $height;
return $height['height'];
}
public function incoming_transfer($type)
@ -271,7 +271,7 @@ class Monero_Library
public function split_integrated_address($integrated_address)
{
if (!isset($integrated_address)) {
echo "Error: Integrated_Address mustn't be null";
if(is_admin()) echo "Error: Integrated_Address must not be null";
} else {
$split_params = array('integrated_address' => $integrated_address);
$split_methods = $this->_run('split_integrated_address', $split_params);
@ -281,8 +281,8 @@ class Monero_Library
public function make_uri($address, $amount, $recipient_name = null, $description = null)
{
// If I pass 1, it will be 0.0000001 xmr. Then
$new_amount = $amount * 100000000;
// Convert to atomic units
$new_amount = $amount * MONERO_GATEWAY_ATOMIC_UNITS_POW;
$uri_params = array('address' => $address, 'amount' => $new_amount, 'payment_id' => '', 'recipient_name' => $recipient_name, 'tx_description' => $description);
$uri = $this->_run('make_uri', $uri_params);
@ -296,9 +296,9 @@ class Monero_Library
return $parsed_uri;
}
public function transfer($amount, $address, $mixin = 4)
public function transfer($amount, $address, $mixin = 12)
{
$new_amount = $amount * 1000000000000;
$new_amount = $amount * MONERO_GATEWAY_ATOMIC_UNITS_POW;
$destinations = array('amount' => $new_amount, 'address' => $address);
$transfer_parameters = array('destinations' => array($destinations), 'mixin' => $mixin, 'get_tx_key' => true, 'unlock_time' => 0, 'payment_id' => '');
$transfer_method = $this->_run('transfer', $transfer_parameters);
@ -309,128 +309,44 @@ class Monero_Library
{
$get_payments_parameters = array('payment_id' => $payment_id);
$get_payments = $this->_run('get_payments', $get_payments_parameters);
return $get_payments;
if(isset($get_payments['payments']))
return $get_payments['payments'];
else
return array();
}
public function get_bulk_payments($payment_id, $min_block_height)
public function get_pool_payments($payment_id)
{
$get_bulk_payments_parameters = array('payment_id' => $payment_id, 'min_block_height' => $min_block_height);
$get_bulk_payments = $this->_run('get_bulk_payments', $get_bulk_payments_parameters);
return $get_bulk_payments;
}
}
class NodeTools
{
private $url;
public function __construct($testnet = false)
{
if(!testnet)
{
$this->url = 'https://xmrchain.net';
}
if(testnet)
{
$this->url = 'https://testnet.xmrchain.net';
}
}
public function get_last_block_height()
{
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => $this->url . 'api/networkinfo',
));
$resp = curl_exec($curl);
curl_close($curl);
$array = json_decode($resp, true);
return $array['data']['height'] - 1;
}
public function get_txs_from_block($height)
{
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => $this->url . '/api/search/' . $height,
));
$resp = curl_exec($curl);
curl_close($curl);
$array = json_decode($resp, true);
return $array['data']['txs'];
}
public function get_outputs($address, $viewkey, $zero_conf = false)
{
$curl = curl_init();
if(!$zero_conf)
{
curl_setopt_array($curl, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => $this->url . '/api/outputsblocks?address=' . $address . '&viewkey=' . $viewkey . '&limit=5&mempool=0',
));
}
// also look in mempool if accepting zero confirmation transactions
if($zero_conf)
{
curl_setopt_array($curl, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => $this->url . '/api/outputsblocks?address=' . $address . '&viewkey=' . $viewkey . '&limit=5&mempool=1',
));
$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;
}
}
$resp = curl_exec($curl);
curl_close($curl);
$array = json_decode($resp, true);
return $array['data']['outputs'];
return $payments;
}
public function check_tx($tx_hash, $address, $viewKey)
public function get_all_payments($payment_id)
{
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => $this-url . '/api/outputs?txhash=' .$tx_hash . '&address='. $address . '&viewkey='. $viewKey .'&txprove=0',
));
$resp = curl_exec($curl);
curl_close($curl);
$array = json_decode($resp, true);
$output_count = count($array['data']['outputs']);
$i = 0;
while($i < $output_count)
{
if($array['data']['outputs'][$i]['match'])
{
return $array['data']['outputs'][$i];
}
$i++;
}
$confirmed_payments = $this->get_payments($payment_id);
$pool_payments = $this->get_pool_payments($payment_id);
return array_merge($pool_payments, $confirmed_payments);
}
function get_mempool_txs()
public function get_bulk_payments($payment_id, $min_block_height)
{
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => $this->url . '/api/mempool',
));
$resp = curl_exec($curl);
curl_close($curl);
$array = json_decode($resp, true);
return $array;
$get_bulk_payments_parameters = array('payment_id' => $payment_id, 'min_block_height' => $min_block_height);
$get_bulk_payments = $this->_run('get_bulk_payments', $get_bulk_payments_parameters);
return $get_bulk_payments;
}
}

@ -23,7 +23,6 @@ vim: ts=4 noet ai */
@file
*/
/**
SHA-3 (FIPS-202) for PHP strings (byte arrays) (PHP 5.2.1+)
PHP 7.0 computes SHA-3 about 4 times faster than PHP 5.2 - 5.6 (on x86_64)
@ -34,6 +33,9 @@ vim: ts=4 noet ai */
This uses PHP's native byte strings. Supports 32-bit as well as 64-bit
systems. Also for LE vs. BE systems.
*/
defined( 'ABSPATH' ) || exit;
class SHA3 {
const SHA3_224 = 1;
const SHA3_256 = 2;

@ -30,6 +30,9 @@
*
* @link http://ed25519.cr.yp.to/software.html Other ED25519 implementations this is referenced from
*/
defined( 'ABSPATH' ) || exit;
class ed25519
{
public $b;

@ -0,0 +1,249 @@
<?php
/*
Plugin Name: Monero Woocommerce Gateway
Plugin URI: https://github.com/monero-integrations/monerowp
Description: Extends WooCommerce by adding a Monero Gateway
Version: 3.0.0
Tested up to: 4.9.8
Author: mosu-forge, SerHack
Author URI: https://monerointegrations.com/
*/
// This code isn't for Dark Net Markets, please report them to Authority!
defined( 'ABSPATH' ) || exit;
// Constants, you can edit these if you fork this repo
define('MONERO_GATEWAY_MAINNET_EXPLORER_URL', 'https://xmrchain.net/');
define('MONERO_GATEWAY_TESTNET_EXPLORER_URL', 'https://testnet.xmrchain.com/');
define('MONERO_GATEWAY_ADDRESS_PREFIX', 0x12);
define('MONERO_GATEWAY_ADDRESS_PREFIX_INTEGRATED', 0x13);
define('MONERO_GATEWAY_ATOMIC_UNITS', 12);
define('MONERO_GATEWAY_ATOMIC_UNIT_THRESHOLD', 10); // Amount under in atomic units payment is valid
define('MONERO_GATEWAY_DIFFICULTY_TARGET', 120);
// 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', 'monero_gateway');
function monero_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', 'monero_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', 'monero_gateway')
);
return $schedules;
}
add_action('wp', 'monero_activate_cron');
function monero_activate_cron() {
if(!wp_next_scheduled('monero_update_event')) {
wp_schedule_event(time(), 'one_minute', 'monero_update_event');
}
}
add_action('monero_update_event', 'monero_update_event');
function monero_update_event() {
Monero_Gateway::do_update_event();
}
add_action('woocommerce_thankyou_'.Monero_Gateway::get_id(), 'monero_order_confirm_page');
add_action('woocommerce_order_details_after_order_table', 'monero_order_page');
add_action('woocommerce_email_after_order_table', 'monero_order_email');
function monero_order_confirm_page($order_id) {
Monero_Gateway::customer_order_page($order_id);
}
function monero_order_page($order) {
if(!is_wc_endpoint_url('order-received'))
Monero_Gateway::customer_order_page($order);
}
function monero_order_email($order) {
Monero_Gateway::customer_order_email($order);
}
add_action('wc_ajax_monero_gateway_payment_details', 'monero_get_payment_details_ajax');
function monero_get_payment_details_ajax() {
Monero_Gateway::get_payment_details_ajax();
}
add_filter('woocommerce_currencies', 'monero_add_currency');
function monero_add_currency($currencies) {
$currencies['Monero'] = __('Monero', 'monero_gateway');
return $currencies;
}
add_filter('woocommerce_currency_symbol', 'monero_add_currency_symbol', 10, 2);
function monero_add_currency_symbol($currency_symbol, $currency) {
switch ($currency) {
case 'Monero':
$currency_symbol = 'XMR';
break;
}
return $currency_symbol;
}
if(Monero_Gateway::use_monero_price()) {
// This filter will replace all prices with amount in Monero (live rates)
add_filter('wc_price', 'monero_live_price_format', 10, 3);
function monero_live_price_format($price_html, $price_float, $args) {
if(!isset($args['currency']) || !$args['currency']) {
global $woocommerce;
$currency = strtoupper(get_woocommerce_currency());
} else {
$currency = strtoupper($args['currency']);
}
return Monero_Gateway::convert_wc_price($price_float, $currency);
}
// These filters will replace the live rate with the exchange rate locked in for the order
// We must be careful to hit all the hooks for price displays associated with an order,
// else the exchange rate can change dynamically (which it should for an order)
add_filter('woocommerce_order_formatted_line_subtotal', 'monero_order_item_price_format', 10, 3);
function monero_order_item_price_format($price_html, $item, $order) {
return Monero_Gateway::convert_wc_price_order($price_html, $order);
}
add_filter('woocommerce_get_formatted_order_total', 'monero_order_total_price_format', 10, 2);
function monero_order_total_price_format($price_html, $order) {
return Monero_Gateway::convert_wc_price_order($price_html, $order);
}
add_filter('woocommerce_get_order_item_totals', 'monero_order_totals_price_format', 10, 3);
function monero_order_totals_price_format($total_rows, $order, $tax_display) {
foreach($total_rows as &$row) {
$price_html = $row['value'];
$row['value'] = Monero_Gateway::convert_wc_price_order($price_html, $order);
}
return $total_rows;
}
}
add_action('wp_enqueue_scripts', 'monero_enqueue_scripts');
function monero_enqueue_scripts() {
if(Monero_Gateway::use_monero_price())
wp_dequeue_script('wc-cart-fragments');
if(Monero_Gateway::use_qr_code())
wp_enqueue_script('monero-qr-code', MONERO_GATEWAY_PLUGIN_URL.'assets/js/qrcode.min.js');
wp_enqueue_script('monero-clipboard-js', MONERO_GATEWAY_PLUGIN_URL.'assets/js/clipboard.min.js');
wp_enqueue_script('monero-gateway', MONERO_GATEWAY_PLUGIN_URL.'assets/js/monero-gateway-order-page.js');
wp_enqueue_style('monero-gateway', MONERO_GATEWAY_PLUGIN_URL.'assets/css/monero-gateway-order-page.css');
}
// [monero-price currency="USD"]
// currency: BTC, GBP, etc
// if no none, then default store currency
function monero_price_func( $atts ) {
global $woocommerce;
$a = shortcode_atts( array(
'currency' => get_woocommerce_currency()
), $atts );
$currency = strtoupper($a['currency']);
$rate = Monero_Gateway::get_live_rate($currency);
if($currency == 'BTC')
$rate_formatted = sprintf('%.8f', $rate / 1e8);
else
$rate_formatted = sprintf('%.5f', $rate / 1e8);
return "<span class=\"monero-price\">1 XMR = $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(16) DEFAULT '' NOT NULL,
currency VARCHAR(6) DEFAULT '' NOT NULL,
rate BIGINT UNSIGNED DEFAULT 0 NOT NULL,
amount BIGINT UNSIGNED DEFAULT 0 NOT NULL,
paid TINYINT NOT NULL DEFAULT 0,
confirmed TINYINT NOT NULL DEFAULT 0,
pending TINYINT NOT NULL DEFAULT 1,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (order_id)
) $charset_collate;";
dbDelta($sql);
}
$table_name = $wpdb->prefix . "monero_gateway_quotes_txids";
if($wpdb->get_var("show tables like '$table_name'") != $table_name) {
$sql = "CREATE TABLE $table_name (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
payment_id VARCHAR(16) DEFAULT '' NOT NULL,
txid VARCHAR(64) DEFAULT '' NOT NULL,
amount BIGINT UNSIGNED DEFAULT 0 NOT NULL,
height MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (id),
UNIQUE KEY (payment_id, txid, amount)
) $charset_collate;";
dbDelta($sql);
}
$table_name = $wpdb->prefix . "monero_gateway_live_rates";
if($wpdb->get_var("show tables like '$table_name'") != $table_name) {
$sql = "CREATE TABLE $table_name (
currency VARCHAR(6) DEFAULT '' NOT NULL,
rate BIGINT UNSIGNED DEFAULT 0 NOT NULL,
updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (currency)
) $charset_collate;";
dbDelta($sql);
}
}

@ -1,354 +0,0 @@
<?php
/*
* base58.php
*
* PHP Base58 codec
*
* Based on https://github.com/MoneroPy/moneropy/base58.py and https://github.com/mymonero/mymonero-core-js/cryptonote_utils/cryptonote_base58.js
*
*/
class base58 {
/**
* @var string
*/
static $alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
static $encoded_block_sizes = [0, 2, 3, 5, 6, 7, 9, 10, 11];
static $full_block_size = 8;
static $full_encoded_block_size = 11;
/**
*
* Convert a hexadecimal string to a binary array
*
* @param string $hex A hexadecimal string to convert to a binary array
* @return array
*
*/
private function hex_to_bin($hex) {
if (gettype($hex) != 'string') {
throw new Exception('base58->hex_to_bin(): Invalid input type (must be a string)');
}
if (strlen($hex) % 2 != 0) {
throw new Exception('base58->hex_to_bin(): Invalid input length (must be even)');
}
$res = array_fill(0, strlen($hex) / 2, 0);
for ($i = 0; $i < strlen($hex) / 2; $i++) {
$res[$i] = intval(substr($hex, $i * 2, $i * 2 + 2 - $i * 2), 16);
}
return $res;
}
/**
*
* Convert a binary array to a hexadecimal string
*
* @param array $bin A binary array to convert to a hexadecimal string
* @return string
*
*/
private function bin_to_hex($bin) {
if (gettype($bin) != 'array') {
throw new Exception('base58->bin_to_hex(): Invalid input type (must be an array)');
}
$res = [];
for ($i = 0; $i < count($bin); $i++) {
$res[] = substr('0'.dechex($bin[$i]), -2);
}
return join($res);
}
/**
*
* Convert a string to a binary array
*
* @param string $str A string to convert to a binary array
* @return array
*
*/
private function str_to_bin($str) {
if (gettype($str) != 'string') {
throw new Exception('base58->str_to_bin(): Invalid input type (must be a string)');
}
$res = array_fill(0, strlen($str), 0);
for ($i = 0; $i < strlen($str); $i++) {
$res[$i] = ord($str[$i]);
}
return $res;
}
/**
*
* Convert a binary array to a string
*
* @param array $bin A binary array to convert to a string
* @return string
*
*/
private function bin_to_str($bin) {
if (gettype($bin) != 'array') {
throw new Exception('base58->bin_to_str(): Invalid input type (must be an array)');
}
$res = array_fill(0, count($bin), 0);
for ($i = 0; $i < count($bin); $i++) {
$res[$i] = chr($bin[$i]);
}
return preg_replace('/[[:^print:]]/', '', join($res)); // preg_replace necessary to strip errant non-ASCII characters eg. ''
}
/**
*
* Convert a UInt8BE (one unsigned big endian byte) array to UInt64
*
* @param array $data A UInt8BE array to convert to UInt64
* @return number
*
*/
private function uint8_be_to_64($data) {
if (gettype($data) != 'array') {
throw new Exception ('base58->uint8_be_to_64(): Invalid input type (must be an array)');
}
$res = 0;
$i = 0;
switch (9 - count($data)) {
case 1:
$res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]);
case 2:
$res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]);
case 3:
$res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]);
case 4:
$res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]);
case 5:
$res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]);
case 6:
$res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]);
case 7:
$res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]);
case 8:
$res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]);
break;
default:
throw new Exception('base58->uint8_be_to_64: Invalid input length (1 <= count($data) <= 8)');
}
return $res;
}
/**
*
* Convert a UInt64 (unsigned 64 bit integer) to a UInt8BE array
*
* @param number $num A UInt64 number to convert to a UInt8BE array
* @param integer $size Size of array to return
* @return array
*
*/
private function uint64_to_8_be($num, $size) {
if (gettype($num) != ('integer' || 'double')) {
throw new Exception ('base58->uint64_to_8_be(): Invalid input type ($num must be a number)');
}
if (gettype($size) != 'integer') {
throw new Exception ('base58->uint64_to_8_be(): Invalid input type ($size must be an integer)');
}
if ($size < 1 || $size > 8) {
throw new Exception ('base58->uint64_to_8_be(): Invalid size (1 <= $size <= 8)');
}
$res = array_fill(0, $size, 0);
for ($i = $size - 1; $i >= 0; $i--) {
$res[$i] = bcmod($num, bcpow(2, 8));
$num = bcdiv($num, bcpow(2, 8));
}
return $res;
}
/**
*
* Convert a hexadecimal (Base16) array to a Base58 string
*
* @param array $data
* @param array $buf
* @param number $index
* @return array
*
*/
private function encode_block($data, $buf, $index) {
if (gettype($data) != 'array') {
throw new Exception('base58->encode_block(): Invalid input type ($data must be an array)');
}
if (gettype($buf) != 'array') {
throw new Exception('base58->encode_block(): Invalid input type ($buf must be an array)');
}
if (gettype($index) != ('integer' || 'double')) {
throw new Exception('base58->encode_block(): Invalid input type ($index must be a number)');
}
if (count($data) < 1 or count($data) > self::$full_encoded_block_size) {
throw new Exception('base58->encode_block(): Invalid input length (1 <= count($data) <= 8)');
}
$num = self::uint8_be_to_64($data);
$i = self::$encoded_block_sizes[count($data)] - 1;
while ($num > 0) {
$remainder = bcmod($num, 58);
$num = bcdiv($num, 58);
$buf[$index + $i] = ord(self::$alphabet[$remainder]);
$i--;
}
return $buf;
}
/**
*
* Encode a hexadecimal (Base16) string to Base58
*
* @param string $hex A hexadecimal (Base16) string to convert to Base58
* @return string
*
*/
public function encode($hex) {
if (gettype($hex) != 'string') {
throw new Exception ('base58->encode(): Invalid input type (must be a string)');
}
$data = self::hex_to_bin($hex);
if (count($data) == 0) {
return '';
}
$full_block_count = floor(count($data) / self::$full_block_size);
$last_block_size = count($data) % self::$full_block_size;
$res_size = $full_block_count * self::$full_encoded_block_size + self::$encoded_block_sizes[$last_block_size];
$res = array_fill(0, $res_size, 0);
for ($i = 0; $i < $res_size; $i++) {
$res[$i] = self::$alphabet[0];
}
for ($i = 0; $i < $full_block_count; $i++) {
$res = self::encode_block(array_slice($data, $i * self::$full_block_size, ($i * self::$full_block_size + self::$full_block_size) - ($i * self::$full_block_size)), $res, $i * self::$full_encoded_block_size);
}
if ($last_block_size > 0) {
$res = self::encode_block(array_slice($data, $full_block_count * self::$full_block_size, $full_block_count * self::$full_block_size + $last_block_size), $res, $full_block_count * self::$full_encoded_block_size);
}
return self::bin_to_str($res);
}
/**
*
* Convert a Base58 input to hexadecimal (Base16)
*
* @param array $data
* @param array $buf
* @param integer $index
* @return array
*
*/
private function decode_block($data, $buf, $index) {
if (gettype($data) != 'array') {
throw new Exception('base58->decode_block(): Invalid input type ($data must be an array)');
}
if (gettype($buf) != 'array') {
throw new Exception('base58->decode_block(): Invalid input type ($buf must be an array)');
}
if (gettype($index) != ('integer' || 'double')) {
throw new Exception('base58->decode_block(): Invalid input type ($index must be a number)');
}
$res_size = self::index_of(self::$encoded_block_sizes, count($data));
if ($res_size <= 0) {
throw new Exception('base58->decode_block(): Invalid input length ($data must be a value from base58::$encoded_block_sizes)');
}
$res_num = 0;
$order = 1;
for ($i = count($data) - 1; $i >= 0; $i--) {
$digit = strpos(self::$alphabet, chr($data[$i]));
if ($digit < 0) {
throw new Exception("base58->decode_block(): Invalid character ($digit \"{$digit}\" not found in base58::$alphabet)");
}
$product = bcadd(bcmul($order, $digit), $res_num);
if ($product > bcpow(2, 64)) {
throw new Exception('base58->decode_block(): Integer overflow ($product exceeds the maximum 64bit integer)');
}
$res_num = $product;
$order = bcmul($order, 58);
}
if ($res_size < self::$full_block_size && bcpow(2, 8 * $res_size) <= 0) {
throw new Exception('base58->decode_block(): Integer overflow (bcpow(2, 8 * $res_size) exceeds the maximum 64bit integer)');
}
$tmp_buf = self::uint64_to_8_be($res_num, $res_size);
for ($i = 0; $i < count($tmp_buf); $i++) {
$buf[$i + $index] = $tmp_buf[$i];
}
return $buf;
}
/**
*
* Decode a Base58 string to hexadecimal (Base16)
*
* @param string $hex A Base58 string to convert to hexadecimal (Base16)
* @return string
*
*/
public function decode($enc) {
if (gettype($enc) != 'string') {
throw new Exception ('base58->decode(): Invalid input type (must be a string)');
}
$enc = self::str_to_bin($enc);
if (count($enc) == 0) {
return '';
}
$full_block_count = floor(bcdiv(count($enc), self::$full_encoded_block_size));
$last_block_size = bcmod(count($enc), self::$full_encoded_block_size);
$last_block_decoded_size = self::index_of(self::$encoded_block_sizes, $last_block_size);
$data_size = $full_block_count * self::$full_block_size + $last_block_decoded_size;
$data = array_fill(0, $data_size, 0);
for ($i = 0; $i <= $full_block_count; $i++) {
$data = self::decode_block(array_slice($enc, $i * self::$full_encoded_block_size, ($i * self::$full_encoded_block_size + self::$full_encoded_block_size) - ($i * self::$full_encoded_block_size)), $data, $i * self::$full_block_size);
}
if ($last_block_size > 0) {
$data = self::decode_block(array_slice($enc, $full_block_count * self::$full_encoded_block_size, $full_block_count * self::$full_encoded_block_size + $last_block_size), $data, $full_block_count * self::$full_block_size);
}
return self::bin_to_hex($data);
}
/**
*
* Search an array for a value
* Source: https://stackoverflow.com/a/30994678
*
* @param array $haystack An array to search
* @param string $needle A string to search for
* @return number The index of the element found (or -1 for no match)
*
*/
private function index_of($haystack, $needle) {
if (gettype($haystack) != 'array') {
throw new Exception ('base58->decode(): Invalid input type ($haystack must be an array)');
}
// if (gettype($needle) != 'string') {
// throw new Exception ('base58->decode(): Invalid input type ($needle must be a string)');
// }
foreach ($haystack as $key => $value) if ($value === $needle) return $key;
return -1;
}
}
?>

@ -1,305 +0,0 @@
<?php
/*
Copyright (c) 2018 Monero-Integrations
*/
require_once("SHA3.php");
require_once("ed25519.php");
require_once("base58.php");
class Cryptonote
{
protected $ed25519;
public function __construct()
{
$this->ed25519 = new ed25519();
$this->base58 = new base58();
}
/*
* @param string Hex encoded string of the data to hash
* @return string Hex encoded string of the hashed data
*
*/
public function keccak_256($message)
{
$keccak256 = SHA3::init (SHA3::KECCAK_256);
$keccak256->absorb (hex2bin($message));
return bin2hex ($keccak256->squeeze (32)) ;
}
/*
* @return string A hex encoded string of 32 random bytes
*
*/
public function gen_new_hex_seed()
{
$bytes = random_bytes(32);
return bin2hex($bytes);
}
public function sc_reduce($input)
{
$integer = $this->ed25519->decodeint(hex2bin($input));
$modulo = bcmod($integer , $this->ed25519->l);
$result = bin2hex($this->ed25519->encodeint($modulo));
return $result;
}
/*
* Hs in the cryptonote white paper
*
* @param string Hex encoded data to hash
*
* @return string A 32 byte encoded integer
*/
public function hash_to_scalar($data)
{
$hash = $this->keccak_256($data);
$scalar = $this->sc_reduce($hash);
return $scalar;
}
/*
* Derive a deterministic private view key from a private spend key
* @param string A private spend key represented as a 32 byte hex string
*
* @return string A deterministic private view key represented as a 32 byte hex string
*/
public function derive_viewKey($spendKey)
{
return $this->hash_to_scalar($spendkey);
}
/*
* Generate a pair of random private keys
*
* @param string A hex string to be used as a seed (this should be random)
*
* @return array An array containing a private spend key and a deterministic view key
*/
public function gen_private_keys($seed)
{
$spendKey = $this->sc_reduce($seed);
$viewKey = $this->derive_viewKey($spendKey);
$result = array("spendKey" => $spendKey,
"viewKey" => $viewKey);
return $result;
}
/*
* Get a public key from a private key on the ed25519 curve
*
* @param string a 32 byte hex encoded private key
*
* @return string a 32 byte hex encoding of a point on the curve to be used as a public key
*/
public function pk_from_sk($privKey)
{
$keyInt = $this->ed25519->decodeint(hex2bin($privKey));
$aG = $this->ed25519->scalarmult_base($keyInt);
return bin2hex($this->ed25519->encodepoint($aG));
}
/*
* Generate key derivation
*
* @param string a 32 byte hex encoding of a point on the ed25519 curve used as a public key
* @param string a 32 byte hex encoded private key
*
* @return string The hex encoded key derivation
*/
public function gen_key_derivation($public, $private)
{
$point = $this->ed25519->scalarmult($this->ed25519->decodepoint(hex2bin($public)), $this->ed25519->decodeint(hex2bin($private)));
$res = $this->ed25519->scalarmult($point, 8);
return bin2hex($this->ed25519->encodepoint($res));
}
public function encode_varint($data)
{
$orig = $data;
if ($data < 0x80)
{
return bin2hex(pack('C', $data));
}
$encodedBytes = [];
while ($data > 0)
{
$encodedBytes[] = 0x80 | ($data & 0x7f);
$data >>= 7;
}
$encodedBytes[count($encodedBytes)-1] &= 0x7f;
$bytes = call_user_func_array('pack', array_merge(array('C*'), $encodedBytes));;
return bin2hex($bytes);
}
public function derivation_to_scalar($der, $index)
{
$encoded = $this->encode_varint($index);
$data = $der . $encoded;
return $this->hash_to_scalar($data);
}
// this is a one way function used for both encrypting and decrypting 8 byte payment IDs
public function stealth_payment_id($payment_id, $tx_pub_key, $viewkey)
{
if(strlen($payment_id) != 16)
{
throw new Exception("Error: Incorrect payment ID size. Should be 8 bytes");
}
$der = $this->gen_key_derivation($tx_pub_key, $viewkey);
$data = $der . '8d';
$hash = $this->keccak_256($data);
$key = substr($hash, 0, 16);
$result = bin2hex(pack('H*',$payment_id) ^ pack('H*',$key));
return $result;
}
// takes transaction extra field as hex string and returns transaction public key 'R' as hex string
public function txpub_from_extra($extra)
{
$parsed = array_map("hexdec", str_split($extra, 2));
if($parsed[0] == 1)
{
return substr($extra, 2, 64);
}
if($parsed[0] == 2)
{
if($parsed[0] == 2 || $parsed[2] == 1)
{
$offset = (($parsed[1] + 2) *2) + 2;
return substr($extra, (($parsed[1] + 2) *2) + 2, 64);
}
}
}
public function derive_public_key($der, $index, $pub)
{
$scalar = $this->derivation_to_scalar($der, $index);
$sG = $this->ed25519->scalarmult_base($this->ed25519->decodeint(hex2bin($scalar)));
$pubPoint = $this->ed25519->decodepoint(hex2bin($pub));
$key = $this->ed25519->encodepoint($this->ed25519->edwards($pubPoint, $sG));
return bin2hex($key);
}
/*
* Perform the calculation P = P' as described in the cryptonote whitepaper
*
* @param string 32 byte transaction public key R
* @param string 32 byte reciever private view key a
* @param string 32 byte reciever public spend key B
* @param int output index
* @param string output you want to check against P
*/
public function is_output_mine($txPublic, $privViewkey, $publicSpendkey, $index, $P)
{
$derivation = $this->gen_key_derivation($txPublic, $privViewkey);
$Pprime = $this->derive_public_key($derivation, $index, $publicSpendkey);
if($P == $Pprime)
{
return true;
}
else
return false;
}
/*
* Create a valid base58 encoded Monero address from public keys
*
* @param string Public spend key
* @param string Public view key
*
* @return string Base58 encoded Monero address
*/
public function encode_address($pSpendKey, $pViewKey)
{
// mainnet network byte is 18 (0x12)
$data = "12" . $pSpendKey . $pViewKey;
$encoded = $this->base58->encode($data);
return $encoded;
}
public function verify_checksum($address)
{
$decoded = $this->base58->decode($address);
$checksum = substr($decoded, -8);
$checksum_hash = $this->keccak_256(substr($decoded, 0, 130));
$calculated = substr($checksum_hash, 0, 8);
if($checksum == $calculated){
return true;
}
else
return false;
}
/*
* Decode a base58 encoded Monero address
*
* @param string A base58 encoded Monero address
*
* @return array An array containing the Address network byte, public spend key, and public view key
*/
public function decode_address($address)
{
$decoded = $this->base58->decode($address);
if(!$this->verify_checksum($address)){
throw new Exception("Error: invalid checksum");
}
$network_byte = substr($decoded, 0, 2);
$public_spendKey = substr($decoded, 2, 64);
$public_viewKey = substr($decoded, 66, 64);
$result = array("networkByte" => $network_byte,
"spendKey" => $public_spendKey,
"viewKey" => $public_viewKey);
return $result;
}
/*
* Get an integrated address from public keys and a payment id
*
* @param string A 32 byte hex encoded public spend key
* @param string A 32 byte hex encoded public view key
* @param string An 8 byte hex string to use as a payment id
*/
public function integrated_addr_from_keys($public_spendkey, $public_viewkey, $payment_id)
{
// 0x13 is the mainnet network byte for integrated addresses
$data = "13".$public_spendkey.$public_viewkey.$payment_id;
$checksum = substr($this->keccak_256($data), 0, 8);
$result = $this->base58->encode($data.$checksum);
return $result;
}
/*
* Generate a Monero address from seed
*
* @param string Hex string to use as seed
*
* @return string A base58 encoded Monero address
*/
public function address_from_seed($hex_seed)
{
$private_keys = $this->gen_private_keys($hex_seed);
$private_viewKey = $private_keys["viewKey"];
$private_spendKey = $private_keys["spendKey"];
$public_spendKey = $this->pk_from_sk($private_spendKey);
$public_viewKey = $this->pk_from_sk($private_viewKey);
$address = $this->encode_address($public_spendKey, $public_viewKey);
return $address;
}
}

@ -1,748 +0,0 @@
<?php
/*
* Main Gateway of Monero using a daemon online
* Authors: Serhack and cryptochangements
*/
require_once("cryptonote.php");
class Monero_Gateway extends WC_Payment_Gateway
{
private $reloadTime = 17000;
private $discount;
private $confirmed = false;
private $monero_daemon;
private $non_rpc = false;
private $zero_cofirm = false;
private $cryptonote;
private $testnet = false;
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 = "2.0";
//
$this->icon = apply_filters('woocommerce_offline_icon', '');
$this->has_fields = false;
$this->log = new WC_Logger();
$this->init_form_fields();
$this->host = $this->get_option('daemon_host');
$this->port = $this->get_option('daemon_port');
$this->address = $this->get_option('monero_address');
$this->viewKey = $this->get_option('viewKey');
$this->discount = $this->get_option('discount');
$this->accept_zero_conf = $this->get_option('zero_conf');
$this->use_viewKey = $this->get_option('use_viewKey');
$this->use_rpc = $this->get_option('use_rpc');
$env = $this->get_option('environment');
if($this->use_viewKey == 'yes')
{
$this->non_rpc = true;
}
if($this->use_rpc == 'yes')
{
$this->non_rpc = false;
}
if($this->accept_zero_conf == 'yes')
{
$this->zero_confirm = true;
}
if($env == 'yes')
{
$this->testnet = true;
}
// After init_settings() is called, you can get the settings and load them into variables, e.g:
// $this->title = $this->get_option('title' );
$this->init_settings();
// Turn these settings into variables we can use
foreach ($this->settings as $setting_key => $value) {
$this->$setting_key = $value;
}
add_action('admin_notices', array($this, 'do_ssl_check'));
add_action('admin_notices', array($this, 'validate_fields'));
add_action('woocommerce_thankyou_' . $this->id, array($this, 'instruction'));
if (is_admin()) {
/* Save Settings */
add_action('woocommerce_update_options_payment_gateways_' . $this->id, array($this, 'process_admin_options'));
add_filter('woocommerce_currencies', array($this,'add_my_currency'));
add_filter('woocommerce_currency_symbol', array($this,'add_my_currency_symbol'), 10, 2);
add_action('woocommerce_email_before_order_table', array($this, 'email_instructions'), 10, 2);
}
$this->monero_daemon = new Monero_Library($this->host, $this->port);
$this->cryptonote = new Cryptonote();
$this->supports = array( 'subscriptions', 'products' );
$this->supports = array(
'products',
'subscriptions',
'subscription_cancellation',
'subscription_suspension',
'subscription_reactivation',
'subscription_amount_changes',
'subscription_date_changes',
'subscription_payment_method_change'
);
}
public function get_icon()
{
return apply_filters('woocommerce_gateway_icon', "<img src='http://cdn.monerointegrations.com/logomonero.png' />");
}
public function init_form_fields()
{
$this->form_fields = array(
'enabled' => array(
'title' => __('Enable / Disable', 'monero_gateway'),
'label' => __('Enable this payment gateway', 'monero_gateway'),
'type' => 'checkbox',
'default' => 'no'
),
'title' => array(
'title' => __('Title', 'monero_gateway'),
'type' => 'text',
'desc_tip' => __('Payment title the customer will see during the checkout process.', 'monero_gateway'),
'default' => __('Monero XMR Payment', 'monero_gateway')
),
'description' => array(
'title' => __('Description', 'monero_gateway'),
'type' => 'textarea',
'desc_tip' => __('Payment description the customer will see during the checkout process.', 'monero_gateway'),
'default' => __('Pay securely using XMR.', 'monero_gateway')
),
'use_viewKey' => array(
'title' => __('Use ViewKey', 'monero_gateway'),
'label' => __(' Verify Transaction with ViewKey ', 'monero_gateway'),
'type' => 'checkbox',
'description' => __('Fill in the Address and ViewKey fields to verify transactions with your ViewKey', 'monero_gateway'),
'default' => 'no'
),
'monero_address' => array(
'title' => __('Monero Address', 'monero_gateway'),
'label' => __('Useful for people that have not a daemon online'),
'type' => 'text',
'desc_tip' => __('Monero Wallet Address', 'monero_gateway')
),
'viewKey' => array(
'title' => __('Secret ViewKey', 'monero_gateway'),
'label' => __('Secret ViewKey'),
'type' => 'text',
'desc_tip' => __('Your secret ViewKey', 'monero_gateway')
),
'use_rpc' => array(
'title' => __('Use monero-wallet-rpc', 'monero_gateway'),
'label' => __(' Verify transactions with the monero-wallet-rpc ', 'monero_gateway'),
'type' => 'checkbox',
'description' => __('This must be setup seperately', 'monero_gateway'),
'default' => 'no'
),
'daemon_host' => array(
'title' => __('Monero wallet RPC Host/ IP', 'monero_gateway'),
'type' => 'text',
'desc_tip' => __('This is the Daemon Host/IP to authorize the payment with port', 'monero_gateway'),
'default' => 'localhost',
),
'daemon_port' => array(
'title' => __('Monero wallet RPC port', 'monero_gateway'),
'type' => 'text',
'desc_tip' => __('This is the Daemon Host/IP to authorize the payment with port', 'monero_gateway'),
'default' => '18080',
),
'discount' => array(
'title' => __('% discount for using XMR', 'monero_gateway'),
'desc_tip' => __('Provide a discount to your customers for making a private payment with XMR!', 'monero_gateway'),
'description' => __('Do you want to spread the word about Monero? Offer a small discount! Leave this empty if you do not wish to provide a discount', 'monero_gateway'),
'type' => __('number'),
'default' => '5'
),
'environment' => array(
'title' => __(' Testnet', 'monero_gateway'),
'label' => __(' Check this if you are using testnet ', 'monero_gateway'),
'type' => 'checkbox',
'description' => __('Check this box if you are using testnet', 'monero_gateway'),
'default' => 'no'
),
'zero_conf' => array(
'title' => __(' Accept 0 conf txs', 'monero_gateway'),
'label' => __(' Accept 0-confirmation transactions ', 'monero_gateway'),
'type' => 'checkbox',
'description' => __('This is faster but less secure', 'monero_gateway'),
'default' => 'no'
),
'onion_service' => array(
'title' => __(' SSL warnings ', 'monero_gateway'),
'label' => __(' Check to Silence SSL warnings', 'monero_gateway'),
'type' => 'checkbox',
'description' => __('Check this box if you are running on an Onion Service (Suppress SSL errors)', 'monero_gateway'),
'default' => 'no'
),
);
}
public function add_my_currency($currencies)
{
$currencies['XMR'] = __('Monero', 'woocommerce');
return $currencies;
}
public function add_my_currency_symbol($currency_symbol, $currency)
{
switch ($currency) {
case 'XMR':
$currency_symbol = 'XMR';
break;
}
return $currency_symbol;
}
public function admin_options()
{
$this->log->add('Monero_gateway', '[SUCCESS] Monero Settings OK');
echo "<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;'>";
if(!$this->non_rpc) // only try to get balance data if using wallet-rpc
$this->getamountinfo();
echo "</div>";
echo "<table class='form-table'>";
$this->generate_settings_html();
echo "</table>";
echo "<h4>Learn more about using monero-wallet-rpc <a href=\"https://github.com/monero-integrations/monerowp/blob/master/README.md\">here</a> and viewkeys <a href=\"https://getmonero.org/resources/moneropedia/viewkey.html\">here</a> </h4>";
}
public function getamountinfo()
{
$wallet_amount = $this->monero_daemon->getbalance();
if (!isset($wallet_amount)) {
$this->log->add('Monero_gateway', '[ERROR] Cannot connect to monero-wallet-rpc');
echo "</br>Your balance is: Not Available </br>";
echo "Unlocked balance: Not Available";
}
else
{
$real_wallet_amount = $wallet_amount['balance'] / 1000000000000;
$real_amount_rounded = round($real_wallet_amount, 6);
$unlocked_wallet_amount = $wallet_amount['unlocked_balance'] / 1000000000000;
$unlocked_amount_rounded = round($unlocked_wallet_amount, 6);
echo "Your balance is: " . $real_amount_rounded . " XMR </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 look valid. Have you checked it?</p></div>";
}
if(!$this->check_viewKey())
{
echo "<div class=\"error\"><p>Your ViewKey doesn't look valid. Have you checked it?</p></div>";
}
if($this->check_checkedBoxes())
{
echo "<div class=\"error\"><p>You must choose to either use monero-wallet-rpc or a ViewKey, not both</p></div>";
}
}
// Validate fields
public function check_monero()
{
$monero_address = $this->settings['monero_address'];
if (strlen($monero_address) == 95 && substr($monero_address, 1))
{
if($this->cryptonote->verify_checksum($monero_address))
{
return true;
}
}
return false;
}
public function check_viewKey()
{
if($this->use_viewKey == 'yes')
{
if (strlen($this->viewKey) == 64) {
return true;
}
return false;
}
return true;
}
public function check_checkedBoxes()
{
if($this->use_viewKey == 'yes')
{
if($this->use_rpc == 'yes')
{
return true;
}
}
else
return false;
}
public function is_virtual_in_cart($order_id)
{
$order = wc_get_order( $order_id );
$items = $order->get_items();
$cart_size = count($items);
$virtual_items = 0;
foreach ( $items as $item ) {
$product = new WC_Product( $item['product_id'] );
if ( $product->is_virtual() ) {
$virtual_items += 1;
}
}
if($virtual_items == $cart_size)
{
return true;
}
else{
return false;
}
}
public function instruction($order_id)
{
if($this->non_rpc)
{
echo "<noscript><h1>You must enable javascript in order to confirm your order</h1></noscript>";
$order = wc_get_order($order_id);
$amount = floatval(preg_replace('#[^\d.]#', '', $order->get_total()));
$payment_id = $this->set_paymentid_cookie(8);
$currency = $order->get_currency();
$amount_xmr2 = $this->changeto($amount, $currency, $payment_id);
$address = $this->address;
$order->update_meta_data( "Payment ID", $payment_id);
$order->update_meta_data( "Amount requested (XMR)", $amount_xmr2);
$order->save();
if (!isset($address)) {
// If there isn't address (merchant missed that field!), $address will be the Monero address for donating :)
$address = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A";
}
$decoded_address = $this->cryptonote->decode_address($address);
$pub_spendKey = $decoded_address['spendKey'];
$pub_viewKey = $decoded_address['viewKey'];
$integrated_addr = $this->cryptonote->integrated_addr_from_keys($pub_spendKey, $pub_viewKey, $payment_id);
$uri = urlencode("monero:".$address."?tx_amount=".$amount_xmr2."&tx_payment_id=".$payment_id);
$this->verify_non_rpc($payment_id, $amount_xmr2, $order_id, $this->zero_confirm);
if($this->confirmed == false)
{
echo "<h4><font color=DC143C> We are waiting for your transaction to be confirmed </font></h4>";
}
if($this->confirmed)
{
echo "<h4><font color=006400> Your transaction has been successfully confirmed! </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>
<div class='xmr-address'>
<span class='xmr-label'>To this address:</span>
<div class='xmr-address-box'>".$integrated_addr."</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>";
}
else
{
$order = wc_get_order($order_id);
$amount = floatval(preg_replace('#[^\d.]#', '', $order->get_total()));
$payment_id = $this->set_paymentid_cookie(8);
$currency = $order->get_currency();
$amount_xmr2 = $this->changeto($amount, $currency, $payment_id);
$order->update_meta_data( "Payment ID", $payment_id);
$order->update_meta_data( "Amount requested (XMR)", $amount_xmr2);
$order->save();
$uri = urlencode("monero:".$address."?tx_amount=".$amount_xmr2."&tx_payment_id=".$payment_id);
$array_integrated_address = $this->monero_daemon->make_integrated_address($payment_id);
if (!isset($array_integrated_address)) {
$this->log->add('Monero_Gateway', '[ERROR] Unable get integrated address');
// Seems that we can't connect with daemon, then set array_integrated_address, little hack
$array_integrated_address["integrated_address"] = $address;
}
$message = $this->verify_payment($payment_id, $amount_xmr2, $order);
if ($this->confirmed) {
$color = "006400";
} else {
$color = "DC143C";
}
echo "<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>
<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($size)
{
if (!isset($_COOKIE['payment_id'])) {
$payment_id = bin2hex(openssl_random_pseudo_bytes($size));
setcookie('payment_id', $payment_id, time() + 2700);
}
else{
$payment_id = $this->sanatize_id($_COOKIE['payment_id']);
}
return $payment_id;
}
public function sanatize_id($payment_id)
{
// Limit payment id to alphanumeric characters
$sanatized_id = preg_replace("/[^a-zA-Z0-9]+/", "", $payment_id);
return $sanatized_id;
}
public function changeto($amount, $currency, $payment_id)
{
global $wpdb;
// This will create a table named whatever the payment id is inside the database "WordPress"
$create_table = "CREATE TABLE IF NOT EXISTS $payment_id (
rate INT
)";
$wpdb->query($create_table);
$rows_num = $wpdb->get_results("SELECT count(*) as count FROM $payment_id");
if ($rows_num[0]->count > 0) // Checks if the row has already been created or not
{
$stored_rate = $wpdb->get_results("SELECT rate FROM $payment_id");
$stored_rate_transformed = $stored_rate[0]->rate / 100; //this will turn the stored rate back into a decimaled number
if (isset($this->discount)) {
$sanatized_discount = preg_replace('/[^0-9]/', '', $this->discount);
$discount_decimal = $sanatized_discount / 100;
$new_amount = $amount / $stored_rate_transformed;
$discount = $new_amount * $discount_decimal;
$final_amount = $new_amount - $discount;
$rounded_amount = round($final_amount, 12);
} else {
$new_amount = $amount / $stored_rate_transformed;
$rounded_amount = round($new_amount, 12); //the Monero wallet can't handle decimals smaller than 0.000000000001
}
} else // If the row has not been created then the live exchange rate will be grabbed and stored
{
$xmr_live_price = $this->retriveprice($currency);
$live_for_storing = $xmr_live_price * 100; //This will remove the decimal so that it can easily be stored as an integer
$wpdb->query("INSERT INTO $payment_id (rate) VALUES ($live_for_storing)");
if(isset($this->discount))
{
$new_amount = $amount / $xmr_live_price;
$discount = $new_amount * $this->discount / 100;
$discounted_price = $new_amount - $discount;
$rounded_amount = round($discounted_price, 12);
}
else
{
$new_amount = $amount / $xmr_live_price;
$rounded_amount = round($new_amount, 12);
}
}
return $rounded_amount;
}
// Check if we are forcing SSL on checkout pages
// Custom function not required by the Gateway
public function retriveprice($currency)
{
$api_link = 'https://min-api.cryptocompare.com/data/price?fsym=XMR&tsyms=BTC,USD,EUR,CAD,INR,GBP,COP,SGD' . ',' . $currency . '&extraParams=monero_woocommerce';
$xmr_price = file_get_contents($api_link);
$price = json_decode($xmr_price, TRUE);
if (!isset($price)) {
$this->log->add('Monero_Gateway', '[ERROR] Unable to get the price of Monero');
}
switch ($currency) {
case 'USD':
return $price['USD'];
case 'EUR':
return $price['EUR'];
case 'CAD':
return $price['CAD'];
case 'GBP':
return $price['GBP'];
case 'INR':
return $price['INR'];
case 'COP':
return $price['COP'];
case 'SGD':
return $price['SGD'];
case $currency:
return $price[$currency];
case 'XMR':
$price = '1';
return $price;
}
}
private function on_verified($payment_id, $amount_atomic_units, $order_id)
{
$message = "Payment has been received and confirmed. Thanks!";
$this->log->add('Monero_gateway', '[SUCCESS] Payment has been recorded. Congratulations!');
$this->confirmed = true;
$order = wc_get_order($order_id);
if($this->is_virtual_in_cart($order_id) == true){
$order->update_status('completed', __('Payment has been received.', 'monero_gateway'));
}
else{
$order->update_status('processing', __('Payment has been received.', 'monero_gateway')); // Show payment id used for order
}
global $wpdb;
$wpdb->query("DROP TABLE $payment_id"); // Drop the table from database after payment has been confirmed as it is no longer needed
$this->reloadTime = 3000000000000; // Greatly increase the reload time as it is no longer needed
return $message;
}
public function verify_payment($payment_id, $amount, $order_id)
{
/*
* function for verifying payments
* Check if a payment has been made with this payment id then notify the merchant
*/
$message = "We are waiting for your payment to be confirmed";
$amount_atomic_units = $amount * 1000000000000;
$get_payments_method = $this->monero_daemon->get_payments($payment_id);
if (isset($get_payments_method["payments"][0]["amount"])) {
if ($get_payments_method["payments"][0]["amount"] >= $amount_atomic_units)
{
$message = $this->on_verified($payment_id, $amount_atomic_units, $order_id);
}
if ($get_payments_method["payments"][0]["amount"] < $amount_atomic_units)
{
$totalPayed = $get_payments_method["payments"][0]["amount"];
$outputs_count = count($get_payments_method["payments"]); // number of outputs recieved with this payment id
$output_counter = 1;
while($output_counter < $outputs_count)
{
$totalPayed += $get_payments_method["payments"][$output_counter]["amount"];
$output_counter++;
}
if($totalPayed >= $amount_atomic_units)
{
$message = $this->on_verified($payment_id, $amount_atomic_units, $order_id);
}
}
}
return $message;
}
public function last_block_seen($height) // sometimes 2 blocks are mined within a few seconds of each other. Make sure we don't miss one
{
if (!isset($_COOKIE['last_seen_block']))
{
setcookie('last_seen_block', $height, time() + 2700);
return 0;
}
else{
$cookie_block = $_COOKIE['last_seen_block'];
$difference = $height - $cookie_block;
setcookie('last_seen_block', $height, time() + 2700);
return $difference;
}
}
public function verify_non_rpc($payment_id, $amount, $order_id, $accept_zero_conf = false)
{
$tools = new NodeTools($this->testnet);
$amount_atomic_units = $amount * 1000000000000;
$outputs = $tools->get_outputs($this->address, $this->viewKey, $accept_zero_conf);
$outs_count = count($outputs);
$i = 0;
$tx_hash;
if($outs_count != 0)
{
while($i < $outs_count )
{
if($outputs[$i]['payment_id'] == $payment_id)
{
if($outputs[$i]['amount'] >= $amount_atomic_units)
{
$this->on_verified($payment_id, $amount_atomic_units, $order_id);
return true;
}
}
$i++;
}
}
return false;
}
public function do_ssl_check()
{
if ($this->enabled == "yes" && !$this->get_option('onion_service')) {
if (get_option('woocommerce_force_ssl_checkout') == "no") {
echo "<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 cannot 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: https://monerointegrations.com
Description: Extends WooCommerce by Adding the Monero Gateway
Version: 2.0
Author: SerHack
Author URI: https://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&section=monero_gateway',
'',
plugins_url('monero/assets/monero_icon.png'),
56 // Position on menu, woocommerce has 55.5, products has 55.6
);
}

@ -1,9 +1,9 @@
=== Monero WooCommerce Extension ===
Contributors: serhack
Contributors: serhack, mosu-forge
Donate link: http://monerointegrations.com/donate.html
Tags: monero, woocommerce, integration, payment, merchant, cryptocurrency, accept monero, monero woocommerce
Requires at least: 4.0
Tested up to: 4.8
Tested up to: 4.9.8
Stable tag: trunk
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
@ -50,6 +50,9 @@ An extension to WooCommerce for accepting Monero as payment in your store.
= 0.2 =
* Bug fixes
= 0.3 =
* Complete rewrite of how the plugin handles payments
== Upgrade Notice ==
soon

@ -0,0 +1,82 @@
<table class="striped" style="width:100%" cellspacing="0" cellpadding="5">
<tr>
<td>Exchange rate</td>
<td>1 XMR = <?php echo $details['rate_formatted'].' '.$details['currency']; ?></td>
</tr>
<tr>
<td>Total amount</td>
<td><?php echo $details['amount_total_formatted']; ?> XMR</td>
</tr>
<tr>
<td>Total paid</td>
<td><?php echo $details['amount_paid_formatted']; ?> XMR</td>
</tr>
<tr>
<td>Total due</td>
<td><?php echo $details['amount_due_formatted']; ?> XMR</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); ?> XMR</td>
</tr>
<?php endforeach; ?>
</table>
<?php endif; ?>

@ -0,0 +1,54 @@
<?php foreach($errors as $error): ?>
<div class="error"><p><strong>Monero Gateway Error</strong>: <?php echo $error; ?></p></div>
<?php endforeach; ?>
<h1>Monero Gateway Settings</h1>
<?php if($confirm_type === 'monero-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 Monero payment gateway</a></h4>
<script>
function moneroUpdateFields() {
var confirmType = jQuery("#woocommerce_monero_gateway_confirm_type").val();
if(confirmType == "monero-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>

@ -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 Monero!</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']; ?> XMR
</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 XMR = <?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>

@ -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>

@ -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 Monero!</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> XMR
</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> XMR
</strong>
</li>
<li>
Total paid:
<strong>
<span id="monero_total_paid"></span> XMR
</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…
Cancel
Save