From 3df893972eb832d79b5c76a9776be9b6583a8095 Mon Sep 17 00:00:00 2001 From: Pieter Poorthuis Date: Fri, 19 Jan 2018 15:29:32 +0100 Subject: [PATCH] first commit --- CHANGELOG.md | 6 + CONTRIBUTING.md | 32 + GUIDE.md | 38 ++ LICENSE.md | 21 + README.md | 84 +++ RoboFile.php | 149 +++++ composer.json | 53 ++ phpunit.xml.dist | 29 + src/.DS_Store | Bin 0 -> 6148 bytes src/upload/.DS_Store | Bin 0 -> 6148 bytes .../controller/extension/payment/bitpay.php | 550 ++++++++++++++++++ .../en-gb/extension/payment/bitpay.php | 112 ++++ .../admin/view/image/payment/bitpay.png | Bin 0 -> 1973 bytes .../view/image/payment/bitpay_banner.png | Bin 0 -> 1220 bytes .../admin/view/image/payment/bitpay_logo.svg | 37 ++ .../template/extension/payment/bitpay.twig | 373 ++++++++++++ .../controller/extension/payment/bitpay.php | 305 ++++++++++ .../en-gb/extension/payment/bitpay.php | 22 + .../model/extension/payment/bitpay.php | 45 ++ .../template/extension/payment/bitpay.twig | 8 + src/upload/system/.DS_Store | Bin 0 -> 6148 bytes src/upload/system/library/bitpay.php | 433 ++++++++++++++ .../Sniffs/Spacing/ConcatenationSniff.php | 34 ++ .../Spacing/OpenBracketSpacingSniff.php | 69 +++ .../Variables/ValidVariableNameSniff.php | 209 +++++++ tests/phpcs/OpenCart/ruleset.xml | 100 ++++ 26 files changed, 2709 insertions(+) create mode 100755 CHANGELOG.md create mode 100755 CONTRIBUTING.md create mode 100644 GUIDE.md create mode 100755 LICENSE.md create mode 100755 README.md create mode 100755 RoboFile.php create mode 100755 composer.json create mode 100755 phpunit.xml.dist create mode 100644 src/.DS_Store create mode 100644 src/upload/.DS_Store create mode 100644 src/upload/admin/controller/extension/payment/bitpay.php create mode 100644 src/upload/admin/language/en-gb/extension/payment/bitpay.php create mode 100755 src/upload/admin/view/image/payment/bitpay.png create mode 100755 src/upload/admin/view/image/payment/bitpay_banner.png create mode 100644 src/upload/admin/view/image/payment/bitpay_logo.svg create mode 100644 src/upload/admin/view/template/extension/payment/bitpay.twig create mode 100644 src/upload/catalog/controller/extension/payment/bitpay.php create mode 100644 src/upload/catalog/language/en-gb/extension/payment/bitpay.php create mode 100644 src/upload/catalog/model/extension/payment/bitpay.php create mode 100644 src/upload/catalog/view/theme/default/template/extension/payment/bitpay.twig create mode 100644 src/upload/system/.DS_Store create mode 100644 src/upload/system/library/bitpay.php create mode 100644 tests/phpcs/OpenCart/Sniffs/Spacing/ConcatenationSniff.php create mode 100644 tests/phpcs/OpenCart/Sniffs/Spacing/OpenBracketSpacingSniff.php create mode 100644 tests/phpcs/OpenCart/Sniffs/Variables/ValidVariableNameSniff.php create mode 100644 tests/phpcs/OpenCart/ruleset.xml diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100755 index 0000000..5837862 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog + +All Notable changes to `opencart3-plugin` will be documented in this file + +## [3.0] - 2018-01-19 +Initial v3 version \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100755 index 0000000..0fbc600 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,32 @@ +# Contributing + +Contributions are **welcome** and will be fully **credited**. + +We accept contributions via Pull Requests on [Github](https://github.com/bitpay/opencart-plugin). + + +## Pull Requests + +- **[OpenCart Coding Standard](https://github.com/opencart/opencart/wiki/Coding-standards)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). + +- **Add tests!** - Your patch won't be accepted if it doesn't have tests. + +- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. + +- **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. + +- **Create feature branches** - Don't ask us to pull from your master branch. + +- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. + +- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting. + + +## Running Tests + +``` bash +$ phpunit +``` + + +**Happy coding**! diff --git a/GUIDE.md b/GUIDE.md new file mode 100644 index 0000000..de456ef --- /dev/null +++ b/GUIDE.md @@ -0,0 +1,38 @@ +# Using BitPay for OpenCart +## Prerequisites +Last Cart Version Tested: 3.0.2.0 + +You must have a BitPay merchant account to use this library. It's free to [sign-up for a BitPay merchant account](https://bitpay.com/start). +You can also test this plugin with a test BitPay merchant account. For more information about setting up a test BitPay merchant account & a testnet bitcoin wallet, please see https://bitpay.com/docs/testing + +## Getting Started +Go to the [latest release](https://github.com/bitpay/opencart-plugin/releases/latest) and download the file called `bitpay-opencart.ocmod.zip` + +Note: if you're running an older version of OpenCart (e.g. v2.2), please select the plugin from https://github.com/bitpay/opencart-plugin/releases + + +## Install +### Via Extension Installer +In your OpenCart store's administration section, go to Extensions > Extension Installer + +Upload `bitpay-opencart.ocmod.zip` + +OpenCart needs a working FTP server to install files. If the progress bar hangs half way, it probably means that your OpenCart FTP settings are incorrect. You can configure the FTP credentials of your server under System -> Settings -> FTP + +After the installation indicates it's successful, you can continue with the setup. + +## Setup +### Install the Payment Extension +Go to Extensions > Payments. +Find the BitPay payment extension and click the green install button. The page will refresh, you'll see a success message, and the install button will turn into a red uninstall button. +Click on the edit button. You are now at the BitPay plugin's configuration screen. + +### Connect to BitPay +For live transactions, just press the Connect to BitPay button. For test transactions, press the drop down button attached to the Connect to Bitpay button and select testnet. +You will be redirected to your BitPay merchant account and asked to approve a token which connects your store to BitPay's API. +After pressing Approve, please go back to the BitPay/OpenCart plugin configuration screen. + +Configure the settings that work best for you. Each setting has a tooltip that can help explain what it does. +Set the status setting to enabled and click save at the top right of the page. + +You're done! diff --git a/LICENSE.md b/LICENSE.md new file mode 100755 index 0000000..da2ca07 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# The MIT License (MIT) + +Copyright (c) 2018 BitPay, Inc. + +> 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. diff --git a/README.md b/README.md new file mode 100755 index 0000000..b35e6c8 --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +# Notice + +This is a Community-supported project. + +If you are interested in becoming a maintainer of this project, please contact us at support@bitpay.com. Developers at BitPay will attempt to work along the new maintainers to ensure the project remains viable for the foreseeable future. + +# BitPay for OpenCart + +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) +[![Build Status](https://img.shields.io/travis/bitpay/opencart-plugin/master.svg?style=flat-square)](https://travis-ci.org/bitpay/opencart-plugin) + +## Last Cart Version Tested: 3.0.2.0 + +## Installation + +Follow the instructions found in the [BitPay for OpenCart Guide](GUIDE.md) + +## Development Setup + +``` bash +# Clone the repo +$ git clone https://github.com/bitpay/opencart-plugin.git +$ cd ./opencart-plugin + +# Install dependencies via Composer +$ composer install + +# Set Environment Variables (variables needed can be found in .env.sample) +$ cp .env.sample .env + +# After modifying the Environment Variables for your environment setup OpenCart +$ ./bin/robo setup +``` + +## Development Workflow + +``` bash +# Run PHP Server of OpenCart installation and redirect bash I/O +$ ./bin/robo server & + +# Watch for source code changes and copy them to the OpenCart installation +$ ./bin/robo watch +``` + +## Testing + +``` bash +$ ./bin/robo test +``` + +## Build + +``` bash +$ ./bin/robo build + +# Outputs: +# ./build/bitpay-opencart - the distribution files +# ./build/bitpay-opencart.ocmod.zip - the distribution archive +``` + +## Contributing + +Please see [CONTRIBUTING](CONTRIBUTING.md) for details. + +## Support + +**BitPay Support:** + +* Last OpenCart Version Tested: 3.0.2.0 (not compatible with v2 branch) +* [GitHub Issues](https://github.com/bitpay/magento-plugin/issues) + * Open an issue if you are having issues with this plugin. +* [Support](https://help.bitpay.com) + * BitPay merchant support documentation + +**OpenCart Support:** + +* [Homepage](http://www.opencart.com) +* [GitHub Issues](https://github.com/opencart/opencart/issues) +* [Support](http://www.opencart.com/index.php?route=support/support) +* [Forums](http://forum.opencart.com) + +## License + +The MIT License (MIT). Please see [License File](LICENSE.md) for more information. diff --git a/RoboFile.php b/RoboFile.php new file mode 100755 index 0000000..141c034 --- /dev/null +++ b/RoboFile.php @@ -0,0 +1,149 @@ + $value) { + if (substr($option, 0, 3) === 'OC_') { + $option = strtolower(substr($option, 3)); + $this->config[$option] = $value; + } + } + $required = array('db_username', 'password', 'email', 'http_server'); + $missing = array(); + foreach ($required as $config) { + if (empty($this->config[$config])) { + $missing[] = 'OC_'.strtoupper($config); + } + } + if (!empty($missing)) { + $this->printTaskError(" Missing ".implode(', ', $missing)); + $this->printTaskError(" See .env.sample "); + die(); + } + } + + public function setup() + { + $this->taskDeleteDir('www')->run(); + $this->taskFileSystemStack() + ->mirror('vendor/opencart/opencart/upload', 'www') + ->chmod('www', 0777, 0000, true) + // ->touch('www/config.php') + // ->touch('www/admin/config.php') + ->run(); + $this->_exec(sprintf('mysql -u %1$s -p%2$s -Nse \'show tables\' %3$s | while read table; do mysql -u %1s -p%2$s -e "drop table $table" %3$s; done', $this->config['db_username'], $this->config['db_password'], $this->config['db_database'])); + $install = $this->taskExec('php')->arg('www/install/cli_install.php')->arg('install'); + foreach ($this->config as $option => $value) { + $install->option($option, $value); + } + $install->run(); + $this->taskDeleteDir('www/install')->run(); + $this->taskGitStack() + ->stopOnFail() + ->dir('www') + ->exec('init') + ->add('-A') + ->commit('Clean install') + ->run(); + $this->taskFileSystemStack() + ->mirror('vendor/bitpay/php-client/src/Bitpay', 'www/system/library/Bitpay') + ->run(); + $this->taskGitStack() + ->stopOnFail() + ->dir('www') + ->add('-A') + ->commit('Added bitpay/php-client library') + ->run(); + + } + + public function setuptest() + { + $this->taskDeleteDir('tests/phpcs')->run(); + $this->taskFileSystemStack() + ->mirror('vendor/opencart/opencart/tests/phpcs', 'tests/phpcs')->run(); + } + + public function test() + { + if (!file_exists(__DIR__.'/tests/phpcs/OpenCart/ruleset.xml')) { + $this->setuptest(); + } + $this->taskExec('phpcs')->arg('src')->arg('--standard='.__DIR__.'/tests/phpcs/OpenCart/ruleset.xml')->run(); + } + + public function server() + { + $port = parse_url($this->config['http_server'], PHP_URL_PORT); + $port = (is_null($port)) ? 80 : $port; + $host = parse_url($this->config['http_server'], PHP_URL_HOST); + $this->taskServer($port) + ->host($host) + ->dir('www') + ->run(); + } + + public function watch() + { + $this->dev(); + $this->taskWatch() + ->monitor('composer.json', function () { + $this->taskComposerUpdate()->run(); + $this->dev(); + })->monitor('src/', function () { + $this->dev(); + })->run(); + } + + public function dev() + { + $this->taskGitStack() + ->stopOnFail() + ->dir('www') + ->exec('clean -df') + ->run(); + $this->taskFileSystemStack()->mirror('src/upload', 'www')->run(); + $this->taskReplaceInFile('www/system/library/bitpay.php') + ->from('{{bitpay_lib_version}}') + ->to($this->depver('bitpay/php-client')) + ->run(); + } + + private function depver($dep) + { + if ($composerLock = file_get_contents(__DIR__.DIRECTORY_SEPARATOR.'composer.lock')) { + $json = json_decode($composerLock); + foreach ($json->packages as $package) { + if ($package->name === $dep) { + return $package->version; + } + } + return "Not Found"; + } + return "Not Installed"; + } + + public function build() + { + $this->taskDeleteDir('build')->run(); + $this->taskFileSystemStack()->mirror('src', 'build/bitpay-opencart')->run(); + $this->taskFileSystemStack()->mirror('vendor/bitpay/php-client/src/Bitpay', 'build/bitpay-opencart/upload/system/library/Bitpay')->run(); + $this->taskReplaceInFile('build/bitpay-opencart/upload/system/library/bitpay.php') + ->from('{{bitpay_lib_version}}') + ->to($this->depver('bitpay/php-client')) + ->run(); + $this->taskExec('zip')->dir('build/bitpay-opencart')->arg('-r')->arg('../bitpay-opencart.ocmod.zip')->arg('./')->run(); + } +} diff --git a/composer.json b/composer.json new file mode 100755 index 0000000..f990f19 --- /dev/null +++ b/composer.json @@ -0,0 +1,53 @@ +{ + "name": "bitpay/opencart-plugin", + "description": "BitPay Payment Method for OpenCart", + "version": "3.0", + "type": "opencart-extension", + "keywords": ["bitcoin", "bitpay", "opencart", "payment", "btc", "xbt"], + "homepage": "https://github.com/bitpay/opencart-plugin", + "license": "MIT", + "authors": [ + { + "name": "BitPay, Inc.", + "homepage": "https://bitpay.com" + } + ], + "support": { + "email": "support@bitpay.com", + "issues": "https://github.com/bitpay/opencart-plugin/issues" + }, + "repositories": [ + { + "type": "package", + "package": { + "name": "opencart/opencart", + "version": "3.0.2.0", + "dist": { + "url": "https://github.com/opencart/opencart/archive/3.0.2.0.zip", + "type": "zip" + }, + "source": { + "url": "https://github.com/opencart/opencart", + "type": "git", + "reference": "7911c76c43e29ddc1e4168f36dff6a0be6179719" + } + } + } + ], + "require": { + "php" : ">=5.4.0", + "bitpay/php-client": "~2.2" + }, + "require-dev": { + "phpunit/phpunit" : "4.*", + "squizlabs/php_codesniffer": "2.*", + "codegyre/robo": "*", + "opencart/opencart" : "3.0.2.0", + "vlucas/phpdotenv": "~1.1.0" + }, + "minimum-stability": "stable", + "config": { + "bin-dir": "bin/", + "preferred-install": "source" + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100755 index 0000000..6864175 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + tests + + + + + src/ + + + + + + + + + + diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..45a5ec2b3c37ed2c33f49c742fb63471e42beb5b GIT binary patch literal 6148 zcmeHKJ8r`;3?*Bm2$02NMqQyd5RCK$xj@<)oeVfY;N4Tt)uZ+CGsJMaIYWy_fO-Yqhg5!t~F<*}nP89x|dpIL>&L{-eLXzm4Ot%KkZE+$WjjB7=;# zeA}Q=0V+TRr~nn90-Gz4C3d&p{A3i-M;m;QfA;))7Tfq$idHpkPk!;`YMt{!K#w!qJD%lU$vVeS+RUXFoYjlb|O`HRrj=0l-{24G^XjI_d3VZ|Zs}*+u literal 0 HcmV?d00001 diff --git a/src/upload/.DS_Store b/src/upload/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..4eebcd86be9f31c45d0c0f5a92ff868944587dbd GIT binary patch literal 6148 zcmeHKJ8l9o5S<|?LQ1nGrLVvZtkrUYTp$XB3PeFt&~3%JI9lF(3Xn+$1<@O6{Os{O zTYkmPj)-XgvtEdFA~J&;%E^Y-Y~Q@GNk$Y1#~EL;+pG_(&;5SeWZw=L_f-yZgnwW1 zZG%Pyr~nn90#tws++Bezv9q_kpUeZP02O$41?>A!;D$AE4D?S225$j?Q-s|x_g(^6 zEC8&DV;~|h4Jt6Gnk|L~9r2QRHE|3Kx@a~Znm229DC)Q4{Nm}NHIO3}paRzlyu`Az z`oDz#(f?nQxS|47;9n`A)8T#S@uaM+&Eu@r7WfC;a?Wrw%$load->language('extension/payment/bitpay'); + + $this->bitpay = new Bitpay($registry); + + // Setup logging + $this->logger = new Log('bitpay.log'); + + // Is this an ajax request? + if (!empty($this->request->server['HTTP_X_REQUESTED_WITH']) && strtolower($this->request->server['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') + { + $this->ajax = true; + $this->response->addHeader('Content-type: application/json'); + } + + // Check for connection + if (!$this->ajax) { + $this->connected(); + } + + } + + /** + * Primary settings page + * @return void + */ + public function index() { + // Saving settings + if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->request->post['action'] === 'save' && $this->validate()) { + + $this->session->data['success'] = $this->language->get('text_success'); + $this->setting('risk_speed', $this->request->post['payment_bitpay_risk_speed']); + $this->setting('send_buyer_info', $this->request->post['payment_bitpay_send_buyer_info']); + $this->setting('geo_zone_id', $this->request->post['payment_bitpay_geo_zone_id']); + $this->setting('status', $this->request->post['payment_bitpay_status']); + $this->setting('sort_order', $this->request->post['payment_bitpay_sort_order']); + $this->setting('paid_status', $this->request->post['payment_bitpay_paid_status']); + $this->setting('confirmed_status', $this->request->post['payment_bitpay_confirmed_status']); + $this->setting('complete_status', $this->request->post['payment_bitpay_complete_status']); + $this->setting('notify_url', $this->request->post['payment_bitpay_notify_url']); + $this->setting('return_url', $this->request->post['payment_bitpay_return_url']); + $this->setting('debug', $this->request->post['payment_bitpay_debug']); + + $this->session->data['success'] = $this->language->get('text_success'); + $this->response->redirect($this->url->link('extension/payment/bitpay', 'user_token=' . $this->session->data['user_token'], true)); + } + + // Send Support Request form submitted + if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->request->post['action'] === 'send' && $this->validateSupportRequest()) { + $this->bitpay->sendSupportRequest(); + $this->session->data['success'] = $this->language->get('success_support_request'); + } + + $this->document->setTitle($this->language->get('heading_title')); + + // #HEADER and globals + $data['heading_title'] = $this->language->get('heading_title'); + + $data['text_general'] = $this->language->get('text_general'); + $data['text_statuses'] = $this->language->get('text_statuses'); + $data['text_advanced'] = $this->language->get('text_advanced'); + $data['text_connect_to_bitpay'] = $this->language->get('text_connect_to_bitpay'); + $data['text_edit'] = $this->language->get('text_edit'); + $data['text_enabled'] = $this->language->get('text_enabled'); + $data['text_disabled'] = $this->language->get('text_disabled'); + $data['text_all_zones'] = $this->language->get('text_all_zones'); + $data['text_livenet'] = $this->language->get('text_livenet'); + $data['text_testnet'] = $this->language->get('text_testnet'); + $data['text_high'] = $this->language->get('text_high'); + $data['text_medium'] = $this->language->get('text_medium'); + $data['text_low'] = $this->language->get('text_low'); + $data['text_forum'] = $this->language->get('text_forum'); + $data['text_bitpay_labs'] = $this->language->get('text_bitpay_labs'); + $data['text_send_request'] = $this->language->get('text_send_request'); + $data['text_support'] = $this->language->get('text_support'); + $data['text_yes'] = $this->language->get('text_yes'); + $data['text_no'] = $this->language->get('text_no'); + $data['text_popup_blocked'] = $this->language->get('text_popup_blocked'); + $data['text_popup'] = $this->language->get('text_popup'); + $data['text_are_you_sure'] = $this->language->get('text_are_you_sure'); + + $data['entry_api_access'] = $this->language->get('entry_api_access'); + $data['entry_risk_speed'] = $this->language->get('entry_risk_speed'); + $data['entry_send_buyer_info'] = $this->language->get('entry_send_buyer_info'); + $data['entry_geo_zone'] = $this->language->get('entry_geo_zone'); + $data['entry_status'] = $this->language->get('entry_status'); + $data['entry_sort_order'] = $this->language->get('entry_sort_order'); + $data['entry_paid_status'] = $this->language->get('entry_paid_status'); + $data['entry_confirmed_status'] = $this->language->get('entry_confirmed_status'); + $data['entry_complete_status'] = $this->language->get('entry_complete_status'); + $data['entry_notify_url'] = $this->language->get('entry_notify_url'); + $data['entry_return_url'] = $this->language->get('entry_return_url'); + $data['entry_debug'] = $this->language->get('entry_debug'); + $data['entry_name'] = $this->language->get('entry_name'); + $data['entry_email_address'] = $this->language->get('entry_email_address'); + $data['entry_subject'] = $this->language->get('entry_subject'); + $data['entry_description'] = $this->language->get('entry_description'); + $data['entry_send_logs'] = $this->language->get('entry_send_logs'); + $data['entry_send_server_info'] = $this->language->get('entry_send_server_info'); + + $data['help_api_access'] = $this->language->get('help_api_access'); + $data['help_risk_speed'] = $this->language->get('help_risk_speed'); + $data['help_send_buyer_info'] = $this->language->get('help_send_buyer_info'); + $data['help_paid_status'] = $this->language->get('help_paid_status'); + $data['help_confirmed_status'] = $this->language->get('help_confirmed_status'); + $data['help_complete_status'] = $this->language->get('help_complete_status'); + $data['help_notify_url'] = $this->language->get('help_notify_url'); + $data['help_return_url'] = $this->language->get('help_return_url'); + $data['help_debug'] = $this->language->get('help_debug'); + $data['help_send_server_info'] = $this->bitpay->getServerInfo(); + + $data['button_save'] = $this->language->get('button_save'); + $data['button_cancel'] = $this->language->get('button_cancel'); + $data['button_support'] = $this->language->get('button_support'); + $data['button_disconnect'] = $this->language->get('button_disconnect'); + $data['button_regenerate'] = $this->language->get('button_regenerate'); + $data['button_clear'] = $this->language->get('button_clear'); + $data['button_send'] = $this->language->get('button_send'); + $data['button_continue'] = $this->language->get('button_continue'); + + $data['tab_settings'] = $this->language->get('tab_settings'); + $data['tab_log'] = $this->language->get('tab_log'); + $data['tab_support'] = $this->language->get('tab_support'); + + $data['url_action'] = $this->url->link('extension/payment/bitpay', 'user_token=' . $this->session->data['user_token'], 'SSL'); + $data['url_cancel'] = $this->url->link('extension/extension', 'user_token=' . $this->session->data['user_token'], 'SSL'); + $data['url_reset'] = $this->url->link('extension/payment/bitpay/reset', 'user_token=' . $this->session->data['user_token'], 'SSL'); + + $data['breadcrumbs'] = array(); + + $data['breadcrumbs'][] = array( + 'text' => $this->language->get('text_home'), + 'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token'], true) + ); + + $data['breadcrumbs'][] = array( + 'text' => $this->language->get('text_payment'), + 'href' => $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=payment', true) + ); + + $data['breadcrumbs'][] = array( + 'text' => $this->language->get('heading_title'), + 'href' => $this->url->link('extension/payment/bitpay', 'user_token=' . $this->session->data['user_token'], true) + ); + // #GENERAL + $data['bitpay_connection'] = $this->config->get('payment_bitpay_connection'); + + $data['bitpay_network'] = $this->setting('network'); + $data['url_connect_livenet'] = $this->url->link('extension/payment/bitpay/connect', 'network=livenet&user_token=' . $this->session->data['user_token'], 'SSL'); + $data['url_connect_testnet'] = $this->url->link('extension/payment/bitpay/connect', 'network=testnet&user_token=' . $this->session->data['user_token'], 'SSL'); + if (isset($this->request->get['network']) && !empty($this->request->get['network']['url']) && !empty($this->request->get['network']['port'])) { + $network = $this->request->get['network']; + $network_param = 'network[url]=' . urlencode($network['url']) . '&network[port]=' . $network['port']; + $network_param .= (isset($network['port_required'])) ? '&network[port_required]' : ''; + $data['url_connect_livenet'] = $this->url->link('extension/payment/bitpay/connect', $network_param . '&user_token=' . $this->session->data['user_token'], 'SSL'); + } + $data['url_disconnect'] = $this->url->link('extension/payment/bitpay/disconnect', 'network=testnet&user_token=' . $this->session->data['user_token'], 'SSL'); + $data['url_connected'] = str_replace('&', '&', $this->url->link('extension/payment/bitpay/connected', 'user_token=' . $this->session->data['user_token'], 'SSL')); + + if (is_array($this->setting('network'))) { + $network_title = 'Customnet(' . $this->setting('network')['url'] . ')'; + } else { + $network_title = ucwords($this->setting('network')); + } + $data['text_connected'] = sprintf($this->language->get('text_connected'), $network_title); + + $this->load->model('localisation/geo_zone'); + $data['geo_zones'] = $this->model_localisation_geo_zone->getGeoZones(); + + $data['bitpay_risk_speed'] = (isset($this->request->post['payment_bitpay_risk_speed'])) ? $this->request->post['payment_bitpay_risk_speed'] : $this->setting('risk_speed'); + $data['bitpay_send_buyer_info'] = (isset($this->request->post['bitpay_send_buyer_info'])) ? $this->request->post['bitpay_send_buyer_info'] : $this->setting('send_buyer_info'); + $data['bitpay_geo_zone_id'] = (isset($this->request->post['bitpay_geo_zone_id'])) ? $this->request->post['bitpay_geo_zone_id'] : $this->setting('geo_zone_id'); + $data['bitpay_status'] = (isset($this->request->post['bitpay_status'])) ? $this->request->post['bitpay_status'] : $this->setting('status'); + $data['bitpay_sort_order'] = (isset($this->request->post['bitpay_sort_order'])) ? $this->request->post['bitpay_sort_order'] : $this->setting('sort_order'); + + // #ORDER STATUSES + $this->load->model('localisation/order_status'); + $data['order_statuses'] = $this->model_localisation_order_status->getOrderStatuses(); + $data['bitpay_paid_status'] = (isset($this->request->post['bitpay_paid_status'])) ? $this->request->post['bitpay_paid_status'] : $this->setting('paid_status'); + $data['bitpay_confirmed_status'] = (isset($this->request->post['bitpay_confirmed_status'])) ? $this->request->post['bitpay_confirmed_status'] : $this->setting('confirmed_status'); + $data['bitpay_complete_status'] = (isset($this->request->post['bitpay_complete_status'])) ? $this->request->post['bitpay_complete_status'] : $this->setting('complete_status'); + + // #ADVANCED + $data['bitpay_notify_url'] = (isset($this->request->post['bitpay_notify_url'])) ? $this->request->post['bitpay_notify_url'] : $this->setting('notify_url'); + $data['bitpay_return_url'] = (isset($this->request->post['bitpay_return_url'])) ? $this->request->post['bitpay_return_url'] : $this->setting('return_url'); + + $default_notify_url = $this->url->link('extension/payment/bitpay/callback', $this->config->get('config_secure')); + $default_notify_url = str_replace(HTTP_SERVER, HTTP_CATALOG, $default_notify_url); + $default_notify_url = str_replace(HTTPS_SERVER, HTTPS_CATALOG, $default_notify_url); + $data['default_notify_url'] = $default_notify_url; + + $default_return_url = $this->url->link('extension/payment/bitpay/success', $this->config->get('config_secure')); + $default_return_url = str_replace(HTTP_SERVER, HTTP_CATALOG, $default_return_url); + $default_return_url = str_replace(HTTPS_SERVER, HTTPS_CATALOG, $default_return_url); + $data['default_return_url'] = $default_return_url; + + $data['bitpay_debug'] = (isset($this->request->post['bitpay_debug'])) ? $this->request->post['bitpay_debug'] : $this->setting('debug'); + + // #LOG + $file = DIR_LOGS . 'bitpay.log'; + $data['log'] = ''; + + $matches_array = array(); + + if (file_exists($file)) { + $lines = file($file, FILE_USE_INCLUDE_PATH, null); + foreach ($lines as $line_num => $line) { + if (preg_match('/^([^\\[]*)\\[([^\\]]*)\\](\\{([^}]*)\\})?(.*)/', $line, $matches)) { + unset($matches[3]); + $level = strtolower($matches[2]); + $matches[0] = ''; + $matches[1] = '' . $matches[1] . ''; + $matches[2] = '[' . $matches[2] . ']'; + if (!empty($matches[4])) { + $matches[4] = '{' . $matches[4] . '}'; + $matches[4] = preg_replace('/((->)|(::))/', '$1', $matches[4]); + } + $matches[5] = '' . $matches[5] . ''; + $line = '' . implode('', $matches) . ''; + } + + $data['log'] .= '
' . $line . "
\n"; + } + } + + $data['url_clear'] = $this->url->link('extension/payment/bitpay/clear', 'user_token=' . $this->session->data['user_token'], 'SSL'); + + // #SUPPORT + $data['request_name'] = (isset($this->request->post['request_name'])) ? $this->request->post['request_name'] : $this->config->get('config_owner'); + $data['request_email_address'] = (isset($this->request->post['request_email_address'])) ? $this->request->post['request_email_address'] : $this->config->get('config_email'); + $data['request_send_logs'] = (isset($this->request->post['request_send_logs'])) ? $this->request->post['request_send_logs'] : true; + $data['request_send_server_info'] = (isset($this->request->post['request_send_server_info'])) ? $this->request->post['request_send_server_info'] : true; + $data['request_subject'] = (isset($this->request->post['request_subject'])) ? $this->request->post['request_subject'] : ''; + $data['request_description'] = (isset($this->request->post['request_description'])) ? $this->request->post['request_description'] : ''; + + // #LAYOUT + $data['header'] = $this->load->controller('common/header'); + $data['column_left'] = $this->load->controller('common/column_left'); + $data['footer'] = $this->load->controller('common/footer'); + + // #NOTIFICATIONS + $data['error_warning'] = ''; + if (isset($this->error['warning'])) { + $data['error_warning'] = $this->error['warning']; + } elseif (isset($this->session->data['warning'])) { + $data['error_warning'] = $this->session->data['warning']; + unset($this->session->data['warning']); + } else { + $data['error_warning'] = ''; + } + + $data['success'] = ''; + if (isset($this->session->data['success'])) { + $data['success'] = $this->session->data['success']; + unset($this->session->data['success']); + } + + $data['error_status'] = ''; + if (isset($this->error['status'])) { + $data['error_status'] = sprintf($this->error['status'], $data['url_connect_livenet']); + } + + $data['error_notify_url'] = ''; + if (isset($this->error['notify_url'])) { + $data['error_notify_url'] = $this->error['notify_url']; + } + + $data['error_return_url'] = ''; + if (isset($this->error['return_url'])) { + $data['error_return_url'] = $this->error['return_url']; + } + + $data['error_request_name'] = ''; + if (isset($this->error['request_name'])) { + $data['error_request_name'] = $this->error['request_name']; + } + + $data['error_request_email_address'] = ''; + if (isset($this->error['request_email_address'])) { + $data['error_request_email_address'] = $this->error['request_email_address']; + } + + $data['error_request_subject'] = ''; + if (isset($this->error['request_subject'])) { + $data['error_request_subject'] = $this->error['request_subject']; + } + + $data['error_request_description'] = ''; + if (isset($this->error['request_description'])) { + $data['error_request_description'] = $this->error['request_description']; + } + + $data['error_request'] = false; + if (isset($this->error['request'])) { + $data['error_request'] = true; + } + + $this->response->setOutput($this->load->view('extension/payment/bitpay', $data)); + } + + /** + * Attempts to connect to BitPay API + * @return void + */ + public function connect() { + $network = $this->bitpay->setNetwork($this->request->get['network']); + $this->log('debug', 'Attempting to connect to ' . $network); + $redirect = str_replace('&', '&', $this->url->link('extension/payment/bitpay', 'user_token=' . $this->session->data['user_token'], true)); + + try { + $url = $this->bitpay->getPairingUrl() . '&redirect=' . urlencode($redirect); + $this->response->redirect($url); + return; + } catch (Exception $e) { + $this->log('error', $this->language->get('log_unable_to_connect')); + $this->session->data['warning'] = $this->language->get('warning_unable_to_connect'); + $this->response->redirect($this->url->link('extension/payment/bitpay', 'user_token=' . $this->session->data['user_token'] . '', true)); + } + } + + /** + * Disconnects from BitPay API + * @return void + */ + public function disconnect() { + $this->setting('connection', 'disconnected'); + $this->setting('network', null); + $this->setting('token', null); + $this->setting('pairing_code', null); + $this->setting('pairing_expiration', null); + $this->session->data['success'] = $this->language->get('success_disconnect'); + $this->session->data['manual_disconnect'] = true; + $this->response->redirect($this->url->link('extension/payment/bitpay', 'user_token=' . $this->session->data['user_token'], 'SSL')); + } + + /** + * Checks the connection to BitPay API + * @return void + */ + public function connected() { + if ($this->setting('connection') !== 'disconnected' && $this->setting('connection') !== null) { + $was_connected = ($this->setting('connection') === 'connected'); + $was_network = $this->setting('network'); + $was_network = (is_array($was_network)) ? 'testnet' : $was_network; + $this->bitpay->checkConnection(); + + // Connection attempt successful! + if (!$was_connected && $this->setting('connection') === 'connected') { + if (is_array($this->setting('network'))) { + $network_title = 'Customnet(' . $this->setting('network')['url'] . ')'; + } else { + $network_title = ucwords($this->setting('network')); + } + + $this->session->data['success'] = sprintf($this->language->get('success_connected'), $network_title); + } + + // Helpful message if a connection breaks (token revoked, etc) + if ($was_connected && $this->setting('connection') === 'disconnected' && true !== $this->session->data['manual_disconnect']) { + $pair_url = $this->url->link('extension/payment/bitpay/connect', 'network=' . $was_network . '&user_token=' . $this->session->data['user_token']); + $notification = sprintf($this->language->get('warning_disconnected'), $pair_url); + if ($this->ajax) { + $data = array('error' => $notification); + $this->response->setOutput(json_encode($data)); + return; + } else { + $this->session->data['warning'] = sprintf($this->language->get('warning_disconnected'), $pair_url); + } + } + + // Heartbeat for ajax + if ($this->ajax) { + $data = array('data' => 'connected'); + $this->response->setOutput(json_encode($data)); + return; + } + } + } + + /** + * Clears the BitPay log + * @return void + */ + public function clear() { + $file = DIR_LOGS . 'bitpay.log'; + $handle = fopen($file, 'w+'); + fclose($handle); + + $this->session->data['success'] = $this->language->get('success_clear'); + $this->response->redirect($this->url->link('extension/payment/bitpay', 'user_token=' . $this->session->data['user_token'], 'SSL')); + } + + /** + * Convenience wrapper for bitpay logs + * @param string $level The type of log. + * Should be 'error', 'warn', 'info', 'debug', 'trace' + * In normal mode, 'error' and 'warn' are logged + * In debug mode, all are logged + * @param string $message The message of the log + * @param int $depth Depth addition for debug backtracing + * @return void + */ + private function log($level, $message, $depth = 0) { + $depth += 1; + $this->bitpay->log($level, $message, $depth); + } + + /** + * Convenience wrapper for bitpay settings + * + * Automatically persists to database on set and combines getting and setting into one method + * Assumes bitpay_ prefix + * + * @param string $key Setting key + * @param string $value Setting value if setting the value + * @return string|null|void Setting value, or void if setting the value + */ + private function setting($key, $value = null) { + // Set the setting + if (func_num_args() === 2) { + + return $this->bitpay->setting($key, $value); + } + + // Get the setting + return $this->bitpay->setting($key); + } + + /** + * Validate the primary settings for the BitPay extension + * @return boolean True if the settings provided are valid + */ + private function validate() { + if (!$this->user->hasPermission('modify', 'extension/payment/bitpay')) { + $this->error['warning'] = $this->language->get('warning_permission'); + } + if (!empty($this->request->post['payment_bitpay_notify_url']) && false === filter_var($this->request->post['payment_bitpay_notify_url'], FILTER_VALIDATE_URL)) { + $this->error['notify_url'] = $this->language->get('error_notify_url'); + } + if (!empty($this->request->post['payment_bitpay_return_url']) && false === filter_var($this->request->post['payment_bitpay_return_url'], FILTER_VALIDATE_URL)) { + $this->error['return_url'] = $this->language->get('error_return_url'); + } + if ($this->request->post['payment_bitpay_status'] && $this->setting('connection') !== 'connected') { + $this->error['status'] = $this->language->get('error_status'); + } + + return !$this->error; + } + + /** + * Validate the values for sending a Support Request + * @return boolean True if the values provided are valid + */ + private function validateSupportRequest() { + if (empty($this->request->post['request_name'])) { + $this->error['request_name'] = $this->language->get('error_request_name'); + } + if (empty($this->request->post['request_email_address'])) { + $this->error['request_email_address'] = $this->language->get('error_request_email_address'); + } elseif (false === filter_var($this->request->post['request_email_address'], FILTER_VALIDATE_EMAIL)) { + $this->error['request_email_address'] = $this->language->get('error_request_email_address_invalid'); + } + if (empty($this->request->post['request_subject'])) { + $this->error['request_subject'] = $this->language->get('error_request_subject'); + } + if (empty($this->request->post['request_description'])) { + $this->error['request_description'] = $this->language->get('error_request_description'); + } + if (!empty($this->error)) { + $this->error['request'] = true; + } + + return !$this->error; + } + + /** + * Install the extension by setting up some smart defaults + * @return void + */ + public function install() { + $this->load->model('localisation/order_status'); + $order_statuses = $this->model_localisation_order_status->getOrderStatuses(); + $default_paid = null; + $default_confirmed = null; + $default_complete= null; + foreach ($order_statuses as $order_status) { + if ($order_status['name'] == 'Processing') { + $default_paid = $order_status['order_status_id']; + } elseif ($order_status['name'] == 'Processed') { + $default_confirmed = $order_status['order_status_id']; + } elseif ($order_status['name'] == 'Complete') { + $default_complete = $order_status['order_status_id']; + } + } + + $this->load->model('setting/setting'); + $default_settings = array( + 'payment_bitpay_private_key' => null, + 'payment_bitpay_public_key' => null, + 'payment_bitpay_connection' => 'disconnected', + 'payment_bitpay_network' => null, + 'payment_bitpay_token' => null, + 'payment_bitpay_risk_speed' => 'high', + 'payment_bitpay_send_buyer_info' => '0', + 'payment_bitpay_geo_zone_id' => '0', + 'payment_bitpay_status' => '0', + 'payment_bitpay_sort_order' => null, + 'payment_bitpay_paid_status' => $default_paid, + 'payment_bitpay_confirmed_status' => $default_confirmed, + 'payment_bitpay_complete_status' => $default_complete, + 'payment_bitpay_notify_url' => null, + 'payment_bitpay_return_url' => null, + 'payment_bitpay_debug' => '0', + 'payment_bitpay_version' => $this->bitpay->version, + ); + $this->model_setting_setting->editSetting('payment_bitpay', $default_settings); + $this->bitpay->generateId(); + } + + /** + * Uninstall the extension by removing the settings + * @return void + */ + public function uninstall() { + $this->load->model('setting/setting'); + $this->model_setting_setting->deleteSetting('bitpay'); + } +} diff --git a/src/upload/admin/language/en-gb/extension/payment/bitpay.php b/src/upload/admin/language/en-gb/extension/payment/bitpay.php new file mode 100644 index 0000000..dcfd8f2 --- /dev/null +++ b/src/upload/admin/language/en-gb/extension/payment/bitpay.php @@ -0,0 +1,112 @@ +BitPay'; +$_['text_general'] = 'General'; +$_['text_statuses'] = 'Order Statuses'; +$_['text_advanced'] = 'Advanced'; +$_['text_connect_to_bitpay'] = 'Connect to '; +$_['text_connected'] = 'Connected to BitPay %s'; +$_['text_high'] = 'High'; +$_['text_medium'] = 'Medium'; +$_['text_low'] = 'Low'; +$_['text_livenet'] = 'Livenet'; +$_['text_testnet'] = 'Testnet'; +$_['text_all_geo_zones'] = 'All Geo Zones'; +$_['text_forum'] = 'Forum'; +$_['text_bitpay_labs'] = 'OpenCart lab on BitPay Labs'; +$_['text_send_request'] = 'Send a Support Request'; +$_['text_support'] = 'Having problems trying to accept Bitcoin with your OpenCart store? Let us help!'; +$_['text_yes'] = 'Yes'; +$_['text_no'] = 'No'; +$_['text_popup_blocked'] = 'Pop-up Blocked'; +$_['text_popup'] = 'It appears your browser is blocking BitPay's pop-up. Click below to continue.'; +$_['text_are_you_sure'] = 'Are you sure?'; + +// Tab +$_['tab_settings'] = 'Settings'; +$_['tab_log'] = 'Log'; +$_['tab_support'] = 'Support'; + +// Button +$_['button_disconnect'] = 'Disconnect'; +$_['button_send'] = 'Send'; +$_['button_continue'] = 'Continue'; + + +// Entry +$_['entry_api_access'] = 'API Access'; +$_['entry_risk_speed'] = 'Risk/Speed'; +$_['entry_send_buyer_info'] = 'Send Buyer Information'; +$_['entry_geo_zone'] = 'Geo Zone'; +$_['entry_status'] = 'Status'; +$_['entry_sort_order'] = 'Sort Order'; +$_['entry_paid_status'] = 'Paid Status'; +$_['entry_confirmed_status'] = 'Confirmed Status'; +$_['entry_complete_status'] = 'Complete Status'; +$_['entry_notify_url'] = 'Notification URL'; +$_['entry_return_url'] = 'Return URL'; +$_['entry_debug'] = 'Debug Logging'; +$_['entry_name'] = 'Name'; +$_['entry_email_address'] = 'Email Address'; +$_['entry_subject'] = 'Subject'; +$_['entry_description'] = 'Description'; +$_['entry_send_logs'] = 'Send BitPay Log'; +$_['entry_send_server_info'] = 'Send Server Information'; + +// Help +$_['help_api_access'] = "Livenet (default): real Bitcoin transactions
\n" . + "Testnet: transactions on the Bitcoin Testnet. Requires an account at test.bitpay.com"; +$_['help_risk_speed'] = "High speed confirmations typically take 5-10 seconds, and can be used for digital goods or low-risk items
\n" . + "Medium speed confirmations take about 10 minutes, and can be used for most items
\n" . + "Low speed confirmations take about 1 hour, and should be used for high-value items"; +$_['help_paid_status'] = 'A fully paid invoice awaiting confirmation'; +$_['help_send_buyer_info'] = 'Buyer information is sent to BitPay so that it can be included on the invoice'; +$_['help_confirmed_status'] = 'A confirmed invoice per Risk/Speed settings'; +$_['help_complete_status'] = 'An invoice that has been credited to your account'; +$_['help_notify_url'] = 'BitPay’s IPN will post invoice status updates to this URL'; +$_['help_return_url'] = 'BitPay will provide a redirect link to the user for this URL upon successful payment of the invoice'; +$_['help_debug'] = 'Enabling debug will write sensitive data to a log file. You should always disable unless instructed otherwise'; + +// Success +$_['success_connected'] = 'Success: Connected to BitPay %s'; +$_['success_disconnect'] = 'Success: Disconnected from BitPay'; +$_['success_clear'] = 'Success: The BitPay log has been cleared'; +$_['success_support_request'] = 'Success: Your Support Request has been sent. Hang in there!'; + +// Warning +$_['warning_permission'] = 'Warning: You do not have permission to modify the BitPay payment module.'; +$_['warning_unable_to_connect'] = 'Warning: Unable to connect to BitPay. Check the log for more information.'; +$_['warning_disconnected'] = 'Warning: You are no longer connected to BitPay. Click here to reconnect.'; + +// Error +$_['error_status'] = 'You cannot enable this payment method until you have connected your OpenCart store to BitPay. Connect now!'; +$_['error_notify_url'] = '`Notification URL` needs to be a valid URL'; +$_['error_return_url'] = '`Return URL` needs to be a valid URL'; +$_['error_request_name'] = 'You must enter a name'; +$_['error_request_email_address'] = 'You must enter an email address'; +$_['error_request_email_address_invalid'] = '`Email Address` needs to be a valid email address'; +$_['error_request_subject'] = 'You must enter a subject'; +$_['error_request_description'] = 'You must enter a description'; + +// Log +$_['log_error_install'] = 'The BitPay payment extension was not installed correctly or the files are corrupt. Please reinstall the extension. If this message persists after a reinstall, contact support@bitpay.com with this message.'; +$_['log_unable_to_connect'] = 'Unable to connect to BitPay'; +$_['log_unknown_network'] = 'Unknown network class: %s. Using Livenet instead'; +$_['log_no_private_key'] = 'No private key found. Generating key...'; +$_['log_private_key_found'] = 'Private key found. Decrypting and unserializing...'; +$_['log_private_key_wrong_type'] = 'The private key is of the wrong type. Received %s'; +$_['log_encrypted_key'] = 'Encrypted key: %s'; +$_['log_decrypted_key'] = 'Decrypted key: %s'; +$_['log_no_public_key'] = 'No public key found. Generating key...'; +$_['log_public_key_found'] = 'Public key found. Decrypting and unserializing...'; +$_['log_public_key_wrong_type'] = 'The public key is of the wrong type. Received %s'; +$_['log_public_key_regenerate'] = 'Attempting to regenerate public key...'; +$_['log_regenerate_success'] = 'Public key was corrupt. Regenerated it from private key'; +$_['log_connection_key'] = 'Could not retrieve tokens from BitPay.'; + diff --git a/src/upload/admin/view/image/payment/bitpay.png b/src/upload/admin/view/image/payment/bitpay.png new file mode 100755 index 0000000000000000000000000000000000000000..0fc9467b320397c48dc74598d0f6eee61d5816c0 GIT binary patch literal 1973 zcmah~2~<;O7LG&fR?wcx(2AqJ##*6}UMSc(b9y@edCPzQbHDF?cRBB+ zi~Kj(Ep}Xt!C>qJK3p;S4n^~tqMWjMMrUF*utLPQ7c!(KnM^4MX59_?BnV>ETB@d zuz^$|QK;oW(JG$=9Tbq@FHt1KC>TntmnYz1WTFIW2$2Iu^%jkuX=Gt%c$w(ldQ89q zGcHIB3;Vk$sZa!PU>yWd@noEW2!a5Oh6gEB8Vz&>NJNlCAfguy2U3_+8k6J(%q}b% zO{a`xin;vRSg6IqMk9!pNgx;u2E2ichjmc|kilSBIY=ZN>Veb8YY@2+r_ryOW8gx1 zg-)eKRImoHGRh<1ScHW|mHwWBS}PR3CD!O?3x$@9V3cbKAf8B2tF3v>cET$N0_{iSnmI69yT1-(bwt%Lxv(Nu4QUVomxV=7@Jf}ENhX7A2AfPL zdlUH}$fK~m$rLt&%%f323Tlz(umX)9k!uvt99H!Q*6lB`OpXqcBd|^a!&~OMCyIs< zSRV~*0Zsr7a0*mul(0eXY%T4qQb?zYgOq$7tOjOk!&LoGe5-agl|rY}X$&HV?9HQq zAcMoEdAnJ&0r_q;8WlT>RsKod{})R@#SpBG{jWyP9ih`>-M$@L)Ob7ikOrN09Xi&S zfru0gX5N&@Pr`dWIXTby%fb7jpYYWozWdODLiH@-NttLl+TcZ{AGx4Lt;&0M^k6wbVQV*NzUP0Q#JK2X}JvMXaR z#|Q3@!n^aEI&O!=9XeCqc%iOsmAC4BYIt(?xm*7pHe{ZeRgH>1mA9ru(LbFbw8zs|8$diShR6VIC(JwC6>I+nSg69v5e14~YJe z;p=?!mzg!zn{;bCc{Kdl$L?j#twP)DwP`Fm zLO7H=@Up(MggSr9b|f!gQjhlwE|t|iSbWppy^X<{3_6$};(7rSv?=kGz&Em^NLIVv z!nwNbyShug%WUW8$dX=@nhHqyO%6Luq9Er=AY@$;|LY8PME4G7D#K&FSO0gCwqV(K z0^K3WjyAH|BRwVg;){m`VLd&0i_$U}HtRYskJ|(5HfCx296VQjVE^t!RR*Uuyu;)Y zegsLy^Y-9t({(Eg!W`4o;zFK zurfilL7WNd4dP7TWP-R8D4D?W1W_iiGJ(|zRO*gySq2~g%DGx+8=>P(+b6Xd%#qXfQh&PVot4U;3^t4KbXjXKGG{hHQ{<`fNc zfuyo0GiK2INIq-8*tHqo97o7)+SJx4J>8nII!8=6VyXdrsQqpbN2t_^QUZKSt=B5R z!w+SYp5DB)>!5}{`){Qq=;c^b;1N0W_sYlUlD6R!x2s9-r2WT@$fz%KX2I&b{5@mTmmN_@LdBWdTiLvi z)f!699kAabvSyZu9$QTRgs?Ogf}22|ro5pRM3f4&(j2dB5cxAA1@EJ_7V?$c$uv@z zH$EgY6@*~T%;hmPqB_9dTv;>Za8}5=Se){L@JAI+H$;MAi^Mxv`|4W1Q3!Q zP%|q*eW_i;V9LHBi!T{zDyFN7jl|Ge9gt%x08cr5Gl8RF`nRrOq-muf1f)C%^FB@* zlXzu{=++Q-5^h~nUdP6bi!uo>s z@yY^)EN=)zCeLXrUovsTUZ-Eq8ahK+I6Ri1+wq#XX-gsdf`HS9$eMM-Xw5(nq7eEa z%0h}NCghzWa7+xGK19|Kyd-@elIRrD^N(R-s6^g1a>08I$HTS*9>!d+&H}hw%5=^h z11Fmea2(|uCbu5FWf~-p02ehfzaaR!k+zI*o}Bq&nOa`hp)CTS>r$u_3*_|w@aMO> z+UXRy7#3967EG8$du^!8^A%_;8t17&Dys@y{8FcDtGtUfB*QA8(|$C8>&AyaHeLk& zRDNvKAzxj!+mNpa>G*zy3%4I$a1 zNoCNV()*nKbwp_wGYVLL$DGQJHCnis*(%f&jUkn3C41gJ*0k$_sP`T;G}=@)CW1Pz i{S*BQR_uNHEx-U-YbSLsH7xM}0000 + + + + + + + + + + + + + diff --git a/src/upload/admin/view/template/extension/payment/bitpay.twig b/src/upload/admin/view/template/extension/payment/bitpay.twig new file mode 100644 index 0000000..c84fe20 --- /dev/null +++ b/src/upload/admin/view/template/extension/payment/bitpay.twig @@ -0,0 +1,373 @@ +{{ header }}{{ column_left }} +
+ +
+ {% if error_warning %} +
{{ error_warning }} + +
+ {% endif %} + {% if success %} +
{{ success }} + +
+ {% endif %} +
+
+

