SuchWOW Gateway release

master
rapeafed 3 months ago
commit 01a3efbe9f

@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

8
.gitignore vendored

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

@ -0,0 +1,15 @@
# .travis.yml
language: php
sudo: false
php:
- 7.0
- 7.1
script:
- echo 'Everything Works. '
before_deploy:
- mkdir build

@ -0,0 +1 @@
,w,l,25.03.2024 03:54,file:///home/w/.config/libreoffice/4;

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2017-2018, Monero Integrations
Copyright (c) 2018, Ryo Currency Project
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,162 @@
#SuchWOW Gateway for WooCommerce
Attention by opening this site you consent, accept and agree to the Terms below
##Now WOW Meow accept Wownero payments in your WooCommerce store directly through your own private SuchWOW gateway.
SuchWOW Gateway is a WordPress plug-in that integrates Wownero as a payment method into your WooCommerce store.
##SuchWOW Gateway Features
* Payment clearance by your private Wownero inter-dimensional bank daemon which is accessed by SuchWOW Gateway trough your own RPC instance of your wowlet
* Alternatively payment clearance can be done through block explorer using view key, by default explorer.suchwow.xyz - the block explorer of the trusted meme authority suchwow.xyz
* Background payment validation
* Order status update 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
to your own wowlet via Wownero daemon
* Live Wownero price updates from TradeOgre.com 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 Wownero
* Shortcodes can be used to display Wownero exchange rate rate to USD or Wownero accepted badge.
Before activating your SuchWOW Gateway you will need
* WordPress website
* WooCommerce plug-in for WordPress
* This SuchWOW Gateway plug-in
* BCMath extension for PHP is activated (it usually is)
* If you will be using block explorer clearance (easy and less private) then you will need Wowlet or Wownero CLI Wallet to create wallet and get your address and viewkey
* If you want to use your own daemon to clear transactions (more private and slightly more complicated setup) then you will need Wownero CLI Wallet downloaded and installed nearby or on the same server with WordPress. You will need wownerod (Wownero daemon) and wownero-wallet-rpc from the Wownero CLI Wallet package
## Installing the plugin
### Semi-Automatic Method
1. Download SuchWOW Gateway plug-in zip file
2. Open WordPress
3. Go “PlugIns” on the left side when you login to your WordPress as admin.
4. Click “Add new Plugin” on top
5. Click “Upload Plugin” on top
6. Choose file zip file with SuchWOW Gateway plug in you downloaded
7. Click install now
If you are still reading this paragraph consider using Option 1 - default block explorer payment clearance using Wownero main address and viewkey for simplicity.
Automatic installation and updates are not available at this moment check back for updates.
### Manual Method
1. Download SuchWOW Gateway plugin from the [releases page](https://git.wownero.com/rapeafed/suchwow-gateway) or clone with `git clone https://git.wownero.com/rapeafed/suchwow-gateway`
2. Unzip or place the `suchwow-gateway` folder in the `wp-content/plugins` directory of
3. Activate "SuchWOW Gateway" in your WordPress admin/plugins dashboard.
4. 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.
## Option 1: Use your wallet address and viewkey
This is the easiest way to start accepting SuchWOW Gateway on your website. You'll need:
* Your Wownero wallet address starting with `Wo`
* Your wallet's secret viewkey
Then simply select the `viewkey` option in the settings page and paste your address and viewkey. You're all set!
Note on privacy: when transactions are validated with your private viewkey, your viewkey is sent to (but not stored on) explorer.suchwow.xyz 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 Option 2 - your own `wownero-wallet-rpc` instance.
## Option 2: Using `wownero-wallet-rpc`- your own RPC instance of your wallet
The most secure way to use SuchWOW Gateway and accept Wownero payments on your website. You'll need:
* Root access to your webserver
* Latest [Wownero CLI Wallet Binaries](https://git.wownero.com/wownero/releases)
1. Downloading or compile the Wownero binaries on your server
2. Run the Wownero daemon and wowlet rpc instance one of the following ways:
a. Install the [systemd unit files](https://git.wownero.com/rapeafed/suchwow-gateway/tree/master/assets/systemd-unit-files) - (make sure you check configuration there first)
or
b. Run `wownerod` and `wownero-wallet-rpc` with `screen` or `tmux`:
Examples of Wownero daemon command line:
When Wownero Daemon is on the same server:
./wownerod local daemon
When you are running Wownero Daemon on the other server (replace IP address):
./wownerod --rpc-bind-ip 22.22.22.22 --confirm-external-bind
Examples of wownero-wallet-rpc:
Replace wowletfile with your wowlet file name and ip with ip address of rpc instance. For best security only run rpc instance locally.
Add --rpc-bind-ip 127.0.0.1 -confirm-external-bind if RPC instance is on the other server
Add --daemon-address node.suchwow.xyz:34568 to use remote daemon
Add --log-level 4 for logging
Add --prompt-for-password to ask for password
./wownero-wallet-rpc --wallet-file ./wowletfile --password walletpassword --rpc-bind-port 14666 --disable-rpc-login
or
./wownero-wallet-rpc --wallet-file ./wowletfile --rpc-bind-port 18666 --disable-rpc-login --prompt-for-password
or
./wownero-wallet-rpc --wallet-file ./wowletfile --rpc-bind-ip 22.22.22.22 --rpc-bind-port 18666 --confirm-external-bind --disable-rpc-login --password walletpassword --daemon-address node.suchwow.xyz:34568 --log-level 4
If for some reason cant run wownerod (daemon) that you should be running anyway already - you can use a remote node with `wownero-wallet-rpc` by adding `--daemon-address node.suchwow.xyz:34568` to the `wownero-wallet-rpc.service` file.
!!!ATTENTION: using this option, while the most secure, requires you to run the Wownero 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.
## SuchWOW Gateway Configuration
* `Enable / Disable` - Turn on or off SuchWOW gateway. (Default: Disable)
* `Title` - Name of the payment gateway as displayed to the customer. (Default: SuchWOW Gateway)
* `Discount for using Wownero` - Percentage discount applied to orders for paying with Wownero. 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 receive before the order is marked as complete. Use `0` for nearly instant confirmation. (Default: 0)
* `Confirmation Type` - Confirm transactions with either your viewkey, or by using `wownero-wallet-rpc`. (Default: viewkey)
* `Wownero Address` (if confirmation type is viewkey) - Your public Wownero address starting with Wo. (No default)
* `Secret Viewkey` (if confirmation type is viewkey) - Your *private* viewkey (No default)
* `Wownero wallet RPC Host/IP` (if confirmation type is `wownero-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)
* `Wownero wallet RPC port` (if confirmation type is `wownero-wallet-rpc`) - Port the wallet rpc is bound to with the `--rpc-bind-port` argument. (Default 14666)
* `Testnet` - Check this to change the blockchain explorer links to the testnet explorer. (Default: unchecked) This feature is not available at this moment.
* `SSL warnings` - Check this to silence SSL warnings. (Default: unchecked)
* `Show QR Code` - Show payment QR codes. (Default: unchecked)
* `Show Prices in Wownero` - Convert all prices on the frontend to Wownero. Experimental feature, not available in the card at this moment, only use if you do not accept any other payment option. (Default: unchecked)
* `Display Decimals` (if show prices in Wownero 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 nanowow. (Default: 12)
## Shortcodes
This plugin makes available two shortcodes that you can use in your theme.
#### Live price shortcode
This will display the price of Wownero in the selected currency. If no currency is provided, the store's default currency will be used.
```
[wownero-price]
[wownero-price currency="USD"]
[wownero-price currency="WOWNERO"]
```
Should display:
```
1 WOW = 123.68000 USD
1 WOW = 123.68000 USD
1 WOW = 1 WOWNERO
```
#### Wownero accepted here badge
This will display a badge showing that you accept Wownero-currency.
`[wownero-accepted-here]`
![Wownero Accepted Here](/assets/images/wownero-accepted-here.png?raw=true "Wownero Accepted Here")
## Donations
rapeafed
WW3BuWSj3ei2zzEho3VPBtC5cYqFbPL589SQ1NKUWCF46YU7NzSpW173umfjfX88Tj2BqkkCaLoicbYG5jJ3zyK91xT1uZyf5
## SuchWOW Gateway Support and Integration
## Credits
monero-integrations
ryo-currency
## Terms
By opening this site you consent, accept and agree to the items 1 to 6 listed below that:
1. you and everyone else have no jurisdiction over rapeafed and any and all developers and users involved directly and indirectly in SuchWOW gateway and Wownero development and use
2. you act on your own free will and understand that code and safety instruction have been completed in a good faith to the best ability with quality of your experience and your safety in mind
3. you accept the license terms
4. you accept and agree that you receive no warrantees and guarantees expressed or implied including but not limited to the warranties of merchantability, fitness for a particular purpose, and noninfrigement
5. you have and will have no claims to and will indemnify and hold harmless rapeafed and any and all other developers and users of SuchWOW Gateway and Wownero against any and all direct, indirect and consequential damages

@ -0,0 +1,78 @@
#wownero_payment_messages > span {
display:none;
}
.wownero_details_row {
display: flex !important;
align-items: center;
margin:0 -8px;
}
.wownero_details_row > * {
padding:0 8px;
}
.wownero_details_left {
}
.wownero_details_main {
flex-grow: 1;
word-break:break-all;
}
.wownero_details_right.button-row {
display:flex;
margin-top: 5px;
align-self: self-start;
}
.wownero_details_right.button-row button {
width: 32px;
height: 32px;
padding: 6px 2px;
margin: 0 4px;
line-height:28px;
text-align:center;
}
#wownero_integrated_address {
line-height: 16px;
}
#wownero_qr_code_container {
position:fixed;
top:0;
left:0;
right:0;
bottom:0;
z-index:9999;
background:rgba(0,0,0,0.5);
}
#wownero_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;
}
#wownero_toast {
position: fixed;
z-index: 999;
top: 32px;
right: 12px;
}
#wownero_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;
}
#wownero_toast > div.success {
background: rgba(68, 190, 117, 0.8);
}
#wownero_toast > div.error {
background: rgba(195, 60, 60, 0.8);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 941 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 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,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,161 @@
/*
* Copyright (c) 2018, Ryo Currency Project
*/
function wownero_showNotification(message, type='success') {
var toast = jQuery('<div class="' + type + '"><span>' + message + '</span></div>');
jQuery('#wownero_toast').append(toast);
toast.animate({ "right": "12px" }, "fast");
setInterval(function() {
toast.animate({ "right": "-400px" }, "fast", function() {
toast.remove();
});
}, 2500)
}
function wownero_showQR(show=true) {
jQuery('#wownero_qr_code_container').toggle(show);
}
function wownero_fetchDetails() {
var data = {
'_': jQuery.now(),
'order_id': wownero_details.order_id
};
jQuery.get(wownero_ajax_url, data, function(response) {
if (typeof response.error !== 'undefined') {
console.log(response.error);
} else {
wownero_details = response;
wownero_updateDetails();
}
});
}
function wownero_updateDetails() {
var details = wownero_details;
jQuery('#wownero_payment_messages').children().hide();
switch(details.status) {
case 'unpaid':
jQuery('.wownero_payment_unpaid').show();
jQuery('.wownero_payment_expire_time').html(details.order_expires);
break;
case 'partial':
jQuery('.wownero_payment_partial').show();
jQuery('.wownero_payment_expire_time').html(details.order_expires);
break;
case 'paid':
jQuery('.wownero_payment_paid').show();
jQuery('.wownero_confirm_time').html(details.time_to_confirm);
jQuery('.button-row button').prop("disabled",true);
break;
case 'confirmed':
jQuery('.wownero_payment_confirmed').show();
jQuery('.button-row button').prop("disabled",true);
break;
case 'expired':
jQuery('.wownero_payment_expired').show();
jQuery('.button-row button').prop("disabled",true);
break;
case 'expired_partial':
jQuery('.wownero_payment_expired_partial').show();
jQuery('.button-row button').prop("disabled",true);
break;
}
jQuery('#wownero_exchange_rate').html('1 WOW = '+details.rate_formatted+' '+details.currency);
jQuery('#wownero_total_amount').html(details.amount_total_formatted);
jQuery('#wownero_total_paid').html(details.amount_paid_formatted);
jQuery('#wownero_total_due').html(details.amount_due_formatted);
jQuery('#wownero_integrated_address').html(details.integrated_address);
if(wownero_show_qr) {
var qr = jQuery('#wownero_qr_code').html('');
new QRCode(qr.get(0), details.qrcode_uri);
}
if(details.txs.length) {
jQuery('#wownero_tx_table').show();
jQuery('#wownero_tx_none').hide();
jQuery('#wownero_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="'+wownero_explorer_url+'/tx/'+tx.txid+'" target="_blank">'+tx.txid+'</a>'+
'</td>'+
'<td>'+height+'</td>'+
'<td>'+tx.amount_formatted+' Wownero</td>'+
'</tr>';
jQuery('#wownero_tx_table tbody').append(row);
}
} else {
jQuery('#wownero_tx_table').hide();
jQuery('#wownero_tx_none').show();
}
// Show state change notifications
var new_txs = details.txs;
var old_txs = wownero_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) {
wownero_showNotification('Transaction received for '+new_txs[i].amount_formatted+' Wownero');
}
}
}
if(details.status != wownero_order_state.status) {
switch(details.status) {
case 'paid':
wownero_showNotification('Your order has been paid in full');
break;
case 'confirmed':
wownero_showNotification('Your order has been confirmed');
break;
case 'expired':
case 'expired_partial':
wownero_showNotification('Your order has expired', 'error');
break;
}
}
wownero_order_state = {
status: wownero_details.status,
txs: wownero_details.txs
};
}
jQuery(document).ready(function($) {
if (typeof wownero_details !== 'undefined') {
wownero_order_state = {
status: wownero_details.status,
txs: wownero_details.txs
};
setInterval(wownero_fetchDetails, 30000);
wownero_updateDetails();
new ClipboardJS('.clipboard').on('success', function(e) {
e.clearSelection();
if(e.trigger.disabled) return;
switch(e.trigger.getAttribute('data-clipboard-target')) {
case '#wownero_integrated_address':
wownero_showNotification('Copied destination address!');
break;
case '#wownero_total_due':
wownero_showNotification('Copied total amount due!');
break;
}
e.clearSelection();
});
}
});

@ -0,0 +1,14 @@
[Unit]
Description=Wownero Wallet RPC
After=network.target wownerod.service
[Service]
User=wowneroservices
Group=wowneroservices
WorkingDirectory=/opt/wownero-wallets
Type=simple
ExecStart=/opt/wownero-bin/wownero-wallet-rpc --wallet-file /opt/wownero-wallets/woocommerce --rpc-bind-port 18080 --password-file /opt/wownero-wallets/woocommerce.password --disable-rpc-login --log-file /var/log/wownero-wallet.log
Restart=always
[Install]
WantedBy=multi-user.target

@ -0,0 +1,14 @@
[Unit]
Description=Wownero Full Node
After=network.target
[Service]
User=wowneroservices
Group=wowneroservices
WorkingDirectory=/opt/wownero-data-dir
Type=simple
LimitNOFILE=65535
ExecStart=/usr/bin/wownerod --log-file /var/log/wownerod.log --data-dir /opt/wownero-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 Wownero gateway
* Authors: mosu-forge
*/
defined( 'ABSPATH' ) || exit;
require_once('class-wownero-admin-payments-list.php');
if (class_exists('Wownero_Admin_Interface', false)) {
return new Wownero_Admin_Interface();
}
class Wownero_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(
'wownero_admin_order_details',
__('Wownero Gateway','wownero_gateway'),
array($this, 'meta_box_order_details'),
'shop_order',
'normal',
'high'
);
}
/**
* Meta box for order page
*/
public function meta_box_order_details($order) {
Wownero_Gateway::admin_order_page($order);
}
/**
* Add menu items.
*/
public function admin_menu() {
add_menu_page(
__('Wownero', 'wownero_gateway'),
__('Wownero', 'wownero_gateway'),
'manage_woocommerce',
'wownero_gateway',
array($this, 'orders_page'),
WOWNERO_GATEWAY_PLUGIN_URL.'/assets/images/wownero-icon-admin.png',
56 // Position on menu, woocommerce has 55.5, products has 55.6
);
add_submenu_page(
'wownero_gateway',
__('Payments', 'wownero_gateway'),
__('Payments', 'wownero_gateway'),
'manage_woocommerce',
'wownero_gateway_payments',
array($this, 'payments_page')
);
$settings_page = add_submenu_page(
'wownero_gateway',
__('Settings', 'wownero_gateway'),
__('Settings', 'wownero_gateway'),
'manage_options',
'wownero_gateway_settings',
array($this, 'settings_page')
);
add_action('load-'.$settings_page, array($this, 'settings_page_init'));
}
/**
* Remove duplicate sub-menu item
*/
public function admin_menu_update() {
global $submenu;
if (isset($submenu['wownero_gateway'])) {
unset($submenu['wownero_gateway'][0]);
}
}
/**
* Wownero payments page
*/
public function payments_page() {
$payments_list = new Wownero_Admin_Payments_List();
$payments_list->prepare_items();
$payments_list->display();
}
/**
* Wownero settings page
*/
public function settings_page() {
WC_Admin_Settings::output();
}
public function settings_page_init() {
global $current_tab, $current_section;
$current_section = 'wownero_gateway';
$current_tab = 'checkout';
// Include settings pages.
WC_Admin_Settings::get_settings_pages();
// Save settings if data has been posted.
if (apply_filters("woocommerce_save_settings_{$current_tab}_{$current_section}", !empty($_POST))) {
WC_Admin_Settings::save();
}
// Add any posted messages.
if (!empty($_GET['wc_error'])) {
WC_Admin_Settings::add_error(wp_kses_post(wp_unslash($_GET['wc_error'])));
}
if (!empty($_GET['wc_message'])) {
WC_Admin_Settings::add_message(wp_kses_post(wp_unslash($_GET['wc_message'])));
}
do_action('woocommerce_settings_page_init');
}
}
return new Wownero_Admin_Interface();

@ -0,0 +1,289 @@
<?php
/*
* Copyright (c) 2018, Ryo Currency Project
* Admin interface for Wownero gateway
* Authors: mosu-forge
*/
if(!class_exists('WP_List_Table')) {
require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php');
}
class Wownero_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(Wownero_Gateway::get_confirm_type() == 'wownero-wallet-rpc') {
$balance = Wownero_Gateway::admin_balance_info();
$balance_info = <<<HTML
<div style="border:1px solid #ddd;padding:5px 10px;">
Wallet height: {$balance['height']}</br>
Your balance is: {$balance['balance']}</br>
Unlocked balance: {$balance['unlocked_balance']}</br>
</div>
HTML;
} else {
$balance_info = '';
}
echo <<<HTML
<div class="wrap">
<h1 class="wp-heading-inline">Wownero Payments</h1>
$balance_info
<hr class="wp-header-end">
<ul class="subsubsub">
<li>
<a href="?page=wownero_gateway_payments&type=all" {$tab_info['all']['active']}>
All <span class="count">({$tab_info['all']['count']})</span>
</a> |
</li>
<li>
<a href="?page=wownero_gateway_payments&type=pending" {$tab_info['pending']['active']}>
Pending <span class="count">({$tab_info['pending']['count']})</span>
</a> |
</li>
<li>
<a href="?page=wownero_gateway_payments&type=paid" {$tab_info['paid']['active']}>
Received <span class="count">({$tab_info['paid']['count']})</span>
</a> |
</li>
<li>
<a href="?page=wownero_gateway_payments&type=confirmed" {$tab_info['confirmed']['active']}>
Confirmed <span class="count">({$tab_info['confirmed']['count']})</span>
</a> |
</li>
<li>
<a href="?page=wownero_gateway_payments&type=expired" {$tab_info['expired']['active']}>
Expired <span class="count">({$tab_info['expired']['count']})</span>
</a>
</li>
</ul>
<form id="wownero-payments-filter" method="get" style="display:none">
<p class="search-box">
<label class="screen-reader-text" for="post-search-input">Search payments:</label>
<input type="search" id="post-search-input" name="s" value="">
<input type="submit" id="search-submit" class="button" value="Search payments">
</p>
$hidden_fields
</form>
<h2 class="screen-reader-text">Wownero Payments List</h2>
<style>
#col_order_id { width: 150px; }
#col_payment_id { width: 150px; }
#col_height { width: 100px; }
#col_amount { width: 150px; }
</style>
<table class="wp-list-table widefat fixed striped posts">
HTML;
} else if ($which == "bottom") {
echo '<br class="clear"></div>';
}
}
/**
* Get column value.
*
* @param mixed $item Item being displayed.
* @param string $column_name Column name.
*/
public function column_default($item, $column_name) {
switch($column_name) {
case 'col_order_id':
// try...catch to prevent page breaking on deleted order
try {
echo $this->get_order_link($item->order_id);
}
catch(Exception $e) {
if ($e->getMessage() === "Invalid order.") {
echo "<p>Order has been deleted.</p>";
}
}
break;
case 'col_payment_id':
echo $item->payment_id;
break;
case 'col_txid':
$url = WOWNERO_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 Wownero_Gateway::format_wownero($item->amount).' Wownero';
break;
}
}
protected function get_order_link($order_id) {
$order = new WC_Order($order_id);
$buyer = '';
if($order->get_billing_first_name() || $order->get_billing_last_name()) {
$buyer = trim(sprintf(_x('%1$s %2$s', 'full name', 'woocommerce'), $order->get_billing_first_name(), $order->get_billing_last_name()));
} else if ($order->get_billing_company()) {
$buyer = trim($order->get_billing_company());
} else if ($order->get_customer_id()) {
$user = get_user_by('id', $order->get_customer_id());
$buyer = ucwords($user->display_name);
}
return '<a href="' . esc_url( admin_url( 'post.php?post=' . absint( $order->get_id() ) ) . '&action=edit' ) . '" class="order-view"><strong>#' . esc_attr( $order->get_order_number() ) . ' ' . esc_html( $buyer ) . '</strong></a>';
}
function get_columns() {
return $columns= array(
'col_order_id' => __('Order'),
'col_payment_id' => __('Payment ID'),
'col_txid' => __('Txid'),
'col_height' => __('Height'),
'col_amount' => __('Amount'),
);
}
public function get_sortable_columns() {
return array();
return $sortable = array(
'col_order_id' => 'col_order_id',
'col_payment_id' => 'payment_id',
'col_txid' => 'txid',
'col_height' => 'height',
'col_amount' => 'amount',
);
}
function prepare_items() {
$this->_column_headers = array($this->get_columns(), array(), $this->get_sortable_columns());
$current_page = absint($this->get_pagenum());
$per_page = 25;
$this->get_items($current_page, $per_page);
}
public function no_items() {
esc_html_e('No Wownero payments found', 'wownero_gateway');
}
protected function get_filter_vars() {
$type = isset($_GET['type']) ? $_GET['type'] : null;
return (object) array(
'type' => $type,
);
}
protected function get_item_count($type) {
global $wpdb;
$table_name_1 = $wpdb->prefix.'wownero_gateway_quotes';
$table_name_2 = $wpdb->prefix.'wownero_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());
//The query argument must have a placeholde to avoid injections ;)
$query_where = $wpdb->prepare(' AND pending = %d AND paid = %d ', array(1, 0));
break;
case 'paid':
//$query_where = $wpdb->prepare(' AND paid = 1 AND confirmed = 0 ', array());
$query_where = $wpdb->prepare(' AND paid = %d AND confirmed = %d ', array(1, 0));
break;
case 'confirmed':
//$query_where = $wpdb->prepare(' AND confirmed = 1 ', array());
$query_where = $wpdb->prepare(' AND confirmed = %d ', array(1));
break;
case 'expired':
//$query_where = $wpdb->prepare(' AND paid = 0 AND pending = 0 ', array());
$query_where = $wpdb->prepare(' AND paid = %d AND pending = %d ', array(0, 0));
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.'wownero_gateway_quotes';
$table_name_2 = $wpdb->prefix.'wownero_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,120 @@
<?php
defined( 'ABSPATH' ) || exit;
return array(
'enabled' => array(
'title' => __('Enable / Disable', 'wownero_gateway'),
'label' => __('Enable this payment gateway', 'wownero_gateway'),
'type' => 'checkbox',
'default' => 'no'
),
'title' => array(
'title' => __('Title', 'wownero_gateway'),
'type' => 'text',
'desc_tip' => __('Payment title the customer will see during the checkout process.', 'wownero_gateway'),
'default' => __('Wownero Gateway', 'wownero_gateway')
),
'description' => array(
'title' => __('Description', 'wownero_gateway'),
'type' => 'textarea',
'desc_tip' => __('Payment description the customer will see during the checkout process.', 'wownero_gateway'),
'default' => __('Pay securely using Wownero. You will be provided payment details after checkout.', 'wownero_gateway')
),
'discount' => array(
'title' => __('Discount for using Wownero', 'wownero_gateway'),
'desc_tip' => __('Provide a discount to your customers for making a private payment with Wownero', 'wownero_gateway'),
'description' => __('Enter a percentage discount (i.e. 5 for 5%) or leave this empty if you do not wish to provide a discount', 'wownero_gateway'),
'type' => __('number'),
'default' => '0'
),
'valid_time' => array(
'title' => __('Order valid time', 'wownero_gateway'),
'desc_tip' => __('Amount of time order is valid before expiring', 'wownero_gateway'),
'description' => __('Enter the number of seconds that the funds must be received in after order is placed. 3600 seconds = 1 hour', 'wownero_gateway'),
'type' => __('number'),
'default' => '3600'
),
'confirms' => array(
'title' => __('Number of confirmations', 'wownero_gateway'),
'desc_tip' => __('Number of confirms a transaction must have to be valid', 'wownero_gateway'),
'description' => __('Enter the number of confirms that transactions must have. Enter 0 to zero-confim. Each confirm will take approximately four minutes', 'wownero_gateway'),
'type' => __('number'),
'default' => '0'
),
'confirm_type' => array(
'title' => __('Confirmation Type', 'wownero_gateway'),
'desc_tip' => __('Select the method for confirming transactions', 'wownero_gateway'),
'description' => __('Select the method for confirming transactions', 'wownero_gateway'),
'type' => 'select',
'options' => array(
'viewkey' => __('viewkey', 'wownero_gateway'),
'wownero-wallet-rpc' => __('wownero-wallet-rpc', 'wownero_gateway')
),
'default' => 'viewkey'
),
'wownero_address' => array(
'title' => __('Wownero Address', 'wownero_gateway'),
'label' => __('Useful for people that have not a daemon online'),
'type' => 'text',
'desc_tip' => __('Wownero Wallet Address (WowneroL)', 'wownero_gateway')
),
'viewkey' => array(
'title' => __('Secret Viewkey', 'wownero_gateway'),
'label' => __('Secret Viewkey'),
'type' => 'text',
'desc_tip' => __('Your secret Viewkey', 'wownero_gateway')
),
'daemon_host' => array(
'title' => __('Wownero wallet RPC Host/IP', 'wownero_gateway'),
'type' => 'text',
'desc_tip' => __('This is the Daemon Host/IP to authorize the payment with', 'wownero_gateway'),
'default' => '127.0.0.1',
),
'daemon_port' => array(
'title' => __('Wownero wallet RPC port', 'wownero_gateway'),
'type' => __('number'),
'desc_tip' => __('This is the Wallet RPC port to authorize the payment with', 'wownero_gateway'),
'default' => '34568',
),
'testnet' => array(
'title' => __(' Testnet', 'wownero_gateway'),
'label' => __(' Check this if you are using testnet ', 'wownero_gateway'),
'type' => 'checkbox',
'description' => __('Advanced usage only', 'wownero_gateway'),
'default' => 'no'
),
'javascript' => array(
'title' => __(' Javascript', 'wownero_gateway'),
'label' => __(' Check this to ENABLE Javascript in Checkout page ', 'wownero_gateway'),
'type' => 'checkbox',
'default' => 'no'
),
'onion_service' => array(
'title' => __(' SSL warnings ', 'wownero_gateway'),
'label' => __(' Check to Silence SSL warnings', 'wownero_gateway'),
'type' => 'checkbox',
'description' => __('Check this box if you are running on an Onion Service (Suppress SSL errors)', 'wownero_gateway'),
'default' => 'no'
),
'show_qr' => array(
'title' => __('Show QR Code', 'wownero_gateway'),
'label' => __('Show QR Code', 'wownero_gateway'),
'type' => 'checkbox',
'description' => __('Enable this to show a QR code after checkout with payment details.'),
'default' => 'no'
),
'use_wownero_price' => array(
'title' => __('Show Prices in Wownero', 'wownero_gateway'),
'label' => __('Show Prices in Wownero', 'wownero_gateway'),
'type' => 'checkbox',
'description' => __('Enable this to convert ALL prices on the frontend to Wownero (experimental)'),
'default' => 'no'
),
'use_wownero_price_decimals' => array(
'title' => __('Display Decimals', 'wownero_gateway'),
'type' => __('number'),
'description' => __('Number of decimal places to display on frontend. Upon checkout exact price will be displayed.'),
'default' => 12,
),
);

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

@ -0,0 +1,312 @@
<?php
/*
Copyright (c) 2018 Wownero-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-wownero-base58.php');
class Wownero_Cryptonote
{
protected $ed25519;
public function __construct()
{
$this->ed25519 = new ed25519();
$this->base58 = new Wownero_base58();
$this->address_prefix = WOWNERO_GATEWAY_ADDRESS_PREFIX;
$this->address_prefix_integrated = WOWNERO_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 Wownero address from public keys
*
* @param string Public spend key
* @param string Public view key
*
* @return string Base58 encoded Wownero 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 Wownero address
*
* @param string A base58 encoded Wownero 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 Wownero address from seed
*
* @param string Hex string to use as seed
*
* @return string A base58 encoded Wownero 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,92 @@
<?php
/**
* wownero_explorer_tools.php
*
* Uses CURL to call API functions from the block explorer
* https://wowchain.net/
*
* @author Serhack
* @author cryptochangements
* @author mosu-forge
*
*/
defined( 'ABSPATH' ) || exit;
class Wownero_Explorer_Tools
{
private $url;
public function __construct($testnet = false)
{
$this->url = $testnet ? WOWNERO_GATEWAY_TESTNET_EXPLORER_URL : WOWNERO_GATEWAY_MAINNET_EXPLORER_URL;
$this->url = preg_replace("/\/+$/", "", $this->url);
}
private function call_api($endpoint)
{
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => $this->url . $endpoint,
));
$data = curl_exec($curl);
curl_close($curl);
return json_decode($data, true);
}
public function get_last_block_height()
{
$data = $this->call_api('/api/networkinfo');
if($data['status'] == 'success')
return $data['data']['height'] - 1;
else
return 0;
}
public function getheight()
{
return $this->get_last_block_height();
}
public function get_txs_from_block($height)
{
$data = $this->call_api("/api/search/$height");
if($data['status'] == 'success')
return $data['data']['txs'];
else
return [];
}
public function get_outputs($address, $viewkey)
{
$data = $this->call_api("/api/outputsblocks?address=$address&viewkey=$viewkey&limit=5&mempool=1");
if($data['status'] == 'success')
return $data['data']['outputs'];
else
return [];
}
public function check_tx($tx_hash, $address, $viewkey)
{
$data = $this->call_api("/api/outputs?txhash=$tx_hash&address=$address&viewkey=$viewkey&txprove=0");
if($data['status'] == 'success') {
foreach($data['data']['outputs'] as $output) {
if($output['match'])
return true;
}
} else {
return false;
}
}
function get_mempool_txs()
{
$data = $this->call_api('/api/mempool');
if($data['status'] == 'success')
return $data['txs'];
else
return [];
}
}

@ -0,0 +1,871 @@
<?php
/*
* Main Gateway of Wownero using either a local daemon or the explorer
* Authors: SerHack, cryptochangements, mosu-forge
*/
defined( 'ABSPATH' ) || exit;
require_once('class-wownero-cryptonote.php');
class Wownero_Gateway extends WC_Payment_Gateway
{
private static $_id = 'wownero_gateway';
private static $_title = 'Wownero Gateway';
private static $_method_title = 'Wownero Gateway';
private static $_method_description = 'Wownero Gateway Plug-in for WooCommerce.';
private static $_errors = [];
private static $discount = false;
private static $valid_time = null;
private static $confirms = null;
private static $confirm_type = null;
private static $address = null;
private static $viewkey = null;
private static $host = null;
private static $port = null;
private static $testnet = false;
private static $onion_service = false;
private static $show_qr = false;
private static $use_wownero_price = false;
private static $use_wownero_price_decimals = WOWNERO_GATEWAY_ATOMIC_UNITS;
private static $cryptonote;
private static $wownero_wallet_rpc;
private static $wownero_explorer_tools;
private static $log;
private static $currencies = array('AED', 'AFN', 'ALL', 'AMD', 'ANG', 'AOA', 'ARS', 'AUD', 'AWG', 'AZN', 'BAM', 'BBD', 'BDT', 'BGN', 'BHD', 'BIF', 'BMD', 'BND', 'BOB', 'BRL', 'BSD', 'BTC', 'BTN', 'BWP', 'BYN', 'BYR', 'BZD', 'CAD', 'CDF', 'CHF', 'CLF', 'CLP', 'CNY', 'COP', 'CRC', 'CUC', 'CUP', 'CVE', 'CZK', 'DJF', 'DKK', 'DOP', 'DZD', 'EGP', 'ERN', 'ETB', 'EUR', 'FJD', 'FKP', 'GBP', 'GEL', 'GGP', 'GHS', 'GIP', 'GMD', 'GNF', 'GTQ', 'GYD', 'HKD', 'HNL', 'HRK', 'HTG', 'HUF', 'IDR', 'ILS', 'IMP', 'INR', 'IQD', 'IRR', 'ISK', 'JEP', 'JMD', 'JOD', 'JPY', 'KES', 'KGS', 'KHR', 'KMF', 'KPW', 'KRW', 'KWD', 'KYD', 'KZT', 'LAK', 'LBP', 'LKR', 'LRD', 'LSL', 'LTL', 'LVL', 'LYD', 'MAD', 'MDL', 'MGA', 'MKD', 'MMK', 'MNT', 'MOP', 'MRO', 'MUR', 'MVR', 'MWK', 'MXN', 'MYR', 'MZN', 'NAD', 'NGN', 'NIO', 'NOK', 'NPR', 'NZD', 'OMR', 'PAB', 'PEN', 'PGK', 'PHP', 'PKR', 'PLN', 'PYG', 'QAR', 'RON', 'RSD', 'RUB', 'RWF', 'SAR', 'SBD', 'SCR', 'SDG', 'SEK', 'SGD', 'SHP', 'SLL', 'SOS', 'SRD', 'STD', 'SVC', 'SYP', 'SZL', 'THB', 'TJS', 'TMT', 'TND', 'TOP', 'TRY', 'TTD', 'TWD', 'TZS', 'UAH', 'UGX', 'USD', 'UYU', 'UZS', 'VEF', 'VND', 'VUV', 'WST', 'XAF', 'XAG', 'XAU', 'XCD', 'XDR', 'XOF', 'XPF', 'YER', 'ZAR', 'ZMK', 'ZMW', 'ZWL');
private static $rates = array();
private static $payment_details = array();
public function get_icon()
{
return apply_filters('woocommerce_gateway_icon', '<img src="'.WOWNERO_GATEWAY_PLUGIN_URL.'assets/images/wownero-icon.png"/>', $this->id);
}
function __construct($add_action=true)
{
$this->id = self::$_id;
$this->method_title = __(self::$_method_title, 'wownero_gateway');
$this->method_description = __(self::$_method_description, 'wownero_gateway');
$this->has_fields = false;
$this->supports = array(
'products',
'subscriptions',
'subscription_cancellation',
'subscription_suspension',
'subscription_reactivation',
'subscription_amount_changes',
'subscription_date_changes',
'subscription_payment_method_change'
);
$this->enabled = $this->get_option('enabled') == 'yes';
$this->init_form_fields();
$this->init_settings();
self::$_title = $this->settings['title'];
$this->title = $this->settings['title'];
$this->description = $this->settings['description'];
self::$discount = $this->settings['discount'];
self::$valid_time = $this->settings['valid_time'];
self::$confirms = $this->settings['confirms'];
self::$confirm_type = $this->settings['confirm_type'];
self::$address = $this->settings['wownero_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_wownero_price = $this->settings['use_wownero_price'] == 'yes';
self::$use_wownero_price_decimals = $this->settings['use_wownero_price_decimals'];
$explorer_url = self::$testnet ? WOWNERO_GATEWAY_TESTNET_EXPLORER_URL : WOWNERO_GATEWAY_MAINNET_EXPLORER_URL;
defined('WOWNERO_GATEWAY_EXPLORER_URL') || define('WOWNERO_GATEWAY_EXPLORER_URL', $explorer_url);
// Add the currency of the shop to $currencies array. Needed for do_update_event() function
$currency_shop = get_woocommerce_currency();
array_push(self::$currencies, $currency_shop);
if($add_action)
add_action('woocommerce_update_options_payment_gateways_'.$this->id, array($this, 'process_admin_options'));
// Initialize helper classes
self::$cryptonote = new Wownero_Cryptonote();
if(self::$confirm_type == 'wownero-wallet-rpc') {
require_once('class-wownero-wallet-rpc.php');
self::$wownero_wallet_rpc = new Wownero_Wallet_Rpc(self::$host, self::$port);
} else {
require_once('class-wownero-explorer-tools.php');
self::$wownero_explorer_tools = new Wownero_Explorer_Tools(self::$testnet);
}
self::$log = new WC_Logger();
}
public function init_form_fields()
{
$this->form_fields = include 'admin/wownero-gateway-admin-settings.php';
}
public function validate_wownero_address_field($key,$address)
{
if($this->settings['confirm_type'] == 'viewkey') {
if (strlen($address) == 97 && substr($address, 0, 2) == 'Wo')
if(self::$cryptonote->verify_checksum($address))
return $address;
self::$_errors[] = 'Wownero address is invalid';
}
return $address;
}
public function validate_viewkey_field($key,$viewkey)
{
if($this->settings['confirm_type'] == 'viewkey') {
if(preg_match('/^[a-z0-9]{64}$/i', $viewkey)) {
return $viewkey;
} else {
self::$_errors[] = 'Viewkey is invalid';
return '';
}
}
return $viewkey;
}
public function validate_confirms_field($key,$confirms)
{
if($confirms >= 0 && $confirms <= 60)
return $confirms;
self::$_errors[] = 'Number of confirms must be between 0 and 60';
}
public function validate_valid_time_field($key,$valid_time)
{
if($valid_time >= 600 && $valid_time < 86400*7)
return $valid_time;
self::$_errors[] = 'Order valid time must be between 600 (10 minutes) and 604800 (1 week)';
}
public function admin_options()
{
$confirm_type = self::$confirm_type;
if($confirm_type === 'wownero-wallet-rpc')
$balance = self::admin_balance_info();
$settings_html = $this->generate_settings_html(array(), false);
$errors = array_merge(self::$_errors, $this->admin_php_module_check(), $this->admin_ssl_check());
include WOWNERO_GATEWAY_PLUGIN_DIR . '/templates/wownero-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::$wownero_wallet_rpc->getbalance();
$height = self::$wownero_wallet_rpc->getheight();
if (!isset($wallet_amount)) {
self::$_errors[] = 'Cannot connect to wownero-wallet-rpc';
self::$log->add('Wownero_Payments', '[ERROR] Cannot connect to wownero-wallet-rpc');
return array(
'height' => 'Not Available',
'balance' => 'Not Available',
'unlocked_balance' => 'Not Available',
);
} else {
return array(
'height' => $height,
'balance' => self::format_wownero($wallet_amount['balance']).' Wownero',
'unlocked_balance' => self::format_wownero($wallet_amount['unlocked_balance']).' Wownero'
);
}
}
protected function admin_ssl_check()
{
$errors = array();
if ($this->enabled && !self::$onion_service)
if (get_option('woocommerce_force_ssl_checkout') == 'no')
$errors[] = sprintf('%s is enabled and WooCommerce is not forcing the SSL certificate on your checkout page. Please ensure that you have a valid SSL certificate and that you are <a href="%s">forcing the checkout pages to be secured.</a>', self::$_method_title, admin_url('admin.php?page=wc-settings&tab=checkout'));
return $errors;
}
protected function admin_php_module_check()
{
$errors = array();
if(!extension_loaded('bcmath'))
$errors[] = 'PHP extension bcmath must be installed';
return $errors;
}
public function process_payment($order_id)
{
global $wpdb;
$table_name = $wpdb->prefix.'wownero_gateway_quotes';
$order = wc_get_order($order_id);
if(self::$confirm_type != 'wownero-wallet-rpc') {
// Generate a unique payment id
do {
$payment_id = bin2hex(openssl_random_pseudo_bytes(8));
$query = $wpdb->prepare("SELECT COUNT(*) FROM $table_name WHERE payment_id=%s", array($payment_id));
$payment_id_used = $wpdb->get_var($query);
} while ($payment_id_used);
}
else {
// Generate subaddress
$payment_id = self::$wownero_wallet_rpc->create_address(0, 'Order: ' . $order_id);
if(isset($payment_id['address'])) {
$payment_id = $payment_id['address'];
}
else {
self::$log->add('Wownero_Gateway', 'Couldn\'t create subaddress for order ' . $order_id);
}
}
//$this->do_update_event(); //force to update rates for debug
$currency = $order->get_currency();
$rate = self::get_live_rate($currency);
$fiat_amount = $order->get_total('');
if($rate != 0)
$wownero_amount = 1e8 * $fiat_amount / $rate;
else{
// Critical, the price has not been retrivied.
$wownero_amount = -1;
$error_message = "The price for Wownero could not be retrieved. Please contact the merchant.";
self::$log->add('Wownero_Payments', "[ERROR] Impossible to retrieve price for order: ".$order_id);
wc_add_notice( __('Payment error:', 'woothemes') . $error_message, 'error' );
return;
}
if(self::$discount)
$wownero_amount = $wownero_amount - $wownero_amount * self::$discount / 100;
$wownero_amount = intval($wownero_amount * WOWNERO_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, $wownero_amount));
$wpdb->query($query);
$order->update_status('on-hold', __('Awaiting offline payment', 'wownero_gateway'));
wc_reduce_stock_levels( $order_id );
WC()->cart->empty_cart(); // Remove cart
return array(
'result' => 'success',
'redirect' => $this->get_return_url($order)
);
}
/*
* function for verifying payments
* This cron runs every 30 seconds
*/
public static function do_update_event()
{
global $wpdb;
// Get Live Price for WOW-USDT from TradeOgre
$api_link = 'https://tradeogre.com/api/v1/ticker/WOW-USDT';
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => $api_link,
));
$resp = curl_exec($curl);
curl_close($curl);
//var_dump(json_decode($resp, true));
$ogre_response=json_decode($resp, true);
//$price = json_decode($resp, true);
if($ogre_response!=NULL) {
$price=floatval($ogre_response['price']); //or $price=floatval($ogre_response['bid']);
$table_name = $wpdb->prefix.'wownero_gateway_live_rates';
if ($price>0) {
// shift decimal eight places for precise int storage
$rate = intval($price * 1e8);
$query = $wpdb->prepare("INSERT INTO $table_name (currency, rate, updated) VALUES (%s, %d, NOW()) ON DUPLICATE KEY UPDATE rate=%d, updated=NOW()", array('USD', $rate, $rate));
$result = $wpdb->query($query);
// ATTENTION 1 WOW = 1 WOW !!!!
$query = $wpdb->prepare("INSERT INTO $table_name (currency, rate, updated) VALUES (%s, %d, NOW()) ON DUPLICATE KEY UPDATE rate=%d, updated=NOW()", array('WOWNERO', 1, 1));
$result = $wpdb->query($query);
if(!$result){
self::$log->add('Wownero_Payments', "[ERROR] Impossible to write DB. Please check your DB connection or enable Debugging.");
}
}
}
else{
self::$log->add('Wownero_Payments', "[ERROR] Unable to fetch prices from TradeOgre.com.");
}
/* Coin Gecko requires API key - Using TradeOgre above
// Get Live Price
$currencies = implode(',', self::$currencies);
$api_link = 'https://api.coingecko.com/api/v3/simple/price?ids=wownero&vs_currencies='.$currencies;
$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)) {
$table_name = $wpdb->prefix.'wownero_gateway_live_rates';
foreach($price['wownero'] as $currency=>$rate) {
// shift decimal eight places for precise int storage
$rate = intval($rate * 1e8);
$query = $wpdb->prepare("INSERT INTO $table_name (currency, rate, updated) VALUES (%s, %d, NOW()) ON DUPLICATE KEY UPDATE rate=%d, updated=NOW()", array($currency, $rate, $rate));
$result = $wpdb->query($query);
if(!$result){
self::$log->add('Wownero_Payments', "[ERROR] Impossible to write DB. Please check your DB connection or enable Debugging.");
}
}
}
else{
self::$log->add('Wownero_Payments', "[ERROR] Unable to fetch prices from coingecko.com.");
}
End of CoinGecko */
// Get current network/wallet height
if(self::$confirm_type == 'wownero-wallet-rpc')
$height = self::$wownero_wallet_rpc->getheight();
else
$height = self::$wownero_explorer_tools->getheight();
set_transient('wownero_gateway_network_height', $height);
// Get pending payments
$table_name_1 = $wpdb->prefix.'wownero_gateway_quotes';
$table_name_2 = $wpdb->prefix.'wownero_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_wownero = $quote->amount_total;
if(self::$confirm_type == 'wownero-wallet-rpc')
$new_txs = self::check_payment_rpc($payment_id);
else
$new_txs = self::check_payment_explorer($payment_id);
foreach($new_txs as $new_tx) {
$is_new_tx = true;
foreach($old_txs as $old_tx) {
if($new_tx['txid'] == $old_tx->txid && $new_tx['amount'] == $old_tx->amount_paid) {
$is_new_tx = false;
break;
}
}
if($is_new_tx) {
$old_txs[] = (object) $new_tx;
}
$query = $wpdb->prepare("INSERT INTO $table_name_2 (payment_id, txid, amount, height) VALUES (%s, %s, %d, %d) ON DUPLICATE KEY UPDATE height=%d", array($payment_id, $new_tx['txid'], $new_tx['amount'], $new_tx['height'], $new_tx['height']));
$wpdb->query($query);
}
$txs = $old_txs;
$heights = array();
$amount_paid = 0;
foreach($txs as $tx) {
$amount_paid += $tx->amount;
$heights[] = $tx->height;
}
$paid = $amount_paid > $amount_wownero - WOWNERO_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('Wownero_Payments', "[SUCCESS] Payment has been confirmed for order id $order_id and payment id $payment_id");
$query = $wpdb->prepare("UPDATE $table_name_1 SET confirmed=1,paid=1,pending=0 WHERE payment_id=%s", array($payment_id));
$wpdb->query($query);
unset(self::$payment_details[$order_id]);
if(self::is_virtual_in_cart($order_id) == true){
$order->update_status('completed', __('Payment has been received.', 'wownero_gateway'));
} else {
$order->update_status('processing', __('Payment has been received.', 'wownero_gateway'));
}
} else if($paid) {
self::$log->add('Wownero_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('Wownero_Payments', "[FAILED] Payment has expired for order id $order_id and payment id $payment_id");
$query = $wpdb->prepare("UPDATE $table_name_1 SET pending=0 WHERE payment_id=%s", array($payment_id));
$wpdb->query($query);
unset(self::$payment_details[$order_id]);
$order->update_status('cancelled', __('Payment has expired.', 'wownero_gateway'));
}
}
}
}
protected static function check_payment_rpc($subaddress)
{
$txs = array();
$address_index = self::$wownero_wallet_rpc->get_address_index($subaddress);
if(isset($address_index['index']['minor'])){
$address_index = $address_index['index']['minor'];
}
else {
self::$log->add('Wownero_Gateway', '[ERROR] Couldn\'t get address index of subaddress: ' . $subaddress);
return $txs;
}
$payments = self::$wownero_wallet_rpc->get_transfers(array( 'in' => true, 'pool' => true, 'subaddr_indices' => array($address_index)));
if(isset($payments['in'])) {
foreach($payments['in'] as $payment) {
$txs[] = array(
'amount' => $payment['amount'],
'txid' => $payment['txid'],
'height' => $payment['height']
);
}
}
if(isset($payments['pool'])) {
foreach($payments['pool'] as $payment) {
$txs[] = array(
'amount' => $payment['amount'],
'txid' => $payment['txid'],
'height' => $payment['height']
);
}
}
return $txs;
}
public static function check_payment_explorer($payment_id)
{
$txs = array();
$outputs = self::$wownero_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.'wownero_gateway_quotes';
$table_name_2 = $wpdb->prefix.'wownero_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_wownero($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('wownero_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 * WOWNERO_GATEWAY_DIFFICULTY_TARGET);
$amount_total = $details[0]->amount_total;
$amount_due = max(0, $amount_total - $amount_paid);
$timestamp_created = new DateTime($details[0]->created);
$timestamp_now = new DateTime($details[0]->now);
$order_age_seconds = $timestamp_now->getTimestamp() - $timestamp_created->getTimestamp();
$order_expires_seconds = self::$valid_time - $order_age_seconds;
$address = self::$address;
$payment_id = self::sanatize_id($details[0]->payment_id);
if(self::$confirm_type == 'wownero-wallet-rpc') {
$integrated_addr = $payment_id;
} else {
if ($address) {
$decoded_address = self::$cryptonote->decode_address($address);
$pub_spendkey = $decoded_address['spendkey'];
$pub_viewkey = $decoded_address['viewkey'];
$integrated_addr = self::$cryptonote->integrated_addr_from_keys($pub_spendkey, $pub_viewkey, $payment_id);
} else {
self::$log->add('Wownero_Gateway', '[ERROR] Merchant has not set Wownero address');
return '[ERROR] Merchant has not set Wownero address';
}
}
$status = '';
$paid = $details[0]->paid == 1;
$confirmed = $details[0]->confirmed == 1;
$pending = $details[0]->pending == 1;
if($confirmed) {
$status = 'confirmed';
} else if($paid) {
$status = 'paid';
} else if($pending && $order_expires_seconds > 0) {
if(count($txs)) {
$status = 'partial';
} else {
$status = 'unpaid';
}
} else {
if(count($txs)) {
$status = 'expired_partial';
} else {
$status = 'expired';
}
}
$amount_formatted = self::format_wownero($amount_due);
$qrcode_uri = 'wownero:'.$integrated_addr.'?tx_amount='.$amount_formatted.'&tx_payment_id='.$payment_id;
$my_order_url = wc_get_endpoint_url('view-order', $order_id, wc_get_page_permalink('myaccount'));
$payment_details = array(
'order_id' => $order_id,
'payment_id' => $payment_id,
'integrated_address' => $integrated_addr,
'qrcode_uri' => $qrcode_uri,
'my_order_url' => $my_order_url,
'rate' => $details[0]->rate,
'rate_formatted' => sprintf('%.8f', $details[0]->rate / 1e8),
'currency' => $details[0]->currency,
'amount_total' => $amount_total,
'amount_paid' => $amount_paid,
'amount_due' => $amount_due,
'amount_total_formatted' => self::format_wownero($amount_total),
'amount_paid_formatted' => self::format_wownero($amount_paid),
'amount_due_formatted' => self::format_wownero($amount_due),
'status' => $status,
'created' => $details[0]->created,
'order_age' => $order_age_seconds,
'order_expires' => self::format_seconds_to_time($order_expires_seconds),
'blocks_to_confirm' => $blocks_to_confirm,
'time_to_confirm' => $time_to_confirm,
'txs' => $txs
);
self::$payment_details[$order_id] = $payment_details;
return $payment_details;
} else {
return '[ERROR] Quote not found';
}
}
public static function get_payment_details_ajax() {
$user = wp_get_current_user();
if($user === 0)
self::ajax_output(array('error' => '[ERROR] User not logged in'));
if(isset($_GET['order_id'])){
$order_id = preg_replace("/[^0-9]+/", "", $_GET['order_id']);
$order = wc_get_order($order_id);
if($order->get_customer_id() != $user->ID)
self::ajax_output(array('error' => '[ERROR] Order does not belong to this user'));
if($order->get_payment_method() != self::$_id)
self::ajax_output(array('error' => '[ERROR] Order not paid for with Wownero'));
$details = self::get_payment_details($order);
if(!is_array($details))
self::ajax_output(array('error' => $details));
self::ajax_output($details);
}
}
public static function ajax_output($response) {
header('Content-type: application/json');
if (ob_get_length() > 0){
ob_clean();
}
echo json_encode($response);
wp_die();
}
public static function admin_order_page($post)
{
$order = wc_get_order($post->ID);
if($order->get_payment_method() != self::$_id)
return;
$method_title = self::$_title;
$details = self::get_payment_details($order);
if(!is_array($details)) {
$error = $details;
include WOWNERO_GATEWAY_PLUGIN_DIR . '/templates/wownero-gateway/admin/order-history-error-page.php';
return;
}
include WOWNERO_GATEWAY_PLUGIN_DIR . '/templates/wownero-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 WOWNERO_GATEWAY_PLUGIN_DIR . '/templates/wownero-gateway/customer/order-error-page.php';
return;
}
$show_qr = self::$show_qr;
$details_json = json_encode($details);
$ajax_url = WC_AJAX::get_endpoint('wownero_gateway_payment_details');
include WOWNERO_GATEWAY_PLUGIN_DIR . '/templates/wownero-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 WOWNERO_GATEWAY_PLUGIN_DIR . '/templates/wownero-gateway/customer/order-email-error-block.php';
return;
}
include WOWNERO_GATEWAY_PLUGIN_DIR . '/templates/wownero-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_wownero_price()
{
return self::$use_wownero_price;
}
public static function convert_wc_price($price, $currency)
{
$rate = self::get_live_rate($currency);
$wownero_amount = intval(WOWNERO_GATEWAY_ATOMIC_UNITS_POW * 1e8 * $price / $rate) / WOWNERO_GATEWAY_ATOMIC_UNITS_POW;
$wownero_amount_formatted = sprintf('%.'.self::$use_wownero_price_decimals.'f', $wownero_amount);
return <<<HTML
<span class="woocommerce-Price-amount amount" data-price="$price" data-currency="$currency"
data-rate="$rate" data-rate-type="live">
$wownero_amount_formatted
<span class="woocommerce-Price-currencySymbol">WOW</span>
</span>
HTML;
}
public static function convert_wc_price_order($price_html, $order)
{
if($order->get_payment_method() != self::$_id)
return $price_html;
$order_id = $order->get_id();
$payment_details = self::get_payment_details($order_id);
if(!is_array($payment_details))
return $price_html;
// Experimental regex, may fail with other custom price formatters
$match_ok = preg_match('/data-price="([^"]*)"/', $price_html, $matches);
if($match_ok !== 1) // regex failed
return $price_html;
$price = array_pop($matches);
$currency = $payment_details['currency'];
$rate = $payment_details['rate'];
$wownero_amount = intval(WOWNERO_GATEWAY_ATOMIC_UNITS_POW * 1e8 * $price / $rate) / WOWNERO_GATEWAY_ATOMIC_UNITS_POW;
$wownero_amount_formatted = sprintf('%.'.WOWNERO_GATEWAY_ATOMIC_UNITS.'f', $wownero_amount);
return <<<HTML
<span class="woocommerce-Price-amount amount" data-price="$price" data-currency="$currency"
data-rate="$rate" data-rate-type="fixed">
$wownero_amount_formatted
<span class="woocommerce-Price-currencySymbol">WOW</span>
</span>
HTML;
}
public static function get_live_rate($currency)
{
if(isset(self::$rates[$currency]))
return self::$rates[$currency];
global $wpdb;
$table_name = $wpdb->prefix.'wownero_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_wownero($atomic_units) {
return sprintf(WOWNERO_GATEWAY_ATOMIC_UNITS_SPRINTF, $atomic_units / WOWNERO_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' : '');
}
}

@ -0,0 +1,371 @@
<?php
/**
* wownero_wallet_rpc
*
* Written using the JSON RPC specification -
* http://json-rpc.org/wiki/specification
*
* @author Kacper Rowinski <krowinski@implix.com>
* http://implix.com
* Modified to work with wownero-rpc wallet by Serhack and cryptochangements
* Modified to work with wownero-wallet-rpc wallet by mosu-forge
*/
defined( 'ABSPATH' ) || exit;
class Wownero_Wallet_Rpc
{
protected $url = null, $is_debug = false;
protected $curl_options = array(
CURLOPT_CONNECTTIMEOUT => 8,
CURLOPT_TIMEOUT => 8
);
protected $host;
protected $port;
private $httpErrors = array(
400 => '400 Bad Request',
401 => '401 Unauthorized',
403 => '403 Forbidden',
404 => '404 Not Found',
405 => '405 Method Not Allowed',
406 => '406 Not Acceptable',
408 => '408 Request Timeout',
500 => '500 Internal Server Error',
502 => '502 Bad Gateway',
503 => '503 Service Unavailable'
);
public function __construct($pHost, $pPort)
{
$this->validate(false === extension_loaded('curl'), 'The curl extension must be loaded to use this class!');
$this->validate(false === extension_loaded('json'), 'The json extension must be loaded to use this class!');
$this->host = $pHost;
$this->port = $pPort;
$this->url = $pHost . ':' . $pPort . '/json_rpc';
}
public function validate($pFailed, $pErrMsg)
{
if ($pFailed) {
if(is_admin()) echo $pErrMsg;
}
}
public function setDebug($pIsDebug)
{
$this->is_debug = !empty($pIsDebug);
return $this;
}
public function setCurlOptions($pOptionsArray)
{
if (is_array($pOptionsArray)) {
$this->curl_options = $pOptionsArray + $this->curl_options;
} else {
if(is_admin()) echo 'Invalid options type.';
}
return $this;
}
public function _print($json)
{
$json_encoded = json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if(is_admin()) echo $json_encoded;
}
public function _run($method, $params = null)
{
$result = $this->request($method, $params);
return $result; //the result is returned as an array
}
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');
// 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);
// check if response is correct
$this->validate(empty($responseDecoded['id']), 'Invalid response data structure: ' . $responseMessage);
//if(is_array($responseDecoded))
$this->validate($responseDecoded['id'] != $requestId, 'Request id: ' . $requestId . ' is different from Response id: ' . $responseDecoded['id']);
if (isset($responseDecoded['error'])) {
$errorMessage = 'Request have return error: ' . $responseDecoded['error']['message'] . '; ' . "\n" .
'Request: ' . $request . '; ';
if (isset($responseDecoded['error']['data'])) {
$errorMessage .= "\n" . 'Error data: ' . $responseDecoded['error']['data'];
}
$this->validate(!is_null($responseDecoded['error']), $errorMessage);
}
return $responseDecoded['result'];
}
protected function debug($pAdd, $pShow = false)
{
static $debug, $startTime;
// is_debug off return
if (false === $this->is_debug) {
return;
}
// add
$debug .= $pAdd;
// get starttime
$startTime = empty($startTime) ? array_sum(explode(' ', microtime())) : $startTime;
if (true === $pShow and !empty($debug)) {
// get endtime
$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";
if(is_admin()) echo nl2br($debug);
// send output immediately
flush();
// clean static
$debug = $startTime = null;
}
}
protected function & getResponse(&$pRequest)
{
// do the actual connection
$ch = curl_init();
if (!$ch) {
throw new RuntimeException('Could\'t initialize a cURL session');
}
curl_setopt($ch, CURLOPT_URL, $this->url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $pRequest);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
curl_setopt($ch, CURLOPT_ENCODING, 'gzip,deflate');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
if (!curl_setopt_array($ch, $this->curl_options)) {
throw new RuntimeException('Error while setting curl options');
}
// send the request
$response = curl_exec($ch);
// check http status code
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (isset($this->httpErrors[$httpCode])) {
if(is_admin())
echo 'Response Http Error - ' . $this->httpErrors[$httpCode];
}
// check for curl error
if (0 < curl_errno($ch)) {
if(is_admin())
echo '[ERROR] Failed to connect to wownero-wallet-rpc at ' . $this->host . ' port '. $this->port .'</br>';
}
// close the connection
curl_close($ch);
return $response;
}
//prints result as json
function getJsonLastErrorMsg()
{
if (!function_exists('json_last_error_msg')) {
function json_last_error_msg()
{
static $errors = array(
JSON_ERROR_NONE => 'No error',
JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch',
JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
JSON_ERROR_SYNTAX => 'Syntax error',
JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded'
);
$error = json_last_error();
return array_key_exists($error, $errors) ? $errors[$error] : 'Unknown error (' . $error . ')';
}
}
// Fix PHP 5.2 error caused by missing json_last_error function
if (function_exists('json_last_error')) {
return json_last_error() ? json_last_error_msg() : null;
} else {
return null;
}
}
/*
* The following functions can all be called to interact with the Wownero 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');
return $balance;
}
public function getheight()
{
$height = $this->_run('getheight');
return $height['height'];
}
public function incoming_transfer($type)
{
$incoming_parameters = array('transfer_type' => $type);
$incoming_transfers = $this->_run('incoming_transfers', $incoming_parameters);
return $incoming_transfers;
}
public function view_key()
{
$query_key = array('key_type' => 'view_key');
$query_key_method = $this->_run('query_key', $query_key);
return $query_key_method;
}
public function make_integrated_address($payment_id)
{
$integrate_address_parameters = array('payment_id' => $payment_id);
$integrate_address_method = $this->_run('make_integrated_address', $integrate_address_parameters);
return $integrate_address_method;
}
/* A payment id can be passed as a string
A random payment id will be generated if one is not given */
public function split_integrated_address($integrated_address)
{
if (!isset($integrated_address)) {
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);
return $split_methods;
}
}
public function make_uri($address, $amount, $recipient_name = null, $description = null)
{
// Convert to atomic units
$new_amount = $amount * WOWNERO_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);
return $uri;
}
public function parse_uri($uri)
{
$uri_parameters = array('uri' => $uri);
$parsed_uri = $this->_run('parse_uri', $uri_parameters);
return $parsed_uri;
}
public function transfer($amount, $address, $mixin = 12)
{
$new_amount = $amount * WOWNERO_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);
return $transfer_method;
}
public function get_payments($payment_id)
{
$get_payments_parameters = array('payment_id' => $payment_id);
$get_payments = $this->_run('get_payments', $get_payments_parameters);
if(isset($get_payments['payments']))
return $get_payments['payments'];
else
return array();
}
public function get_pool_payments($payment_id)
{
$get_payments_parameters = array('pool' => true);
$get_payments = $this->_run('get_transfers', $get_payments_parameters);
if(!isset($get_payments['pool']))
return array();
$payments = array();
foreach($get_payments['pool'] as $payment) {
if($payment['double_spend_seen'])continue;
if($payment['payment_id'] == $payment_id) {
$payment['tx_hash'] = $payment['txid'];
$payment['block_height'] = $payment['height'];
$payments[] = $payment;
}
}
return $payments;
}
public function get_all_payments($payment_id)
{
$confirmed_payments = $this->get_payments($payment_id);
$pool_payments = $this->get_pool_payments($payment_id);
return array_merge($pool_payments, $confirmed_payments);
}
public function get_bulk_payments($payment_id, $min_block_height)
{
$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;
}
public function get_transfers($arr)
{
$get_parameters = $arr;
$get_transfers = $this->_run('get_transfers', $get_parameters);
return $get_transfers;
}
public function get_address_index($subaddress)
{
$params = array('address' => $subaddress);
return $this->_run('get_address_index', $params);
}
public function store()
{
return $this->_run('store');
}
public function create_address($account_index = 0, $label = '')
{
$params = array('account_index' => $account_index, 'label' => $label);
$create_address_method = $this->_run('create_address', $params);
$save = $this->store(); // Save wallet state after subaddress creation
return $create_address_method;
}
}

@ -0,0 +1,324 @@
<?php /* -*- coding: utf-8; indent-tabs-mode: t; tab-width: 4 -*-
vim: ts=4 noet ai */
/**
Streamable SHA-3 for PHP 5.2+, with no lib/ext dependencies!
Copyright © 2018 Desktopd Developers
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
@license LGPL-3+
@file
*/
/**
SHA-3 (FIPS-202) for PHP strings (byte arrays) (PHP 5.2.1+)
PHP 7.0 computes SHA-3 about 4 times faster than PHP 5.2 - 5.6 (on x86_64)
Based on the reference implementations, which are under CC-0
Reference: http://keccak.noekeon.org/
This uses PHP's native byte strings. Supports 32-bit as well as 64-bit
systems. Also for LE vs. BE systems.
*/
defined( 'ABSPATH' ) || exit;
class SHA3 {
const SHA3_224 = 1;
const SHA3_256 = 2;
const SHA3_384 = 3;
const SHA3_512 = 4;
const SHAKE128 = 5;
const SHAKE256 = 6;
const KECCAK_256 = 7;
public static function init ($type = null) {
switch ($type) {
case self::SHA3_224: return new self (1152, 448, 0x06, 28);
case self::SHA3_256: return new self (1088, 512, 0x06, 32);
case self::SHA3_384: return new self (832, 768, 0x06, 48);
case self::SHA3_512: return new self (576, 1024, 0x06, 64);
case self::SHAKE128: return new self (1344, 256, 0x1f);
case self::SHAKE256: return new self (1088, 512, 0x1f);
case self::KECCAK_256: return new self (1088, 512, 0x01, 32);
}
throw new Exception ('Invalid operation type');
}
/**
Feed input to SHA-3 "sponge"
*/
public function absorb ($data) {
if (self::PHASE_INPUT != $this->phase) {
throw new Exception ('No more input accepted');
}
$rateInBytes = $this->rateInBytes;
$this->inputBuffer .= $data;
while (strlen ($this->inputBuffer) >= $rateInBytes) {
list ($input, $this->inputBuffer) = array (
substr ($this->inputBuffer, 0, $rateInBytes)
, substr ($this->inputBuffer, $rateInBytes));
$blockSize = $rateInBytes;
for ($i = 0; $i < $blockSize; $i++) {
$this->state[$i] = $this->state[$i] ^ $input[$i];
}
$this->state = self::keccakF1600Permute ($this->state);
$this->blockSize = 0;
}
return $this;
}
/**
Get hash output
*/
public function squeeze ($length = null) {
$outputLength = $this->outputLength; // fixed length output
if ($length && 0 < $outputLength && $outputLength != $length) {
throw new Exception ('Invalid length');
}
if (self::PHASE_INPUT == $this->phase) {
$this->finalizeInput ();
}
if (self::PHASE_OUTPUT != $this->phase) {
throw new Exception ('No more output allowed');
}
if (0 < $outputLength) {
$this->phase = self::PHASE_DONE;
return $this->getOutputBytes ($outputLength);
}
$blockLength = $this->rateInBytes;
list ($output, $this->outputBuffer) = array (
substr ($this->outputBuffer, 0, $length)
, substr ($this->outputBuffer, $length));
$neededLength = $length - strlen ($output);
$diff = $neededLength % $blockLength;
if ($diff) {
$readLength = (($neededLength - $diff) / $blockLength + 1)
* $blockLength;
} else {
$readLength = $neededLength;
}
$read = $this->getOutputBytes ($readLength);
$this->outputBuffer .= substr ($read, $neededLength);
return $output . substr ($read, 0, $neededLength);
}
// internally used
const PHASE_INIT = 1;
const PHASE_INPUT = 2;
const PHASE_OUTPUT = 3;
const PHASE_DONE = 4;
private $phase = self::PHASE_INIT;
private $state; // byte array (string)
private $rateInBytes; // positive integer
private $suffix; // 8-bit unsigned integer
private $inputBuffer = ''; // byte array (string): max length = rateInBytes
private $outputLength = 0;
private $outputBuffer = '';
public function __construct ($rate, $capacity, $suffix, $length = 0) {
if (1600 != ($rate + $capacity)) {
throw new Error ('Invalid parameters');
}
if (0 != ($rate % 8)) {
throw new Error ('Invalid rate');
}
$this->suffix = $suffix;
$this->state = str_repeat ("\0", 200);
$this->blockSize = 0;
$this->rateInBytes = $rate / 8;
$this->outputLength = $length;
$this->phase = self::PHASE_INPUT;
return;
}
protected function finalizeInput () {
$this->phase = self::PHASE_OUTPUT;
$input = $this->inputBuffer;
$inputLength = strlen ($input);
if (0 < $inputLength) {
$blockSize = $inputLength;
for ($i = 0; $i < $blockSize; $i++) {
$this->state[$i] = $this->state[$i] ^ $input[$i];
}
$this->blockSize = $blockSize;
}
// Padding
$rateInBytes = $this->rateInBytes;
$this->state[$this->blockSize] = $this->state[$this->blockSize]
^ chr ($this->suffix);
if (($this->suffix & 0x80) != 0
&& $this->blockSize == ($rateInBytes - 1)) {
$this->state = self::keccakF1600Permute ($this->state);
}
$this->state[$rateInBytes - 1] = $this->state[$rateInBytes - 1] ^ "\x80";
$this->state = self::keccakF1600Permute ($this->state);
}
protected function getOutputBytes ($outputLength) {
// Squeeze
$output = '';
while (0 < $outputLength) {
$blockSize = min ($outputLength, $this->rateInBytes);
$output .= substr ($this->state, 0, $blockSize);
$outputLength -= $blockSize;
if (0 < $outputLength) {
$this->state = self::keccakF1600Permute ($this->state);
}
}
return $output;
}
/**
1600-bit state version of Keccak's permutation
*/
protected static function keccakF1600Permute ($state) {
$lanes = str_split ($state, 8);
$R = 1;
$values = "\1\2\4\10\20\40\100\200";
for ($round = 0; $round < 24; $round++) {
// θ step
$C = array ();
for ($x = 0; $x < 5; $x++) {
// (x, 0) (x, 1) (x, 2) (x, 3) (x, 4)
$C[$x] = $lanes[$x] ^ $lanes[$x + 5] ^ $lanes[$x + 10]
^ $lanes[$x + 15] ^ $lanes[$x + 20];
}
for ($x = 0; $x < 5; $x++) {
//$D = $C[($x + 4) % 5] ^ self::rotL64 ($C[($x + 1) % 5], 1);
$D = $C[($x + 4) % 5] ^ self::rotL64One ($C[($x + 1) % 5]);
for ($y = 0; $y < 5; $y++) {
$idx = $x + 5 * $y; // x, y
$lanes[$idx] = $lanes[$idx] ^ $D;
}
}
unset ($C, $D);
// ρ and π steps
$x = 1;
$y = 0;
$current = $lanes[1]; // x, y
for ($t = 0; $t < 24; $t++) {
list ($x, $y) = array ($y, (2 * $x + 3 * $y) % 5);
$idx = $x + 5 * $y;
list ($current, $lanes[$idx]) = array ($lanes[$idx]
, self::rotL64 ($current
, (($t + 1) * ($t + 2) / 2) % 64));
}
unset ($temp, $current);
// χ step
$temp = array ();
for ($y = 0; $y < 5; $y++) {
for ($x = 0; $x < 5; $x++) {
$temp[$x] = $lanes[$x + 5 * $y];
}
for ($x = 0; $x < 5; $x++) {
$lanes[$x + 5 * $y] = $temp[$x]
^ ((~ $temp[($x + 1) % 5]) & $temp[($x + 2) % 5]);
}
}
unset ($temp);
// ι step
for ($j = 0; $j < 7; $j++) {
$R = (($R << 1) ^ (($R >> 7) * 0x71)) & 0xff;
if ($R & 2) {
$offset = (1 << $j) - 1;
$shift = $offset % 8;
$octetShift = ($offset - $shift) / 8;
$n = "\0\0\0\0\0\0\0\0";
$n[$octetShift] = $values[$shift];
$lanes[0] = $lanes[0]
^ $n;
//^ self::rotL64 ("\1\0\0\0\0\0\0\0", (1 << $j) - 1);
}
}
}
return implode ($lanes);
}
protected static function rotL64_64 ($n, $offset) {
return ($n << $offset) & ($n >> (64 - $offset));
}
/**
64-bit bitwise left rotation (Little endian)
*/
protected static function rotL64 ($n, $offset) {
//$n = (binary) $n;
//$offset = ((int) $offset) % 64;
//if (8 != strlen ($n)) throw new Exception ('Invalid number');
//if ($offset < 0) throw new Exception ('Invalid offset');
$shift = $offset % 8;
$octetShift = ($offset - $shift) / 8;
$n = substr ($n, - $octetShift) . substr ($n, 0, - $octetShift);
$overflow = 0x00;
for ($i = 0; $i < 8; $i++) {
$a = ord ($n[$i]) << $shift;
$n[$i] = chr (0xff & $a | $overflow);
$overflow = $a >> 8;
}
$n[0] = chr (ord ($n[0]) | $overflow);
return $n;
}
/**
64-bit bitwise left rotation (Little endian)
*/
protected static function rotL64One ($n) {
list ($n[0], $n[1], $n[2], $n[3], $n[4], $n[5], $n[6], $n[7])
= array (
chr (((ord ($n[0]) << 1) & 0xff) ^ (ord ($n[7]) >> 7))
,chr (((ord ($n[1]) << 1) & 0xff) ^ (ord ($n[0]) >> 7))
,chr (((ord ($n[2]) << 1) & 0xff) ^ (ord ($n[1]) >> 7))
,chr (((ord ($n[3]) << 1) & 0xff) ^ (ord ($n[2]) >> 7))
,chr (((ord ($n[4]) << 1) & 0xff) ^ (ord ($n[3]) >> 7))
,chr (((ord ($n[5]) << 1) & 0xff) ^ (ord ($n[4]) >> 7))
,chr (((ord ($n[6]) << 1) & 0xff) ^ (ord ($n[5]) >> 7))
,chr (((ord ($n[7]) << 1) & 0xff) ^ (ord ($n[6]) >> 7)));
return $n;
}
}

@ -0,0 +1,501 @@
<?php
/*
* The MIT License (MIT)
*
* Copyright (c) 2013 John Judy
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
//ini_set('xdebug.max_nesting_level', 0);
/**
* A PHP implementation of the Python ED25519 library
*
* @author johnj
*
* @link http://ed25519.cr.yp.to/software.html Other ED25519 implementations this is referenced from
*/
defined( 'ABSPATH' ) || exit;
class ed25519
{
public $b;
public $q;
public $l;
public $d;
public $I;
public $By;
public $Bx;
public $B;
private $gmp; // Is the GMP extension available?
public function __construct()
{
$this->b = 256;
$this->q = "57896044618658097711785492504343953926634992332820282019728792003956564819949"; //bcsub(bcpow(2, 255),19);
$this->l = "7237005577332262213973186563042994240857116359379907606001950938285454250989"; //bcadd(bcpow(2,252),27742317777372353535851937790883648493);
$this->d = "-4513249062541557337682894930092624173785641285191125241628941591882900924598840740"; //bcmul(-121665,$this->inv(121666));
$this->I = "19681161376707505956807079304988542015446066515923890162744021073123829784752"; //$this->expmod(2, bcdiv((bcsub($this->q,1)),4),$this->q);
$this->By = "46316835694926478169428394003475163141307993866256225615783033603165251855960"; //bcmul(4,$this->inv(5));
$this->Bx = "15112221349535400772501151409588531511454012693041857206046113283949847762202"; //$this->xrecover($this->By);
$this->B = array(
"15112221349535400772501151409588531511454012693041857206046113283949847762202",
"46316835694926478169428394003475163141307993866256225615783033603165251855960"
); //array(bcmod($this->Bx,$this->q),bcmod($this->By,$this->q));
$this->gmp = extension_loaded('gmp');
}
public function H($m)
{
return hash('sha512', $m, true);
}
//((n % M) + M) % M //python modulus craziness
public function pymod($x, $m)
{
if ($this->gmp) {
$mod = gmp_mod($x, $m);
if ($mod < 0) {
$mod = gmp_add($mod, $m);
}
} else {
$mod = bcmod($x, $m);
if ($mod < 0) {
$mod = bcadd($mod, $m);
}
}
return $mod;
}
public function expmod($b, $e, $m)
{
//if($e==0){return 1;}
if ($this->gmp) {
$t = gmp_powm($b, $e, $m);
if ($t < 0) {
$t = gmp_add($t, $m);
}
} else {
$t = bcpowmod($b, $e, $m);
if ($t[0] === '-') {
$t = bcadd($t, $m);
}
}
return $t;
}
public function inv($x)
{
if ($this->gmp) {
return $this->expmod($x, gmp_sub($this->q, 2), $this->q);
} else {
return $this->expmod($x, bcsub($this->q, 2), $this->q);
}
}
public function xrecover($y)
{
if ($this->gmp) {
$y2 = gmp_pow($y, 2);
$xx = gmp_mul(gmp_sub($y2, 1), $this->inv(gmp_add(gmp_mul($this->d, $y2), 1)));
$x = $this->expmod($xx, gmp_div(gmp_add($this->q, 3), 8, 0), $this->q);
if ($this->pymod(gmp_sub(gmp_pow($x, 2), $xx), $this->q) != 0) {
$x = $this->pymod(gmp_mul($x, $this->I), $this->q);
}
if (substr($x, -1)%2 != 0) {
$x = gmp_sub($this->q, $x);
}
} else {
$y2 = bcpow($y, 2);
$xx = bcmul(bcsub($y2, 1), $this->inv(bcadd(bcmul($this->d, $y2), 1)));
$x = $this->expmod($xx, bcdiv(bcadd($this->q, 3), 8, 0), $this->q);
if ($this->pymod(bcsub(bcpow($x, 2), $xx), $this->q) != 0) {
$x = $this->pymod(bcmul($x, $this->I), $this->q);
}
if (substr($x, -1)%2 != 0) {
$x = bcsub($this->q, $x);
}
}
return $x;
}
public function edwards($P, $Q)
{
if ($this->gmp) {
list($x1, $y1) = $P;
list($x2, $y2) = $Q;
$xmul = gmp_mul($x1, $x2);
$ymul = gmp_mul($y1, $y2);
$com = gmp_mul($this->d, gmp_mul($xmul, $ymul));
$x3 = gmp_mul(gmp_add(gmp_mul($x1, $y2), gmp_mul($x2, $y1)), $this->inv(gmp_add(1, $com)));
$y3 = gmp_mul(gmp_add($ymul, $xmul), $this->inv(gmp_sub(1, $com)));
return array($this->pymod($x3, $this->q), $this->pymod($y3, $this->q));
} else {
list($x1, $y1) = $P;
list($x2, $y2) = $Q;
$xmul = bcmul($x1, $x2);
$ymul = bcmul($y1, $y2);
$com = bcmul($this->d, bcmul($xmul, $ymul));
$x3 = bcmul(bcadd(bcmul($x1, $y2), bcmul($x2, $y1)), $this->inv(bcadd(1, $com)));
$y3 = bcmul(bcadd($ymul, $xmul), $this->inv(bcsub(1, $com)));
return array($this->pymod($x3, $this->q), $this->pymod($y3, $this->q));
}
}
public function scalarmult($P, $e)
{
if ($this->gmp) {
if ($e == 0) {
return array(0, 1);
}
$Q = $this->scalarmult($P, gmp_div($e, 2, 0));
$Q = $this->edwards($Q, $Q);
if (substr($e, -1)%2 == 1) {
$Q = $this->edwards($Q, $P);
}
} else {
if ($e == 0) {
return array(0, 1);
}
$Q = $this->scalarmult($P, bcdiv($e, 2, 0));
$Q = $this->edwards($Q, $Q);
if (substr($e, -1)%2 == 1) {
$Q = $this->edwards($Q, $P);
}
}
return $Q;
}
public function scalarloop($P, $e)
{
if ($this->gmp) {
$temp = array();
$loopE = $e;
while ($loopE > 0) {
array_unshift($temp, $loopE);
$loopE = gmp_div($loopE, 2, 0);
}
$Q = array();
foreach ($temp as $e) {
if ($e == 1) {
$Q = $this->edwards(array(0, 1), $P);
} elseif (substr($e, -1)%2 == 1) {
$Q = $this->edwards($this->edwards($Q, $Q), $P);
} else {
$Q = $this->edwards($Q, $Q);
}
}
} else {
$temp = array();
$loopE = $e;
while ($loopE > 0) {
array_unshift($temp, $loopE);
$loopE = bcdiv($loopE, 2, 0);
}
$Q = array();
foreach ($temp as $e) {
if ($e == 1) {
$Q = $this->edwards(array(0, 1), $P);
} elseif (substr($e, -1)%2 == 1) {
$Q = $this->edwards($this->edwards($Q, $Q), $P);
} else {
$Q = $this->edwards($Q, $Q);
}
}
}
return $Q;
}
public function bitsToString($bits)
{
$string = '';
for ($i = 0; $i < $this->b/8; $i++) {
$sum = 0;
for ($j = 0; $j < 8; $j++) {
$bit = $bits[$i*8+$j];
$sum += (int) $bit << $j;
}
$string .= chr($sum);
}
return $string;
}
public function dec2bin_i($decimal_i)
{
if ($this->gmp) {
$binary_i = '';
do {
$binary_i = substr($decimal_i, -1)%2 .$binary_i;
$decimal_i = gmp_div($decimal_i, '2', 0);
} while (gmp_cmp($decimal_i, '0'));
} else {
$binary_i = '';
do {
$binary_i = substr($decimal_i, -1)%2 .$binary_i;
$decimal_i = bcdiv($decimal_i, '2', 0);
} while (bccomp($decimal_i, '0'));
}
return ($binary_i);
}
public function encodeint($y)
{
$bits = substr(str_pad(strrev($this->dec2bin_i($y)), $this->b, '0', STR_PAD_RIGHT), 0, $this->b);
return $this->bitsToString($bits);
}
public function encodepoint($P)
{
list($x, $y) = $P;
$bits = substr(str_pad(strrev($this->dec2bin_i($y)), $this->b-1, '0', STR_PAD_RIGHT), 0, $this->b-1);
$bits .= (substr($x, -1)%2 == 1 ? '1' : '0');
return $this->bitsToString($bits);
}
public function bit($h, $i)
{
if ($this->gmp) {
return (ord($h[(int) gmp_div($i, 8, 0)]) >> substr($i, -3)%8) & 1;
} else {
return (ord($h[(int) bcdiv($i, 8, 0)]) >> substr($i, -3)%8) & 1;
}
}
/**
* Generates the public key of a given private key
*
* @param string $sk the secret key
*
* @return string
*/
public function publickey($sk)
{
if ($this->gmp) {
$h = $this->H($sk);
$sum = 0;
for ($i = 3; $i < $this->b-2; $i++) {
$sum = gmp_add($sum, gmp_mul(gmp_pow(2, $i), $this->bit($h, $i)));
}
$a = gmp_add(gmp_pow(2, $this->b-2), $sum);
$A = $this->scalarmult($this->B, $a);
$data = $this->encodepoint($A);
} else {
$h = $this->H($sk);
$sum = 0;
for ($i = 3; $i < $this->b-2; $i++) {
$sum = bcadd($sum, bcmul(bcpow(2, $i), $this->bit($h, $i)));
}
$a = bcadd(bcpow(2, $this->b-2), $sum);
$A = $this->scalarmult($this->B, $a);
$data = $this->encodepoint($A);
}
return $data;
}
public function Hint($m)
{
if ($this->gmp) {
$h = $this->H($m);
$sum = 0;
for ($i = 0; $i < $this->b*2; $i++) {
$sum = gmp_add($sum, gmp_mul(gmp_pow(2, $i), $this->bit($h, $i)));
}
} else {
$h = $this->H($m);
$sum = 0;
for ($i = 0; $i < $this->b*2; $i++) {
$sum = bcadd($sum, bcmul(bcpow(2, $i), $this->bit($h, $i)));
}
}
return $sum;
}
public function signature($m, $sk, $pk)
{
if ($this->gmp) {
$h = $this->H($sk);
$a = gmp_pow(2, (gmp_sub($this->b, 2)));
for ($i = 3; $i < $this->b-2; $i++) {
$a = gmp_add($a, gmp_mul(gmp_pow(2, $i), $this->bit($h, $i)));
}
$r = $this->Hint(substr($h, $this->b/8, ($this->b/4-$this->b/8)).$m);
$R = $this->scalarmult($this->B, $r);
$encR = $this->encodepoint($R);
$S = $this->pymod(gmp_add($r, gmp_mul($this->Hint($encR.$pk.$m), $a)), $this->l);
} else {
$h = $this->H($sk);
$a = bcpow(2, (bcsub($this->b, 2)));
for ($i = 3; $i < $this->b-2; $i++) {
$a = bcadd($a, bcmul(bcpow(2, $i), $this->bit($h, $i)));
}
$r = $this->Hint(substr($h, $this->b/8, ($this->b/4-$this->b/8)).$m);
$R = $this->scalarmult($this->B, $r);
$encR = $this->encodepoint($R);
$S = $this->pymod(bcadd($r, bcmul($this->Hint($encR.$pk.$m), $a)), $this->l);
}
return $encR.$this->encodeint($S);
}
public function isoncurve($P)
{
if ($this->gmp) {
list($x, $y) = $P;
$x2 = gmp_pow($x, 2);
$y2 = gmp_pow($y, 2);
return $this->pymod(gmp_sub(gmp_sub(gmp_sub($y2, $x2), 1), gmp_mul($this->d, gmp_mul($x2, $y2))), $this->q) == 0;
} else {
list($x, $y) = $P;
$x2 = bcpow($x, 2);
$y2 = bcpow($y, 2);
return $this->pymod(bcsub(bcsub(bcsub($y2, $x2), 1), bcmul($this->d, bcmul($x2, $y2))), $this->q) == 0;
}
}
public function decodeint($s)
{
if ($this->gmp) {
$sum = 0;
for ($i = 0; $i < $this->b; $i++) {
$sum = gmp_add($sum, gmp_mul(gmp_pow(2, $i), $this->bit($s, $i)));
}
} else {
$sum = 0;
for ($i = 0; $i < $this->b; $i++) {
$sum = bcadd($sum, bcmul(bcpow(2, $i), $this->bit($s, $i)));
}
}
return $sum;
}
/*
* def decodepoint(s):
y = sum(2**i * bit(s,i) for i in range(0,b-1))
x = xrecover(y)
if x & 1 != bit(s,b-1): x = q-x
P = [x,y]
if not isoncurve(P): raise Exception("decoding point that is not on curve")
return P
*/
public function decodepoint($s)
{
if ($this->gmp) {
$y = 0;
for ($i = 0; $i < $this->b-1; $i++) {
$y = gmp_add($y, gmp_mul(gmp_pow(2, $i), $this->bit($s, $i)));
}
$x = $this->xrecover($y);
if (substr($x, -1)%2 != $this->bit($s, $this->b-1)) {
$x = gmp_sub($this->q, $x);
}
$P = array($x, $y);
if (!$this->isoncurve($P)) {
throw new \Exception("Decoding point that is not on curve");
}
} else {
$y = 0;
for ($i = 0; $i < $this->b-1; $i++) {
$y = bcadd($y, bcmul(bcpow(2, $i), $this->bit($s, $i)));
}
$x = $this->xrecover($y);
if (substr($x, -1)%2 != $this->bit($s, $this->b-1)) {
$x = bcsub($this->q, $x);
}
$P = array($x, $y);
if (!$this->isoncurve($P)) {
throw new \Exception("Decoding point that is not on curve");
}
}
return $P;
}
public function checkvalid($s, $m, $pk)
{
if (strlen($s) != $this->b/4) {
throw new \Exception('Signature length is wrong');
}
if (strlen($pk) != $this->b/8) {
throw new \Exception('Public key length is wrong: '.strlen($pk));
}
$R = $this->decodepoint(substr($s, 0, $this->b/8));
try {
$A = $this->decodepoint($pk);
} catch (\Exception $e) {
return false;
}
$S = $this->decodeint(substr($s, $this->b/8, $this->b/4));
$h = $this->Hint($this->encodepoint($R).$pk.$m);
return $this->scalarmult($this->B, $S) == $this->edwards($R, $this->scalarmult($A, $h));
}
// The code below is by the Wownero-Integrations team
public function scalarmult_base($e)
{
if ($this->gmp) {
if ($e == 0) {
return array(0, 1);
}
$Q = $this->scalarmult($this->B, gmp_div($e, 2, 0));
$Q = $this->edwards($Q, $Q);
if (substr($e, -1)%2 == 1) {
$Q = $this->edwards($Q, $this->B);
}
} else {
if ($e == 0) {
return array(0, 1);
}
$Q = $this->scalarmult($this->B, bcdiv($e, 2, 0));
$Q = $this->edwards($Q, $Q);
if (substr($e, -1)%2 == 1) {
$Q = $this->edwards($Q, $this->B);
}
}
return $Q;
}
}

@ -0,0 +1,135 @@
=== Wownero WooCommerce Extension ===
Contributors: serhack, mosu-forge and Wownero Integrations contributors
Donate link: http://wownerointegrations.com/donate.html
Tags: wownero, woocommerce, integration, payment, merchant, cryptocurrency, accept wownero, wownero woocommerce
Requires at least: 4.0
Tested up to: 5.7.2
Stable tag: trunk
License: MIT license
License URI: https://github.com/wownero-integrations/wownerowp/blob/master/LICENSE
Wownero WooCommerce Extension is a Wordpress plugin that allows to accept wownero at WooCommerce-powered online stores.
= Benefits =
* Payment validation done through either `wownero-wallet-rpc` or the [wowchain.net blockchain explorer](https://wowchain.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 Wownero.
* Shortcodes! Display exchange rates in numerous currencies.
= Installation =
== Automatic method ==
In the "Add Plugins" section of the WordPress admin UI, search for "wownero" and click the Install Now button next to "Wownero WooCommerce Extension" by mosu-forge, SerHack. This will enable auto-updates, but only for official releases, so if you need to work from git master or your local fork, please use the manual method below.
== Manual method ==
* Download the plugin from the releases page (https://github.com/wownero-integrations/wownerowp) or clone with `git clone https://github.com/wownero-integrations/wownerowp`
* Unzip or place the `wownero-woocommerce-gateway` folder in the `wp-content/plugins` directory.
* Activate "Wownero 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.
= Configuration =
== Option 1: Use your wallet address and viewkey ==
This is the easiest way to start accepting Wownero on your website. You'll need:
* Your Wownero wallet address starting with `4`
* Your wallet's secret viewkey
Then simply select the `viewkey` option in the settings page and paste your address and viewkey. You're all set!
Note on privacy: when you validate transactions with your private viewkey, your viewkey is sent to (but not stored on) wowchain.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 `wownero-wallet-rpc` instance.
== Option 2: Using wownero wallet rpc ==
The most secure way to accept Wownero on your website. You'll need:
* Root access to your webserver
* Latest [Wownero-currency binaries](https://github.com/wownero-project/wownero/releases)
After downloading (or compiling) the Wownero binaries on your server, install the [systemd unit files](https://github.com/wownero-integrations/wownerowp/tree/master/assets/systemd-unit-files) or run `wownerod` and `wownero-wallet-rpc` with `screen` or `tmux`. You can skip running `wownerod` by using a remote node with `wownero-wallet-rpc` by adding `--daemon-address node.wowneroworld.com:18089` to the `wownero-wallet-rpc.service` file.
Note on security: using this option, while the most secure, requires you to run the Wownero 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.
== Remove plugin ==
1. Deactivate plugin through the 'Plugins' menu in WordPress
2. Delete plugin through the 'Plugins' menu in WordPress
== Screenshots ==
1. Wownero Payment Box
2. Wownero Options
== Changelog ==
= 0.1 =
* First version ! Yay!
= 1.0 =
* Added the view key option
= 2.1 =
* Verify transactions without wownero-wallet-rpc
* Optionally accept zero confirmation transactions
* bug fixing
= 2.2 =
* Fix some bugs
= 2.3 =
* Bug fixing
= 3.0.0 =
Huge shoutout to mosu-forge who contributed a lot to make 3.0 possible.
* Ability to set number of confirms: 0 for zero conf, up to 60.
* Amount owed in WOW gets locked in after the order for a configurable amount of time after which the order is invalid, default 60 minutes.
* Shows transactions received along with the number of confirms right on the order success page, auto-updates through AJAX.
* QR code generation is done with Javascript instead of sending payment details to a 3rd party.
* Admin page for showing all transactions made to the wallet.
* Logic is done via cron, instead of the user having to stay on the order page until payment is confirmed.
* Payment details (along with the txid) are always visible on the customer's account dashboard on the my orders section.
* Live prices are also run via cron, shortcodes for showing exchange rates.
* Properly hooks into order confirmation email page.
= 3.0.1 =
* Fixed the incorrect generation of integrated addresses;
= 3.0.2 =
* Fixed the problem of 'hard-coded' prices which causes a division by zero: now any currencies supported by cryptocompare API should work;
= 3.0.3 =
* Fixed the problem related to explorer;
= 3.0.4 =
* Bug fixing;
= 3.0.5 =
* Removed cryptocompare.com API and switched to CoinGecko
== Upgrade Notice ==
soon
== Frequently Asked Questions ==
* What is Wownero ?
Wownero is completely private, cryptographically secure, digital cash used across the globe. See https://getwownero.org for more information
* What is a Wownero wallet?
A Wownero wallet is a piece of software that allows you to store your funds and interact with the Wownero network. You can get a Wownero wallet from https://getwownero.org/downloads
* What is wownero-wallet-rpc ?
The wownero-wallet-rpc is an RPC server that will allow this plugin to communicate with the Wownero network. You can download it from https://getwownero.org/downloads with the command-line tools.
* Why do I see `[ERROR] Failed to connect to wownero-wallet-rpc at localhost port 18080
Syntax error: Invalid response data structure: Request id: 1 is different from Response id: ` ?
This is most likely because this plugin can not reach your wownero-wallet-rpc. Make sure that you have supplied the correct host IP and port to the plugin in their fields. If your wownero-wallet-rpc is on a different server than your wordpress site, make sure that the appropriate port is open with port forwarding enabled.

@ -0,0 +1,250 @@
<?php
/*
* Plugin Name: SuchWOW Wownero Gateway
* Plugin URI: https://git.wownero.com/rapeafed/wownero-gateway-plugin.html
* Description: Accept Wownero payments in your WooCommerce store
* Author: rapeafed, mosu-forge, SerHack
* Author URI: http://suchwowxys.com
* Version: 14.22.666
*/
// 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('WOWNERO_GATEWAY_MAINNET_EXPLORER_URL', 'https://explorer.suchwow.xyz/');
define('WOWNERO_GATEWAY_TESTNET_EXPLORER_URL', 'https://testnet.suchwow.xyz/');
define('WOWNERO_GATEWAY_ADDRESS_PREFIX', 0x1032);
define('WOWNERO_GATEWAY_ADDRESS_PREFIX_INTEGRATED', 0x1A9A);
define('WOWNERO_GATEWAY_ATOMIC_UNITS', 11);
define('WOWNERO_GATEWAY_ATOMIC_UNIT_THRESHOLD', 10); // Amount under in atomic units payment is valid
define('WOWNERO_GATEWAY_DIFFICULTY_TARGET', 300);
// Do not edit these constants
define('WOWNERO_GATEWAY_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WOWNERO_GATEWAY_PLUGIN_URL', plugin_dir_url(__FILE__));
define('WOWNERO_GATEWAY_ATOMIC_UNITS_POW', pow(10, WOWNERO_GATEWAY_ATOMIC_UNITS));
define('WOWNERO_GATEWAY_ATOMIC_UNITS_SPRINTF', '%.'.WOWNERO_GATEWAY_ATOMIC_UNITS.'f');
// Include our Gateway Class and register Payment Gateway with WooCommerce
add_action('plugins_loaded', 'wownero_init', 1);
function wownero_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-wownero-gateway.php');
// Create a new instance of the gateway so we have static variables set up
new Wownero_Gateway($add_action=false);
// Include our Admin interface class
require_once('include/admin/class-wownero-admin-interface.php');
add_filter('woocommerce_payment_gateways', 'wownero_gateway');
function wownero_gateway($methods) {
$methods[] = 'Wownero_Gateway';
return $methods;
}
add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'wownero_payment');
function wownero_payment($links) {
$plugin_links = array(
'<a href="'.admin_url('admin.php?page=wownero_gateway_settings').'">'.__('Settings', 'wownero_gateway').'</a>'
);
return array_merge($plugin_links, $links);
}
add_filter('cron_schedules', 'wownero_cron_add_one_minute');
function wownero_cron_add_one_minute($schedules) {
$schedules['one_minute'] = array(
'interval' => 60,
'display' => __('Once every minute', 'wownero_gateway')
);
return $schedules;
}
add_action('wp', 'wownero_activate_cron');
function wownero_activate_cron() {
if(!wp_next_scheduled('wownero_update_event')) {
wp_schedule_event(time(), 'one_minute', 'wownero_update_event');
}
}
add_action('wownero_update_event', 'wownero_update_event');
function wownero_update_event() {
Wownero_Gateway::do_update_event();
}
add_action('woocommerce_thankyou_'.Wownero_Gateway::get_id(), 'wownero_order_confirm_page');
add_action('woocommerce_order_details_after_order_table', 'wownero_order_page');
add_action('woocommerce_email_after_order_table', 'wownero_order_email');
function wownero_order_confirm_page($order_id) {
Wownero_Gateway::customer_order_page($order_id);
}
function wownero_order_page($order) {
if(!is_wc_endpoint_url('order-received'))
Wownero_Gateway::customer_order_page($order);
}
function wownero_order_email($order) {
Wownero_Gateway::customer_order_email($order);
}
add_action('wc_ajax_wownero_gateway_payment_details', 'wownero_get_payment_details_ajax');
function wownero_get_payment_details_ajax() {
Wownero_Gateway::get_payment_details_ajax();
}
add_filter('woocommerce_currencies', 'wownero_add_currency');
function wownero_add_currency($currencies) {
$currencies['Wownero'] = __('Wownero', 'wownero_gateway');
return $currencies;
}
add_filter('woocommerce_currency_symbol', 'wownero_add_currency_symbol', 10, 2);
function wownero_add_currency_symbol($currency_symbol, $currency) {
switch ($currency) {
case 'Wownero':
$currency_symbol = 'WOW';
break;
}
return $currency_symbol;
}
if(Wownero_Gateway::use_wownero_price()) {
// This filter will replace all prices with amount in Wownero (live rates)
add_filter('wc_price', 'wownero_live_price_format', 10, 3);
function wownero_live_price_format($price_html, $price_float, $args) {
$price_float = wc_format_decimal($price_float);
if(!isset($args['currency']) || !$args['currency']) {
global $woocommerce;
$currency = strtoupper(get_woocommerce_currency());
} else {
$currency = strtoupper($args['currency']);
}
return Wownero_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', 'wownero_order_item_price_format', 10, 3);
function wownero_order_item_price_format($price_html, $item, $order) {
return Wownero_Gateway::convert_wc_price_order($price_html, $order);
}
add_filter('woocommerce_get_formatted_order_total', 'wownero_order_total_price_format', 10, 2);
function wownero_order_total_price_format($price_html, $order) {
return Wownero_Gateway::convert_wc_price_order($price_html, $order);
}
add_filter('woocommerce_get_order_item_totals', 'wownero_order_totals_price_format', 10, 3);
function wownero_order_totals_price_format($total_rows, $order, $tax_display) {
foreach($total_rows as &$row) {
$price_html = $row['value'];
$row['value'] = Wownero_Gateway::convert_wc_price_order($price_html, $order);
}
return $total_rows;
}
}
add_action('wp_enqueue_scripts', 'wownero_enqueue_scripts');
function wownero_enqueue_scripts() {
if(Wownero_Gateway::use_wownero_price())
wp_dequeue_script('wc-cart-fragments');
if(Wownero_Gateway::use_qr_code())
wp_enqueue_script('wownero-qr-code', WOWNERO_GATEWAY_PLUGIN_URL.'assets/js/qrcode.min.js');
wp_enqueue_script('wownero-clipboard-js', WOWNERO_GATEWAY_PLUGIN_URL.'assets/js/clipboard.min.js');
wp_enqueue_script('wownero-gateway', WOWNERO_GATEWAY_PLUGIN_URL.'assets/js/wownero-gateway-order-page.js');
wp_enqueue_style('wownero-gateway', WOWNERO_GATEWAY_PLUGIN_URL.'assets/css/wownero-gateway-order-page.css');
}
// [wownero-price currency="USD"]
// currency: BTC, GBP, etc
// if no none, then default store currency
function wownero_price_func( $atts ) {
global $woocommerce;
$a = shortcode_atts( array(
'currency' => get_woocommerce_currency()
), $atts );
$currency = strtoupper($a['currency']);
$rate = Wownero_Gateway::get_live_rate($currency);
if($currency == 'BTC')
$rate_formatted = sprintf('%.8f', $rate / 1e8);
else
$rate_formatted = sprintf('%.5f', $rate / 1e8);
return "<span class=\"wownero-price\">1 WOW = $rate_formatted $currency</span>";
}
add_shortcode('wownero-price', 'wownero_price_func');
// [wownero-accepted-here]
function wownero_accepted_func() {
return '<img src="'.WOWNERO_GATEWAY_PLUGIN_URL.'assets/images/wownero-accepted-here.png" />';
}
add_shortcode('wownero-accepted-here', 'wownero_accepted_func');
}
register_deactivation_hook(__FILE__, 'wownero_deactivate');
function wownero_deactivate() {
$timestamp = wp_next_scheduled('wownero_update_event');
wp_unschedule_event($timestamp, 'wownero_update_event');
}
register_activation_hook(__FILE__, 'wownero_install');
function wownero_install() {
global $wpdb;
require_once( ABSPATH . '/wp-admin/includes/upgrade.php' );
$charset_collate = $wpdb->get_charset_collate();
$table_name = $wpdb->prefix . "wownero_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(97) 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 . "wownero_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(97) 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 . "wownero_gateway_live_rates";
if($wpdb->get_var("show tables like '$table_name'") != $table_name) {
$sql = "CREATE TABLE $table_name (
currency VARCHAR(7) DEFAULT '' NOT NULL,
rate BIGINT UNSIGNED DEFAULT 0 NOT NULL,
updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (currency)
) $charset_collate;";
dbDelta($sql);
}
}

@ -0,0 +1,82 @@
<table class="striped" style="width:100%" cellspacing="0" cellpadding="5">
<tr>
<td>Exchange rate</td>
<td>1 WOW = <?php echo $details['rate_formatted'].' '.$details['currency']; ?></td>
</tr>
<tr>
<td>Total amount</td>
<td><?php echo $details['amount_total_formatted']; ?> WOW</td>
</tr>
<tr>
<td>Total paid</td>
<td><?php echo $details['amount_paid_formatted']; ?> WOW</td>
</tr>
<tr>
<td>Total due</td>
<td><?php echo $details['amount_due_formatted']; ?> WOW</td>
</tr>
<tr>
<td>Order age</td>
<td><?php echo Wownero_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 WOWNERO_GATEWAY_EXPLORER_URL.'tx/'.$tx['txid']; ?>" target="_blank"><?php echo $tx['txid']; ?></a>
</td>
<td><?php echo $tx['height']; ?></td>
<td><?php echo sprintf(WOWNERO_GATEWAY_ATOMIC_UNITS_SPRINTF, $tx['amount'] / WOWNERO_GATEWAY_ATOMIC_UNITS_POW); ?> WOW</td>
</tr>
<?php endforeach; ?>
</table>
<?php endif; ?>

@ -0,0 +1,54 @@
<?php foreach($errors as $error): ?>
<div class="error"><p><strong>Wownero Gateway Error</strong>: <?php echo $error; ?></p></div>
<?php endforeach; ?>
<h1>Wownero Gateway Settings</h1>
<?php if($confirm_type === 'wownero-wallet-rpc'): ?>
<div style="border:1px solid #ddd;padding:5px 10px;">
<?php
echo 'Wallet height: ' . $balance['height'] . '</br>';
echo 'Your balance is: ' . $balance['balance'] . '</br>';
echo 'Unlocked balance: ' . $balance['unlocked_balance'] . '</br>';
?>
</div>
<?php endif; ?>
<table class="form-table">
<?php echo $settings_html ?>
</table>
<h4><a href="https://github.com/wownero-integrations/wownerowp">Learn more about using the Wownero payment gateway</a></h4>
<script>
function wowneroUpdateFields() {
var confirmType = jQuery("#woocommerce_wownero_gateway_confirm_type").val();
if(confirmType == "wownero-wallet-rpc") {
jQuery("#woocommerce_wownero_gateway_wownero_address").closest("tr").hide();
jQuery("#woocommerce_wownero_gateway_viewkey").closest("tr").hide();
jQuery("#woocommerce_wownero_gateway_daemon_host").closest("tr").show();
jQuery("#woocommerce_wownero_gateway_daemon_port").closest("tr").show();
} else {
jQuery("#woocommerce_wownero_gateway_wownero_address").closest("tr").show();
jQuery("#woocommerce_wownero_gateway_viewkey").closest("tr").show();
jQuery("#woocommerce_wownero_gateway_daemon_host").closest("tr").hide();
jQuery("#woocommerce_wownero_gateway_daemon_port").closest("tr").hide();
}
var useWowneroPrices = jQuery("#woocommerce_wownero_gateway_use_wownero_price").is(":checked");
if(useWowneroPrices) {
jQuery("#woocommerce_wownero_gateway_use_wownero_price_decimals").closest("tr").show();
} else {
jQuery("#woocommerce_wownero_gateway_use_wownero_price_decimals").closest("tr").hide();
}
}
wowneroUpdateFields();
jQuery("#woocommerce_wownero_gateway_confirm_type").change(wowneroUpdateFields);
jQuery("#woocommerce_wownero_gateway_use_wownero_price").change(wowneroUpdateFields);
</script>
<style>
#woocommerce_wownero_gateway_wownero_address,
#woocommerce_wownero_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 Wownero!</p>
<?php elseif($details['status'] == 'expired' || $details['status'] == 'expired_partial'): ?>
<h2 style="color: #96588a; display: block; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; font-size: 18px; font-weight: bold; line-height: 130%; margin: 0 0 18px; text-align: left;">
<?php echo $method_title ?>
</h2>
<p style="margin: 0 0 16px;">Your order has expired. Please place another order to complete your purchase.</p>
<?php elseif($details['status'] == 'unpaid'): ?>
<h2 style="color: #96588a; display: block; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; font-size: 18px; font-weight: bold; line-height: 130%; margin: 0 0 18px; text-align: left;">
<?php echo $method_title ?>
</h2>
<p style="margin: 0 0 16px;">Please pay the amount due to complete your transactions. Your order will expire in <?php echo $details['order_expires']; ?> if payment is not received.</p>
<div style="margin-bottom: 40px;">
<table class="td" cellspacing="0" cellpadding="6" style="width: 100%; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; color: #636363; border: 1px solid #e5e5e5; vertical-align: middle;" border="1">
<tbody>
<tr>
<td class="td" style="text-align: left; vertical-align: middle; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; word-wrap: break-word; color: #636363; border: 1px solid #e5e5e5; padding: 12px;">
PAY TO: <br/>
<strong>
<?php echo $details['integrated_address']; ?>
</strong>
</td>
</tr>
<tr>
<td class="td" style="text-align: left; vertical-align: middle; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; word-wrap: break-word; color: #636363; border: 1px solid #e5e5e5; padding: 12px;">
TOTAL DUE: <br/>
<strong>
<?php echo $details['amount_total_formatted']; ?> 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><p>Javascript is disabled, please refresh the page to check your order status. Your order will expire in <?php echo $details['order_expires']; ?> if no payment is received in that time.</p></noscript>
<strong id="wownero_payment_messages">
<span class="wownero_payment_unpaid">Please pay the amount due to complete your transactions. Your order will expire in <span class="wownero_payment_expire_time"></span> if payment is not received.</span>
<span class="wownero_payment_partial">We have received partial payment. Please pay the remaining amount to complete your transactions. Your order will expire in <span class="wownero_payment_expire_time"></span> if payment is not received.</span>
<span class="wownero_payment_paid">We have received your payment in full. Please wait while amount is confirmed. Approximate confirm time is <span class="wownero_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="wownero_payment_confirmed">Your order has been confirmed. Thank you for paying with Wownero!</span>
<span class="wownero_payment_expired">Your order has expired. Please place another order to complete your purchase.</span>
<span class="wownero_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="wownero_details_row">
<span class="wownero_details_main" id="wownero_integrated_address"><noscript><?php echo $details['integrated_address']; ?></noscript></span>
<span class="wownero_details_right button-row">
<?php if($show_qr): ?>
<button href="#" class="button" title="Show QR Code" onclick="wownero_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="#wownero_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="wownero_details_row">
<span class="wownero_details_main">
<span id="wownero_total_due"></span><noscript><?php echo $details['amount_due_formatted']; ?></noscript> WOW
</span>
<span class="wownero_details_right button-row">
<button href="#" class="button clipboard" title="Copy Amount"
data-clipboard-target="#wownero_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="wownero_total_amount"></span><noscript><?php echo $details["amount_total_formatted"]; ?></noscript> WOW
</strong>
</li>
<li>
Total paid:
<strong>
<span id="wownero_total_paid"></span><noscript><?php echo $details["amount_paid_formatted"]; ?></noscript> WOW
</strong>
</li>
<li>
Exchange rate:<strong id="wownero_exchange_rate"><noscript>1 WOW = <?php echo $details["rate_formatted"]; ?></noscript></strong>
</li>
</ul>
<table id="wownero_tx_table" style="display:none;">
<thead>
<tr>
<th>Transaction id</th>
<th>Height</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div id="wownero_tx_none" style="display:none;">
</div>
<div id="wownero_qr_code_container" style="display:none;" onclick="wownero_showQR(false)">
<div id="wownero_qr_code">
</div>
</div>
</section>
<div id="wownero_toast"></div>
<script type="text/javascript">
var wownero_show_qr = <?php echo $show_qr ? 'true' : 'false'; ?>;
var wownero_ajax_url = '<?php echo $ajax_url; ?>';
var wownero_explorer_url = '<?php echo WOWNERO_GATEWAY_EXPLORER_URL; ?>';
var wownero_details = <?php echo $details_json; ?>;
</script>
Loading…
Cancel
Save