diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..045f0e7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,33 @@ +# The MIT License (MIT) +# +# Copyright (c) 2011-2014 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. + +# These files/directories are not included when making +# an archive of this repository. +/build export-ignore +/scripts export-ignore +/tests export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.travis.yml export-ignore +build.xml export-ignore +composer.json export-ignore +modman export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5520859 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# The MIT License (MIT) +# +# Copyright (c) 2011-2014 BitPay +# +# 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. + +*.swp +lib/bitpay/bp_config.php +/bin/ +/build/cache/ +/build/dist/ +/build/docs/ +/build/logs/ +/build/magento/ +/vendor/ +composer.lock +composer.phar diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..3df5d21 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,41 @@ +# The MIT License (MIT) +# +# Copyright (c) 2011-2015 BitPay +# +# 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. +language: php +php: + - 5.6 + - 5.5 + - 5.4 + - hhvm +install: + - composer install +#script: ./bin/phing -verbose -propertyfile build/travis.properties build-travis +script: phpunit -c build/ +cache: + directories: + - bin/ + - build/cache/ + - build/magento/ + - vendor/ +matrix: + fast_finish: true + allow_failures: + - php: hhvm diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..0777cfa --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,76 @@ +2.1.19 (2018-04-09) + Simplified BitPay Magento configuration screen + Updated PHP library to v2.2.20 (supporting BitPay's API after May 1st 2018) + Support for Magento Marketplace: https://marketplace.magento.com/bitpay-bitpay-core.html + + +2.1.18 (2018-03-16) + Fixed dependencies in composer.json (#127) + Fixed landing page after failure or expired payment (#124, #123, #117, #120) + Fixed missing apikeys configuration in system.xml (#122) + Fixed IPN creating 2nd invoice instead of updating invoice (#121) + Fixed non-debug logging (#115) + Fixed checkout page text (#112) + +2.1.17 + Added missing methods in the Magento version below 1.9: _isSameCurrency, getStatusStatuses, addStatusfilter. +2.1.16 + Fixed creating after payment a second invoice in database instead of update invoice issue +2.1.14 + Use Magento shop base currency to create a BitPay invoice (instead of the shopper's selected currency) +2.1.13 + Redirected check out and iframe checkout share the same order creation logic. + The order will be created during saveOrder call to backend. +2.1.12 + iFrame order will create order +2.1.11 + Repaired redirected checkout invoice amount calculation +2.1.10 + Fixed currency issue + +2.1.9 + Fixed code incompatible with PHP 5.4 + +2.1.8 + Customer info now logged for invoice + Added direct checkout feature + +2.1.7 + MagentoStorage::load returns KeyInterface instance or throws an exception. + IPN handler now does not override manually updated order statuses + +2.1.6 + Pegged bitpay/php-client to commit 9027ce67e4b28516ff1ebd1046bdd15c37a7a59f + Fixes IPN issues + +2.1.5 + Updated bitpay/php-client to 2.2.4 + +2.1.4 + Removed Symfony dependency + Fixed timestamp logging in database + +2.1.2 + Added extra error handling and fixed debugging + Re-added new order email notifications + Updated README documentation for accuracy + Re-wrote delete.sh shell script to autodetect Magento directory and + be more helpful when cleaning an old BitPay plugin from the server + +2.1.1 + Bug fix sanitizing labels for token pairing + Replaced GMP only requirement with GMP or BC Math requirement + +2.1.0 + Bug fix with IPNs not working when debug mode is enabled + Updated BitPay PHP SDK + Added a release checklist + +2.0.1 + Small bug fix in how package script + +2.0.0 + Initial release of the new Magento Extension that supports BitPay's new + cryptographically secure API. You can read more about the new API on the + blog at + http://blog.bitpay.com/2014/09/18/announcing-the-new-bitpay-api.html diff --git a/GUIDE.md b/GUIDE.md new file mode 100644 index 0000000..3e6fe5c --- /dev/null +++ b/GUIDE.md @@ -0,0 +1,70 @@ +# Using the BitPay plugin for Magento + +## Prerequisites +You must have a BitPay merchant account to use this plugin. It's free to [sign-up for a BitPay merchant account](https://bitpay.com/start). + + +## Server Requirements + +* Last Cart Version Tested: 1.9.3.8 +* [Magento CE](http://magento.com/resources/system-requirements) 1.9.0.1 or higher. Older versions might work, however this plugin has been validated to work against the 1.9.0.1 Community Edition release. +* [GMP](http://us2.php.net/gmp) or [BC Math](http://us2.php.net/manual/en/book.bc.php) PHP extensions. GMP is preferred for performance reasons but you may have to install this as most servers do not come with it installed by default. BC Math is commonly installed however and the plugin will fall back to this method if GMP is not found. +* [OpenSSL](http://us2.php.net/openssl) Must be compiled with PHP and is used for certain cryptographic operations. +* [PHP](http://us2.php.net/downloads.php) 5.4 or higher. This plugin will not work on PHP 5.3 and below. + +## Installation + +**From the Magento Market Place** + +* Goto [https://marketplace.magento.com/bitpay-bitpay-core.html](https://marketplace.magento.com/bitpay-bitpay-core.html) and click the *Add to Cart* link. +* Select your shopping cart, click *Checkout* and then *Place order* +* Click *Install* and copy the access key of the latest version of the BitPay bitcoin acceptance plugin + +Once you have the key, log into you Magento Store's Admin Panel and navigate to **System > Magento Connect > Magento Connect Manager**. + +**NOTE:** It may ask you to log in again using the same credentials that you use to log into the Admin Panel. + +All you need to do is paste the extension key and click on the *Install* button. + +**WARNING:** It is good practice to backup your database before installing extensions. Please make sure you Create Backups. + + +**From the Releases Page:** + +Visit the [Releases](https://github.com/bitpay/magento-plugin/releases) page of this repository and download the latest version. Once this is done, you can just unzip the contents and use any method you want to put them on your server. The contents will mirror the Magento directory structure. + +**WARNING:** It is good practice to backup your database before installing extensions. Please make sure you Create Backups. + +## Configuration + +Configuration can be done using the Administrator section of your Megento store. Once Logged in, you will find the configuration settings under **System > Configuration > Sales > Payment Methods**. + +![BitPay Magento Settings](https://raw.githubusercontent.com/bitpay/magento-plugin/master/docs/MagentoSettings.png "BitPay Megento Settings") + +Here your will need to create a [pairing code](https://bitpay.com/api-tokens) using your BitPay merchant account. Once you have a Pairing Code, put the code in the Pairing Code field. This will take care of the rest for you. + +**NOTE:** Pairing Codes are only valid for a short period of time. If it expires before you get to use it, you can always create a new one an use the new one. + +**NOTE:** You will only need to do this once since each time you do this, the extension will generate public and private keys that are used to identify you when using the API. + +You are also able to configure how BitPay's IPN (Instant Payment Notifications) changes the order in your Magento store. + +![BitPay Invoice Settings](https://raw.githubusercontent.com/bitpay/magento-plugin/master/docs/MagentoInvoiceSettings.png "BitPay Invoice Settings") + + +## Usage + +Once enabled, your customers will be given the option to pay with Bitcoins. Once they checkout they are redirected to a full screen BitPay invoice to pay for the order. + +As a merchant, the orders in your Magento store can be treated as any other order. You may need to adjust the Invoice Settings depending on your order fulfillment. + +## When Upgrading From Plugin Version 1.x to 2.x: + +**Very Important:** You must complete remove any previous versions of the Bitpay Magento plugin before installing this new updated version. The plugin has been completely re-written to work with BitPay's new cryptographically secure RESTful API and will conflict with any previous plugin versions which use the old API. To help you remove the old plugin files from your system, we have created a convenient shell script for Unix/Linux/Mac OS systems which will scan your webserver for these older files and delete them. You may also remove these files by hand of course and the complete list of the files can be found in the source of the script for your convenience. You can download this delete script here: [scripts/delete.sh](https://github.com/bitpay/magento-plugin/blob/master/scripts/delete.sh). + +To use this script, simply download to your server and execute the script from a shell. You may have to mark the script executable before first use. + +```sh +chmod +x delete.sh +./delete.sh +``` diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e6a7a65 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2011-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 index d3a0e35..43af0ae 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,74 @@ -©2011 BIT-PAY LLC. -Permission is hereby granted to any person obtaining a copy of this software -and associated documentation for use and/or modification in association with -the bitpay.com service. - -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. - -Bitcoin payment module using the bitpay.com service. - -Installation ------------- -Copy these files into your Magento directory. - -Configuration -------------- -1. Create an API key at bitpay.com by clicking My Account > API Access Keys > Add New API Key. -2. In Admin panel under "System > Configuration > Sales > Payment Methods > Bitcoins": - a. Verify that the module is enabled. - b. Enter your API key - c. Select a transaction speed. The high speed will send a confirmation as soon as a transaction is received in the bitcoin network (usually a few seconds). A medium speed setting will typically take 10 minutes. The low speed setting usually takes around 1 hour. See the bitpay.com merchant documentation for a full description of the transaction speed settings. - d. Verify that the currencies option includes your store's currencies. If it doesn't, check bitpay.com to see if they support your desired currency. If so, you may simply add the currency to the list using this setting. If not, you will not be able to use that currency. - e. (optional) Adjust the "Fullscreen Invoice" setting. "No" means that payment instructions are embedded in the checkout page. "Yes" means that the buyer will be redirected to bitpay.com to pay their order. - -Usage ------ -When a shopper chooses the Bitcoin payment method, they will be presented with an order summary as the next step (prices are shown in whatever currency they've selected for shopping). If the fullscreen option is disabled, they can pay for their order using the address shown on the screen. Otherwise they will place their order and be redirected to bitpay.com to pay. - -The order status in the admin panel will be "Processing" if payment has been confirmed. - -Note: This extension does not provide a means of automatically pulling a current BTC exchange rate for presenting BTC prices to shoppers. - -Change Log ----------- -Version 1 - - Initial version, tested against Magento 1.6.0.0 - -Version 2 - - Now supports API keys instead of SSL files. Tested against 1.7.0.2. - -Version 3 - - Now shows an iframe on the checkout page instead of redirecting to bitpay.com. +# NOTICE +This is a Community-supported project. + +If you are interested in becoming a maintainer of this project, please contact us at integrations@bitpay.com. Developers at BitPay will attempt to work along the new maintainers to ensure the project remains viable for the foreseeable future. + +# Description + +Bitcoin payment plugin for Magento using the bitpay.com service. + +[![Build Status](https://travis-ci.org/bitpay/magento-plugin.svg?branch=master)](https://travis-ci.org/bitpay/magento-plugin) + + +## Quick Start Guide + +To get up and running with our plugin quickly, see the GUIDE here: https://github.com/bitpay/magento-plugin/blob/master/GUIDE.md + +## Support + +**BitPay Support:** + +* Last Cart Version Tested: 1.9.3.8 +* [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 + +**Magento Support:** + +* [Homepage](http://magento.com) +* [Documentation](http://docs.magentocommerce.com) +* [Community Edition Support Forums](https://www.magentocommerce.com/support/ce/) + +## Troubleshooting + +1. Ensure a valid SSL certificate is installed on your server. Also ensure your root CA cert is updated. If your CA cert is not current, you will see curl SSL verification errors. +2. Verify that your web server is not blocking POSTs from servers it may not recognize. Double check this on your firewall as well, if one is being used. +3. Check the `payment_bitpay.log` file for any errors during BitPay payment attempts. If you contact BitPay support, they will ask to see the log file to help diagnose the problem. The log file will be found inside your Magento's `var/log/` directory. **NOTE:** You will need to enable the debugging setting for the extension to output information into the log file. +4. Check the version of this plugin against the official plugin repository to ensure you are using the latest version. Your issue might have been addressed in a newer version! See the [Releases](https://github.com/bitpay/magento-plugin/releases) page or the Magento Connect store for the latest version. +5. If all else fails, send an email describing your issue **in detail** to support@bitpay.com + +**TIP:** When contacting support it will help us is you provide: + +* Magento CE Version (Found at the bottom page in the Administration section) +* Other extensions you have installed + * Some extensions do not play nice +* Configuration settings for the extension (Most merchants take screen grabs) +* Any log files that will help + * web server error logs + * enabled debugging for this extension and send us `var/log/payment_bitpay.log` +* Screen grabs of error message if applicable. + + +## Contribute + +For developers wanting to contribute to this project, it is assumed you have a stable Magento environment to work with, and are familiar with developing for Magento. You will need to clone this repository or fork and clone the repository you created. + +Once you have cloned the repository, you will need to run [composer install](https://getcomposer.org/doc/00-intro.md#using-composer). Using and setting up composer is outside the scope, however you will find the documentation on their site comprehensive. You can then run the ``scripts/package`` script to create a distribution files which you can find in ``build/dist``. This is the file that you can upload to your server to unzip or do with what you will. + +If you encounter any issues or implement any updates or changes, please open an [issue](https://github.com/bitpay/magento-plugin/issues) or submit a Pull Request. + +**NOTE:** The ``scripts/package`` file contains some configuration settings that will need to change for different releases. If you are using this script to build files that are for distribution, these will need to be updated. + + +## License + +The MIT License (MIT) + +Copyright (c) 2011-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/app/code/community/Bitpay/Bitcoins/Block/Iframe.php b/app/code/community/Bitpay/Bitcoins/Block/Iframe.php deleted file mode 100644 index 3611ef5..0000000 --- a/app/code/community/Bitpay/Bitcoins/Block/Iframe.php +++ /dev/null @@ -1,76 +0,0 @@ -setTemplate('bitcoins/iframe.phtml'); - parent::_construct(); - } - - public function GetQuoteId() - { - $quote = $this->getQuote(); - $quoteId = $quote->getId(); - return $quoteId; - } - - - // create an invoice and return the url so that iframe.phtml can display it - public function GetIframeUrl() - { - // are they using bitpay? - if (!($quote = Mage::getSingleton('checkout/session')->getQuote()) - or !($payment = $quote->getPayment()) - or !($instance = $payment->getMethodInstance()) - or ($instance->getCode() != 'Bitcoins')) - return 'notbitpay'; - - // fullscreen disabled? - if (Mage::getStoreConfig('payment/Bitcoins/fullscreen')) - return 'disabled'; - - include Mage::getBaseDir('lib').'/bitpay/bp_lib.php'; - - $apiKey = Mage::getStoreConfig('payment/Bitcoins/api_key'); - $speed = Mage::getStoreConfig('payment/Bitcoins/speed'); - - $quote = $this->getQuote(); - $quoteId = $quote->getId(); - if (Mage::getModel('Bitcoins/ipn')->GetQuotePaid($quoteId)) - return 'paid'; // quote's already paid, so don't show the iframe - - $options = array( - 'currency' => $quote->getQuoteCurrencyCode(), - 'fullNotifications' => 'true', - 'notificationURL' => Mage::getUrl('bitpay_callback'), - 'redirectURL' => Mage::getUrl('customer/account'), - 'transactionSpeed' => $speed, - 'apiKey' => $apiKey, - ); - - // customer data - $method = Mage::getModel('Bitcoins/paymentMethod'); - $options += $method->ExtractAddress($quote->getShippingAddress()); - - // Mage doesn't round the total until saving and it can have more precision at this point which would be bad for later comparing records w/ bitpay. So round here to match what the price will be saved as: - $price = round($quote->getGrandTotal(),4); - - //serialize info about the quote to detect changes - $hash = $method->getQuoteHash($quoteId); - - Mage::log('invoicing for '.$price.' '.$quote->getQuoteCurrencyCode(), NULL, 'bitpay.log'); - $invoice = bpCreateInvoice($quoteId, $price, array('quoteId' => $quoteId, 'quoteHash' => $hash), $options); - Mage::log($invoice, NULL, 'bitpay.log'); - - if (array_key_exists('error', $invoice)) - { - Mage::log('Error creating bitpay invoice', null, 'bitpay.log'); - Mage::log($invoice['error'], null, 'bitpay.log'); - Mage::throwException("Error creating bit-pay invoice. Please try again or use another payment option."); - return false; - } - - return $invoice['url'].'&view=iframe'; - } -} \ No newline at end of file diff --git a/app/code/community/Bitpay/Bitcoins/Model/Ipn.php b/app/code/community/Bitpay/Bitcoins/Model/Ipn.php deleted file mode 100644 index 5fc9578..0000000 --- a/app/code/community/Bitpay/Bitcoins/Model/Ipn.php +++ /dev/null @@ -1,80 +0,0 @@ -_init('Bitcoins/ipn'); - return parent::_construct(); - } - - function Record($invoice) - { - return $this - ->setQuoteId(isset($invoice['posData']['quoteId']) ? $invoice['posData']['quoteId'] : NULL) - ->setOrderId(isset($invoice['posData']['orderId']) ? $invoice['posData']['orderId'] : NULL) - ->setPosData(json_encode($invoice['posData'])) - ->setInvoiceId($invoice['id']) - ->setUrl($invoice['url']) - ->setStatus($invoice['status']) - ->setBtcPrice($invoice['btcPrice']) - ->setPrice($invoice['price']) - ->setCurrency($invoice['currency']) - ->setInvoiceTime(intval($invoice['invoiceTime']/1000.0)) - ->setExpirationTime(intval($invoice['expirationTime']/1000.0)) - ->setCurrentTime(intval($invoice['currentTime']/1000.0)) - ->save(); - } - - function GetStatusReceived($quoteId, $statuses) - { - if (!$quoteId) - return false; - - $quote = Mage::getModel('sales/quote')->load($quoteId, 'entity_id'); - if (!$quote) - { - Mage::log('quote not found', NULL, 'bitpay.log'); - return false; - } - - $quoteHash = Mage::getModel('Bitcoins/paymentMethod')->getQuoteHash($quoteId); - if (!$quoteHash) - { - Mage::log('Could not find quote hash for quote '.$quoteId, NULL, 'bitpay.log'); - return false; - } - - $collection = $this->getCollection()->AddFilter('quote_id', $quoteId); - foreach($collection as $i) - { - if (in_array($i->getStatus(), $statuses)) - { - // check that quote data was not updated after IPN sent - $posData = json_decode($i->getPosData()); - if (!$posData) - continue; - - if ($quoteHash == $posData->quoteHash) - return true; - } - } - - return false; - } - - function GetQuotePaid($quoteId) - { - return $this->GetStatusReceived($quoteId, array('paid', 'confirmed', 'complete')); - } - - function GetQuoteComplete($quoteId) - { - return $this->GetStatusReceived($quoteId, array('confirmed', 'complete')); - } - - - -} - -?> \ No newline at end of file diff --git a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php deleted file mode 100644 index 53d6a9d..0000000 --- a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php +++ /dev/null @@ -1,253 +0,0 @@ -_canUseCheckout; - } - - public function authorize(Varien_Object $payment, $amount) - { - if (!Mage::getStoreConfig('payment/Bitcoins/fullscreen')) - return $this->CheckForPayment($payment); - else - return $this->CreateInvoiceAndRedirect($payment, $amount); - - } - - function CheckForPayment($payment) - { - $quoteId = $payment->getOrder()->getQuoteId(); - $ipn = Mage::getModel('Bitcoins/ipn'); - if (!$ipn->GetQuotePaid($quoteId)) - { - Mage::throwException("Order not paid for. Please pay first and then Place your Order."); - } - else if (!$ipn->GetQuoteComplete($quoteId)) - { - // order status will be PAYMENT_REVIEW instead of PROCESSING - $payment->setIsTransactionPending(true); - } - else - { - $this->MarkOrderPaid($payment->getOrder()); - } - - return $this; - } - - function MarkOrderPaid($order) - { - $order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, true)->save(); - if (!count($order->getInvoiceCollection())) - { - $invoice = $order->prepareInvoice() - ->setTransactionId(1) - ->addComment('Invoiced automatically by Bitpay/Bitcoins/controllers/IndexController.php') - ->register() - ->pay(); - - $transactionSave = Mage::getModel('core/resource_transaction') - ->addObject($invoice) - ->addObject($invoice->getOrder()); - $transactionSave->save(); - } - } - - // given Mage_Core_Model_Abstract, return api-friendly address - function ExtractAddress($address) - { - $options = array(); - $options['buyerName'] = $address->getName(); - if ($address->getCompany()) - $options['buyerName'] = $options['buyerName'].' c/o '.$address->getCompany(); - $options['buyerAddress1'] = $address->getStreet1(); - $options['buyerAddress2'] = $address->getStreet2(); - $options['buyerAddress3'] = $address->getStreet3(); - $options['buyerAddress4'] = $address->getStreet4(); - $options['buyerCity'] = $address->getCity(); - $options['buyerState'] = $address->getRegionCode(); - $options['buyerZip'] = $address->getPostcode(); - $options['buyerCountry'] = $address->getCountry(); - $options['buyerEmail'] = $address->getEmail(); - $options['buyerPhone'] = $address->getTelephone(); - // trim to fit API specs - foreach(array('buyerName', 'buyerAddress1', 'buyerAddress2', 'buyerAddress3', 'buyerAddress4', 'buyerCity', 'buyerState', 'buyerZip', 'buyerCountry', 'buyerEmail', 'buyerPhone') as $f) - $options[$f] = substr($options[$f], 0, 100); - return $options; - } - - function CreateInvoiceAndRedirect($payment, $amount) - { - include Mage::getBaseDir('lib').'/bitpay/bp_lib.php'; - - $apiKey = Mage::getStoreConfig('payment/Bitcoins/api_key'); - $speed = Mage::getStoreConfig('payment/Bitcoins/speed'); - - $order = $payment->getOrder(); - $orderId = $order->getIncrementId(); - $options = array( - 'currency' => $order->getBaseCurrencyCode(), - 'buyerName' => $order->getCustomerFirstname().' '.$order->getCustomerLastname(), - 'fullNotifications' => 'true', - 'notificationURL' => Mage::getUrl('bitpay_callback'), - 'redirectURL' => Mage::getUrl('customer/account'), - 'transactionSpeed' => $speed, - 'apiKey' => $apiKey, - ); - $options += $this->ExtractAddress($order->getShippingAddress()); - - $invoice = bpCreateInvoice($orderId, $amount, array('orderId' => $orderId), $options); - - $payment->setIsTransactionPending(true); // status will be PAYMENT_REVIEW instead of PROCESSING - - if (array_key_exists('error', $invoice)) - { - Mage::log('Error creating bitpay invoice', null, 'bitpay.log'); - Mage::log($invoice['error'], null, 'bitpay.log'); - Mage::throwException("Error creating bit-pay invoice. Please try again or use another payment option."); - } - else - { - $invoiceId = Mage::getModel('sales/order_invoice_api')->create($orderId, array()); - Mage::getSingleton('customer/session')->setRedirectUrl($invoice['url']); - } - - return $this; - } - - public function getOrderPlaceRedirectUrl() - { - if (Mage::getStoreConfig('payment/Bitcoins/fullscreen')) - return Mage::getSingleton('customer/session')->getRedirectUrl(); - else - return ''; - } - - # computes a unique hash determined by the contents of the cart - public function getQuoteHash($quoteId) - { - $quote = Mage::getModel('sales/quote')->load($quoteId, 'entity_id'); - if (!$quote) - { - Mage::log('getQuoteTimestamp: quote not found', NULL, 'bitpay.log'); - return false; - } - - #encode items - $items = $quote->getAllItems(); - $latest = NULL; - $description = ''; - foreach($items as $i) - { - $description.= 'i'.$i->getItemId().'q'.$i->getQty(); - - # could encode $i->getOptions() here but item ids are incremented if options are changed - } - - $hash = base64_encode(hash_hmac('sha256', $description, $quoteId)); - $hash = substr($hash, 0, 30); // fit it in posData maxlen - - Mage::log("quote $quoteId descr $description hash $hash", NULL, 'bitpay.log'); - - return $hash; - } - -} -?> \ No newline at end of file diff --git a/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn.php b/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn.php deleted file mode 100644 index eaa3571..0000000 --- a/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn.php +++ /dev/null @@ -1,11 +0,0 @@ -_init('Bitcoins/ipn', 'id'); - } -} - -?> \ No newline at end of file diff --git a/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn/Collection.php b/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn/Collection.php deleted file mode 100644 index 53a2063..0000000 --- a/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn/Collection.php +++ /dev/null @@ -1,11 +0,0 @@ -_init('Bitcoins/ipn'); - } -} - -?> \ No newline at end of file diff --git a/app/code/community/Bitpay/Bitcoins/Model/Source/Speed.php b/app/code/community/Bitpay/Bitcoins/Model/Source/Speed.php deleted file mode 100644 index 76b556b..0000000 --- a/app/code/community/Bitpay/Bitcoins/Model/Source/Speed.php +++ /dev/null @@ -1,24 +0,0 @@ - 'low', - 'label' => 'Low', - ), - array( - 'value' => 'medium', - 'label' => 'Medium', - ), - array( - 'value' => 'high', - 'label' => 'High', - )); - } -} - -?> \ No newline at end of file diff --git a/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php b/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php deleted file mode 100644 index 356d1b0..0000000 --- a/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php +++ /dev/null @@ -1,53 +0,0 @@ -getRequest()->getParams(); - $quoteId = $params['quote']; - $paid = Mage::getModel('Bitcoins/ipn')->GetQuotePaid($quoteId); - print json_encode(array('paid' => $paid)); - exit(); - } - - // bitpay's IPN lands here - public function indexAction() { - require Mage::getBaseDir('lib').'/bitpay/bp_lib.php'; - - Mage::log(file_get_contents('php://input'), null, 'bitpay.log'); - - $apiKey = Mage::getStoreConfig('payment/Bitcoins/api_key'); - $invoice = bpVerifyNotification($apiKey); - - if (is_string($invoice)) - Mage::log("bitpay callback error: $invoice", null, 'bitpay.log'); - else { - // get the order - if (isset($invoice['posData']['quoteId'])) { - $quoteId = $invoice['posData']['quoteId']; - $order = Mage::getModel('sales/order')->load($quoteId, 'quote_id'); - } - else { - $orderId = $invoice['posData']['orderId']; - $order = Mage::getModel('sales/order')->loadByIncrementId($orderId); - } - - // save the ipn so that we can find it when the user clicks "Place Order" - Mage::getModel('Bitcoins/ipn')->Record($invoice); - - // update the order if it exists already - if ($order->getId()) - switch($invoice['status']) { - case 'confirmed': - case 'complete': - $method = Mage::getModel('Bitcoins/paymentMethod'); - $method->MarkOrderPaid($order); - - break; - } - } - } - -} diff --git a/app/code/community/Bitpay/Bitcoins/etc/config.xml b/app/code/community/Bitpay/Bitcoins/etc/config.xml deleted file mode 100644 index 0a83912..0000000 --- a/app/code/community/Bitpay/Bitcoins/etc/config.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - standard - - Bitpay_Bitcoins - bitpay_callback - - - - - - - bitcoins.xml - - - - - - - - - 1.1.0 - - - - - - - Bitpay_Bitcoins_Block - - - - - - Bitpay_Bitcoins_Model - Bitcoins_resource - - - Bitpay_Bitcoins_Model_Resource - - - bitpay_ipns
-
-
-
-
- - - - - - Bitpay_Bitcoins - - - core_setup - - - - - core_write - - - - - core_read - - - -
- - - - - 1 - Bitcoins/paymentMethod - Bitcoins - low - 0 - BTC, USD, EUR, GBP, AUD, BGN, BRL, CAD, CHF, CNY, CZK, DKK, HKD, HRK, HUF, IDR, ILS, INR, JPY, KRW, LTL, LVL, MXN, MYR, NOK, NZD, PHP, PLN, RON, RUB, SEK, SGD, THB, TRY, ZAR - authorize - - - -
\ No newline at end of file diff --git a/app/code/community/Bitpay/Bitcoins/etc/system.xml b/app/code/community/Bitpay/Bitcoins/etc/system.xml deleted file mode 100644 index f610b95..0000000 --- a/app/code/community/Bitpay/Bitcoins/etc/system.xml +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - 670 - 1 - 1 - 0 - - - - select - adminhtml/system_config_source_yesno - 0 - 1 - 1 - 0 - - - - <label>Title</label> - <frontend_type>text</frontend_type> - <sort_order>1</sort_order> - <show_in_default>1</show_in_default> - <show_in_website>1</show_in_website> - <show_in_store>0</show_in_store> - - - - - select - adminhtml/system_config_source_yesno - 2 - 1 - 1 - 0 - - - - - text - 3 - 1 - 1 - 0 - - - - - select - Bitpay_Bitcoins_Model_Source_Speed - 5 - 1 - 1 - 0 - - - - - text - 6 - 1 - 1 - 0 - - - - - text - 7 - 1 - 1 - 0 - - - - - - - - \ No newline at end of file diff --git a/app/code/community/Bitpay/Bitcoins/sql/Bitcoins_setup/upgrade-0.1.0-1.0.0.php b/app/code/community/Bitpay/Bitcoins/sql/Bitcoins_setup/upgrade-0.1.0-1.0.0.php deleted file mode 100644 index 6d486c2..0000000 --- a/app/code/community/Bitpay/Bitcoins/sql/Bitcoins_setup/upgrade-0.1.0-1.0.0.php +++ /dev/null @@ -1,26 +0,0 @@ -startSetup(); - -$installer->run(" -CREATE TABLE IF NOT EXISTS `{$installer->getTable('Bitcoins/ipn')}` ( - `id` int(10) unsigned NOT NULL auto_increment, - `quote_id` int(10) unsigned default NULL, - `order_id` int(10) unsigned default NULL, - `invoice_id` varchar(200) NOT NULL, - `url` varchar(400) NOT NULL, - `status` varchar(20) NOT NULL, - `btc_price` decimal(16,8) NOT NULL, - `price` decimal(16,8) NOT NULL, - `currency` varchar(10) NOT NULL, - `invoice_time` int(11) unsigned NOT NULL, - `expiration_time` int(11) unsigned NOT NULL, - `current_time` int(11) unsigned NOT NULL, - PRIMARY KEY (`id`), - KEY `quote_id` (`quote_id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 ; - -"); - -$installer->endSetup(); \ No newline at end of file diff --git a/app/code/community/Bitpay/Bitcoins/sql/Bitcoins_setup/upgrade-1.0.0-1.1.0.php b/app/code/community/Bitpay/Bitcoins/sql/Bitcoins_setup/upgrade-1.0.0-1.1.0.php deleted file mode 100644 index 470be03..0000000 --- a/app/code/community/Bitpay/Bitcoins/sql/Bitcoins_setup/upgrade-1.0.0-1.1.0.php +++ /dev/null @@ -1,8 +0,0 @@ -startSetup(); - -$installer->run("ALTER TABLE `{$installer->getTable('Bitcoins/ipn')}` ADD `pos_data` VARCHAR( 256 ) NOT NULL AFTER `url`;"); - -$installer->endSetup(); \ No newline at end of file diff --git a/app/code/community/Bitpay/Core/Block/Adminhtml/System/Config/Form/Field/Extension.php b/app/code/community/Bitpay/Core/Block/Adminhtml/System/Config/Form/Field/Extension.php new file mode 100644 index 0000000..812836a --- /dev/null +++ b/app/code/community/Bitpay/Core/Block/Adminhtml/System/Config/Form/Field/Extension.php @@ -0,0 +1,31 @@ +debugData('[ERROR] In Bitpay_Core_Block_Adminhtml_System_Config_Form_Field_Extension::_getElementHtml(): Missing or invalid $element parameter passed to function.'); + throw new \Exception('In Bitpay_Core_Block_Adminhtml_System_Config_Form_Field_Extension::_getElementHtml(): Missing or invalid $element parameter passed to function.'); + } + + $phpExtension = $element->getFieldConfig()->php_extension; + + if (true === in_array($phpExtension, get_loaded_extensions())) { + return 'Installed'; + } + + return 'Not Installed'; + } +} diff --git a/app/code/community/Bitpay/Core/Block/Adminhtml/System/Config/Form/Field/Header.php b/app/code/community/Bitpay/Core/Block/Adminhtml/System/Config/Form/Field/Header.php new file mode 100644 index 0000000..80ab46f --- /dev/null +++ b/app/code/community/Bitpay/Core/Block/Adminhtml/System/Config/Form/Field/Header.php @@ -0,0 +1,33 @@ +debugData('[ERROR] In Bitpay_Core_Block_Adminhtml_System_Config_Form_Field_Header::render(): Missing or invalid $element parameter passed to function.'); + throw new \Exception('In Bitpay_Core_Block_Adminhtml_System_Config_Form_Field_Header::render(): Missing or invalid $element parameter passed to function.'); + } + + return $this->toHtml(); + } +} diff --git a/app/code/community/Bitpay/Core/Block/Form/Bitpay.php b/app/code/community/Bitpay/Core/Block/Form/Bitpay.php new file mode 100644 index 0000000..78bd184 --- /dev/null +++ b/app/code/community/Bitpay/Core/Block/Form/Bitpay.php @@ -0,0 +1,17 @@ +setTemplate($payment_template); + } +} diff --git a/app/code/community/Bitpay/Core/Block/Iframe.php b/app/code/community/Bitpay/Core/Block/Iframe.php new file mode 100644 index 0000000..327894b --- /dev/null +++ b/app/code/community/Bitpay/Core/Block/Iframe.php @@ -0,0 +1,49 @@ +setTemplate('bitpay/iframe.phtml'); + } + + /** + * create an invoice and return the url so that iframe.phtml can display it + * + * @return string + */ + public function getIframeUrl() + { + + if (!($quote = Mage::getSingleton('checkout/session')->getQuote()) + or !($payment = $quote->getPayment()) + or !($paymentMethod = $payment->getMethod()) + or ($paymentMethod !== 'bitpay') + or (Mage::getStoreConfig('payment/bitpay/fullscreen'))) + { + return 'notbitpay'; + } + + \Mage::helper('bitpay')->registerAutoloader(); + + // fullscreen disabled? + if (Mage::getStoreConfig('payment/bitpay/fullscreen')) + { + return 'disabled'; + } + + if (\Mage::getModel('bitpay/ipn')->getQuotePaid($this->getQuote()->getId())) { + return 'paid'; // quote's already paid, so don't show the iframe + } + + return 'bitpay'; + } +} diff --git a/app/code/community/Bitpay/Core/Block/Info.php b/app/code/community/Bitpay/Core/Block/Info.php new file mode 100644 index 0000000..32761bb --- /dev/null +++ b/app/code/community/Bitpay/Core/Block/Info.php @@ -0,0 +1,37 @@ +setTemplate('bitpay/info/default.phtml'); + } + + public function getBitpayInvoiceUrl() + { + $order = $this->getInfo()->getOrder(); + + if (false === isset($order) || true === empty($order)) { + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Info::getBitpayInvoiceUrl(): could not obtain the order.'); + throw new \Exception('In Bitpay_Core_Block_Info::getBitpayInvoiceUrl(): could not obtain the order.'); + } + + $incrementId = $order->getIncrementId(); + + if (false === isset($incrementId) || true === empty($incrementId)) { + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Info::getBitpayInvoiceUrl(): could not obtain the incrementId.'); + throw new \Exception('In Bitpay_Core_Block_Info::getBitpayInvoiceUrl(): could not obtain the incrementId.'); + } + + $bitpayInvoice = \Mage::getModel('bitpay/invoice')->load($incrementId, 'increment_id'); + + if (true === isset($bitpayInvoice) && false === empty($bitpayInvoice)) { + return $bitpayInvoice->getUrl(); + } + } +} diff --git a/app/code/community/Bitpay/Core/Helper/Data.php b/app/code/community/Bitpay/Core/Helper/Data.php new file mode 100644 index 0000000..6d5680f --- /dev/null +++ b/app/code/community/Bitpay/Core/Helper/Data.php @@ -0,0 +1,398 @@ +getExtensionVersion(), + ); + foreach($extendedDebugData as &$param) + { + $param = PHP_EOL . "\t\t" . $param; + } + + if (true === isset($debugData) && false === empty($debugData)) { + \Mage::getModel('bitpay/method_bitcoin')->debugData($extendedDebugData); + \Mage::getModel('bitpay/method_bitcoin')->debugData($debugData); + } + } + + /** + * @return boolean + */ + public function isDebug() + { + return (boolean) \Mage::getStoreConfig('payment/bitpay/debug'); + } + + /** + * Returns true if Transaction Speed has been configured + * + * @return boolean + */ + public function hasTransactionSpeed() + { + $speed = \Mage::getStoreConfig('payment/bitpay/speed'); + + return !empty($speed); + } + + /** + * Returns the URL where the IPN's are sent + * + * @return string + */ + public function getNotificationUrl() + { + return \Mage::getUrl(\Mage::getStoreConfig('payment/bitpay/notification_url')); + } + + /** + * Returns the URL where customers are redirected + * + * @return string + */ + public function getRedirectUrl() + { + return \Mage::getUrl(\Mage::getStoreConfig('payment/bitpay/redirect_url')); + } + + /** + * Registers the BitPay autoloader to run before Magento's. This MUST be + * called before using any bitpay classes. + */ + public function registerAutoloader() + { + if (true === empty($this->_autoloaderRegistered)) { + $autoloader_filename = \Mage::getBaseDir('lib').'/Bitpay/Autoloader.php'; + + if (true === is_file($autoloader_filename) && true === is_readable($autoloader_filename)) { + require_once $autoloader_filename; + \Bitpay\Autoloader::register(); + $this->_autoloaderRegistered = true; + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::registerAutoloader(): autoloader file was found and has been registered.'); + } else { + $this->_autoloaderRegistered = false; + $this->debugData('[ERROR] In Bitpay_Core_Helper_Data::registerAutoloader(): autoloader file was not found or is not readable. Cannot continue!'); + throw new \Exception('In Bitpay_Core_Helper_Data::registerAutoloader(): autoloader file was not found or is not readable. Cannot continue!'); + } + } + } + + /** + * This function will generate keys that will need to be paired with BitPay + * using + */ + public function generateAndSaveKeys() + { + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::generateAndSaveKeys(): attempting to generate new keypair and save to database.'); + + if (true === empty($this->_autoloaderRegistered)) { + $this->registerAutoloader(); + } + + $this->_privateKey = new Bitpay\PrivateKey('payment/bitpay/private_key'); + + if (false === isset($this->_privateKey) || true === empty($this->_privateKey)) { + $this->debugData('[ERROR] In Bitpay_Core_Helper_Data::generateAndSaveKeys(): could not create new Bitpay private key object. Cannot continue!'); + throw new \Exception('In Bitpay_Core_Helper_Data::generateAndSaveKeys(): could not create new Bitpay private key object. Cannot continue!'); + } else { + $this->_privateKey->generate(); + } + + $this->_publicKey = new Bitpay\PublicKey('payment/bitpay/public_key'); + + if (false === isset($this->_publicKey) || true === empty($this->_publicKey)) { + $this->debugData('[ERROR] In Bitpay_Core_Helper_Data::generateAndSaveKeys(): could not create new Bitpay public key object. Cannot continue!'); + throw new \Exception('In Bitpay_Core_Helper_Data::generateAndSaveKeys(): could not create new Bitpay public key object. Cannot continue!'); + } else { + $this->_publicKey + ->setPrivateKey($this->_privateKey) + ->generate(); + } + + $this->getKeyManager()->persist($this->_publicKey); + $this->getKeyManager()->persist($this->_privateKey); + + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::generateAndSaveKeys(): key manager called to persist keypair to database.'); + } + + /** + * Send a pairing request to BitPay to receive a Token + */ + public function sendPairingRequest($pairingCode) + { + if (false === isset($pairingCode) || true === empty($pairingCode)) { + $this->debugData('[ERROR] In Bitpay_Core_Helper_Data::sendPairingRequest(): missing or invalid pairingCode parameter.'); + throw new \Exception('In Bitpay_Core_Helper_Data::sendPairingRequest(): missing or invalid pairingCode parameter.'); + } else { + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::sendPairingRequest(): function called with the pairingCode parameter: ' . $pairingCode); + } + + if (true === empty($this->_autoloaderRegistered)) { + $this->registerAutoloader(); + } + + // Generate/Regenerate keys + $this->generateAndSaveKeys(); + $sin = $this->getSinKey(); + + if (false === isset($sin) || true === empty($sin)) { + $this->debugData('[ERROR] In Bitpay_Core_Helper_Data::sendPairingRequest(): could not retrieve the SIN parameter. Cannot continue!'); + throw new \Exception('In Bitpay_Core_Helper_Data::sendPairingRequest(): could not retrieve the SIN parameter. Cannot continue!'); + } else { + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::sendPairingRequest(): attempting to pair with the SIN parameter: ' . $sin); + } + + // Sanitize label + $label = preg_replace('/[^a-zA-Z0-9 ]/', '', \Mage::app()->getStore()->getName()); + $label = substr('Magento ' . $label, 0, 59); + + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::sendPairingRequest(): using the label "' . $label . '".'); + + $token = $this->getBitpayClient()->createToken( + array( + 'id' => (string) $sin, + 'pairingCode' => (string) $pairingCode, + 'label' => (string) $label, + ) + ); + $network = \Mage::getStoreConfig('payment/bitpay/network'); + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::sendPairingRequest(): using the network "' . $network . '".'); + + if (false === isset($token) || true === empty($token)) { + $this->debugData('[ERROR] In Bitpay_Core_Helper_Data::sendPairingRequest(): could not obtain the token from the pairing process. Cannot continue!'); + throw new \Exception('In Bitpay_Core_Helper_Data::sendPairingRequest(): could not obtain the token from the pairing process. Cannot continue!'); + } else { + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::sendPairingRequest(): token successfully obtained.'); + } + + $config = new \Mage_Core_Model_Config(); + + if (false === isset($config) || true === empty($config)) { + $this->debugData('[ERROR] In Bitpay_Core_Helper_Data::sendPairingRequest(): could not create new Mage_Core_Model_Config object. Cannot continue!'); + throw new \Exception('In Bitpay_Core_Helper_Data::sendPairingRequest(): could not create new Mage_Core_Model_Config object. Cannot continue!'); + } + + if($config->saveConfig('payment/bitpay/token', $token->getToken())) { + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::sendPairingRequest(): token saved to database.'); + } else { + $this->debugData('[ERROR] In Bitpay_Core_Helper_Data::sendPairingRequest(): token could not be saved to database.'); + throw new \Exception('In Bitpay_Core_Helper_Data::sendPairingRequest(): token could not be saved to database.'); + } + } + + /** + * @return Bitpay\SinKey + */ + public function getSinKey() + { + if (false === empty($this->_sin)) { + return $this->_sin; + } + + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getSinKey(): attempting to get the SIN parameter.'); + + if (true === empty($this->_autoloaderRegistered)) { + $this->registerAutoloader(); + } + + $this->_sin = new Bitpay\SinKey(); + + if (false === isset($this->_sin) || true === empty($this->_sin)) { + $this->debugData('[ERROR] In Bitpay_Core_Helper_Data::getSinKey(): could not create new BitPay SinKey object. Cannot continue!'); + throw new \Exception('In Bitpay_Core_Helper_Data::getSinKey(): could not create new BitPay SinKey object. Cannot continue!'); + } + + $this->_sin + ->setPublicKey($this->getPublicKey()) + ->generate(); + + if (false === isset($this->_sin) || true === empty($this->_sin)) { + $this->debugData('[ERROR] In Bitpay_Core_Helper_Data::getSinKey(): could not generate a new SIN from the public key. Cannot continue!'); + throw new \Exception('In Bitpay_Core_Helper_Data::getSinKey(): could not generate a new SIN from the public key. Cannot continue!'); + } + + return $this->_sin; + } + + public function getPublicKey() + { + if (true === isset($this->_publicKey) && false === empty($this->_publicKey)) { + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getPublicKey(): found an existing public key, returning that.'); + return $this->_publicKey; + } + + if (true === empty($this->_autoloaderRegistered)) { + $this->registerAutoloader(); + } + + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getPublicKey(): did not find an existing public key, attempting to load one from the key manager.'); + + $this->_publicKey = $this->getKeyManager()->load('payment/bitpay/public_key'); + + if (true === empty($this->_publicKey)) { + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getPublicKey(): could not load a public key from the key manager, generating a new one.'); + $this->generateAndSaveKeys(); + } else { + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getPublicKey(): successfully loaded public key from the key manager, returning that.'); + return $this->_publicKey; + } + + if (false === empty($this->_publicKey)) { + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getPublicKey(): successfully generated a new public key.'); + return $this->_publicKey; + } else { + $this->debugData('[ERROR] In Bitpay_Core_Helper_Data::getPublicKey(): could not load or generate a new public key. Cannot continue!'); + throw new \Exception('In Bitpay_Core_Helper_Data::getPublicKey(): could not load or generate a new public key. Cannot continue!'); + } + } + + public function getPrivateKey() + { + if (false === empty($this->_privateKey)) { + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getPrivateKey(): found an existing private key, returning that.'); + return $this->_privateKey; + } + + if (true === empty($this->_autoloaderRegistered)) { + $this->registerAutoloader(); + } + + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getPrivateKey(): did not find an existing private key, attempting to load one from the key manager.'); + + $this->_privateKey = $this->getKeyManager()->load('payment/bitpay/private_key'); + + if (true === empty($this->_privateKey)) { + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getPrivateKey(): could not load a private key from the key manager, generating a new one.'); + $this->generateAndSaveKeys(); + } else { + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getPrivateKey(): successfully loaded private key from the key manager, returning that.'); + return $this->_privateKey; + } + + if (false === empty($this->_privateKey)) { + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getPrivateKey(): successfully generated a new private key.'); + return $this->_privateKey; + } else { + $this->debugData('[ERROR] In Bitpay_Core_Helper_Data::getPrivateKey(): could not load or generate a new private key. Cannot continue!'); + throw new \Exception('In Bitpay_Core_Helper_Data::getPrivateKey(): could not load or generate a new private key. Cannot continue!'); + } + } + + /** + * @return Bitpay\KeyManager + */ + public function getKeyManager() + { + if (true === empty($this->_keyManager)) { + if (true === empty($this->_autoloaderRegistered)) { + $this->registerAutoloader(); + } + + $this->_keyManager = new Bitpay\KeyManager(new Bitpay\Storage\MagentoStorage()); + + if (false === isset($this->_keyManager) || true === empty($this->_keyManager)) { + $this->debugData('[ERROR] In Bitpay_Core_Helper_Data::getKeyManager(): could not create new BitPay KeyManager object. Cannot continue!'); + throw new \Exception('In Bitpay_Core_Helper_Data::getKeyManager(): could not create new BitPay KeyManager object. Cannot continue!'); + } else { + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getKeyManager(): successfully created new BitPay KeyManager object.'); + } + } + + return $this->_keyManager; + } + + /** + * @return Bitpay\Client + */ + public function getBitpayClient() + { + if (false === empty($this->_client)) { + return $this->_client; + } + + if (true === empty($this->_autoloaderRegistered)) { + $this->registerAutoloader(); + } + + $this->_client = new Bitpay\Client\Client(); + + if (false === isset($this->_client) || true === empty($this->_client)) { + $this->debugData('[ERROR] In Bitpay_Core_Helper_Data::getBitpayClient(): could not create new BitPay Client object. Cannot continue!'); + throw new \Exception('In Bitpay_Core_Helper_Data::getBitpayClient(): could not create new BitPay Client object. Cannot continue!'); + } else { + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getBitpayClient(): successfully created new BitPay Client object.'); + } + + if(\Mage::getStoreConfig('payment/bitpay/network') === 'livenet') { + $network = new Bitpay\Network\Livenet(); + } else { + $network = new Bitpay\Network\Testnet(); + } + $adapter = new Bitpay\Client\Adapter\CurlAdapter(); + + $this->_client->setPublicKey($this->getPublicKey()); + $this->_client->setPrivateKey($this->getPrivateKey()); + $this->_client->setNetwork($network); + $this->_client->setAdapter($adapter); + $this->_client->setToken($this->getToken()); + + return $this->_client; + } + + public function getToken() + { + if (true === empty($this->_autoloaderRegistered)) { + $this->registerAutoloader(); + } + + $token = new Bitpay\Token(); + + if (false === isset($token) || true === empty($token)) { + $this->debugData('[ERROR] In Bitpay_Core_Helper_Data::getToken(): could not create new BitPay Token object. Cannot continue!'); + throw new \Exception('In Bitpay_Core_Helper_Data::getToken(): could not create new BitPay Token object. Cannot continue!'); + } else { + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getToken(): successfully created new BitPay Token object.'); + } + + $token->setToken(\Mage::getStoreConfig('payment/bitpay/token')); + + return $token; + } + + /** + * @return string + */ + public function getLogFile() + { + return "payment_bitpay.log"; + } + + public function getExtensionVersion() + { + return (string) \Mage::getConfig()->getNode()->modules->Bitpay_Core->version; + } +} diff --git a/app/code/community/Bitpay/Core/Model/Config/PairingCode.php b/app/code/community/Bitpay/Core/Model/Config/PairingCode.php new file mode 100644 index 0000000..4526fc2 --- /dev/null +++ b/app/code/community/Bitpay/Core/Model/Config/PairingCode.php @@ -0,0 +1,43 @@ +getValue()); + + if (true === empty($pairingCode)) { + return; + } + + \Mage::helper('bitpay')->debugData('[INFO] In Bitpay_Core_Model_Config_PairingCode::save(): attempting to pair with BitPay with pairing code ' . $pairingCode); + + try { + \Mage::helper('bitpay')->sendPairingRequest($pairingCode); + } catch (\Exception $e) { + \Mage::helper('bitpay')->debugData(sprintf('[ERROR] Exception thrown while calling the sendPairingRequest() function. The specific error message is: "%s"', $e->getMessage())); + \Mage::getSingleton('core/session')->addError('There was an error while trying to pair with BitPay using the pairing code '.$pairingCode.'. Please make sure you select the correct Network (Livenet vs Testnet) and try again with a new 7 character pairing code or enable debug mode and send the "payment_bitpay.log" file to support@bitpay.com for more help.'); + + return; + } + + \Mage::getSingleton('core/session')->addSuccess('Pairing with BitPay was successful.'); + } +} diff --git a/app/code/community/Bitpay/Core/Model/Invoice.php b/app/code/community/Bitpay/Core/Model/Invoice.php new file mode 100644 index 0000000..c1c41b9 --- /dev/null +++ b/app/code/community/Bitpay/Core/Model/Invoice.php @@ -0,0 +1,74 @@ +_init('bitpay/invoice'); + } + + /** + * Adds data to model based on an Invoice that has been retrieved from + * BitPay's API + * + * @param Bitpay\Invoice $invoice + * @return Bitpay_Core_Model_Invoice + */ + public function prepareWithBitpayInvoice($invoice) + { + if (false === isset($invoice) || true === empty($invoice)) { + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Model_Invoice::prepareWithBitpayInvoice(): Missing or empty $invoice parameter.'); + throw new \Exception('In Bitpay_Core_Model_Invoice::prepareWithBitpayInvoice(): Missing or empty $invoice parameter.'); + } + + $this->addData( + array( + 'id' => $invoice->getId(), + 'url' => $invoice->getUrl(), + 'pos_data' => $invoice->getPosData(), + 'status' => $invoice->getStatus(), + 'price' => $invoice->getPrice(), + 'currency' => $invoice->getCurrency()->getCode(), + 'order_id' => $invoice->getOrderId(), + 'invoice_time' => intval(date_format($invoice->getInvoiceTime(), 'U')), + 'expiration_time' => intval(date_format($invoice->getExpirationTime(), 'U')), + 'current_time' => intval(date_format($invoice->getCurrentTime(), 'U')), + 'exception_status' => $invoice->getExceptionStatus(), + 'transactionCurrency' => $invoice->getTransactionCurrency() + ) + ); + + return $this; + } + + /** + * Adds information to based on the order object inside magento + * + * @param Mage_Sales_Model_Order $order + * @return Bitpay_Core_Model_Invoice + */ + public function prepareWithOrder($order) + { + if (false === isset($order) || true === empty($order)) { + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Model_Invoice::prepateWithOrder(): Missing or empty $order parameter.'); + throw new \Exception('In Bitpay_Core_Model_Invoice::prepateWithOrder(): Missing or empty $order parameter.'); + } + + $this->addData( + array( + 'quote_id' => $order['quote_id'], + 'increment_id' => $order['increment_id'], + ) + ); + + return $this; + } +} diff --git a/app/code/community/Bitpay/Core/Model/Ipn.php b/app/code/community/Bitpay/Core/Model/Ipn.php new file mode 100644 index 0000000..428c9c9 --- /dev/null +++ b/app/code/community/Bitpay/Core/Model/Ipn.php @@ -0,0 +1,71 @@ +_init('bitpay/ipn'); + } + + /** + * @param string $quoteId + * @param array $statuses + * + * @return boolean + */ + function GetStatusReceived($quoteId, $statuses) + { + if (!$quoteId) + { + return false; + } + + $order = \Mage::getModel('sales/order')->load($quoteId, 'quote_id'); + + if (false === isset($order) || true === empty($order)) { + \Mage::helper('bitpay')->debugData('[DEBUG] Bitpay_Core_Model_Ipn::GetStatusReceived(), order not found for quoteId' . $quoteId); + return false; + } + + + $orderId = $order->getIncrementId(); + + if (false === isset($orderId) || true === empty($orderId)) { + \Mage::helper('bitpay')->debugData('[DEBUG] Bitpay_Core_Model_Ipn::GetStatusReceived(), orderId not found for quoteId' . $quoteId); + return false; + } + + $collection = $this->getCollection(); + + foreach ($collection as $i) + { + if ($orderId == json_decode($i->pos_data, true)['orderId']) { + if (in_array($i->status, $statuses)) { + return true; + } + } + } + + return false; + } + + /** + * @param string $quoteId + * + * @return boolean + */ + function GetQuotePaid($quoteId) + { + return $this->GetStatusReceived($quoteId, array('paid', 'confirmed', 'complete')); + } + +} diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php new file mode 100644 index 0000000..4d97d2b --- /dev/null +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -0,0 +1,363 @@ +debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::authorize(): missing payment or amount parameters.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::authorize(): missing payment or amount parameters.'); + } + + // use the price in the currency of the store (not in the user selected currency) + $amount = $payment->getOrder()->getQuote()->getBaseGrandTotal(); + + $this->debugData('[INFO] Bitpay_Core_Model_Method_Bitcoin::authorize(): authorizing new order.'); + + // Create BitPay Invoice + $invoice = $this->initializeInvoice(); + + if (false === isset($invoice) || true === empty($invoice)) { + $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::authorize(): could not initialize invoice.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::authorize(): could not initialize invoice.'); + } + + //add order id to the redirect url to match order in the checkout/onepage/success if bitpay invoice expired + $invoice->setRedirectUrl(\Mage::getUrl(\Mage::getStoreConfig('payment/bitpay/redirect_url') . '/order_id/'.$payment->getOrder()->getId())); + + $invoice = $this->prepareInvoice($invoice, $payment, $amount); + + try { + $bitpayInvoice = \Mage::helper('bitpay')->getBitpayClient()->createInvoice($invoice); + } catch (\Exception $e) { + $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::authorize(): ' . $e->getMessage()); + //display min invoice value error + if(strpos($e->getMessage(), 'Invoice price must be') !== FALSE) + { + \Mage::throwException($e->getMessage()); + } + \Mage::throwException('In Bitpay_Core_Model_Method_Bitcoin::authorize(): Could not authorize transaction.'); + } + + self::$_redirectUrl = (Mage::getStoreConfig('payment/bitpay/fullscreen')) ? $bitpayInvoice->getUrl(): $bitpayInvoice->getUrl().'&view=iframe'; + + $this->debugData( + array( + '[INFO] BitPay Invoice created', + sprintf('Invoice URL: "%s"', $bitpayInvoice->getUrl()), + ) + ); + + $quote = \Mage::getSingleton('checkout/session')->getQuote(); + $order = \Mage::getModel('sales/order')->load($quote->getId(), 'quote_id'); + + // Save BitPay Invoice in database for reference + $mirrorInvoice = \Mage::getModel('bitpay/invoice') + ->prepareWithBitpayInvoice($bitpayInvoice) + ->prepareWithOrder(array('increment_id' => $order->getIncrementId(), 'quote_id'=> $quote->getId())) + ->save(); + + $this->debugData('[INFO] Leaving Bitpay_Core_Model_Method_Bitcoin::authorize(): invoice id ' . $bitpayInvoice->getId()); + + return $this; + } + + /** + * This makes sure that the merchant has setup the extension correctly + * and if they have not, it will not show up on the checkout. + * + * @see Mage_Payment_Model_Method_Abstract::canUseCheckout() + * @return bool + */ + public function canUseCheckout() + { + $token = \Mage::getStoreConfig('payment/bitpay/token'); + + if (false === isset($token) || true === empty($token)) { + /** + * Merchant must goto their account and create a pairing code to + * enter in. + */ + $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::canUseCheckout(): There was an error retrieving the token store param from the database or this Magento store does not have a BitPay token.'); + + return false; + } + + $this->debugData('[INFO] Leaving Bitpay_Core_Model_Method_Bitcoin::canUseCheckout(): token obtained from storage successfully.'); + + return true; + } + + /** + * Fetchs an invoice from BitPay + * + * @param string $id + * @return Bitpay\Invoice + */ + public function fetchInvoice($id) + { + if (false === isset($id) || true === empty($id)) { + $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::fetchInvoice(): missing or invalid id parameter.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::fetchInvoice(): missing or invalid id parameter.'); + } else { + $this->debugData('[INFO] In Bitpay_Core_Model_Method_Bitcoin::fetchInvoice(): function called with id ' . $id); + } + + \Mage::helper('bitpay')->registerAutoloader(); + + $client = \Mage::helper('bitpay')->getBitpayClient(); + + if (false === isset($client) || true === empty($client)) { + $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::fetchInvoice(): could not obtain BitPay client.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::fetchInvoice(): could not obtain BitPay client.'); + } else { + $this->debugData('[INFO] In Bitpay_Core_Model_Method_Bitcoin::fetchInvoice(): obtained BitPay client successfully.'); + } + + $invoice = $client->getInvoice($id); + + if (false === isset($invoice) || true === empty($invoice)) { + $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::fetchInvoice(): could not retrieve invoice from BitPay.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::fetchInvoice(): could not retrieve invoice from BitPay.'); + } else { + $this->debugData('[INFO] In Bitpay_Core_Model_Method_Bitcoin::fetchInvoice(): successfully retrieved invoice id ' . $id . ' from BitPay.'); + } + + return $invoice; + } + + /** + * given Mage_Core_Model_Abstract, return api-friendly address + * + * @param $address + * + * @return array + */ + public function extractAddress($address) + { + if (false === isset($address) || true === empty($address)) { + $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::extractAddress(): missing or invalid address parameter.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::extractAddress(): missing or invalid address parameter.'); + } else { + $this->debugData('[INFO] In Bitpay_Core_Model_Method_Bitcoin::extractAddress(): called with good address parameter, extracting now.'); + } + + $options = array(); + $options['buyerEmail'] = $address->getEmail(); + + // trim to fit API specs + foreach (array('buyerEmail') as $f) { + if (true === isset($options[$f]) && strlen($options[$f]) > 100) { + $this->debugData('[WARNING] In Bitpay_Core_Model_Method_Bitcoin::extractAddress(): the ' . $f . ' parameter was greater than 100 characters, trimming.'); + $options[$f] = substr($options[$f], 0, 100); + } + } + + return $options; + } + + /** + * This is called when a user clicks the `Place Order` button + * + * @return string + */ + public function getOrderPlaceRedirectUrl() + { + $this->debugData('[INFO] In Bitpay_Core_Model_Method_Bitcoin::getOrderPlaceRedirectUrl(): $_redirectUrl is ' . self::$_redirectUrl); + + return self::$_redirectUrl; + + } + + /** + * Create a new invoice with as much info already added. It should add + * some basic info and setup the invoice object. + * + * @return Bitpay\Invoice + */ + private function initializeInvoice() + { + \Mage::helper('bitpay')->registerAutoloader(); + + $invoice = new Bitpay\Invoice(); + + if (false === isset($invoice) || true === empty($invoice)) { + $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::initializeInvoice(): could not construct new BitPay invoice object.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::initializeInvoice(): could not construct new BitPay invoice object.'); + } else { + $this->debugData('[INFO] In Bitpay_Core_Model_Method_Bitcoin::initializeInvoice(): constructed new BitPay invoice object successfully.'); + } + + $invoice->setFullNotifications(true); + $invoice->setTransactionSpeed('medium'); + $invoice->setNotificationUrl(\Mage::getUrl(\Mage::getStoreConfig('payment/bitpay/notification_url'))); + $invoice->setRedirectUrl(\Mage::getUrl(\Mage::getStoreConfig('payment/bitpay/redirect_url'))); + + return $invoice; + } + + /** + * Prepares the invoice object to be sent to BitPay's API. This method sets + * all the other info that we have to rely on other objects for. + * + * @param Bitpay\Invoice $invoice + * @param Mage_Sales_Model_Order_Payment $payment + * @param float $amount + * @return Bitpay\Invoice + */ + private function prepareInvoice($invoice, $payment, $amount) + { + if (false === isset($invoice) || true === empty($invoice) || false === isset($payment) || true === empty($payment) || false === isset($amount) || true === empty($amount)) { + $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::prepareInvoice(): missing or invalid invoice, payment or amount parameter.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::prepareInvoice(): missing or invalid invoice, payment or amount parameter.'); + } else { + $this->debugData('[INFO] In Bitpay_Core_Model_Method_Bitcoin::prepareInvoice(): entered function with good invoice, payment and amount parameters.'); + } + + $quote = Mage::getSingleton('checkout/session')->getQuote(); + $order = \Mage::getModel('sales/order')->load($quote->getId(), 'quote_id'); + + $invoice->setOrderId($order->getIncrementId()); + $invoice->setExtendedNotifications(true); + $invoice->setPosData(json_encode(array('orderId' => $order->getIncrementId()))); + + $invoice = $this->addCurrencyInfo($invoice, $order); + $invoice = $this->addPriceInfo($invoice, $amount); + $invoice = $this->addBuyerInfo($invoice, $order); + + return $invoice; + } + + /** + * This adds the buyer information to the invoice. + * + * @param Bitpay\Invoice $invoice + * @param Mage_Sales_Model_Order $order + * @return Bitpay\Invoice + */ + private function addBuyerInfo($invoice, $order) + { + if (false === isset($invoice) || true === empty($invoice) || false === isset($order) || true === empty($order)) { + $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::addBuyerInfo(): missing or invalid invoice or order parameter.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::addBuyerInfo(): missing or invalid invoice or order parameter.'); + } else { + $this->debugData('[INFO] In Bitpay_Core_Model_Method_Bitcoin::addBuyerInfo(): function called with good invoice and order parameters.'); + } + + $buyer = new Bitpay\Buyer(); + + if (false === isset($buyer) || true === empty($buyer)) { + $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::addBuyerInfo(): could not construct new BitPay buyer object.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::addBuyerInfo(): could not construct new BitPay buyer object.'); + } + + if (Mage::getStoreConfig('payment/bitpay/fullscreen')) { + $address = $order->getBillingAddress(); + } else { + $quote = Mage::getSingleton('checkout/session')->getQuote(); + $address = $quote->getBillingAddress(); + } + + $email = $address->getEmail(); + if (null !== $email && '' !== $email) { + $buyer->setEmail($email); + } + + $invoice->setBuyer($buyer); + + return $invoice; + } + + /** + * Adds currency information to the invoice + * + * @param Bitpay\Invoice $invoice + * @param Mage_Sales_Model_Order $order + * @return Bitpay\Invoice + */ + private function addCurrencyInfo($invoice, $order) + { + if (false === isset($invoice) || true === empty($invoice) || false === isset($order) || true === empty($order)) { + $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::addCurrencyInfo(): missing or invalid invoice or order parameter.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::addCurrencyInfo(): missing or invalid invoice or order parameter.'); + } else { + $this->debugData('[INFO] In Bitpay_Core_Model_Method_Bitcoin::addCurrencyInfo(): function called with good invoice and order parameters.'); + } + + $currency = new Bitpay\Currency(); + + if (false === isset($currency) || true === empty($currency)) { + $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::addCurrencyInfo(): could not construct new BitPay currency object.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::addCurrencyInfo(): could not construct new BitPay currency object.'); + } + + //$currency->setCode($order->getOrderCurrencyCode()); + //use the store currency code (not the customer selected currency) + $currency->setCode(\Mage::app()->getStore()->getBaseCurrencyCode()); + $invoice->setCurrency($currency); + + return $invoice; + } + + /** + * Adds pricing information to the invoice + * + * @param Bitpay\Invoice invoice + * @param float $amount + * @return Bitpay\Invoice + */ + private function addPriceInfo($invoice, $amount) + { + if (false === isset($invoice) || true === empty($invoice) || false === isset($amount) || true === empty($amount)) { + $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::addPriceInfo(): missing or invalid invoice or amount parameter.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::addPriceInfo(): missing or invalid invoice or amount parameter.'); + } else { + $this->debugData('[INFO] In Bitpay_Core_Model_Method_Bitcoin::addPriceInfo(): function called with good invoice and amount parameters.'); + } + + $item = new \Bitpay\Item(); + + if (false === isset($item) || true === empty($item)) { + $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::addPriceInfo(): could not construct new BitPay item object.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::addPriceInfo(): could not construct new BitPay item object.'); + } + + $item->setPrice($amount); + $invoice->setItem($item); + + return $invoice; + } +} diff --git a/app/code/community/Bitpay/Core/Model/Mysql4/Invoice.php b/app/code/community/Bitpay/Core/Model/Mysql4/Invoice.php new file mode 100644 index 0000000..b4ad214 --- /dev/null +++ b/app/code/community/Bitpay/Core/Model/Mysql4/Invoice.php @@ -0,0 +1,19 @@ +_init('bitpay/invoice', 'id'); + } +} diff --git a/app/code/community/Bitpay/Core/Model/Mysql4/Invoice/Collection.php b/app/code/community/Bitpay/Core/Model/Mysql4/Invoice/Collection.php new file mode 100644 index 0000000..a02ae15 --- /dev/null +++ b/app/code/community/Bitpay/Core/Model/Mysql4/Invoice/Collection.php @@ -0,0 +1,19 @@ +_init('bitpay/invoice'); + } +} diff --git a/app/code/community/Bitpay/Core/Model/Mysql4/Ipn.php b/app/code/community/Bitpay/Core/Model/Mysql4/Ipn.php new file mode 100644 index 0000000..b997234 --- /dev/null +++ b/app/code/community/Bitpay/Core/Model/Mysql4/Ipn.php @@ -0,0 +1,17 @@ +_init('bitpay/ipn', 'id'); + } +} diff --git a/app/code/community/Bitpay/Core/Model/Mysql4/Ipn/Collection.php b/app/code/community/Bitpay/Core/Model/Mysql4/Ipn/Collection.php new file mode 100644 index 0000000..37dda59 --- /dev/null +++ b/app/code/community/Bitpay/Core/Model/Mysql4/Ipn/Collection.php @@ -0,0 +1,16 @@ +_init('bitpay/ipn'); + } +} diff --git a/app/code/community/Bitpay/Core/Model/Network.php b/app/code/community/Bitpay/Core/Model/Network.php new file mode 100644 index 0000000..b5a4aaa --- /dev/null +++ b/app/code/community/Bitpay/Core/Model/Network.php @@ -0,0 +1,25 @@ + self::NETWORK_LIVENET, 'label' => \Mage::helper('bitpay')->__(ucwords(self::NETWORK_LIVENET))), + array('value' => self::NETWORK_TESTNET, 'label' => \Mage::helper('bitpay')->__(ucwords(self::NETWORK_TESTNET))), + ); + } +} diff --git a/app/code/community/Bitpay/Core/Model/Observer.php b/app/code/community/Bitpay/Core/Model/Observer.php new file mode 100644 index 0000000..1dac437 --- /dev/null +++ b/app/code/community/Bitpay/Core/Model/Observer.php @@ -0,0 +1,124 @@ + getOrder(); + $paymentCode = $order -> getPayment() -> getMethodInstance() -> getCode(); + if ($paymentCode == 'bitpay') { + $order -> setState(Mage_Sales_Model_Order::STATE_NEW, true); + $order -> save(); + } + + // Mage::log('$order = $event->getOrder();' . $order -> getState()); + } + + /* + * Queries BitPay to update the order states in magento to make sure that + * open orders are closed/canceled if the BitPay invoice expires or becomes + * invalid. + */ + public function updateOrderStates() { + $apiKey = \Mage::getStoreConfig('payment/bitpay/api_key'); + + if (false === isset($apiKey) || empty($apiKey)) { + \Mage::helper('bitpay') -> debugData('[INFO] Bitpay_Core_Model_Observer::updateOrderStates() could not start job to update the order states because the API key was not set.'); + return; + } else { + \Mage::helper('bitpay') -> debugData('[INFO] Bitpay_Core_Model_Observer::updateOrderStates() started job to query BitPay to update the existing order states.'); + } + + /* + * Get all of the orders that are open and have not received an IPN for + * complete, expired, or invalid. + */ + $orders = \Mage::getModel('bitpay/ipn') -> getOpenOrders(); + + if (false === isset($orders) || empty($orders)) { + \Mage::helper('bitpay') -> debugData('[INFO] Bitpay_Core_Model_Observer::updateOrderStates() could not retrieve the open orders.'); + return; + } else { + \Mage::helper('bitpay') -> debugData('[INFO] Bitpay_Core_Model_Observer::updateOrderStates() successfully retrieved existing open orders.'); + } + + /* + * Get all orders that have been paid using bitpay and + * are not complete/closed/etc + */ + foreach ($orders as $order) { + /* + * Query BitPay with the invoice ID to get the status. We must take + * care not to anger the API limiting gods and disable our access + * to the API. + */ + $status = null; + + // TODO: + // Does the order need to be updated? + // Yes? Update Order Status + // No? continue + } + + \Mage::helper('bitpay') -> debugData('[INFO] Bitpay_Core_Model_Observer::updateOrderStates() order status update job finished.'); + } + + /** + * Method that is called via the magento cron to update orders if the + * invoice has expired + */ + public function cleanExpired() { + \Mage::helper('bitpay') -> debugData('[INFO] Bitpay_Core_Model_Observer::cleanExpired() called.'); + \Mage::helper('bitpay') -> cleanExpired(); + } + + /** + * Event Hook: checkout_onepage_controller_success_action + * @param $observer Varien_Event_Observer + */ + public function redirectToCartIfExpired(Varien_Event_Observer $observer) + { + if ($observer->getEvent()->getName() == 'checkout_onepage_controller_success_action') + { + $lastOrderId = null; + foreach(\Mage::app()->getRequest()->getParams() as $key=>$value) + { + if($key == 'order_id') + $lastOrderId = $value; + } + + if($lastOrderId != null) + { + //get order + $order = \Mage::getModel('sales/order')->load($lastOrderId); + if (false === isset($order) || true === empty($order)) { + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Model_Observer::redirectToCartIfExpired(), Invalid Order ID received.'); + return; + } + //check if order is pending + if($order->getStatus() != 'pending') + { + return; + } + + //check if invoice for order exist in bitpay_invoices table + $bitpayInvoice = \Mage::getModel('bitpay/invoice')->load($order->getIncrementId(), 'increment_id'); + $bitpayInvoiceData = $bitpayInvoice->getData(); + //if is empty or not is array abort + if(!is_array($bitpayInvoiceData) || is_array($bitpayInvoiceData) && empty($bitpayInvoiceData)) + return; + + //check if bitpay invoice id expired + $invoiceExpirationTime = $bitpayInvoiceData['expiration_time']; + if($invoiceExpirationTime < strtotime('now')) + { + $failure_url = \Mage::getUrl(\Mage::getStoreConfig('payment/bitpay/failure_url')); + \Mage::app()->getResponse()->setRedirect($failure_url)->sendResponse(); + } + } + } + } +} diff --git a/app/code/community/Bitpay/Core/Model/Order/Payment.php b/app/code/community/Bitpay/Core/Model/Order/Payment.php new file mode 100644 index 0000000..fd5a945 --- /dev/null +++ b/app/code/community/Bitpay/Core/Model/Order/Payment.php @@ -0,0 +1,273 @@ +setShouldCloseParentTransaction(false); + $isSameCurrency = $this->_isSameCurrency(); + if (!$isSameCurrency || !$this->_isCaptureFinal($amount)) { + $this->setIsFraudDetected(true); + } + + // update totals + $amount = $this->_formatAmount($amount, true); + $this->setBaseAmountAuthorized($amount); + + // do authorization + $order = $this->getOrder(); + $payment = $order -> getPayment(); + $paymentMethodCode = $payment -> getMethodInstance() -> getCode(); + if ($paymentMethodCode != 'bitpay'){ + $state = Mage_Sales_Model_Order::STATE_PROCESSING; + } + else { + $state = Mage_Sales_Model_Order::STATE_NEW; + } + $status = true; + if ($isOnline) { + // invoke authorization on gateway + $this->getMethodInstance()->setStore($order->getStoreId())->authorize($this, $amount); + } + + // similar logic of "payment review" order as in capturing + if ($this->getIsTransactionPending()) { + $message = Mage::helper('sales')->__('Authorizing amount of %s is pending approval on gateway.', $this->_formatPrice($amount)); + $state = Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW; + if ($this->getIsFraudDetected()) { + $status = Mage_Sales_Model_Order::STATUS_FRAUD; + } + } else { + if ($this->getIsFraudDetected()) { + $state = Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW; + $message = Mage::helper('sales')->__('Order is suspended as its authorizing amount %s is suspected to be fraudulent.', $this->_formatPrice($amount, $this->getCurrencyCode())); + $status = Mage_Sales_Model_Order::STATUS_FRAUD; + } else { + $message = Mage::helper('sales')->__('Authorized amounta of %s.', $this->_formatPrice($amount)); + } + } + + // update transactions, order state and add comments + $transaction = $this->_addTransaction(Mage_Sales_Model_Order_Payment_Transaction::TYPE_AUTH); + if ($order->isNominal()) { + $message = $this->_prependMessage(Mage::helper('sales')->__('Nominal order registered.')); + } else { + $message = $this->_prependMessage($message); + $message = $this->_appendTransactionToMessage($transaction, $message); + } + $order->setState($state, $status, $message); + + return $this; + } + + public function registerCaptureNotification($amount, $skipFraudDetection = false) + { + $this->_generateTransactionId(Mage_Sales_Model_Order_Payment_Transaction::TYPE_CAPTURE, + $this->getAuthorizationTransaction() + ); + + $order = $this->getOrder(); + $amount = (float)$amount; + $invoice = $this->_getInvoiceForTransactionId($this->getTransactionId()); + + // register new capture + if (!$invoice) { + $isSameCurrency = $this->_isSameCurrency(); + if ($isSameCurrency && $this->_isCaptureFinal($amount)) { + $invoice = $order->prepareInvoice()->register(); + $order->addRelatedObject($invoice); + $this->setCreatedInvoice($invoice); + } else { + if (!$skipFraudDetection || !$isSameCurrency) { + $this->setIsFraudDetected(true); + } + $this->_updateTotals(array('base_amount_paid_online' => $amount)); + } + } + + $status = true; + if ($this->getIsTransactionPending()) { + $message = Mage::helper('sales')->__('Capturing amount of %s is pending approval on gateway.', $this->_formatPrice($amount)); + $state = Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW; + if ($this->getIsFraudDetected()) { + $message = Mage::helper('sales')->__('Order is suspended as its capture amount %s is suspected to be fraudulent.', $this->_formatPrice($amount, $this->getCurrencyCode())); + $status = Mage_Sales_Model_Order::STATUS_FRAUD; + } + } else { + $message = Mage::helper('sales')->__('Registered notification about captured amount of %s.', $this->_formatPrice($amount)); + $payment = $order -> getPayment(); + $paymentMethodCode = $payment -> getMethodInstance() -> getCode(); + if ($paymentMethodCode != 'bitpay'){ + $state = Mage_Sales_Model_Order::STATE_PROCESSING; + } + else { + $state = Mage_Sales_Model_Order::STATE_NEW; + } + + if ($this->getIsFraudDetected()) { + $state = Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW; + $message = Mage::helper('sales')->__('Order is suspended as its capture amount %s is suspected to be fraudulent.', $this->_formatPrice($amount, $this->getCurrencyCode())); + $status = Mage_Sales_Model_Order::STATUS_FRAUD; + } + // register capture for an existing invoice + if ($invoice && Mage_Sales_Model_Order_Invoice::STATE_OPEN == $invoice->getState()) { + $invoice->pay(); + $this->_updateTotals(array('base_amount_paid_online' => $amount)); + $order->addRelatedObject($invoice); + } + } + + $transaction = $this->_addTransaction(Mage_Sales_Model_Order_Payment_Transaction::TYPE_CAPTURE, $invoice, true); + $message = $this->_prependMessage($message); + $message = $this->_appendTransactionToMessage($transaction, $message); + $order->setState($state, $status, $message); + return $this; + } + + + public function place() + { + Mage::dispatchEvent('sales_order_payment_place_start', array('payment' => $this)); + $order = $this->getOrder(); + + $this->setAmountOrdered($order->getTotalDue()); + $this->setBaseAmountOrdered($order->getBaseTotalDue()); + $this->setShippingAmount($order->getShippingAmount()); + $this->setBaseShippingAmount($order->getBaseShippingAmount()); + + $methodInstance = $this->getMethodInstance(); + $methodInstance->setStore($order->getStoreId()); + $orderState = Mage_Sales_Model_Order::STATE_NEW; + $stateObject = new Varien_Object(); + + /** + * Do order payment validation on payment method level + */ + $methodInstance->validate(); + $action = $methodInstance->getConfigPaymentAction(); + if ($action) { + if ($methodInstance->isInitializeNeeded()) { + /** + * For method initialization we have to use original config value for payment action + */ + $methodInstance->initialize($methodInstance->getConfigData('payment_action'), $stateObject); + } else { + $payment = $order -> getPayment(); + $paymentMethodCode = $payment -> getMethodInstance() -> getCode(); + if ($paymentMethodCode != 'bitpay'){ + $orderState = Mage_Sales_Model_Order::STATE_PROCESSING; + } + else { + $orderState = Mage_Sales_Model_Order::STATE_NEW; + } + + switch ($action) { + case Mage_Payment_Model_Method_Abstract::ACTION_ORDER: + $this->_order($order->getBaseTotalDue()); + break; + case Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE: + $this->_authorize(true, $order->getBaseTotalDue()); // base amount will be set inside + $this->setAmountAuthorized($order->getTotalDue()); + break; + case Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE: + $this->setAmountAuthorized($order->getTotalDue()); + $this->setBaseAmountAuthorized($order->getBaseTotalDue()); + $this->capture(null); + break; + default: + break; + } + } + } + + $this->_createBillingAgreement(); + + $orderIsNotified = null; + if ($stateObject->getState() && $stateObject->getStatus()) { + $orderState = $stateObject->getState(); + $orderStatus = $stateObject->getStatus(); + $orderIsNotified = $stateObject->getIsNotified(); + } else { + $orderStatus = $methodInstance->getConfigData('order_status'); + if (!$orderStatus) { + $orderStatus = $order->getConfig()->getStateDefaultStatus($orderState); + } else { + // check if $orderStatus has assigned a state + if(method_exists($order->getConfig(), 'getStatusStates')) + { + $states = $order->getConfig()->getStatusStates($orderStatus); + } + else + { + $states = $this->getStatusStates($orderStatus); + } + + if (count($states) == 0) { + $orderStatus = $order->getConfig()->getStateDefaultStatus($orderState); + } + } + } + $isCustomerNotified = (null !== $orderIsNotified) ? $orderIsNotified : $order->getCustomerNoteNotify(); + $message = $order->getCustomerNote(); + + // add message if order was put into review during authorization or capture + if ($order->getState() == Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW) { + if ($message) { + $order->addStatusToHistory($order->getStatus(), $message, $isCustomerNotified); + } + } elseif ($order->getState() && ($orderStatus !== $order->getStatus() || $message)) { + // add message to history if order state already declared + $order->setState($orderState, $orderStatus, $message, $isCustomerNotified); + } elseif (($order->getState() != $orderState) || ($order->getStatus() != $orderStatus) || $message) { + // set order state + $order->setState($orderState, $orderStatus, $message, $isCustomerNotified); + } + + Mage::dispatchEvent('sales_order_payment_place_end', array('payment' => $this)); + + return $this; + } + + /** + * Check whether payment currency corresponds to order currency + * + * @return bool + */ + public function _isSameCurrency() + { + return !$this->getCurrencyCode() || $this->getCurrencyCode() == $this->getOrder()->getBaseCurrencyCode(); + } + + /** + * Retrieve state available for status + * Get all assigned states for specified status + * + * @param string $status + * @return array + */ + + private function getStatusStates($status) + { + $states = array(); + $collectionObj = Mage::getResourceModel('sales/order_status_collection'); + $collection = $this->addStatusFilter($collectionObj, $status); + + foreach ($collection as $state) { + $states[] = $state; + } + return $states; + } + /** + * add status code filter to collection + * + * @param object Mage_Sales_Model_Resource_Order_Status_Collection + * @param string $status + * @return Mage_Sales_Model_Resource_Order_Status_Collection + */ + private function addStatusFilter($collectionObj, $status) + { + $collectionObj->joinStates(); + $collectionObj->getSelect()->where('state_table.status=?', $status); + return $collectionObj; + } +} diff --git a/app/code/community/Bitpay/Core/Model/Resource/Ipn/Collection.php b/app/code/community/Bitpay/Core/Model/Resource/Ipn/Collection.php new file mode 100644 index 0000000..fbde0e4 --- /dev/null +++ b/app/code/community/Bitpay/Core/Model/Resource/Ipn/Collection.php @@ -0,0 +1,44 @@ +_init('bitpay/ipn'); + } + + public function delete() + { + foreach ($this->getItems() as $item) { + $item->delete(); + } + } +} diff --git a/app/code/community/Bitpay/Core/Model/Resource/Mysql4/Setup.php b/app/code/community/Bitpay/Core/Model/Resource/Mysql4/Setup.php new file mode 100644 index 0000000..b91a0fc --- /dev/null +++ b/app/code/community/Bitpay/Core/Model/Resource/Mysql4/Setup.php @@ -0,0 +1,11 @@ +toOptionArray(); + + $allowCountry = array(); + foreach($country as $v) + { + if($v['value'] != '' && $v['value'] != 'SY' && $v['value'] != 'IR' && $v['value'] != 'KP' && $v['value'] != 'SD' && $v['value'] != 'CU') + { + $allowCountry[] = $v; + } + } + + return $allowCountry; + } +} diff --git a/app/code/community/Bitpay/Core/Model/Status.php b/app/code/community/Bitpay/Core/Model/Status.php new file mode 100644 index 0000000..10cecad --- /dev/null +++ b/app/code/community/Bitpay/Core/Model/Status.php @@ -0,0 +1,30 @@ + self::STATUS_NEW, 'label' => \Mage::helper('bitpay')->__(ucwords(self::STATUS_NEW))), + array('value' => self::STATUS_PAID, 'label' => \Mage::helper('bitpay')->__(ucwords(self::STATUS_PAID))), + array('value' => self::STATUS_CONFIRMED, 'label' => \Mage::helper('bitpay')->__(ucwords(self::STATUS_CONFIRMED))), + array('value' => self::STATUS_COMPLETE, 'label' => \Mage::helper('bitpay')->__(ucwords(self::STATUS_COMPLETE))), + array('value' => self::STATUS_EXPIRED, 'label' => \Mage::helper('bitpay')->__(ucwords(self::STATUS_EXPIRED))), + array('value' => self::STATUS_INVALID, 'label' => \Mage::helper('bitpay')->__(ucwords(self::STATUS_INVALID))), + ); + } +} diff --git a/app/code/community/Bitpay/Core/Model/TransactionSpeed.php b/app/code/community/Bitpay/Core/Model/TransactionSpeed.php new file mode 100644 index 0000000..a23f752 --- /dev/null +++ b/app/code/community/Bitpay/Core/Model/TransactionSpeed.php @@ -0,0 +1,24 @@ + self::SPEED_LOW, 'label' => \Mage::helper('bitpay')->__(ucwords(self::SPEED_LOW))), + array('value' => self::SPEED_MEDIUM, 'label' => \Mage::helper('bitpay')->__(ucwords(self::SPEED_MEDIUM))), + array('value' => self::SPEED_HIGH, 'label' => \Mage::helper('bitpay')->__(ucwords(self::SPEED_HIGH))), + ); + } +} diff --git a/app/code/community/Bitpay/Core/controllers/IndexController.php b/app/code/community/Bitpay/Core/controllers/IndexController.php new file mode 100644 index 0000000..f954ddb --- /dev/null +++ b/app/code/community/Bitpay/Core/controllers/IndexController.php @@ -0,0 +1,34 @@ +registerAutoloader(); + \Mage::helper('bitpay')->debugData($params); + + $params = $this->getRequest()->getParams(); + $quoteId = $params['quote']; + + if (!is_numeric($quoteId)) + { + return $this->getResponse()->setHttpResponseCode(400); + } + + $paid = \Mage::getModel('bitpay/ipn')->GetQuotePaid($quoteId); + $this->loadLayout(); + $this->getResponse()->setHeader('Content-type', 'application/json'); + + return $this->getResponse()->setBody(json_encode(array('paid' => $paid))); + } +} diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php new file mode 100644 index 0000000..7c58d60 --- /dev/null +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -0,0 +1,196 @@ +debugData('[ERROR] In Bitpay_Core_IpnController::indexAction(), Could not read from the php://input stream or invalid Bitpay IPN received.'); + throw new \Exception('Could not read from the php://input stream or invalid Bitpay IPN received.'); + } + + \Mage::helper('bitpay')->registerAutoloader(); + + \Mage::helper('bitpay')->debugData(array(sprintf('[INFO] In Bitpay_Core_IpnController::indexAction(), Incoming IPN message from BitPay: '),$raw_post_data,)); + + // Magento doesn't seem to have a way to get the Request body + $ipn = json_decode($raw_post_data); + + if(isset($ipn->data)) + { + $ipn = $ipn->data; + } + + if (true === empty($ipn)) { + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IpnController::indexAction(), Could not decode the JSON payload from BitPay.'); + throw new \Exception('Could not decode the JSON payload from BitPay.'); + } + + if (true === empty($ipn->id) || false === isset($ipn->posData)) { + \Mage::helper('bitpay')->debugData(sprintf('[ERROR] In Bitpay_Core_IpnController::indexAction(), Did not receive order ID in IPN: ', $ipn)); + throw new \Exception('Invalid Bitpay payment notification message received - did not receive order ID.'); + } + + $ipn->posData = is_string($ipn->posData) ? json_decode($ipn->posData) : $ipn->posData; + $ipn->buyerFields = isset($ipn->buyerFields) ? $ipn->buyerFields : new stdClass(); + + \Mage::helper('bitpay')->debugData($ipn); + + // Log IPN + $mageIpn = \Mage::getModel('bitpay/ipn')->addData( + array( + 'invoice_id' => isset($ipn->id) ? $ipn->id : '', + 'url' => isset($ipn->url) ? $ipn->url : '', + 'pos_data' => json_encode($ipn->posData), + 'status' => isset($ipn->status) ? $ipn->status : '', + 'price' => isset($ipn->price) ? $ipn->price : '', + 'currency' => isset($ipn->currency) ? $ipn->currency : '', + 'invoice_time' => isset($ipn->invoiceTime) ? intval($ipn->invoiceTime / 1000) : '', + 'expiration_time' => isset($ipn->expirationTime) ? intval($ipn->expirationTime / 1000) : '', + 'current_time' => isset($ipn->currentTime) ? intval($ipn->currentTime / 1000) : '', + 'exception_status' => isset($ipn->exceptionStatus) ? $ipn->exceptionStatus : '', + 'transactionCurrency' => isset($ipn->transactionCurrency) ? $ipn->transactionCurrency : '' + ) + )->save(); + + + // Order isn't being created for iframe... + if (isset($ipn->posData->orderId)) { + $order = \Mage::getModel('sales/order')->loadByIncrementId($ipn->posData->orderId); + } else { + $order = \Mage::getModel('sales/order')->load($ipn->posData->quoteId, 'quote_id'); + } + + if (false === isset($order) || true === empty($order)) { + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IpnController::indexAction(), Invalid Bitpay IPN received.'); + \Mage::throwException('Invalid Bitpay IPN received.'); + } + + $orderId = $order->getId(); + if (false === isset($orderId) || true === empty($orderId)) { + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IpnController::indexAction(), Invalid Bitpay IPN received.'); + \Mage::throwException('Invalid Bitpay IPN received.'); + } + + /** + * Ask BitPay to retreive the invoice so we can make sure the invoices + * match up and no one is using an automated tool to post IPN's to merchants + * store. + */ + $invoice = \Mage::getModel('bitpay/method_bitcoin')->fetchInvoice($ipn->id); + + if ($invoice->getStatus() == 'paid') + { + $invoice_magento = \Mage::getModel('bitpay/invoice')->load($ipn->id); + $quoteID_magento = $invoice_magento->getData('quote_id'); + $quoteData = Mage::getModel('sales/quote')->setStoreId(Mage::app()->getStore('default')->getId())->load($quoteID_magento); + $quoteData->setIsActive(0)->save(); + } + + if (false === isset($invoice) || true === empty($invoice)) { + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IpnController::indexAction(), Could not retrieve the invoice details for the ipn ID of ' . $ipn->id); + \Mage::throwException('Could not retrieve the invoice details for the ipn ID of ' . $ipn->id); + } + + // Does the status match? + /* if ($invoice->getStatus() != $ipn->status) { + \Mage::getModel('bitpay/method_bitcoin')->debugData('[ERROR] In Bitpay_Core_IpnController::indexAction(), IPN status and status from BitPay are different. Rejecting this IPN!'); + \Mage::throwException('There was an error processing the IPN - statuses are different. Rejecting this IPN!'); + }*/ + + // Does the price match? + if ($invoice->getPrice() != $ipn->price) { + \Mage::getModel('bitpay/method_bitcoin')>debugData('[ERROR] In Bitpay_Core_IpnController::indexAction(), IPN price and invoice price are different. Rejecting this IPN!'); + \Mage::throwException('There was an error processing the IPN - invoice price does not match the IPN price. Rejecting this IPN!'); + } + + // use state as defined by Merchant + $state = \Mage::getStoreConfig(sprintf('payment/bitpay/invoice_%s', $invoice->getStatus())); + + if (false === isset($state) || true === empty($state)) { + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IpnController::indexAction(), Could not retrieve the defined state parameter to update this order to in the Bitpay IPN controller.'); + \Mage::throwException('Could not retrieve the defined state parameter to update this order in the Bitpay IPN controller.'); + } + + // Check if status should be updated + switch ($order->getStatus()) { + case Mage_Sales_Model_Order::STATE_CANCELED: + case Mage_Sales_Model_Order::STATUS_FRAUD: + case Mage_Sales_Model_Order::STATE_CLOSED: + case Mage_Sales_Model_Order::STATE_COMPLETE: + case Mage_Sales_Model_Order::STATE_HOLDED: + // Do not Update + break; + case Mage_Sales_Model_Order::STATE_PENDING_PAYMENT: + case Mage_Sales_Model_Order::STATE_PROCESSING: + default: + $order->addStatusToHistory( + $state, + sprintf('[INFO] In Bitpay_Core_IpnController::indexAction(), Incoming IPN status "%s" updated order state to "%s"', $invoice->getStatus(), $state) + )->save(); + break; + } + + if($ipn->status == 'expired') + { + $order->cancel(); + $order->setState(Mage_Sales_Model_Order::STATE_CANCELED, true, 'Cancel Transaction.'); + $order->setStatus("canceled"); + $order->save(); + } + + if ($ipn->status === 'paid') { + \Mage::helper('bitpay')->debugData('[INFO] Receiving paid IPN, creating invoice for order.'); + // Create a Magento invoice for the order when the 'paid' notification comes in + if ($payments = $order->getPaymentsCollection()) + { + $payment = count($payments->getItems())>0 ? end($payments->getItems()) : \Mage::getModel('sales/order_payment')->setOrder($order); + } + + if (true === isset($payment) && false === empty($payment)) { + $payment->registerCaptureNotification($invoice->getPrice()); + $order->setPayment($payment); + + $order_confirmation = \Mage::getStoreConfig('payment/bitpay/order_confirmation'); + if($order_confirmation == '1') { + if (!$order->getEmailSent()) { + \Mage::helper('bitpay')->debugData('[INFO] In Bitpay_Core_IpnController::indexAction(), Order email not sent so I am calling $order->sendNewOrderEmail() now...'); + $order->sendNewOrderEmail(); + } + else + { + \Mage::helper('bitpay')->debugData('[INFO] Plugin configured to send order confirmation, but order confirmation already sent.'); + } + } + else { + \Mage::helper('bitpay')->debugData('[INFO] Plugin configured to not send order confirmation.'); + } + $order->save(); + } + else { + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IpnController::indexAction(), Could not create a payment object in the Bitpay IPN controller.'); + \Mage::throwException('Could not create a payment object in the Bitpay IPN controller.'); + } + } + + } +} \ No newline at end of file diff --git a/app/code/community/Bitpay/Core/etc/adminhtml.xml b/app/code/community/Bitpay/Core/etc/adminhtml.xml new file mode 100644 index 0000000..362344d --- /dev/null +++ b/app/code/community/Bitpay/Core/etc/adminhtml.xml @@ -0,0 +1,47 @@ + + + + + + + + + BitPay + + + + + + + BitPay Settings + + + + + + + + + + + + + bitpay.xml + + + + + + + + Bitpay_Core.csv + + + + + diff --git a/app/code/community/Bitpay/Core/etc/config.xml b/app/code/community/Bitpay/Core/etc/config.xml new file mode 100644 index 0000000..d8406cc --- /dev/null +++ b/app/code/community/Bitpay/Core/etc/config.xml @@ -0,0 +1,154 @@ + + + + + + 2.1.20 + + + + + + standard + + Bitpay_Core + bitpay + + + + + + + bitpay.xml + + + + + + + + Bitpay_Core.csv + + + + + + + + + + + Bitpay_Core_Adminhtml + + + + + + + + + Bitpay_Core_Block + + + + + Bitpay_Core_Helper + + + + + Bitpay_Core_Model + bitpay_mysql4 + + + + Bitpay_Core_Model_Mysql4 + + + bitpay_invoices
+
+ + bitpay_ipns
+
+
+
+ + + Bitpay_Core_Model_Order_Payment + + +
+ + + + + singleton + Bitpay_Core_Model_Observer + implementOrderStatus + + + + + + + singleton + bitpay_core_model_observer + redirectToCartIfExpired + + + + + + + + core_write + + + + + core_read + + + + + Bitpay_Core + Bitpay_Core_Model_Resource_Mysql4_Setup + + + core_setup + + + +
+ + + + bitpay/method_bitcoin + new + authorize + 0 + Bitcoin and Bitcoin Cash + test + livenet + 1 + 0 + bitpay/ipn + checkout/onepage/success + checkout/cart + medium + 0 + new + pending + processing + processing + canceled + pending + + + +
diff --git a/app/code/community/Bitpay/Core/etc/system.xml b/app/code/community/Bitpay/Core/etc/system.xml new file mode 100644 index 0000000..a70356b --- /dev/null +++ b/app/code/community/Bitpay/Core/etc/system.xml @@ -0,0 +1,265 @@ + + + + + + + + + 670 + 1 + 1 + 1 + +
+ + complex + bitpay/adminhtml_system_config_form_field_header + 0 + 1 + 1 + 1 +
+ + + + https://bitpay.com/api-tokens -> Add New Token -> Add Token and copy/paste + the 7 character pairing code here.]]> + + text + bitpay/config_pairingCode + 1 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 2 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 10 + 1 + 1 + 1 + + + <label>Title</label> + <comment> + <![CDATA[This is the payment method name your customers will see during checkout.]]> + </comment> + <frontend_type>text</frontend_type> + <sort_order>20</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + + + + + test.bitpay.com.]]> + + select + bitpay/network + 30 + 1 + 1 + 1 + + + + + + + select + adminhtml/system_config_source_yesno + 40 + 1 + 1 + 1 + + + + text + 50 + 1 + 1 + 1 + + + + text + 60 + 1 + 1 + 1 + + + + text + 60 + 1 + 1 + 1 + + + + + + adminhtml/system_config_form_field_heading + 70 + 1 + 1 + 1 + + + + + + + select + adminhtml/system_config_source_order_status + 71 + 1 + 1 + 1 + + + + + + + select + adminhtml/system_config_source_order_status + 72 + 1 + 1 + 1 + + + + + + + select + adminhtml/system_config_source_order_status + 73 + 1 + 1 + 1 + + + + + select + bitpay/transactionSpeed + 74 + 0 + 0 + 0 + + High: an invoice is confirmed immediately when payment received.
Medium: an invoice is confirmed after 1 block confirmation by the Bitcoin network (~10 mins).
Low: an invoice is confirmed after 6 block confirmations by the Bitcoin network (~1 hour).
The default and safest setting is "Low". A "High" setting is quicker to generate a payment confirmation but is riskier since the transaction could have not been officially confirmed by the Bitcoin network itself.]]> +
+
+ + + + select + adminhtml/system_config_source_yesno + 70 + 1 + 1 + 0 + + + + + adminhtml/system_config_form_field_heading + 75 + 1 + 1 + 1 + + + + allowspecific + 76 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 77 + bitpay/specificCountry + 1 + 1 + 0 + + + + text + 80 + 1 + 1 + 0 + + + + text + 90 + 1 + 1 + 0 + + + + text + 99 + 1 + 1 + 0 + validate-number + + + + + adminhtml/system_config_form_field_heading + + + + 500 + 1 + 1 + 1 + + + + openssl + bitpay/adminhtml_system_config_form_field_extension + 510 + 1 + 1 + 1 + +
+
+
+
+
+
diff --git a/app/code/community/Bitpay/Core/sql/bitpay_setup/install-2.1.4.php b/app/code/community/Bitpay/Core/sql/bitpay_setup/install-2.1.4.php new file mode 100644 index 0000000..b4b135d --- /dev/null +++ b/app/code/community/Bitpay/Core/sql/bitpay_setup/install-2.1.4.php @@ -0,0 +1,64 @@ +startSetup(); + +/** + * IPN Log Table, used to keep track of incoming IPNs + */ +$this->run(sprintf('DROP TABLE IF EXISTS `%s`;', $this->getTable('bitpay/ipn'))); +$ipnTable = new Varien_Db_Ddl_Table(); +$ipnTable->setName($this->getTable('bitpay/ipn')); +$ipnTable->addColumn('id', Varien_Db_Ddl_Table::TYPE_INTEGER, 11, array('auto_increment' => true, 'nullable' => false, 'primary' => true,)); +$ipnTable->addColumn('invoice_id', Varien_Db_Ddl_Table::TYPE_TEXT, 200); +$ipnTable->addColumn('url', Varien_Db_Ddl_Table::TYPE_TEXT, 400); +$ipnTable->addColumn('status', Varien_Db_Ddl_Table::TYPE_TEXT, 20); +$ipnTable->addColumn('btc_price', Varien_Db_Ddl_Table::TYPE_DECIMAL, array(16, 8)); +$ipnTable->addColumn('price', Varien_Db_Ddl_Table::TYPE_DECIMAL, array(16, 8)); +$ipnTable->addColumn('currency', Varien_Db_Ddl_Table::TYPE_TEXT, 10); +$ipnTable->addColumn('invoice_time', Varien_Db_Ddl_Table::TYPE_INTEGER, 11); +$ipnTable->addColumn('expiration_time', Varien_Db_Ddl_Table::TYPE_INTEGER, 11); +$ipnTable->addColumn('current_time', Varien_Db_Ddl_Table::TYPE_INTEGER, 11); +$ipnTable->addColumn('pos_data', Varien_Db_Ddl_Table::TYPE_TEXT, 255); +$ipnTable->addColumn('btc_paid', Varien_Db_Ddl_Table::TYPE_DECIMAL, array(16, 8)); +$ipnTable->addColumn('rate', Varien_Db_Ddl_Table::TYPE_DECIMAL, array(16, 8)); +$ipnTable->addColumn('exception_status', Varien_Db_Ddl_Table::TYPE_TEXT, 255); + +$ipnTable->setOption('type', 'InnoDB'); +$ipnTable->setOption('charset', 'utf8'); +$this->getConnection()->createTable($ipnTable); + +/** + * Table used to keep track of invoices that have been created. The + * IPNs that are received are used to update this table. + */ +$this->run(sprintf('DROP TABLE IF EXISTS `%s`;', $this->getTable('bitpay/invoice'))); +$invoiceTable = new Varien_Db_Ddl_Table(); +$invoiceTable->setName($this->getTable('bitpay/invoice')); +$invoiceTable->addColumn('id', Varien_Db_Ddl_Table::TYPE_TEXT, 64, array('nullable' => false, 'primary' => true)); +$invoiceTable->addColumn('quote_id', Varien_Db_Ddl_Table::TYPE_INTEGER, 11); +$invoiceTable->addColumn('increment_id', Varien_Db_Ddl_Table::TYPE_INTEGER, 11); +$invoiceTable->addColumn('updated_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP); +$invoiceTable->addColumn('url', Varien_Db_Ddl_Table::TYPE_TEXT, 200); +$invoiceTable->addColumn('pos_data', Varien_Db_Ddl_Table::TYPE_TEXT, 255); +$invoiceTable->addColumn('status', Varien_Db_Ddl_Table::TYPE_TEXT, 20); +$invoiceTable->addColumn('btc_price', Varien_Db_Ddl_Table::TYPE_DECIMAL, array(16, 8)); +$invoiceTable->addColumn('btc_due', Varien_Db_Ddl_Table::TYPE_DECIMAL, array(16, 8)); +$invoiceTable->addColumn('price', Varien_Db_Ddl_Table::TYPE_DECIMAL, array(16, 8)); +$invoiceTable->addColumn('currency', Varien_Db_Ddl_Table::TYPE_TEXT, 10); +$invoiceTable->addColumn('ex_rates', Varien_Db_Ddl_Table::TYPE_TEXT, 255); +$invoiceTable->addColumn('order_id', Varien_Db_Ddl_Table::TYPE_TEXT, 64); +$invoiceTable->addColumn('invoice_time', Varien_Db_Ddl_Table::TYPE_INTEGER, 11); +$invoiceTable->addColumn('expiration_time', Varien_Db_Ddl_Table::TYPE_INTEGER, 11); +$invoiceTable->addColumn('current_time', Varien_Db_Ddl_Table::TYPE_INTEGER, 11); +$invoiceTable->addColumn('btc_paid', Varien_Db_Ddl_Table::TYPE_DECIMAL, array(16, 8)); +$invoiceTable->addColumn('rate', Varien_Db_Ddl_Table::TYPE_DECIMAL, array(16, 8)); +$invoiceTable->addColumn('exception_status', Varien_Db_Ddl_Table::TYPE_TEXT, 255); +$invoiceTable->addColumn('token', Varien_Db_Ddl_Table::TYPE_TEXT, 164); +$invoiceTable->setOption('type', 'InnoDB'); +$invoiceTable->setOption('charset', 'utf8'); +$this->getConnection()->createTable($invoiceTable); + +$this->endSetup(); diff --git a/app/code/community/Bitpay/Core/sql/bitpay_setup/upgrade-2.1.2-2.1.4.php b/app/code/community/Bitpay/Core/sql/bitpay_setup/upgrade-2.1.2-2.1.4.php new file mode 100644 index 0000000..d78f850 --- /dev/null +++ b/app/code/community/Bitpay/Core/sql/bitpay_setup/upgrade-2.1.2-2.1.4.php @@ -0,0 +1,16 @@ +startSetup(); + +/** + * IPN Log Table, used to keep track of incoming IPNs + * + * Fixes `curent_time` typo + */ +$ipnTable = new Varien_Db_Ddl_Table(); +$this->getConnection()->changeColumn($this->getTable('bitpay/ipn'), 'curent_time', 'current_time', array('type' => Varien_Db_Ddl_Table::TYPE_INTEGER)); + +$this->endSetup(); diff --git a/app/design/adminhtml/default/default/layout/bitpay.xml b/app/design/adminhtml/default/default/layout/bitpay.xml new file mode 100644 index 0000000..eac80cb --- /dev/null +++ b/app/design/adminhtml/default/default/layout/bitpay.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/design/adminhtml/default/default/template/bitpay/info/default.phtml b/app/design/adminhtml/default/default/template/bitpay/info/default.phtml new file mode 100644 index 0000000..79122f2 --- /dev/null +++ b/app/design/adminhtml/default/default/template/bitpay/info/default.phtml @@ -0,0 +1,17 @@ +Ordered with BitPay'; + +if ($url = $this->getBitpayInvoiceUrl()) { + echo '

View Invoice

'; +} + +?> diff --git a/app/design/adminhtml/default/default/template/bitpay/system/config/field/header.phtml b/app/design/adminhtml/default/default/template/bitpay/system/config/field/header.phtml new file mode 100644 index 0000000..6c74aed --- /dev/null +++ b/app/design/adminhtml/default/default/template/bitpay/system/config/field/header.phtml @@ -0,0 +1,18 @@ +' . + 'BitPay' . + 'Support' . + 'Sign Up' . + 'Login' . + ''; + +?> diff --git a/app/design/frontend/base/default/layout/bitcoins.xml b/app/design/frontend/base/default/layout/bitcoins.xml deleted file mode 100644 index e2ae5ad..0000000 --- a/app/design/frontend/base/default/layout/bitcoins.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/design/frontend/base/default/layout/bitpay.xml b/app/design/frontend/base/default/layout/bitpay.xml new file mode 100644 index 0000000..f90422b --- /dev/null +++ b/app/design/frontend/base/default/layout/bitpay.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/app/design/frontend/base/default/template/bitcoins/iframe.phtml b/app/design/frontend/base/default/template/bitcoins/iframe.phtml deleted file mode 100644 index 617f652..0000000 --- a/app/design/frontend/base/default/template/bitcoins/iframe.phtml +++ /dev/null @@ -1,34 +0,0 @@ -GetIframeUrl(); -switch($url){ - case 'notbitpay': break; // customer is using another payment method - case 'paid': - echo 'Order payment received. Place Order to complete.'; break; - case 'disabled': - echo 'Please click Place Order to continue to bitpay.com.'; break; - case false: - echo 'Error creating invoice. Please try again or try another payment solution.'; break; - default: - echo ''; break; -} -$quoteId = $this->GetQuoteId(); -?> - -getRequest(); -$url = Mage::getUrl('bitpay_callback/index/checkForPayment/'); -if ($request->getScheme() == 'https') - $url = str_replace('http://', 'https://', $url); -?> - \ No newline at end of file diff --git a/app/design/frontend/base/default/template/bitpay/form/bitpay.phtml b/app/design/frontend/base/default/template/bitpay/form/bitpay.phtml new file mode 100644 index 0000000..87091e0 --- /dev/null +++ b/app/design/frontend/base/default/template/bitpay/form/bitpay.phtml @@ -0,0 +1,17 @@ +getMethodCode(); +if (Mage::getStoreConfig('payment/bitpay/fullscreen')) { + echo ''; +} +?> diff --git a/app/design/frontend/base/default/template/bitpay/iframe.phtml b/app/design/frontend/base/default/template/bitpay/iframe.phtml new file mode 100644 index 0000000..50143d8 --- /dev/null +++ b/app/design/frontend/base/default/template/bitpay/iframe.phtml @@ -0,0 +1,92 @@ +getIframeUrl(); +switch($url) { + case 'notbitpay': + break; // customer is using another payment method + case 'paid': + echo 'Order payment received. Place Order to complete.'; + break; + case 'disabled': + echo 'Please click Place Order to continue to bitpay.com.'; + break; + case false: + echo 'Error creating invoice. Please try again or try another payment solution.'; + break; + default: + echo ''; + break; +} +$quoteId = $this->getQuote()->getId(); +$request = Mage::app()->getRequest(); +$url = Mage::getUrl('bitpay/index/index/'); +if ($request->getScheme() == 'https') { + $url = str_replace('http://', 'https://', $url); +} + +?> + + + diff --git a/app/design/frontend/base/default/template/bitpay/info/default.phtml b/app/design/frontend/base/default/template/bitpay/info/default.phtml new file mode 100644 index 0000000..905db79 --- /dev/null +++ b/app/design/frontend/base/default/template/bitpay/info/default.phtml @@ -0,0 +1,10 @@ +' . $this->escapeHtml($this->getMethod()->getTitle()) . '

'; + +echo $this->getChildHtml(); +?> diff --git a/app/design/frontend/base/default/template/bitpay/json.phtml b/app/design/frontend/base/default/template/bitpay/json.phtml new file mode 100644 index 0000000..f6802fd --- /dev/null +++ b/app/design/frontend/base/default/template/bitpay/json.phtml @@ -0,0 +1,9 @@ + +{ + "paid": isPaid(); ?> +} diff --git a/app/etc/modules/Bitpay_Bitcoins.xml b/app/etc/modules/Bitpay_Bitcoins.xml deleted file mode 100644 index aadee6e..0000000 --- a/app/etc/modules/Bitpay_Bitcoins.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - true - community - - - - - - diff --git a/app/etc/modules/Bitpay_Core.xml b/app/etc/modules/Bitpay_Core.xml new file mode 100644 index 0000000..896fa96 --- /dev/null +++ b/app/etc/modules/Bitpay_Core.xml @@ -0,0 +1,18 @@ + + + + + + true + community + + + + + + diff --git a/app/locale/en_US/Bitpay_Core.csv b/app/locale/en_US/Bitpay_Core.csv new file mode 100644 index 0000000..bc98c80 --- /dev/null +++ b/app/locale/en_US/Bitpay_Core.csv @@ -0,0 +1,15 @@ +"Enabled","Enabled" +"Title","Title" +"Network","Network" +"Debug","Debug" +"New","New" +"Paid","Paid" +"Confirmed","Confirmed" +"Complete","Complete" +"Expired","Expired" +"Invalid","Invalid" +"Low","Low" +"Medium","Medium" +"High","High" +"Livenet","Livenet" +"Testnet","Testnet" diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..7cb68f3 --- /dev/null +++ b/build.xml @@ -0,0 +1,277 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/build.properties b/build/build.properties new file mode 100644 index 0000000..d10344e --- /dev/null +++ b/build/build.properties @@ -0,0 +1,154 @@ +# The MIT License (MIT) +# +# Copyright (c) 2011-2014 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. + +# Running a build locally, this also is used +# to define all the default properties. +db.user=root +db.pass= +db.name=magento +db.host=127.0.0.1 +magento.version=1.9.0.1 +magento.baseurl=http://www.localhost.com + +#### +# +# This properties file is used to configure the entire build. The purpose of +# this file is so that it is easy to drop it into a project and change a few +# of these settings and have a build run successfully. +# +# NOTE: You may still need to take a look at and edit `phpunit.xml.dist`. +# + +#### +# +# Directory Configuration +# + +# The main location of all the projects source code, this should be the only +# property that you need to change. Code can live in the root directory or it +# can live inside of it's own directory. +project.source=${project.basedir} +#project.source=${project.basedir}/src + +# --- You shouldn't need to edit any of the below value --- + +# Location of all executables, this is the location on the `bin-dir` that you +# have configured in `composer.json`. If you have not configured this in +# 1composer.json` the default is to put it in the `vendor` directory. +project.bindir=${project.basedir}/bin +#project.bindir=${project.basedir}/vendor/bin + +# Where to put the build artifacts, cache, logs, docs, etc. +project.builddir=${project.basedir}/build + +# Location of vendor directory created by composer. +project.vendordir=${project.basedir}/vendor + +# build artifacts for code analysis +project.logsdir=${project.builddir}/logs + +# Build artifacts that can be reused or updated, they do not need to be deleted +# every build. These would include charts and images. +project.cachedir=${project.builddir}/cache + +# Location of generated documentation such as API and code coverage +project.docsdir=${project.builddir}/docs +#### directory #### + +#### +# +# phpunit configuration +# + +# Directory that contains phpunit.xml or the phpunit.xml file itself +phpunit.configuration=${project.builddir}/phpunit.xml.dist +#### phpunit #### + +#### +# +# phpmd configuration +# + +# php source code filename or directory. Can be a comma-separated string +phpmd.source=${project.source} + +# report format, text, xml, or html +phpmd.report.format=xml + +# ruleset filename or a comma-separated string of rulesetfilenames +phpmd.ruleset=${project.builddir}/rulesets/phpmd.xml + +# send report output to this file +phpmd.report.file=${project.logsdir}/phpmd.xml + +# comma-separated string of patterns that are used to ignore directories +phpmd.exclude=${project.bindir},${project.builddir},${project.vendordir},app/code/community/Bitpay/Bitcoins/tests +#### phpmd #### + +#### +# +# phploc Configuration +# + +# Source directory of project +phploc.source=${project.source}/ + +# Where to put the csv log +phploc.log.csv=${project.logsdir}/phploc.csv + +# Location of xml log +phploc.log.xml=${project.logsdir}/phploc.xml + +# Small hack to exclude multiple directories +phploc.exclude=vendor --exclude=build --exclude=bin --exclude=app/code/community/Bitpay/Bitcoins/tests +#### phploc #### + +#### +# +# pdepend configuration +# +pdepend.source=${project.source} +pdepend.jdepend.chart=${project.cachedir}/jdepend_chart.svg +pdepend.jdepend.xml=${project.logsdir}/jdepend.xml +pdepend.overview.pyramid=${project.cachedir}/pyramid.svg +pdepend.summary.xml=${project.logsdir}/jdepend_summary.xml +pdepend.ignore=bin,build,vendor,app/code/community/Bitpay/Bitcoins/tests +#### pdepend #### + +#### +# +# phpcs configuration +# +phpcs.source=${project.source} +phpcs.report.xml=${project.logsdir}/phpcs.xml +phpcs.standard=PSR1 --standard=PSR2 +phpcs.ignore=vendor --ignore=bin --ignore=build --ignore=app/code/community/Bitpay/Bitcoins/tests/ +#### phpcs #### + +#### +# +# phpdoc configuration +# +phpdoc.directory=${project.source}/ +phpdoc.target=${project.docsdir}/api +phpdoc.ignore=vendor/,bin/,app/code/community/Bitpay/Bitcoins/tests/,build/ +#### phpdoc #### diff --git a/build/n98-magerun.yaml b/build/n98-magerun.yaml new file mode 100644 index 0000000..d797295 --- /dev/null +++ b/build/n98-magerun.yaml @@ -0,0 +1,28 @@ +# The MIT License (MIT) +# +# Copyright (c) 2011-2014 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. +commands: + N98\Magento\Command\Installer\InstallCommand: + installation: + defaults: + currency: USD + locale: en_US + timezone: America/New_York diff --git a/build/phpunit.xml.dist b/build/phpunit.xml.dist new file mode 100644 index 0000000..72a17db --- /dev/null +++ b/build/phpunit.xml.dist @@ -0,0 +1,60 @@ + + + + + + + ../tests/ + + + + + + ../app/code/community/Bitpay/Core/ + ../shell + + ../app/code/community/Bitpay/Core/sql + + + + + + + + + diff --git a/build/rulesets/phpmd.xml b/build/rulesets/phpmd.xml new file mode 100644 index 0000000..feb5ee7 --- /dev/null +++ b/build/rulesets/phpmd.xml @@ -0,0 +1,43 @@ + + + + + Custom phpmd ruleset to be used with BitPay projects. + + + + + + + + + + diff --git a/build/travis.properties b/build/travis.properties new file mode 100644 index 0000000..a21c8f1 --- /dev/null +++ b/build/travis.properties @@ -0,0 +1,24 @@ +# The MIT License (MIT) +# +# Copyright (c) 2011-2014 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. + +# Properties file for running a build on Travis CI +db.user=travis diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..c33070e --- /dev/null +++ b/composer.json @@ -0,0 +1,47 @@ +{ + "name": "bitpay/magento-plugin", + "description": "Bitcoin payment module using the bitpay.com service", + "keywords": ["magento","bitcoin", "bitcoin cash"], + "minimum-stability": "stable", + "type": "magento-plugin", + "homepage": "https://github.com/bitpay/magento-plugin", + "license": "MIT", + "support": { + "email": "support@bitpay.com", + "issues": "https://github.com/bitpay/magento-plugin/issues", + "source": "https://github.com/bitpay/magento-plugin" + }, + "require": { + "bitpay/php-client": "~2.2" + }, + "require-dev": { + "symfony/finder": "~2.3", + "symfony/process": "~2.3", + "phpmd/phpmd": "~2.1", + "phpdocumentor/phpdocumentor": "~2.7", + "daedalus/daedalus": "~1.0@dev", + "pdepend/pdepend" : "~2.0", + "squizlabs/php_codesniffer": "~1.5", + "phpunit/phpunit": "~3.7", + "phploc/phploc": "~2.0", + "fzaninotto/faker": "~1.3" + }, + "config": { + "bin-dir": "bin" + }, + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "archive": { + "exclude": [ + "tests/", + ".gitattributes", + ".gitignore", + ".travis.yml", + "build/", + "build.xml" + ] + } +} diff --git a/docs/MagentoInvoiceSettings.png b/docs/MagentoInvoiceSettings.png new file mode 100644 index 0000000..eeb0163 Binary files /dev/null and b/docs/MagentoInvoiceSettings.png differ diff --git a/docs/MagentoSettings.png b/docs/MagentoSettings.png new file mode 100644 index 0000000..d31bbea Binary files /dev/null and b/docs/MagentoSettings.png differ diff --git a/lib/Bitpay/Storage/MagentoStorage.php b/lib/Bitpay/Storage/MagentoStorage.php new file mode 100644 index 0000000..fbff56d --- /dev/null +++ b/lib/Bitpay/Storage/MagentoStorage.php @@ -0,0 +1,69 @@ +_keys[$key->getId()] = $key; + + $data = serialize($key); + $encryptedData = \Mage::helper('core')->encrypt($data); + $config = new \Mage_Core_Model_Config(); + + if (true === isset($config) && false === empty($config)) { + $config->saveConfig($key->getId(), $encryptedData); + } else { + \Mage::helper('bitpay')->debugData('[ERROR] In file lib/Bitpay/Storage/MagentoStorage.php, class MagentoStorage::persist - Could not instantiate a \Mage_Core_Model_Config object.'); + throw new \Exception('[ERROR] In file lib/Bitpay/Storage/MagentoStorage.php, class MagentoStorage::persist - Could not instantiate a \Mage_Core_Model_Config object.'); + } + } + + /** + * @inheritdoc + */ + public function load($id) + { + if (true === isset($id) && true === isset($this->_keys[$id])) { + return $this->_keys[$id]; + } + + $entity = \Mage::getStoreConfig($id); + + /** + * Not in database + */ + if (false === isset($entity) || true === empty($entity)) { + \Mage::helper('bitpay')->debugData('[INFO] Call to MagentoStorage::load($id) with the id of ' . $id . ' did not return the store config parameter because it was not found in the database.'); + throw new \Exception('[INFO] Call to MagentoStorage::load($id) with the id of ' . $id . ' did not return the store config parameter because it was not found in the database.'); + } + + $decodedEntity = unserialize(\Mage::helper('core')->decrypt($entity)); + + if (false === isset($decodedEntity) || true === empty($decodedEntity)) { + \Mage::helper('bitpay')->debugData('[INFO] Call to MagentoStorage::load($id) with the id of ' . $id . ' could not decrypt & unserialize the entity ' . $entity . '.'); + throw new \Exception('[INFO] Call to MagentoStorage::load($id) with the id of ' . $id . ' could not decrypt & unserialize the entity ' . $entity . '.'); + } + + \Mage::helper('bitpay')->debugData('[INFO] Call to MagentoStorage::load($id) with the id of ' . $id . ' successfully decrypted & unserialized the entity ' . $entity . '.'); + + return $decodedEntity; + } +} diff --git a/lib/bitpay/bp_lib.php b/lib/bitpay/bp_lib.php deleted file mode 100644 index 3ead3fb..0000000 --- a/lib/bitpay/bp_lib.php +++ /dev/null @@ -1,130 +0,0 @@ - curl_error($curl)); - } else { - $response = json_decode($responseString, true); - if (!$response) - $response = array('error' => 'invalid json: '.$responseString); - } - curl_close($curl); - return $response; -} -// $orderId: Used to display an orderID to the buyer. In the account summary view, this value is used to -// identify a ledger entry if present. -// -// $price: by default, $price is expressed in the currency you set in bp_options.php. The currency can be -// changed in $options. -// -// $posData: this field is included in status updates or requests to get an invoice. It is intended to be used by -// the merchant to uniquely identify an order associated with an invoice in their system. Aside from that, Bit-Pay does -// not use the data in this field. The data in this field can be anything that is meaningful to the merchant. -// -// $options keys can include any of: -// ('itemDesc', 'itemCode', 'notificationEmail', 'notificationURL', 'redirectURL', 'apiKey' -// 'currency', 'physical', 'fullNotifications', 'transactionSpeed', 'buyerName', -// 'buyerAddress1', 'buyerAddress2', 'buyerCity', 'buyerState', 'buyerZip', 'buyerEmail', 'buyerPhone') -// If a given option is not provided here, the value of that option will default to what is found in bp_options.php -// (see api documentation for information on these options). -function bpCreateInvoice($orderId, $price, $posData, $options = array()) { - global $bpOptions; - - $options = array_merge($bpOptions, $options); // $options override any options found in bp_options.php - - $pos = array('posData' => $posData); - if ($bpOptions['verifyPos']) - $pos['hash'] = crypt(serialize($posData), $options['apiKey']); - $options['posData'] = json_encode($pos); - - $options['orderID'] = $orderId; - $options['price'] = $price; - - $postOptions = array('orderID', 'itemDesc', 'itemCode', 'notificationEmail', 'notificationURL', 'redirectURL', - 'posData', 'price', 'currency', 'physical', 'fullNotifications', 'transactionSpeed', 'buyerName', - 'buyerAddress1', 'buyerAddress2', 'buyerCity', 'buyerState', 'buyerZip', 'buyerEmail', 'buyerPhone'); - foreach($postOptions as $o) - if (array_key_exists($o, $options)) - $post[$o] = $options[$o]; - $post = json_encode($post); - - $response = bpCurl('https://bitpay.com/api/invoice/', $options['apiKey'], $post); - - return $response; -} - -// Call from your notification handler to convert $_POST data to an object containing invoice data -function bpVerifyNotification($apiKey = false) { - global $bpOptions; - if (!$apiKey) - $apiKey = $bpOptions['apiKey']; - - $post = file_get_contents("php://input"); - if (!$post) - return 'No post data'; - - $json = json_decode($post, true); - - if (is_string($json)) - return $json; // error - - if (!array_key_exists('posData', $json)) - return 'no posData'; - - $posData = json_decode($json['posData'], true); - if($bpOptions['verifyPos'] and $posData['hash'] != crypt(serialize($posData['posData']), $apiKey)) - return 'authentication failed (bad hash)'; - $json['posData'] = $posData['posData']; - - return $json; -} - -// $options can include ('apiKey') -function bpGetInvoice($invoiceId, $apiKey=false) { - global $bpOptions; - if (!$apiKey) - $apiKey = $bpOptions['apiKey']; - - $response = bpCurl('https://bitpay.com/api/invoice/'.$invoiceId, $apiKey); - if (is_string($response)) - return $response; // error - $response['posData'] = json_decode($response['posData'], true); - $response['posData'] = $response['posData']['posData']; - - return $response; -} - - -?> \ No newline at end of file diff --git a/lib/bitpay/bp_options.php b/lib/bitpay/bp_options.php deleted file mode 100644 index d8d3075..0000000 --- a/lib/bitpay/bp_options.php +++ /dev/null @@ -1,30 +0,0 @@ - \ No newline at end of file diff --git a/modman b/modman new file mode 100644 index 0000000..43f0a58 --- /dev/null +++ b/modman @@ -0,0 +1,40 @@ +# The MIT License (MIT) +# +# Copyright (c) 2011-2014 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. + +# Modman file generated by 'generate-modman' +# app/code +app/code/community/Bitpay/Core app/code/community/Bitpay/Core +# app/etc +app/etc/modules/Bitpay_Core.xml app/etc/modules/Bitpay_Core.xml +# app/locale +app/locale/en_US/Bitpay_Core.csv app/locale/en_US/Bitpay_Core.csv +# app/design +app/design/adminhtml/default/default/template/bitpay/info/default.phtml app/design/adminhtml/default/default/template/bitpay/info/default.phtml +app/design/adminhtml/default/default/template/bitpay/system/config/field/header.phtml app/design/adminhtml/default/default/template/bitpay/system/config/field/header.phtml +app/design/adminhtml/default/default/layout/bitpay.xml app/design/adminhtml/default/default/layout/bitpay.xml +app/design/frontend/base/default/template/bitpay/iframe.phtml app/design/frontend/base/default/template/bitpay/iframe.phtml +app/design/frontend/base/default/template/bitpay/info/default.phtml app/design/frontend/base/default/template/bitpay/info/default.phtml +app/design/frontend/base/default/template/bitpay/json.phtml app/design/frontend/base/default/template/bitpay/json.phtml +app/design/frontend/base/default/template/bitpay/form/bitpay.phtml app/design/frontend/base/default/template/bitpay/form/bitpay.phtml +app/design/frontend/base/default/layout/bitpay.xml app/design/frontend/base/default/layout/bitpay.xml +# lib +lib/Bitpay/Storage/MagentoStorage.php lib/Bitpay/Storage/MagentoStorage.php diff --git a/scripts/config.json b/scripts/config.json new file mode 100644 index 0000000..412a628 --- /dev/null +++ b/scripts/config.json @@ -0,0 +1,11 @@ +{ + "mysql": { + "host": "localhost", + "user": "magento", + "password": "magento", + "database": "magento" + }, + "host": "http://127.0.0.1/magento/index.php/bitpay/ipn", + "status": "confirmed" +} + diff --git a/scripts/delete.sh b/scripts/delete.sh new file mode 100755 index 0000000..1ffb56c --- /dev/null +++ b/scripts/delete.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env sh +# +# (c) 2014 BitPay, Inc. +# +# Shell script that locates and removes +# any previously installed BitPay Magento +# plugin files. +# +# Written by Rich Morgan + +# For full transparency I've listed all the depricated files and folders +# related to the old plugin here for your information. This is the complete +# list of items you would want to backup and/or remove if you wanted to do +# this by hand yourself or if you just wanted a list for tracking purposes. +old_files[0]="/lib/bitpay/bp_config_default.php" +old_files[1]="/lib/bitpay/bp_lib.php" +old_files[2]="/lib/bitpay/bp_options.php" +old_files[3]="/lib/bitpay" +old_files[4]="/app/design/frontend/base/default/template/bitcoins/iframe.phtml" +old_files[5]="/app/design/frontend/base/default/template/bitcoins" +old_files[6]="/app/design/frontend/base/default/layout/bitcoins.xml" +old_files[7]="/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn/Collection.php" +old_files[8]="/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn" +old_files[9]="/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn.php" +old_files[10]="/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php" +old_files[11]="/app/code/community/Bitpay/Bitcoins/Model/Ipn.php" +old_files[12]="/app/code/community/Bitpay/Bitcoins/Model/Source/Speed.php" +old_files[13]="/app/code/community/Bitpay/Bitcoins/Model/Source" +old_files[14]="/app/code/community/Bitpay/Bitcoins/Model/Resource" +old_files[15]="/app/code/community/Bitpay/Bitcoins/Model" +old_files[16]="/app/code/community/Bitpay/Bitcoins/sql/Bitcoins_setup/upgrade-0.1.0-1.0.0.php" +old_files[17]="/app/code/community/Bitpay/Bitcoins/sql/Bitcoins_setup/upgrade-1.0.0-1.1.0.php" +old_files[18]="/app/code/community/Bitpay/Bitcoins/sql/Bitcoins_setup" +old_files[19]="/app/code/community/Bitpay/Bitcoins/sql" +old_files[20]="/app/code/community/Bitpay/Bitcoins/Block/Iframe.php" +old_files[21]="/app/code/community/Bitpay/Bitcoins/Block" +old_files[22]="/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php" +old_files[23]="/app/code/community/Bitpay/Bitcoins/controllers" +old_files[24]="/app/code/community/Bitpay/Bitcoins/etc/config.xml" +old_files[25]="/app/code/community/Bitpay/Bitcoins/etc/system.xml" +old_files[26]="/app/code/community/Bitpay/Bitcoins/etc" +old_files[27]="/app/code/community/Bitpay/Bitcoins" +old_files[28]="/app/code/community/Bitpay" +old_files[29]="/app/etc/modules/Bitpay_Bitcoins.xml" +old_files[30]="composer.json" +old_files[31]="magento-plugin-master.zip" +old_files[32]="modman" +old_files[33]="README.md" + +CLEAN="true" +RMOPTS="-vrfd" + +echo "Looking for your Magento installation. Please stand by - this may take a few minutes while I search..." + +# In case we have multiple Magento installs on this one +# server, we will just take the first one and ask... +i=`find /var /usr /opt -name Mage.php -type f | head -n 1 2>/dev/null` + +if [ -e $i ] +then + DIR=`dirname $i` + cd $DIR && cd ../ + mage_dir=`pwd` + echo "It looks like Magento is installed in the $mage_dir directory." + echo "Is this correct? (y/n) > " +else + # In case we can't find the Magento folder, we are just + # providing a default value here. This happens to be the + # default for an Ubuntu-based machine, for example. + DIR="/var/www/html/magento" + cd $DIR + mage_dir=`pwd` + echo "You don't have Magento installed or this script doesn't have permissions to view the directory it's contained in." + echo "Should I default to $mage_dir ? (y/n) > " +fi + +read answer + +if [ $answer = "y" ] +then + echo "Attempting to delete the old plugin files now. Please stand by..." + for filename in "${old_files[@]}" + do + fullname=$mage_dir$filename + if [ -e $fullname ] + then + echo " Found $fullname - removing!" + rm $RMOPTS $fullname + else + echo " $filename does not exist - skipping!" + fi + done + echo "File removal process complete. Checking to make sure your Magento environment was completely cleaned of old BitPay files..." + echo "" + for filename in "${old_files[@]}" + do + fullname=$mage_dir$filename + if [ -e $fullname ] + then + echo " The old plugin file $fullname is still present!" + CLEAN="false" + else + echo " $filename is not present - good!" + fi + done + if [ $CLEAN = "false" ] + then + echo "Old BitPay plugin files are still present in your Magento directory. This is likely due to this script not having permissions to delete them. You can fix this by running this script as superuser or you can remove the files by hand." + else + echo "Good! I didn't find any remaining old BitPay plugin files in your Magento directory! You can now safely install the new BitPay plugin." + fi + echo "Process complete." + echo "" +else + echo "Okay, please enter the path you would like me to use or QUIT if you wish to abort the process." + echo "Full path or QUIT ? > " + read answer + if [ $answer = "QUIT" ] + then + echo "Quitting!" + else + echo "Attempting to delete the old plugin files at the directory you provided. Please stand by..." + for filename in "${old_files[@]}" + do + fullname=$answer$filename + if [ -e $fullname ] + then + echo " Found $fullname - removing!" + rm $RMOPTS $fullname + else + echo " $filename does not exist - skipping!" + fi + done + echo "File removal process complete. Checking to make sure your Magento environment was completely cleaned of old BitPay files..." + echo "" + for filename in "${old_files[@]}" + do + fullname=$mage_dir$filename + if [ -e $fullname ] + then + echo " The old plugin file $fullname is still present!" + CLEAN="false" + else + echo " $filename is not present - good!" + fi + done + if [ $CLEAN = "false" ] + then + echo "Old BitPay plugin files are still present in your Magento directory. This is likely due to this script not having permissions to delete them. You can fix this by running this script as superuser or you can remove the files by hand." + else + echo "Good! I didn't find any remaining old BitPay plugin files in your Magento directory! You can now safely install the new BitPay plugin." + fi + echo "Process complete." + echo "" + fi +fi diff --git a/scripts/package b/scripts/package new file mode 100755 index 0000000..fa55a52 --- /dev/null +++ b/scripts/package @@ -0,0 +1,154 @@ +#!/usr/bin/env php +files() + ->in($vendorDir . '/bitpay/php-client/src') + ->exclude('Tests'); + +foreach ($finder as $file) { + $path = $file->getRelativePathname(); + $filesystem->mkdir( + sprintf( + '%s/lib/%s', + $tmpDistDir, + dirname($file->getRelativePathname()) + ) + ); + $filesystem->copy( + $file->getRealPath(), + sprintf( + '%s/lib/%s', + $tmpDistDir, + $file->getRelativePathname() + ), + true + ); +} +$filesystem->mirror('app/', sprintf('%s/app/', $tmpDistDir)); +$filesystem->mirror('lib/', sprintf('%s/lib/', $tmpDistDir)); +$filesystem->copy('LICENSE', sprintf('%s/app/code/community/Bitpay/Core/LICENSE', $tmpDistDir)); +$filesystem->copy('README.md', sprintf('%s/app/code/community/Bitpay/Core/README.md', $tmpDistDir)); + +// All required files are in the temp. distribution directory + +/** + * Need to create the package.xml file required by Magento + */ +$xml = simplexml_load_string(''); +$xml->addChild('name', 'Bitpay_Core'); +$xml->addChild('version', $version); +$xml->addChild('stability', 'stable'); +$xml->addChild('license', 'MIT') + ->addAttribute('uri', 'https://github.com/bitpay/magento-plugin/blob/master/LICENSE'); +$xml->addChild('channel', 'community'); +$xml->addChild('extends'); +$xml->addChild('summary', 'Accept Bitcoin and Bitcoin Cash on your Magento-based e-commerce site via BitPay.'); +$xml->addChild('description', 'Use BitPays plugin to accept Bitcoin and Bitcoin Cash payments from customers anywhere on earth. You do not need to worry about bitcoin as a currency, because the plugin uses your local currency to create a BitPay invoice. BitPay will instantly update the Magento order status once the bitcoin payment has been received.'); +$xml->addChild('notes', 'Full support for Magento 1.9'); + +$authorsNode = $xml->addChild('authors'); + +$authors = array( + array( + 'Integrations Team', // Name + 'BitPayInc', // Magento Connect Username + 'support@bitpay.com', // Email + ), +); + +foreach ($authors as $author) { + $authorNode = $authorsNode->addChild('author'); + $authorNode->addChild('name', $author[0]); + $authorNode->addChild('user', $author[1]); + $authorNode->addChild('email', $author[2]); +} + +$xml->addChild('date', date('Y-m-d')); +$xml->addChild('time', date('G:i:s')); +$xml->addChild('compatible'); +$xml->addChild('dependencies'); + +$requiredNode = $xml->addChild('required', 'php'); +$requiredNode->addAttribute('php_min', '5.4.0'); +$requiredNode->addAttribute('php_max', '7.2.0'); + +$extensionsNode = $xml->addChild('extensions'); + +foreach (array('openssl', 'mcrypt') as $ext) { + $extNode = $extensionsNode->addChild('name', $ext); + $extNode->addChild('min'); + $extNode->addChild('max'); +} + +$targetNode = $xml->addChild('contents')->addChild('target'); +$targetNode->addAttribute('name', 'mage'); + +$finder = new \Symfony\Component\Finder\Finder(); +$finder + ->files() + ->in($tmpDistDir); + +foreach ($finder as $file) { + $node = $targetNode; + $directories = explode('/', $file->getRelativePathname()); + $filename = array_pop($directories); + + for ($i = 1; $i <= count($directories); $i++) { + $dir = $directories[$i - 1]; + $nodes = $node->xpath('dir[@name="' . $dir . '"]'); + if (count($nodes)) { + $node = array_pop($nodes); + } else { + $node = $node->addChild('dir'); + $node->addAttribute('name', $dir); + } + } + + $fileNode = $node->addChild('file'); + $fileNode->addAttribute('name', $file->getBaseName()); + $fileNode->addAttribute('hash', md5_file($file->getRealPath())); +} + +$xml->asXml($tmpDistDir . '/package.xml'); + +// package.xml created, just need to tar/zip everything +$filesystem->remove($distFile.'.zip'); +$filesystem->remove($distFile.'.tgz'); + +$process = new \Symfony\Component\Process\Process( + sprintf('cd %s; tar -czf %s *', $tmpDistDir, $distFile.'.tgz') +); +$process->run(); + +$process = new \Symfony\Component\Process\Process( + sprintf('cd %s; zip -r %s *', $tmpDistDir, $distFile.'.zip') +); +$process->run(); + +// Cleanup +$filesystem->remove($tmpDistDir); diff --git a/scripts/send_ipn_for_last_order_created.js b/scripts/send_ipn_for_last_order_created.js new file mode 100644 index 0000000..b52db24 --- /dev/null +++ b/scripts/send_ipn_for_last_order_created.js @@ -0,0 +1,95 @@ +var mysql = require('mysql'); +var format = require('string-template'); +var query = 'select * from bitpay_invoices where quote_id=(select MAX(quote_id) from bitpay_invoices)'; +var spawn = require('child_process').spawn; +var config = require('./config.json'); +var data = {}; +var connection = mysql.createConnection(config.mysql); + +function postIpn() { + connection.connect(); + connection.query(query, processRows); +} + +function send(curl_args) { + var curl = spawn('curl', curl_args); + var stderr; + curl.stdout.on('data', function(data) { + console.log(data.toString()); + }); + curl.stderr.on('data', function(data) { + stderr = data; + }); + curl.on('close', function(code) { + if (code === 0) { + console.log('curl exited successfully'); + } else { + console.log('curl exited with an error: ' + stderr); + } + }); +} + +function processRows(err, rows, fields) { + if (err) { + throw err; + } + var curl_args = [ + '-X', 'POST', '-H', + 'Content-Type: application/json', + '-H', "Content-Length: {length}", + '-H', 'Connection: close', + '-H', 'Accept: application/json', + '-d', '', + config.host ]; + var convertedKeys = convertNames(fields); + var timeRegExp = new RegExp(/.*Time$/); + for (var i=0; iassertSame( + 'payment_bitpay.log', + Mage::helper('bitpay')->getLogFile() + ); + } + + public function testDebugData() + { + Mage::helper('bitpay')->debugData('Testing'); + } + + public function testIsDebugMode() + { + Mage::app()->getStore()->setConfig('payment/bitpay/debug', null); + $this->assertFalse(Mage::helper('bitpay')->isDebug()); + + Mage::app()->getStore()->setConfig('payment/bitpay/debug', false); + $this->assertFalse(Mage::helper('bitpay')->isDebug()); + + Mage::app()->getStore()->setConfig('payment/bitpay/debug', true); + $this->assertTrue(Mage::helper('bitpay')->isDebug()); + } + + public function testHasTransactionSpeedFalse() + { + Mage::app()->getStore()->setConfig('payment/bitpay/speed', null); + + $this->assertFalse(Mage::helper('bitpay')->hasTransactionSpeed()); + } + + public function testHasTransactionSpeedTrue() + { + Mage::app()->getStore()->setConfig('payment/bitpay/speed', 'low'); + + $this->assertTrue(Mage::helper('bitpay')->hasTransactionSpeed()); + } + + /** + * Location where BitPay IPNs will go + */ + public function testGetNotificationUrl() + { + $this->assertSame( + 'http://www.localhost.com/bitpay/ipn/', + Mage::helper('bitpay')->getNotificationUrl() + ); + } + + public function testGetRedirectUrl() + { + $this->assertSame( + 'http://www.localhost.com/checkout/onepage/success/', + Mage::helper('bitpay')->getRedirectUrl() + ); + } + + public function testRegisterAutoloader() + { + Mage::helper('bitpay')->registerAutoloader(); + } + + public function testGenerateAndSaveKeys() + { + Mage::helper('bitpay')->generateAndSaveKeys(); + } + + public function testGetSinKey() + { + Mage::helper('bitpay')->getSinKey(); + } + + private function createInvalidIpn() + { + $ipn = new Bitpay_Core_Model_Ipn(); + $ipn->setData( + array( + 'quote_id' => '', + 'order_id' => '', + 'invoice_id' => '', + 'url' => '', + 'pos_data' => '', + 'status' => '', + 'btc_price' => '', + 'price' => '', + 'currency' => '', + 'invoice_time' => '', + 'expiration_time' => '', + 'current_time' => '', + ) + ); + $ipn->save(); + $ipn->load($ipn->getId()); + + return $ipn; + } + + private function createExpiredIpn() + { + $order = $this->createOrder(); + $ipn = new Bitpay_Core_Model_Ipn(); + $ipn->setData( + array( + 'quote_id' => '', + 'order_id' => $order->getIncrementId(), + 'invoice_id' => '', + 'url' => '', + 'pos_data' => '', + 'status' => '', + 'btc_price' => '', + 'price' => '', + 'currency' => '', + 'invoice_time' => '', + 'expiration_time' => '', + 'current_time' => '', + ) + ); + $ipn->save(); + $ipn->load($ipn->getId()); + + return $ipn; + } + + private function createOrder() + { + $product = $this->createProduct(); + $quote = $this->createQuote(); + $quote->addProduct( + $product, + new Varien_Object( + array( + 'qty' => 1, + ) + ) + ); + $address = array( + 'firstname' => self::$faker->firstName, + 'lastname' => self::$faker->lastName, + 'company' => self::$faker->company, + 'email' => self::$faker->email, + 'city' => self::$faker->city, + 'region_id' => '', + 'region' => 'State/Province', + 'postcode' => self::$faker->postcode, + 'telephone' => self::$faker->phoneNumber, + 'country_id' => self::$faker->state, + 'customer_password' => '', + 'confirm_password' => '', + 'save_in_address_book' => 0, + 'use_for_shipping' => 1, + 'street' => array( + self::$faker->streetAddress, + ), + ); + + $quote->getBillingAddress() + ->addData($address); + + $quote->getShippingAddress() + ->addData($address) + ->setShippingMethod('flatrate_flatrate') + ->setPaymentMethod('checkmo') + ->setCollectShippingRates(true) + ->collectTotals(); + + $quote + ->setCheckoutMethod('guest') + ->setCustomerId(null) + ->setCustomerEmail($address['email']) + ->setCustomerIsGuest(true) + ->setCustomerGroupId(Mage_Customer_Model_Group::NOT_LOGGED_IN_ID); + + $quote->getPayment() + ->importData(array('method' => 'checkmo')); + + $quote->save(); + + $service = Mage::getModel('sales/service_quote', $quote); + $service->submitAll(); + $order = $service->getOrder(); + + $order->save(); + $order->load($order->getId()); + + return $order; + } + + private function createProduct() + { + $product = Mage::getModel('catalog/product'); + + $product->addData( + array( + 'attribute_set_id' => 1, + 'website_ids' => array(1), + 'categories' => array(), + 'type_id' => Mage_Catalog_Model_Product_Type::TYPE_SIMPLE, + 'sku' => self::$faker->randomNumber, + 'name' => self::$faker->name, + 'weight' => self::$faker->randomDigit, + 'status' => Mage_Catalog_Model_Product_Status::STATUS_ENABLED, + 'tax_class_id' => 2, + 'visibility' => Mage_Catalog_Model_Product_Visibility::VISIBILITY_BOTH, + 'price' => self::$faker->randomFloat(2), + 'description' => self::$faker->paragraphs, + 'short_description' => self::$faker->sentence, + 'stock_data' => array( + 'is_in_stock' => 1, + 'qty' => 100, + ), + ) + ); + + $product->save(); + $product->load($product->getId()); + + return $product; + } + + private function createQuote() + { + return Mage::getModel('sales/quote') + ->setStoreId(Mage::app()->getStore('default')->getId()); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..a2a0e1a --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,18 @@ +