{{ text_edit }}

+
+
+
+ + +
+
+

{{ text_general }}

+
+ +
+ +
+

{{ text_connected }}

+ + {{ button_disconnect }} + +
+
+
+
+ +
+
+ +
+
+
+
+ +
+ + +
+
+
+ +
+
+ +
+
+
+
+ +
+ + {% if error_status %} +
{{ error_status }}
+ {% endif %} +
+
+
+ +
+ +
+
+
+

{{ text_statuses }}

+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+

{{ text_advanced }}

+
+ +
+
+ +
+ {% if error_notify_url %} +
{{ error_notify_url }}
+ {% endif %} +
+
+
+ +
+
+ +
+ {% if error_return_url %} +
{{ error_return_url }}
+ {% endif %} +
+
+
+ +
+ +
+
+
+
+

+

{{ log }}
+

+ +
+
+
+

{{ text_forum }}

+

+ {{ text_bitpay_labs }} +

+
+
+
+

{{ text_send_request }}

+

+ {{ text_support }} +

+
+
+
+ +
+
+ +
+ {% if error_request_name %} +
{{ error_request_name }}
+ {% endif %} +
+
+
+ +
+
+ +
+ {% if error_request_email_address %} +
{{ error_request_email_address }}
+ {% endif %} +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + {% if error_request_subject %} +
{{ error_request_subject }}
+ {% endif %} +
+
+
+ +
+ + {% if error_request_description %} +
{{ error_request_description }}
+ {% endif %} +
+
+
+
+
+
+
+
+
+
+ + +{{ footer }} \ No newline at end of file diff --git a/src/upload/catalog/controller/extension/payment/bitpay.php b/src/upload/catalog/controller/extension/payment/bitpay.php new file mode 100644 index 0000000..56dedba --- /dev/null +++ b/src/upload/catalog/controller/extension/payment/bitpay.php @@ -0,0 +1,305 @@ +load->language('extension/payment/bitpay'); + $this->bitpay = new Bitpay($registry); + + // Setup logging + $this->logger = new Log('bitpay.log'); + + // Is this an ajax request? + if (!empty($this->request->server['HTTP_X_REQUESTED_WITH']) && + strtolower($this->request->server['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') + { + $this->ajax = true; + } + + // Check for connection + if ($this->setting('connection') !== 'disconnected' && $this->setting('connection') !== null) { + $was_connected = ($this->setting('connection') === 'connected'); + $was_network = $this->setting('network'); + $this->bitpay->checkConnection(); + } + } + + /** + * Displays the Payment Method (a redirect button) + * @return void + */ + public function index() { + + $data['testnet'] = ($this->setting('network') === 'livenet') ? false : true; + $data['warning_testnet'] = $this->language->get('warning_testnet'); + $data['url_redirect'] = $this->url->link('extension/payment/bitpay/confirm', $this->config->get('config_secure')); + $data['button_confirm'] = $this->language->get('button_confirm'); + + if (isset($this->session->data['error_bitpay'])) { + unset($this->session->data['error_bitpay']); + } + + if (file_exists(DIR_TEMPLATE . $this->config->get('config_template') . '/template/extension/payment/bitpay')) { + return $this->load->view($this->config->get('config_template') . '/template/extension/payment/bitpay', $data); + } else { + return $this->load->view('extension/payment/bitpay', $data); + } + } + + /** + * Generates redirect to invoice url + * @return void + */ + public function confirm() { + + $this->load->model('checkout/order'); + + if (!isset($this->session->data['order_id'])) { + $this->response->redirect($this->url->link('checkout/cart')); + return; + } + $order_info = $this->model_checkout_order->getOrder($this->session->data['order_id']); + if (false === $order_info) { + $this->response->redirect($this->url->link('checkout/cart')); + return; + } + + try { + $invoice = $this->prepareInvoice($order_info); + $invoice = $this->bitpay->createInvoice($invoice); + } catch (Exception $e) { + $this->session->data['error_bitpay'] = 'Sorry, but there was a problem communicating with BitPay for Bitcoin checkout.'; + $this->response->redirect($this->url->link('checkout/checkout')); + return; + } + + $this->session->data['bitpay_invoice'] = $invoice->getId(); + $this->response->redirect($invoice->getUrl()); + } + + /** + * Convenience wrapper for bitpay logs + * @param string $level The type of log. + * Should be 'error', 'warn', 'info', 'debug', 'trace' + * In normal mode, 'error' and 'warn' are logged + * In debug mode, all are logged + * @param string $message The message of the log + * @param int $depth Depth addition for debug backtracing + * @return void + */ + public function log($level, $message, $depth = 0) { + $depth += 1; + $this->bitpay->log($level, $message, $depth); + } + + /** + * Convenience wrapper for bitpay settings + * + * Automatically persists to database on set and combines getting and setting into one method + * Assumes 'bitpay_' prefix + * + * @param string $key Setting key + * @param string $value Setting value if setting the value + * @return string|null|void Setting value, or void if setting the value + */ + public function setting($key, $value = null) { + // Set the setting + if (func_num_args() === 2) { + return $this->bitpay->setting($key, $value); + } + + // Get the setting + return $this->bitpay->setting($key); + } + + /** + * Prepares an Invoice to send to BitPay + * + * @param array $order_info OpenCart checkout order + * @return Invoice + */ + private function prepareInvoice($order_info = array()) { + $invoice = new \Bitpay\Invoice(); + if (empty($order_info['order_id'])) { + $this->log('error', 'Cannot prepare invoice without `order_id`'); + throw Exception('Cannot prepare invoice without `order_id`'); + } + $this->log('info', sprintf('Preparing Invoice for Order %s', (string)$order_info['order_id'])); + $invoice->setOrderId((string)$order_info['order_id']); + if (empty($order_info['currency_code'])) { + $this->log('error', 'Cannot prepare invoice without `currency_code`'); + throw Exception('Cannot prepare invoice without `currency_code`'); + } + $invoice->setCurrency(new \Bitpay\Currency($order_info['currency_code'])); + if (empty($order_info['total'])) { + $this->log('error', 'Cannot prepare invoice without `total`'); + throw Exception('Cannot prepare invoice without `total`'); + } + + (float)$foo = $order_info['total']*$order_info['currency_value']; + $total = (float)number_format((float)$foo, 2, '.', ''); + $invoice->setPrice($total); + + // Send Buyer Information? + if ($this->setting('send_buyer_info')) { + $buyer = new \Bitpay\Buyer(); + $buyer->setFirstName($order_info['firstname']) + ->setLastName($order_info['lastname']) + ->setEmail($order_info['email']) + ->setPhone($order_info['telephone']) + ->setAddress(array($order_info['payment_address_1'], $order_info['payment_address_2'])) + ->setCity($order_info['payment_city']) + ->setState($order_info['payment_zone_code']) + ->setZip($order_info['payment_postcode']) + ->setCountry($order_info['payment_country']); + $invoice->setBuyer($buyer); + } + + $invoice->setFullNotifications(true); + + $return_url = $this->setting('return_url'); + if (empty($return_url)) { + $return_url = $this->url->link('extension/payment/bitpay/success', $this->config->get('config_secure')); + } + + $notify_url = $this->setting('notify_url'); + if (empty($notify_url)) { + $notify_url = $this->url->link('extension/payment/bitpay/callback', $this->config->get('config_secure')); + } + + $invoice->setRedirectUrl($return_url); + $invoice->setNotificationUrl($notify_url); + $invoice->setTransactionSpeed($this->setting('risk_speed')); + return $invoice; + } + + /** + * Success return page + * + * Progresses the order if valid, and redirects to OpenCart's Checkout Success page + * + * @return void + */ + public function success() { + $this->load->model('checkout/order'); + $order_id = $this->session->data['order_id']; + if (is_null($order_id)) { + $this->response->redirect($this->url->link('checkout/success')); + return; + } + + $order = $this->model_checkout_order->getOrder($order_id); + try { + $invoice = $this->bitpay->getInvoice($this->session->data['bitpay_invoice']); + } catch (Exception $e) { + $this->response->redirect($this->url->link('checkout/success')); + return; + } + + switch ($invoice->getStatus()) { + case 'paid': + $order_status_id = $this->setting('paid_status'); + $order_message = $this->language->get('text_progress_paid'); + break; + case 'confirmed': + $order_status_id = $this->setting('confirmed_status'); + $order_message = $this->language->get('text_progress_confirmed'); + break; + case 'complete': + $order_status_id = $this->setting('complete_status'); + $order_message = $this->language->get('text_progress_complete'); + break; + default: + $this->response->redirect($this->url->link('checkout/checkout')); + return; + } + + // Progress the order status + $this->model_checkout_order->addOrderHistory($order_id, $order_status_id); + $this->session->data['bitpay_invoice'] = null; + $this->response->redirect($this->url->link('checkout/success')); + } + + /** + * IPN Handler + * @return void + */ + public function callback() { + $this->load->model('checkout/order'); + $this->log('info', 'IPN handler called'); + + $post = file_get_contents("php://input"); + if (empty($post)) { + $this->log('warn', 'IPN handler called with no data'); + return; + } + $json = @json_decode($post, true); + if (empty($json)) { + $this->log('warn', 'IPN handler called with invalid data'); + $this->log('trace', 'Invalid JSON: ' . $post); + return; + } + + if (!array_key_exists('id', $json)) { + $this->log('warn', 'IPN handler called with invalid data'); + $this->log('trace', 'Invoice object missing ID field: ' . $var_export($json, true)); + return; + } + + if (!array_key_exists('url', $json)) { + $this->log('warn', 'IPN handler called with invalid data'); + $this->log('trace', 'Invoice object missing URL field: ' . $var_export($json, true)); + return; + } + + // Try to set the network based on the url first since the merchant may have + // switched networks while test invoices are still being confirmed + $network = null; + if (true === strpos($json['url'], 'https://test.bitpay.com')) { + $network = 'testnet'; + } elseif (true === strpos($json['url'], 'https://bitpay.com')) { + $network = 'livenet'; + } + $this->log('info', 'Fetching status of invoice '.$json['id']); + + $invoice = $this->bitpay->getInvoice($json['id'], $network); + $this->log('info', 'Invoice status = '.$invoice->getStatus()); + + switch ($invoice->getStatus()) { + case 'paid': + $order_status_id = $this->setting('paid_status'); + $order_message = $this->language->get('text_progress_paid'); + break; + case 'confirmed': + $order_status_id = $this->setting('confirmed_status'); + $order_message = $this->language->get('text_progress_confirmed'); + break; + case 'complete': + $order_status_id = $this->setting('complete_status'); + $order_message = $this->language->get('text_progress_complete'); + break; + default: + $this->log('info', 'Status is not paid/confirmed/complete. Redirecting to checkout/checkout'); + $this->response->redirect($this->url->link('checkout/checkout')); + return; + } + + // Progress the order status + $this->model_checkout_order->addOrderHistory($invoice->getOrderId(), $order_status_id); + } +} diff --git a/src/upload/catalog/language/en-gb/extension/payment/bitpay.php b/src/upload/catalog/language/en-gb/extension/payment/bitpay.php new file mode 100644 index 0000000..a07f4b2 --- /dev/null +++ b/src/upload/catalog/language/en-gb/extension/payment/bitpay.php @@ -0,0 +1,22 @@ +load->language('extension/payment/bitpay'); + $this->bitpay = new Bitpay($registry); + } + + /** + * Returns the BitPay Payment Method if available + * @param array $address Customer billing address + * @return array|void BitPay Payment Method if available + */ + public function getMethod($address) { + // Check for connection to BitPay + $this->bitpay->checkConnection(); + if ($this->bitpay->setting('connection') === 'disconnected') { + $this->bitpay->log('warn', 'You cannot have BitPay enabled as a payment method without being connected to BitPay.'); + return; + } + + $query = $this->db->query("SELECT * FROM " . DB_PREFIX . "zone_to_geo_zone WHERE geo_zone_id = '" . (int)$this->bitpay->setting('geo_zone_id') . "' AND country_id = '" . (int)$address['country_id'] . "' AND (zone_id = '" . (int)$address['zone_id'] . "' OR zone_id = '0')"); + + // All Geo Zones configured or address is in configured Geo Zone + if (!$this->config->get('bitpay_geo_zone_id') || $query->num_rows) { + return array( + 'code' => 'bitpay', + 'title' => $this->language->get('text_title'), + 'terms' => '', + 'sort_order' => $this->bitpay->setting('sort_order') + ); + } + } +} diff --git a/src/upload/catalog/view/theme/default/template/extension/payment/bitpay.twig b/src/upload/catalog/view/theme/default/template/extension/payment/bitpay.twig new file mode 100644 index 0000000..abb066c --- /dev/null +++ b/src/upload/catalog/view/theme/default/template/extension/payment/bitpay.twig @@ -0,0 +1,8 @@ +{% if testnet %} +
{{ warning_testnet }}
+{% endif %} + \ No newline at end of file diff --git a/src/upload/system/.DS_Store b/src/upload/system/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..05b4ec9523c765ae34b7d6a50e95bff08d117e27 GIT binary patch literal 6148 zcmeHKJBk895Uo-{!NhRGzJfPs#65u*(Ahx{1VJ${o5*u{w0-pnl-bQ@gOyiM{kp5` zb<;1Hni3IRUE8I|LPTb8L%G|~H`_Pw*(f6lgyW2zoaMG}FJH~6m;H0VxKlaGK^kzM z_@|?7(Wn3wpaN8Y3Q&Q8703cRn-6?0kD~%q;QuRN--iM>tchKqe>yOD3jmxT?1s7b z62M{sU`^}-5rJt?fkD-5F*N9im&~h)U0~2fv-!}xS+he?za8fnPZzC$9H{^mcvWB= z%Z1hdPxvqW|0{_rDnJF^N&%g$H|rIil(n_@IIFbregistry = $registry; + + // Load up the BitPay library + $autoloader = __DIR__ . '/Bitpay/Autoloader.php'; + if (true === file_exists($autoloader) && + true === is_readable($autoloader)) + { + require_once $autoloader; + \Bitpay\Autoloader::register(); + } else { + // OpenCart uses a custom error handler for reporting instead of using exceptions + // Which is why an error is triggered instead of an exception being thrown + trigger_error($this->language->get('log_error_install'), E_USER_ERROR); + } + + // Setup logging + $this->logger = new Log('bitpay.log'); + + // Setup encryption + $fingerprint = substr(sha1(sha1(__DIR__)), 0, 24); + $this->encryption = new Encryption($fingerprint); + } + + /** + * Magic getter for Registry items + * + * Allows use of $this->db instead of $this->registry->get('db') for example + * + * @return mixed + */ + public function __get($name) { + return $this->registry->get($name); + } + + /** + * Generates a token and returns a link to pair with it on BitPay + * @return string BitPay pairing url for connection token + */ + public function getPairingUrl() { + // Sanitize label + $label = preg_replace('/[^a-zA-Z0-9 \-\_\.]/', '', $this->config->get('config_name')); + $label = substr('OpenCart - ' . $label, 0, 59); + + // Generate a new Client ID each time. Prevents multiple pair, and revoked token before return issues + $this->generateId(); + + $client = $this->getClient(); + $token = $client->createToken(array( + 'facade' => 'merchant', + 'label' => $label, + 'id' => (string)$this->getPublicKey()->getSin(), + )); + + $this->setting('connection', 'connecting'); + $this->setting('token', $token->getToken()); + + $network = $this->getNetwork(); + if (443 === $network->getApiPort()) { + return 'https://' . $network->getApiHost() . '/api-access-request?pairingCode=' . $token->getPairingCode(); + } else { + return 'http://' . $network->getApiHost() . ':' . $network->getApiPort() . '/api-access-request?pairingCode=' . $token->getPairingCode(); + } + } + + /** + * Checks to see if we've successfully connected to BitPay's API + * @return void + */ + public function checkConnection() { + $connection_token = $this->setting('token'); + $this->log('info', 'Looking for matching token = ' . $connection_token); + + try { + $client = $this->getClient(); + $tokens = $client->getTokens(); + foreach ($tokens as $token) { + if ($token->getToken() === $connection_token) { + $this->setting('connection', 'connected'); + $this->setting('pairing_code', null); + $this->setting('pairing_expiration', null); + return; + } + } + } catch (\UnexpectedValueException $e) { + $this->log('error', $this->language->get('log_connection_key')); + $this->log('error', 'Exception message = '.$e->getMessage()); + // Do not mark the plugin as disconnected, since this is very likely a temporarily network failure. + return; + } catch (\Exception $e) { + $this->log('info', 'No tokens could be found. Exception = '.$e->getMessage()); + // An exception is raised when no tokens can be found + // This is fine and expected + } + + $this->log('info', 'Could not find matching token.'); + // Disconnect if token doesn't exist anymore + if ($this->setting('connection') === 'connected') { + $this->log('info', 'Marking plugin as disconnected.'); + $this->setting('connection', 'disconnected'); + $this->setting('token', null); + $this->setting('network', null); + $this->setting('status', '0'); + } + } + + /** + * Generates a new set of keys to interact with BitPay's API + * @return PrivateKey + */ + public function generateId() { + // Generate new keys + $this->log('info', 'Generating new keys.'); + $private_key = new Bitpay\PrivateKey(); + $private_key->generate(); + $public_key = $private_key->getPublicKey(); + + // Persist the keys to the database + $this->setting('private_key', $this->encryption->encrypt('0123456789',serialize($private_key))); + $this->setting('public_key', $this->encryption->encrypt('0123456789',serialize($public_key))); + $this->setting('connection', 'disconnected'); + $this->setting('token', null); + + return $private_key; + } + + /** + * Retrieves a client to interact with BitPay's API + * @param string $network Optional network identifier + * @return Client + */ + public function getClient($network = null) { + $network = $this->getNetwork($network); + + $curl_options = array(); + if ($network instanceof Bitpay\Network\Customnet) { + + //Customize the curl options + $curl_options = array( + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + ); + + } + $adapter = new Bitpay\Client\Adapter\CurlAdapter($curl_options); + + $private_key = $this->getPrivateKey(); + $public_key = $this->getPublicKey(); + + $client = new Bitpay\Client\Client(); + $client->setPrivateKey($private_key); + $client->setPublicKey($public_key); + $client->setNetwork($network); + $client->setAdapter($adapter); + + return $client; + } + + /** + * Validates and sets the network setting + * @param string|array $network Network to connect to or custom Network parameter array + * @return Network + */ + public function setNetwork($network) { + if ($network === 'livenet') { + $this->setting('network', 'livenet'); + return 'Livenet'; + } elseif ($network === 'testnet') { + $this->setting('network', 'testnet'); + return 'Testnet'; + } elseif (is_array($network)) { + if (isset($network['url']) && isset($network['port'])) { + $port_required = (isset($network['port_required'])) ? true : false; + $network_params = array( + 'url' => $network['url'], + 'port' => $network['port'], + 'port_required' => $port_required + ); + $this->setting('network', $network_params); + return 'Customnet'; + } + } + $this->setting('network', 'livenet'); + return 'Livenet'; + } + + /** + * Retrieves a BitPay's API Network object + * @param string $network Optional network identifier + * @return Network + */ + public function getNetwork($network = null) { + $network = (empty($network)) ? $this->setting('network') : $network; + $this->log('trace', 'Getting Network: ' . var_export($network, true)); + if ($network === 'livenet') { + return new Bitpay\Network\Livenet(); + } elseif ($network === 'testnet') { + return new Bitpay\Network\Testnet(); + } elseif (is_array($network)) { + if (isset($network['url']) && isset($network['port']) && isset($network['port_required'])) { + return new Bitpay\Network\Customnet($network['url'], (int)$network['port'], (boolean)$network['port_required']); + } + } + $this->setting('network', 'livenet'); + return new Bitpay\Network\Livenet(); + } + + /** + * Retrieves the private key used to interact with BitPay's API + * @throws UnexpectedValueException If the key cannot be decrypted and unserialized to a PrivateKey object + * @return PrivateKey + */ + private function getPrivateKey() { + $private_key = $this->setting('private_key'); + + // Null check the private key, and generate it should it not exist + if (is_null($private_key)) { + $this->log('info', $this->language->get('log_no_private_key')); + return $this->generateId(); + } + + $this->log('debug', $this->language->get('log_private_key_found')); + $private_key = unserialize($this->encryption->decrypt('0123456789', $private_key)); + + // Check for key integrity + if (!($private_key instanceof Bitpay\PrivateKey)) { + $this->log('error', sprintf($this->language->get('log_private_key_wrong_type'), gettype($private_key))); + $this->log('trace', sprintf($this->language->get('log_encrypted_key'), $private_key)); + $this->log('trace', sprintf($this->language->get('log_decrypted_key'), $this->encryption->decrypt('0123456789', $private_key))); + throw new UnexpectedValueException(); + } + + return $private_key; + } + + /** + * Retrieves the public key used to interact with BitPay's API + * @return PublicKey + */ + public function getPublicKey() { + $public_key = $this->setting('public_key'); + + // Null check the private key, and generate it should it not exist + if (is_null($public_key)) { + $this->log('info', $this->language->get('log_no_public_key')); + $private_key = $this->getPrivateKey(); + return $private_key->getPublicKey(); + } + + $this->log('debug', $this->language->get('log_public_key_found')); + $public_key = @unserialize($this->encryption->decrypt('0123456789', $public_key)); + + // Check for key integrity + if (!($public_key instanceof Bitpay\PublicKey)) { + $this->log('error', sprintf($this->language->get('log_public_key_wrong_type'), gettype($public_key))); + $this->log('trace', sprintf($this->language->get('log_encrypted_key'), $public_key)); + $this->log('trace', sprintf($this->language->get('log_decrypted_key'), $this->encryption->decrypt('0123456789', $public_key))); + + // Try to fix the problem by regenerating it + $this->log('debug', $this->language->get('log_public_key_regenerate')); + $private_key = $this->getPrivateKey(); + $public_key = $private_key->getPublicKey(); + $this->log('warn', $this->language->get('log_regenerate_success')); + $this->setting('public_key', $this->encryption->encrypt(serialize($public_key))); + } + + return $public_key; + } + + /** + * Constructs some helpful diagnostic info. + * @return string + */ + public function getServerInfo() { + $gmp = extension_loaded('gmp') ? 'enabled' : 'missing'; + $bcmath = extension_loaded('bcmath') ? 'enabled' : 'missing'; + $info = "
Server Information:\n" .
+					"PHP: " . phpversion() . "\n" .
+					"PHP-GMP: " . $gmp . "\n" .
+					"PHP-BCMATH: " . $bcmath . "\n" .
+					"OpenCart: " . VERSION . "\n" .
+					"BitPay Plugin: " . $this->version . "\n" .
+					"BitPay Lib: {{bitpay_lib_version}}\n";
+		return $info;
+	}
+
+	/**
+	 * Sends a support request to BitPay.
+	 * @return void
+	 */
+	public function sendSupportRequest() {
+
+		$mail = new Mail();
+
+		$mail->setTo('support@bitpay.com');
+		$mail->setFrom($this->request->post['request_email_address']);
+		$mail->setSender($this->request->post['request_name']);
+		$mail->setSubject($this->request->post['request_subject']);
+
+		$description = $this->request->post['request_description'];
+
+		// Include server info?
+		if ($this->request->post['request_send_server_info'] === "1") {
+			$description .= "\n\n" . $this->getServerInfo();
+		}
+		$mail->setHtml($description);
+
+		// Include BitPay logs?
+		if ($this->request->post['request_send_logs'] === "1") {
+			$mail->addAttachment(DIR_LOGS . 'bitpay.log');
+		}
+
+		$mail->send();
+	}
+
+	/**
+	 *  Creates the BitPay Invoice from a prepared Invoice
+	 *  @param Invoice $invoice Prepared invoice to send to BitPay
+	 *  @return Invoice complete invoice returned by BitPay
+	 */
+	public function createInvoice($invoice) {
+		$this->log('info', 'Attempting to generate invoice for ' . $invoice->getOrderId() . '...');
+
+		$token = new \Bitpay\Token();
+		$token->setToken($this->setting('token'));
+
+		$client = $this->getClient();
+		$client->setToken($token);
+
+		return $client->createInvoice($invoice);
+	}
+
+	/**
+	 *  Retrieves a BitPay Invoice by ID
+	 *  @param string $invoice_id
+	 *  @param string $network Optional network identifier
+	 *  @return Invoice
+	 */
+	public function getInvoice($invoice_id, $network = null) {
+		$this->log('info', 'Attempting to retrieve invoice for ' . $invoice_id . '...');
+
+		$token = new \Bitpay\Token();
+		$token->setToken($this->setting('token'));
+
+		$client = $this->getClient($network);
+		$client->setToken($token);
+
+		return $client->getInvoice($invoice_id);
+	}
+
+	/**
+	 * Logs with an arbitrary level.
+	 * @param string $level The type of log.
+	 *						Should be 'error', 'warn', 'info', 'debug', 'trace'
+	 *						In normal mode, 'error' and 'warn' are logged
+	 *						In debug mode, all are logged
+	 * @param string $message The message of the log
+	 * @param int $depth How deep to go to find the calling function
+	 * @return void
+	 */
+	public function log($level, $message, $depth = 0) {
+		$level = strtoupper($level);
+		$prefix = '[' . $level . ']';
+
+		// Debug formatting
+		if ($this->setting('debug') === '1') {
+			$depth += 1;
+			$prefix .= '{';
+			$backtrace = debug_backtrace();
+			if (isset($backtrace[$depth]['class'])) {
+				$class = preg_replace('/[a-z]/', '', $backtrace[$depth]['class']);
+				$prefix .= $class . $backtrace[$depth]['type'];
+			}
+			if (isset($backtrace[$depth]['function'])) {
+				$prefix .= $backtrace[$depth]['function'];
+			}
+			$prefix .= '}';
+		}
+
+		if ('ERROR' === $level || 'WARN' === $level || $this->setting('debug') === '1') {
+			$this->logger->write($prefix . ' ' . $message);
+		}
+	}
+
+	/**
+	 * Better setting method for bitpay settings
+	 *
+	 * Automatically persists to database on set and combines getting and setting into one method
+	 * Assumes bitpay_ prefix
+	 *
+	 * @param string $key Setting key
+	 * @param string $value Setting value if setting the value
+	 * @return string|null|void Setting value, or void if setting the value
+	 */
+	public function setting($key, $value = null) {
+		// Normalize key
+		$code = 'payment_bitpay';
+		$key = $code.'_' . $key;
+
+		// Set the setting
+		if (func_num_args() === 2) {
+			if (!is_array($value)) {
+				$this->db->query("UPDATE " . DB_PREFIX . "setting SET `value` = '" . $this->db->escape($value) . "', serialized = '0' WHERE `code` = '".$code."' AND `key` = '" . $this->db->escape($key) . "' AND store_id = '0'");
+			} else {
+				$this->db->query("UPDATE " . DB_PREFIX . "setting SET `value` = '" . $this->db->escape(serialize($value)) . "', serialized = '1' code `group` = 'bitpay' AND `key` = '" . $this->db->escape($key) . "' AND store_id = '0'");
+			}
+			return $this->config->set($key, $value);
+		}
+
+		// Get the setting
+		return $this->config->get($key);
+	}
+}
diff --git a/tests/phpcs/OpenCart/Sniffs/Spacing/ConcatenationSniff.php b/tests/phpcs/OpenCart/Sniffs/Spacing/ConcatenationSniff.php
new file mode 100644
index 0000000..432c921
--- /dev/null
+++ b/tests/phpcs/OpenCart/Sniffs/Spacing/ConcatenationSniff.php
@@ -0,0 +1,34 @@
+
+ * @link      http://pear.php.net/package/PHP_CodeSniffer
+ * @Licence   http://www.gnu.org/licenses/gpl-2.0.html
+ */
+class OpenCart_Sniffs_Spacing_ConcatenationSniff implements PHP_CodeSniffer_Sniff {
+	/**
+	 * Returns an array of tokens this test wants to listen for.
+	 *
+	 * @return array
+	 */
+	public function register() {
+		return array(T_STRING_CONCAT);
+
+	}//end register()
+
+
+	/**
+	 * Processes this test, when one of its tokens is encountered.
+	 */
+	public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) {
+		$tokens = $phpcsFile->getTokens();
+		if ($tokens[($stackPtr - 1)]['code'] !== T_WHITESPACE || $tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) {
+			$message = 'PHP concat operator must be surrounded by spaces';
+			$phpcsFile->addError($message, $stackPtr, 'Missing');
+		}
+	}
+}//end class
\ No newline at end of file
diff --git a/tests/phpcs/OpenCart/Sniffs/Spacing/OpenBracketSpacingSniff.php b/tests/phpcs/OpenCart/Sniffs/Spacing/OpenBracketSpacingSniff.php
new file mode 100644
index 0000000..924b873
--- /dev/null
+++ b/tests/phpcs/OpenCart/Sniffs/Spacing/OpenBracketSpacingSniff.php
@@ -0,0 +1,69 @@
+getTokens();
+
+        // Ignore curly brackets in javascript files.
+        if ($tokens[$stackPtr]['code'] === T_OPEN_CURLY_BRACKET
+            && $phpcsFile->tokenizerType === 'JS'
+        ) {
+            return;
+        }
+
+        if (isset($tokens[($stackPtr + 1)]) === true
+            && $tokens[($stackPtr + 1)]['code'] === T_WHITESPACE
+            && strpos($tokens[($stackPtr + 1)]['content'], $phpcsFile->eolChar) === false
+        ) {
+            $error = 'There should be no white space after an opening "%s"';
+            $phpcsFile->addError(
+                $error,
+                ($stackPtr + 1),
+                'OpeningWhitespace',
+                array($tokens[$stackPtr]['content'])
+            );
+        }
+
+    }//end process()
+}//end class
\ No newline at end of file
diff --git a/tests/phpcs/OpenCart/Sniffs/Variables/ValidVariableNameSniff.php b/tests/phpcs/OpenCart/Sniffs/Variables/ValidVariableNameSniff.php
new file mode 100644
index 0000000..0e5be78
--- /dev/null
+++ b/tests/phpcs/OpenCart/Sniffs/Variables/ValidVariableNameSniff.php
@@ -0,0 +1,209 @@
+
+ * @author    Marc McIntyre 
+ * @copyright 2006-2011 Squiz Pty Ltd (ABN 77 084 670 600)
+ * @license   http://opensource.org/licenses/BSD-3-Clause
+ * @link      http://pear.php.net/package/PHP_CodeSniffer
+ */
+
+if (class_exists('PHP_CodeSniffer_Standards_AbstractVariableSniff', true) === false) {
+    throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Standards_AbstractVariableSniff not found');
+}
+
+/**
+ * Squiz_Sniffs_NamingConventions_ValidVariableNameSniff.
+ *
+ * Checks the naming of variables and member variables.
+ *
+ * @category  PHP
+ * @package   PHP_CodeSniffer
+ * @author    Greg Sherwood 
+ * @author    Marc McIntyre 
+ * @copyright 2006-2011 Squiz Pty Ltd (ABN 77 084 670 600)
+ * @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
+ * @version   Release: 1.3.3
+ * @link      http://pear.php.net/package/PHP_CodeSniffer
+ */
+class OpenCart_Sniffs_Variables_ValidVariableNameSniff extends PHP_CodeSniffer_Standards_AbstractVariableSniff {
+
+    /**
+     * Tokens to ignore so that we can find a DOUBLE_COLON.
+     *
+     * @var array
+     */
+    private $_ignore = array(
+                        T_WHITESPACE,
+                        T_COMMENT,
+                       );
+
+	/**
+	 * Regex to match valid underscore names for variables
+	 *
+	 * @var string
+	 */
+	private static $underscore_var = '/^(_|[a-z](?:_?[a-z0-9]+)*)$/';
+
+	/**
+	 * Complementary regex to just exclude camel casing
+	 * @var string
+	 */
+	private static $camelcase = '/[a-z][A-Z]/';
+
+    /**
+     * Processes this test, when one of its tokens is encountered.
+     *
+     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
+     * @param int                  $stackPtr  The position of the current token in the
+     *                                        stack passed in $tokens.
+     *
+     * @return void
+     */
+    protected function processVariable(PHP_CodeSniffer_File $phpcsFile, $stackPtr) {
+        $tokens  = $phpcsFile->getTokens();
+        $varName = ltrim($tokens[$stackPtr]['content'], '$');
+
+        $phpReservedVars = array(
+                            '_SERVER',
+                            '_GET',
+                            '_POST',
+                            '_REQUEST',
+                            '_SESSION',
+                            '_ENV',
+                            '_COOKIE',
+                            '_FILES',
+                            'GLOBALS',
+                           );
+
+        // If it's a php reserved var, then its ok.
+        if (in_array($varName, $phpReservedVars) === true) {
+            return;
+        }
+
+		if ($tokens[$stackPtr - 1]['code'] === T_PAAMAYIM_NEKUDOTAYIM) {
+			// static vars just ensure no camelcase (caps allowed)
+			if (preg_match(self::$camelcase, $varName)) {
+				$error = 'Variable "%s" is not in valid underscore format';
+				$phpcsFile->addError($error, $stackPtr, 'NotUnderscore', array($varName));
+			}
+			return;
+		}
+
+        $objOperator = $phpcsFile->findNext(array(T_WHITESPACE), ($stackPtr + 1), null, true);
+        if ($tokens[$objOperator]['code'] === T_OBJECT_OPERATOR) {
+            // Check to see if we are using a variable from an object.
+            $var = $phpcsFile->findNext(array(T_WHITESPACE), ($objOperator + 1), null, true);
+            if ($tokens[$var]['code'] === T_STRING) {
+                $bracket = $objOperator = $phpcsFile->findNext(array(T_WHITESPACE), ($var + 1), null, true);
+                if ($tokens[$bracket]['code'] !== T_OPEN_PARENTHESIS) {
+                    $objVarName = $tokens[$var]['content'];
+
+                    if (preg_match(self::$underscore_var, $objVarName) === 0) {
+						$phpcsFile->addError(
+							'Variable "%s" is not in valid underscore format',
+							$var,
+							'NotUnderscore',
+							array($objVarName)
+						);
+                    }
+                }//end if
+            }//end if
+        }//end if
+
+		if (preg_match(self::$underscore_var, $varName) === 0) {
+            $error = 'Variable "%s" is not in valid underscore format';
+            $phpcsFile->addError($error, $stackPtr, 'NotUnderscore', array($varName));
+        }
+    }//end processVariable()
+
+    /**
+     * Processes class member variables.
+     *
+     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
+     * @param int                  $stackPtr  The position of the current token in the
+     *                                        stack passed in $tokens.
+     *
+     * @return void
+     */
+    protected function processMemberVar(PHP_CodeSniffer_File $phpcsFile, $stackPtr) {
+        $tokens = $phpcsFile->getTokens();
+
+        $varName     = ltrim($tokens[$stackPtr]['content'], '$');
+        $memberProps = $phpcsFile->getMemberProperties($stackPtr);
+        if (empty($memberProps) === true) {
+            // Couldn't get any info about this variable, which
+            // generally means it is invalid or possibly has a parse
+            // error. Any errors will be reported by the core, so
+            // we can ignore it.
+            return;
+        }
+
+        $errorData = array($varName);
+
+		if (
+			($memberProps['is_static'] && preg_match(self::$camelcase, $varName))
+			|| (!$memberProps['is_static'] && preg_match(self::$underscore_var, $varName) === 0)
+		) {
+            $error = 'Variable "%s" is not in valid underscore format';
+            $phpcsFile->addError($error, $stackPtr, 'MemberNotUnderscore', $errorData);
+        }
+
+    }//end processMemberVar()
+
+
+    /**
+     * Processes the variable found within a double quoted string.
+     *
+     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
+     * @param int                  $stackPtr  The position of the double quoted
+     *                                        string.
+     *
+     * @return void
+     */
+    protected function processVariableInString(PHP_CodeSniffer_File $phpcsFile, $stackPtr) {
+        $tokens = $phpcsFile->getTokens();
+
+        $phpReservedVars = array(
+                            '_SERVER',
+                            '_GET',
+                            '_POST',
+                            '_REQUEST',
+                            '_SESSION',
+                            '_ENV',
+                            '_COOKIE',
+                            '_FILES',
+                            'GLOBALS',
+                           );
+        if (preg_match_all('|[^\\\]\${?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)|', $tokens[$stackPtr]['content'], $matches) !== 0) {
+            foreach ($matches[1] as $varName) {
+                // If it's a php reserved var, then its ok.
+                if (in_array($varName, $phpReservedVars) === true) {
+                    continue;
+                }
+
+                // There is no way for us to know if the var is public or private,
+                // so we have to ignore a leading underscore if there is one and just
+                // check the main part of the variable name.
+                $originalVarName = $varName;
+                if (substr($varName, 0, 1) === '_') {
+                    if ($phpcsFile->hasCondition($stackPtr, array(T_CLASS, T_INTERFACE)) === true) {
+                        $varName = substr($varName, 1);
+                    }
+                }
+
+                if (preg_match(self::$underscore_var, $varName) === 0) {
+                    $varName = $matches[0];
+                    $error = 'Variable "%s" is not in valid underscore format';
+                    $data  = array($originalVarName);
+                    $phpcsFile->addError($error, $stackPtr, 'StringNotUnderscore', $data);
+
+                }
+            }
+        }//end if
+
+    }//end processVariableInString()
+}//end class
\ No newline at end of file
diff --git a/tests/phpcs/OpenCart/ruleset.xml b/tests/phpcs/OpenCart/ruleset.xml
new file mode 100644
index 0000000..4cf38c1
--- /dev/null
+++ b/tests/phpcs/OpenCart/ruleset.xml
@@ -0,0 +1,100 @@
+
+
+    The Code Sniffer rule set for OpenCart
+
+    
+
+    
+    
+        5
+        error
+    
+
+    
+    
+
+    
+    
+        1
+        warning
+    
+    
+    
+    
+
+    
+    
+        5
+        error
+    
+
+    
+    
+        5
+        error
+    
+
+    
+    
+
+    
+    
+
+    
+    
+
+    
+    
+
+    
+    
+
+    
+    
+
+    
+    
+
+    
+    
+
+    
+        *.tpl
+        *.css
+        *.html
+        *.ini
+        *.txt
+        1
+        warning
+    
+
+    
+    
+        Please review this TODO comment: %s
+        3
+        warning
+    
+
+    
+    
+        Please review this FIXME comment: %s
+        5
+        warning
+    
+
+    
+    
+        
+            
+        
+    
+
+    
+    
+
+    
+    */tests/*
+
+
+
+
\ No newline at end of file