From 0934c77008674e9d5c8d50e966ea719595ed604e Mon Sep 17 00:00:00 2001 From: Kevin McInturff Date: Wed, 24 Apr 2013 09:50:31 -0400 Subject: [PATCH 001/315] Added note to readme regarding SSL requirement --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d3a0e35..3428af6 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Copy these files into your Magento directory. Configuration ------------- +NOTE: SSL is required for use of the BitPay plugin for Magento 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. From 2239c49f4e88d4bdef8b9e20c918590bf5ff8115 Mon Sep 17 00:00:00 2001 From: Kevin McInturff Date: Wed, 24 Apr 2013 09:51:06 -0400 Subject: [PATCH 002/315] Added note to readme regarding SSL requirement --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3428af6..a2d13f3 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Copy these files into your Magento directory. Configuration ------------- NOTE: SSL is required for use of the BitPay plugin for Magento + 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. From 3003805bfb02c66a185eeb9e3571a899771572b6 Mon Sep 17 00:00:00 2001 From: Kevin McInturff Date: Wed, 24 Apr 2013 10:10:56 -0400 Subject: [PATCH 003/315] Added note to readme regarding SSL requirement --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a2d13f3..36f8b01 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ NOTE: SSL is required for use of the BitPay plugin for Magento 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. From 4c595441d39472abeb9079f0e5878154c27cdf91 Mon Sep 17 00:00:00 2001 From: Kevin McInturff Date: Wed, 24 Apr 2013 11:12:48 -0300 Subject: [PATCH 004/315] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 36f8b01..eaebd42 100644 --- a/README.md +++ b/README.md @@ -25,10 +25,10 @@ NOTE: SSL is required for use of the BitPay plugin for Magento 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. + a. Enter your API key + a. 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. + a. 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. + a. (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 ----- From 65b0e0e5d0deb230ff968b0448c25ea18d972ff7 Mon Sep 17 00:00:00 2001 From: Kevin McInturff Date: Wed, 24 Apr 2013 11:13:06 -0300 Subject: [PATCH 005/315] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index eaebd42..36f8b01 100644 --- a/README.md +++ b/README.md @@ -25,10 +25,10 @@ NOTE: SSL is required for use of the BitPay plugin for Magento 2. In Admin panel under "System > Configuration > Sales > Payment Methods > Bitcoins": a. Verify that the module is enabled. - a. Enter your API key - a. 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. - a. 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. - a. (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. + 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 ----- From 1b7a4c995d1c224e4a5991508170a5e597f31827 Mon Sep 17 00:00:00 2001 From: Kevin McInturff Date: Wed, 24 Apr 2013 12:08:09 -0300 Subject: [PATCH 006/315] Update README.md --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 36f8b01..963b751 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,13 @@ NOTE: SSL is required for use of the BitPay plugin for Magento 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. +
    +
  1. Verify that the module is enabled. +
  2. Enter your API key. +
  3. 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. +
  4. 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. +
  5. (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 ----- From 134679026a1d9379ffa84a1654d2927db98fcfad Mon Sep 17 00:00:00 2001 From: evan Date: Wed, 24 Jul 2013 14:58:45 -0400 Subject: [PATCH 007/315] added iframe attributes to be compatible with new invoice --- .../frontend/base/default/template/bitcoins/iframe.phtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/design/frontend/base/default/template/bitcoins/iframe.phtml b/app/design/frontend/base/default/template/bitcoins/iframe.phtml index 617f652..7a18f4b 100644 --- a/app/design/frontend/base/default/template/bitcoins/iframe.phtml +++ b/app/design/frontend/base/default/template/bitcoins/iframe.phtml @@ -9,7 +9,7 @@ switch($url){ case false: echo 'Error creating invoice. Please try again or try another payment solution.'; break; default: - echo ''; break; + echo ''; break; } $quoteId = $this->GetQuoteId(); ?> @@ -31,4 +31,4 @@ new PeriodicalExecuter(function() {new Ajax.Request("?quote=< }})}, 5); //]]> - \ No newline at end of file + From 5d81aea7783914b32f77434a3be90c2cf16fb076 Mon Sep 17 00:00:00 2001 From: evan Date: Thu, 25 Jul 2013 09:14:43 -0400 Subject: [PATCH 008/315] updated README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 963b751..45c8361 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ NOTE: SSL is required for use of the BitPay plugin for Magento
  1. Verify that the module is enabled.
  2. Enter your API key. -
  3. 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. +
  4. 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.
  5. 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.
  6. (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.
@@ -48,4 +48,4 @@ 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. + - Now gives the option to show an iframe on the checkout page instead of redirecting to bitpay.com. From 95994217a86fc22707c96f480960edbdfce7cf1c Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 12 Dec 2013 13:09:08 -0500 Subject: [PATCH 009/315] add *.swp to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1377554 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.swp From 0f4a101d505775ee5858ff8da6aa080891444fc5 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 12 Dec 2013 13:48:35 -0500 Subject: [PATCH 010/315] add config and support for alt domains/ports --- .gitignore | 1 + lib/bitpay/bp_config_default.php | 7 +++++++ lib/bitpay/bp_lib.php | 9 ++++++--- 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 lib/bitpay/bp_config_default.php diff --git a/.gitignore b/.gitignore index 1377554..7eb4c07 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.swp +lib/bitpay/bp_config.php diff --git a/lib/bitpay/bp_config_default.php b/lib/bitpay/bp_config_default.php new file mode 100644 index 0000000..0997dfe --- /dev/null +++ b/lib/bitpay/bp_config_default.php @@ -0,0 +1,7 @@ +0) + $bpconfig_hostAndPort.=":".$bpconfig_port; +?> diff --git a/lib/bitpay/bp_lib.php b/lib/bitpay/bp_lib.php index 3ead3fb..c614c71 100644 --- a/lib/bitpay/bp_lib.php +++ b/lib/bitpay/bp_lib.php @@ -1,5 +1,8 @@ \ No newline at end of file +?> From 10a9e7f8928603dba8a7cb4644808994c9f44599 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 12 Dec 2013 14:05:51 -0500 Subject: [PATCH 011/315] add config port to curl --- lib/bitpay/bp_config_default.php | 4 ++-- lib/bitpay/bp_lib.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/bitpay/bp_config_default.php b/lib/bitpay/bp_config_default.php index 0997dfe..5333b5b 100644 --- a/lib/bitpay/bp_config_default.php +++ b/lib/bitpay/bp_config_default.php @@ -1,7 +1,7 @@ 0) +if ($bpconfig_port!=443) $bpconfig_hostAndPort.=":".$bpconfig_port; ?> diff --git a/lib/bitpay/bp_lib.php b/lib/bitpay/bp_lib.php index c614c71..8600bca 100644 --- a/lib/bitpay/bp_lib.php +++ b/lib/bitpay/bp_lib.php @@ -24,7 +24,7 @@ function bpCurl($url, $apiKey, $post = false) { "Authorization: Basic $uname", ); - curl_setopt($curl, CURLOPT_PORT, 443); + curl_setopt($curl, CURLOPT_PORT, $bpconfig_port); curl_setopt($curl, CURLOPT_HTTPHEADER, $header); curl_setopt($curl, CURLOPT_TIMEOUT, 10); curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC ) ; From b3b145f0eaa05a1ac2ab213e7e50b558d611c90f Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 12 Dec 2013 14:19:12 -0500 Subject: [PATCH 012/315] move custom config to default config file --- lib/bitpay/bp_config_default.php | 4 ++++ lib/bitpay/bp_lib.php | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/bitpay/bp_config_default.php b/lib/bitpay/bp_config_default.php index 5333b5b..1c64d36 100644 --- a/lib/bitpay/bp_config_default.php +++ b/lib/bitpay/bp_config_default.php @@ -4,4 +4,8 @@ $bpconfig_hostAndPort=$bpconfig_host; if ($bpconfig_port!=443) $bpconfig_hostAndPort.=":".$bpconfig_port; + +//include custom config overrides if it exists +if (file_exists('bp_config.php')) + require_once 'bp_config.php'; ?> diff --git a/lib/bitpay/bp_lib.php b/lib/bitpay/bp_lib.php index 8600bca..d448c10 100644 --- a/lib/bitpay/bp_lib.php +++ b/lib/bitpay/bp_lib.php @@ -1,8 +1,6 @@ Date: Thu, 12 Dec 2013 15:49:02 -0500 Subject: [PATCH 013/315] add ssl config --- lib/bitpay/bp_config_default.php | 3 ++- lib/bitpay/bp_lib.php | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/bitpay/bp_config_default.php b/lib/bitpay/bp_config_default.php index 1c64d36..0a9f248 100644 --- a/lib/bitpay/bp_config_default.php +++ b/lib/bitpay/bp_config_default.php @@ -4,7 +4,8 @@ $bpconfig_hostAndPort=$bpconfig_host; if ($bpconfig_port!=443) $bpconfig_hostAndPort.=":".$bpconfig_port; - +$bpconfig_ssl_verifypeer=1; +$bpconfig_ssl_verifyhost=2; //include custom config overrides if it exists if (file_exists('bp_config.php')) require_once 'bp_config.php'; diff --git a/lib/bitpay/bp_lib.php b/lib/bitpay/bp_lib.php index d448c10..3dc1829 100644 --- a/lib/bitpay/bp_lib.php +++ b/lib/bitpay/bp_lib.php @@ -26,8 +26,8 @@ function bpCurl($url, $apiKey, $post = false) { curl_setopt($curl, CURLOPT_HTTPHEADER, $header); curl_setopt($curl, CURLOPT_TIMEOUT, 10); curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC ) ; - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 1); // verify certificate - curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); // check existence of CN and verify that it matches hostname + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $bpconfig_ssl_verifypeer); // verify certificate + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $bpconfig_ssl_verifyhost); // check existence of CN and verify that it matches hostname curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_FORBID_REUSE, 1); curl_setopt($curl, CURLOPT_FRESH_CONNECT, 1); From c6f13ab95dbbf6841e92170ea52ac5db9cb4074a Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 12 Dec 2013 19:48:23 -0500 Subject: [PATCH 014/315] fix config --- lib/bitpay/bp_config_default.php | 21 ++++++++++++--------- lib/bitpay/bp_lib.php | 20 ++++++++++---------- lib/bitpay/bp_options.php | 2 +- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/lib/bitpay/bp_config_default.php b/lib/bitpay/bp_config_default.php index 0a9f248..4b24c09 100644 --- a/lib/bitpay/bp_config_default.php +++ b/lib/bitpay/bp_config_default.php @@ -1,12 +1,15 @@ diff --git a/lib/bitpay/bp_lib.php b/lib/bitpay/bp_lib.php index 3dc1829..effdd31 100644 --- a/lib/bitpay/bp_lib.php +++ b/lib/bitpay/bp_lib.php @@ -4,7 +4,7 @@ require_once 'bp_options.php'; function bpCurl($url, $apiKey, $post = false) { - global $bpOptions; + global $bpOptions, $bpconfig; $curl = curl_init($url); $length = 0; @@ -22,12 +22,12 @@ function bpCurl($url, $apiKey, $post = false) { "Authorization: Basic $uname", ); - curl_setopt($curl, CURLOPT_PORT, $bpconfig_port); + curl_setopt($curl, CURLOPT_PORT, $bpconfig['port']); curl_setopt($curl, CURLOPT_HTTPHEADER, $header); curl_setopt($curl, CURLOPT_TIMEOUT, 10); curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC ) ; - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $bpconfig_ssl_verifypeer); // verify certificate - curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $bpconfig_ssl_verifyhost); // check existence of CN and verify that it matches hostname + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $bpconfig['ssl_verifypeer']); // verify certificate + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $bpconfig['ssl_verifyhost']); // check existence of CN and verify that it matches hostname curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_FORBID_REUSE, 1); curl_setopt($curl, CURLOPT_FRESH_CONNECT, 1); @@ -61,7 +61,7 @@ function bpCurl($url, $apiKey, $post = false) { // 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; + global $bpOptions, $bpconfig; $options = array_merge($bpOptions, $options); // $options override any options found in bp_options.php @@ -81,14 +81,14 @@ function bpCreateInvoice($orderId, $price, $posData, $options = array()) { $post[$o] = $options[$o]; $post = json_encode($post); - $response = bpCurl('https://'.$bpconfig_hostAndPort.'/api/invoice/', $options['apiKey'], $post); + $response = bpCurl('https://'.$bpconfig['hostAndPort'].'/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; + global $bpOptions, $bpconfig; if (!$apiKey) $apiKey = $bpOptions['apiKey']; @@ -114,11 +114,11 @@ function bpVerifyNotification($apiKey = false) { // $options can include ('apiKey') function bpGetInvoice($invoiceId, $apiKey=false) { - global $bpOptions; + global $bpOptions, $bpconfig; if (!$apiKey) $apiKey = $bpOptions['apiKey']; - $response = bpCurl('https://'.$bpconfig_hostAndPort.'/api/invoice/'.$invoiceId, $apiKey); + $response = bpCurl('https://'.$bpconfig['hostAndPort'].'/api/invoice/'.$invoiceId, $apiKey); if (is_string($response)) return $response; // error $response['posData'] = json_decode($response['posData'], true); @@ -128,4 +128,4 @@ function bpGetInvoice($invoiceId, $apiKey=false) { } -?> +?> diff --git a/lib/bitpay/bp_options.php b/lib/bitpay/bp_options.php index d8d3075..11cf1ad 100644 --- a/lib/bitpay/bp_options.php +++ b/lib/bitpay/bp_options.php @@ -27,4 +27,4 @@ $bpOptions['transactionSpeed'] = 'low'; -?> \ No newline at end of file +?> From 0d3851f65d0c7fb015e0ae67267f377d80cb001b Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Mon, 16 Dec 2013 18:49:47 -0500 Subject: [PATCH 015/315] redirect to success page --- app/code/community/Bitpay/Bitcoins/Block/Iframe.php | 4 ++-- app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/Block/Iframe.php b/app/code/community/Bitpay/Bitcoins/Block/Iframe.php index 3611ef5..8110fd2 100644 --- a/app/code/community/Bitpay/Bitcoins/Block/Iframe.php +++ b/app/code/community/Bitpay/Bitcoins/Block/Iframe.php @@ -44,7 +44,7 @@ public function GetIframeUrl() 'currency' => $quote->getQuoteCurrencyCode(), 'fullNotifications' => 'true', 'notificationURL' => Mage::getUrl('bitpay_callback'), - 'redirectURL' => Mage::getUrl('customer/account'), + 'redirectURL' => Mage::getUrl('checkout/onepage/success'), 'transactionSpeed' => $speed, 'apiKey' => $apiKey, ); @@ -73,4 +73,4 @@ public function GetIframeUrl() return $invoice['url'].'&view=iframe'; } -} \ 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 index 53d6a9d..d17f484 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php +++ b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php @@ -187,7 +187,7 @@ function CreateInvoiceAndRedirect($payment, $amount) 'buyerName' => $order->getCustomerFirstname().' '.$order->getCustomerLastname(), 'fullNotifications' => 'true', 'notificationURL' => Mage::getUrl('bitpay_callback'), - 'redirectURL' => Mage::getUrl('customer/account'), + 'redirectURL' => Mage::getUrl('checkout/onepage/success'), 'transactionSpeed' => $speed, 'apiKey' => $apiKey, ); @@ -250,4 +250,4 @@ public function getQuoteHash($quoteId) } } -?> \ No newline at end of file +?> From b2639b24faa732f2eb7e1085d8ddc4bd3a226165 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Tue, 28 Jan 2014 16:21:15 -0500 Subject: [PATCH 016/315] Corrected company name. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 45c8361..94e5494 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -©2011 BIT-PAY LLC. +©2011-2014 BITPAY, INC. + 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. From 935578114abe5b36fde84440706077bd2759f5f7 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Tue, 28 Jan 2014 16:23:07 -0500 Subject: [PATCH 017/315] Corrected company name, license & formatting --- lib/bitpay/bp_config_default.php | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/bitpay/bp_config_default.php b/lib/bitpay/bp_config_default.php index 4b24c09..36f9941 100644 --- a/lib/bitpay/bp_config_default.php +++ b/lib/bitpay/bp_config_default.php @@ -1,15 +1,40 @@ From 3ce0b202d10fa55959752fff57ac37a7b580b443 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Tue, 28 Jan 2014 16:29:34 -0500 Subject: [PATCH 018/315] Corrected company name, license & formatting --- lib/bitpay/bp_lib.php | 219 ++++++++++++++++++++++++------------------ 1 file changed, 124 insertions(+), 95 deletions(-) diff --git a/lib/bitpay/bp_lib.php b/lib/bitpay/bp_lib.php index effdd31..a46e03f 100644 --- a/lib/bitpay/bp_lib.php +++ b/lib/bitpay/bp_lib.php @@ -1,49 +1,70 @@ curl_error($curl)); - } else { - $response = json_decode($responseString, true); - if (!$response) - $response = array('error' => 'invalid json: '.$responseString); - } - curl_close($curl); - return $response; + global $bpOptions, $bpconfig; + + $curl = curl_init($url); + $length = 0; + + if ($post) { + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $post); + $length = strlen($post); + } + + $uname = base64_encode($apiKey); + $header = array( + 'Content-Type: application/json', + 'Content-Length: ' . $length, + 'Authorization: Basic ' . $uname, + ); + + curl_setopt($curl, CURLOPT_PORT, $bpconfig['port']); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header); + curl_setopt($curl, CURLOPT_TIMEOUT, 10); + curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC ) ; + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $bpconfig['ssl_verifypeer']); // verify certificate + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $bpconfig['ssl_verifyhost']); // check existence of CN and verify that it matches hostname + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FORBID_REUSE, 1); + curl_setopt($curl, CURLOPT_FRESH_CONNECT, 1); + + $responseString = curl_exec($curl); + + if($responseString == false) { + $response = array('error' => 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. // @@ -61,71 +82,79 @@ function bpCurl($url, $apiKey, $post = false) { // 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, $bpconfig; - - $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://'.$bpconfig['hostAndPort'].'/api/invoice/', $options['apiKey'], $post); - - return $response; + global $bpOptions, $bpconfig; + + $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://'.$bpconfig['hostAndPort'].'/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, $bpconfig; - 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; + global $bpOptions, $bpconfig; + + 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, $bpconfig; - if (!$apiKey) - $apiKey = $bpOptions['apiKey']; + global $bpOptions, $bpconfig; - $response = bpCurl('https://'.$bpconfig['hostAndPort'].'/api/invoice/'.$invoiceId, $apiKey); - if (is_string($response)) - return $response; // error - $response['posData'] = json_decode($response['posData'], true); - $response['posData'] = $response['posData']['posData']; + if (!$apiKey) + $apiKey = $bpOptions['apiKey']; - return $response; -} + $response = bpCurl('https://'.$bpconfig['hostAndPort'].'/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; +} -?> +?> From 62905e2f43cbd7b66c0e3aeddb1750ab32e9c70d Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Tue, 28 Jan 2014 16:30:10 -0500 Subject: [PATCH 019/315] Corrected company name, license & formatting --- lib/bitpay/bp_options.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/bitpay/bp_options.php b/lib/bitpay/bp_options.php index 11cf1ad..be28f91 100644 --- a/lib/bitpay/bp_options.php +++ b/lib/bitpay/bp_options.php @@ -1,5 +1,24 @@ Date: Tue, 28 Jan 2014 16:36:16 -0500 Subject: [PATCH 020/315] Corrected company name, license & formatting --- .../Bitpay/Bitcoins/Block/Iframe.php | 162 ++++++++++-------- 1 file changed, 90 insertions(+), 72 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/Block/Iframe.php b/app/code/community/Bitpay/Bitcoins/Block/Iframe.php index 8110fd2..4630a62 100644 --- a/app/code/community/Bitpay/Bitcoins/Block/Iframe.php +++ b/app/code/community/Bitpay/Bitcoins/Block/Iframe.php @@ -1,76 +1,94 @@ setTemplate('bitcoins/iframe.phtml'); - parent::_construct(); +/** + * ©2011,2012,2013,2014 BITPAY, INC. + * + * 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 plugin using the bitpay.com service. + * + */ + +class Bitpay_Bitcoins_Block_Iframe extends Mage_Checkout_Block_Onepage_Payment { + protected function _construct() { + $this->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('checkout/onepage/success'), + '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; } - - 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('checkout/onepage/success'), - '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'; - } + + return $invoice['url'].'&view=iframe'; + } + } From 7071a2c9785cfdcbedfa1b3c9b44336a0957b063 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Tue, 28 Jan 2014 16:44:45 -0500 Subject: [PATCH 021/315] Corrected company name, license & formatting --- .../community/Bitpay/Bitcoins/Model/Ipn.php | 168 ++++++++++-------- 1 file changed, 90 insertions(+), 78 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/Model/Ipn.php b/app/code/community/Bitpay/Bitcoins/Model/Ipn.php index 5fc9578..5ad9776 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/Ipn.php +++ b/app/code/community/Bitpay/Bitcoins/Model/Ipn.php @@ -1,80 +1,92 @@ _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 +/** + * ©2011,2012,2013,2014 BITPAY, INC. + * + * 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 plugin using the bitpay.com service. + * + */ + +class Bitpay_Bitcoins_Model_Ipn extends Mage_Core_Model_Abstract { + + function _construct() { + $this->_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')); + } + +} + +?> From 843769a8d66672a84ebca952eaf20c53cbe052b1 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Tue, 28 Jan 2014 21:33:34 -0600 Subject: [PATCH 022/315] Added license info & corrected company name. --- .../Bitpay/Bitcoins/Model/Source/Speed.php | 56 ++++++++++++------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/Model/Source/Speed.php b/app/code/community/Bitpay/Bitcoins/Model/Source/Speed.php index 76b556b..145a1c6 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/Source/Speed.php +++ b/app/code/community/Bitpay/Bitcoins/Model/Source/Speed.php @@ -1,24 +1,40 @@ 'low', - 'label' => 'Low', - ), - array( - 'value' => 'medium', - 'label' => 'Medium', - ), - array( - 'value' => 'high', - 'label' => 'High', - )); - } +/** + * ©2011,2012,2013,2014 BITPAY, INC. + * + * 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 plugin using the bitpay.com service. + * + */ + +class Bitpay_Bitcoins_Model_Source_Speed { + public function toOptionArray() { + return array( + array( + 'value' => 'low', + 'label' => 'Low', + ), + array( + 'value' => 'medium', + 'label' => 'Medium', + ), + array( + 'value' => 'high', + 'label' => 'High', + )); + } } -?> \ No newline at end of file +?> From 8b01715d63fc7bfaa193d91ec629c29674dc1876 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Tue, 28 Jan 2014 21:34:56 -0600 Subject: [PATCH 023/315] Added license info & corrected company name. --- .../Bitpay/Bitcoins/Model/Resource/Ipn.php | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn.php b/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn.php index eaa3571..c192d53 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn.php +++ b/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn.php @@ -1,11 +1,28 @@ _init('Bitcoins/ipn', 'id'); - } +/** + * ©2011,2012,2013,2014 BITPAY, INC. + * + * 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 plugin using the bitpay.com service. + * + */ + +class Bitpay_Bitcoins_Model_Resource_Ipn extends Mage_Core_Model_Resource_Db_Abstract { + protected function _construct() { + $this->_init('Bitcoins/ipn', 'id'); + } } -?> \ No newline at end of file +?> From 389c4008a4aff2919451d6d6c353478d1f1a6638 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Tue, 28 Jan 2014 21:35:41 -0600 Subject: [PATCH 024/315] Added license info & corrected company name. --- .../Model/Resource/Ipn/Collection.php | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn/Collection.php b/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn/Collection.php index 53a2063..871918b 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn/Collection.php +++ b/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn/Collection.php @@ -1,11 +1,28 @@ _init('Bitcoins/ipn'); - } -} - -?> \ No newline at end of file +/** + * ©2011,2012,2013,2014 BITPAY, INC. + * + * 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 plugin using the bitpay.com service. + * + */ + +class Bitpay_Bitcoins_Model_Resource_Ipn_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract { + protected function _construct() { + $this->_init('Bitcoins/ipn'); + } +} + +?> From baba81fbc577a0a6ee70a3383ba2d094d07aa47e Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Tue, 28 Jan 2014 21:40:51 -0600 Subject: [PATCH 025/315] Added license, formatting & correct company name. --- .../Bitcoins/controllers/IndexController.php | 108 ++++++++++-------- 1 file changed, 62 insertions(+), 46 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php b/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php index 356d1b0..6c90e60 100644 --- a/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php +++ b/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php @@ -1,53 +1,69 @@ 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; - } - } - } + public function checkForPaymentAction() { + $params = $this->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; + } + + } + + } } From 393788aba970074ab5fde4a88bca530a16870261 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Wed, 29 Jan 2014 12:54:59 -0500 Subject: [PATCH 026/315] Corrected company name, license & formatting --- .../Bitpay/Bitcoins/Model/PaymentMethod.php | 478 +++++++++--------- 1 file changed, 240 insertions(+), 238 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php index d17f484..3b1b513 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php +++ b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php @@ -1,253 +1,255 @@ _canUseCheckout; + /** + * Can save credit card information for future processing? + */ + protected $_canSaveCc = false; + + //protected $_formBlockType = 'bitcoins/form'; + //protected $_infoBlockType = 'bitcoins/info'; + + function canUseForCurrency($currencyCode) { + $currencies = Mage::getStoreConfig('payment/Bitcoins/currencies'); + $currencies = array_map('trim', explode(',', $currencies)); + return array_search($currencyCode, $currencies) !== false; + } + + public function canUseCheckout() { + $secret = Mage::getStoreConfig('payment/Bitcoins/api_key'); + + if (!$secret or !strlen($secret)) { + Mage::log('Bitpay/Bitcoins: API key not entered', null, 'bitpay.log'); + return false; + } + + $speed = Mage::getStoreConfig('payment/Bitcoins/speed'); + + if (!$speed or !strlen($speed)) { + Mage::log('Bitpay/Bitcoins: Transaction Speed invalid', null, 'bitpay.log'); + return false; + } + + return $this->_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('checkout/onepage/success'), + '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']); } - 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('checkout/onepage/success'), - '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; - } - + 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; + } + } -?> +?> From e5022eb14e68c3466e7f799adb7ab02fee0d1403 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Wed, 5 Feb 2014 13:57:55 -0500 Subject: [PATCH 027/315] Added support for expired/invalid statuses --- .../Bitpay/Bitcoins/controllers/IndexController.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php b/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php index 6c90e60..b270a1f 100644 --- a/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php +++ b/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php @@ -60,6 +60,11 @@ public function indexAction() { $method = Mage::getModel('Bitcoins/paymentMethod'); $method->MarkOrderPaid($order); break; + case 'expired': + case 'invalid': + $method = Mage::getModel('Bitcoins/paymentMethod'); + $method->MarkOrderCancelled($order); + break; } } From b567932282a9aedf708d58fe0e590dc9d7d5510e Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Wed, 5 Feb 2014 13:58:59 -0500 Subject: [PATCH 028/315] Added support for expired/invalid statuses --- app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php index 3b1b513..b6dd510 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php +++ b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php @@ -158,6 +158,10 @@ function MarkOrderPaid($order) { } } + function MarkOrderCancelled($order) { + $order->setState(Mage_Sales_Model_Order::STATE_CANCELLED, true)->save(); +} + // given Mage_Core_Model_Abstract, return api-friendly address function ExtractAddress($address) { $options = array(); From c6d4fe22da85a60f478a6de948861355131beb28 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Wed, 5 Feb 2014 14:30:11 -0500 Subject: [PATCH 029/315] Removed nonexistent 'expired' status --- .../community/Bitpay/Bitcoins/controllers/IndexController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php b/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php index b270a1f..da32957 100644 --- a/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php +++ b/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php @@ -60,7 +60,6 @@ public function indexAction() { $method = Mage::getModel('Bitcoins/paymentMethod'); $method->MarkOrderPaid($order); break; - case 'expired': case 'invalid': $method = Mage::getModel('Bitcoins/paymentMethod'); $method->MarkOrderCancelled($order); From 0b2dde4e6991b767df9a0e594921d184b3741082 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Mon, 10 Feb 2014 10:57:42 -0500 Subject: [PATCH 030/315] Added error for order not invoiced, state_complete --- app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php index b6dd510..fd4155b 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php +++ b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php @@ -155,6 +155,9 @@ function MarkOrderPaid($order) { ->addObject($invoice->getOrder()); $transactionSave->save(); + $order->addStatusToHistory(Mage_Sales_Model_Order::STATE_COMPLETE); + } else { + Mage::log('Count of InvoiceCollection was zero! Order not invoiced.', null, 'bitpay.log'); } } From fb2a5b06e5e03a779b694cf64add59880b5956f4 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Mon, 10 Feb 2014 11:04:24 -0500 Subject: [PATCH 031/315] Installable via composer Credit goes to GitHub user martindines. --- composer.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 composer.json diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..4686e66 --- /dev/null +++ b/composer.json @@ -0,0 +1,9 @@ +{ + "name": "bitpay/bitcoins", + "description": "Bitcoin payment module using the bitpay.com service", + "minimum-stability": "dev", + "type": "magento-module", + "require": { + "magento-hackathon/magento-composer-installer": "dev-master" + } +} From e547cbd6b244b3ced5a37b098fafe51bff7e57be Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Mon, 10 Feb 2014 11:05:18 -0500 Subject: [PATCH 032/315] Installable via composer Credit goes to GitHub user martindines --- modman | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 modman diff --git a/modman b/modman new file mode 100644 index 0000000..baa95dd --- /dev/null +++ b/modman @@ -0,0 +1,5 @@ +app/code/community/Bitpay/Bitcoins app/code/community/Bitpay/Bitcoins +app/design/frontend/base/default/layout/bitcoins.xml app/design/frontend/base/default/layout/bitcoins.xml +app/design/frontend/base/default/template/bitcoins app/design/frontend/base/default/template/bitcoins +app/etc/modules/Bitpay_Bitcoins.xml app/etc/modules/Bitpay_Bitcoins.xml +lib/bitpay lib/bitpay From f385778e19799cf1947f1404e8adb9d032b83419 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Mon, 10 Feb 2014 11:35:41 -0500 Subject: [PATCH 033/315] Send new order email after invoice paid --- app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php index fd4155b..1c7ec81 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php +++ b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php @@ -156,6 +156,11 @@ function MarkOrderPaid($order) { $transactionSave->save(); $order->addStatusToHistory(Mage_Sales_Model_Order::STATE_COMPLETE); + try { + $order->sendNewOrderEmail(); + } catch (Exception $e) { + Mage::logException($e); + } } else { Mage::log('Count of InvoiceCollection was zero! Order not invoiced.', null, 'bitpay.log'); } From ac03c7fa440c7d6f7603bd1d5a4ec206d9c0d8ae Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Thu, 13 Mar 2014 11:25:58 -0400 Subject: [PATCH 034/315] Updated currency list --- app/code/community/Bitpay/Bitcoins/etc/config.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/etc/config.xml b/app/code/community/Bitpay/Bitcoins/etc/config.xml index 0a83912..fc50827 100644 --- a/app/code/community/Bitpay/Bitcoins/etc/config.xml +++ b/app/code/community/Bitpay/Bitcoins/etc/config.xml @@ -79,9 +79,9 @@ 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 + BTC, USD, EUR, GBP, JPY, CAD, AUD, CNY, CHF, SEK, NZD, KRW, AED, AFN, ALL, AMD, ANG, AOA, ARS, AWG, AZN, BAM, BBD, BDT, BGN, BHD, BIF, BMD, BND, BOB, BRL, BSD, BTN, BWP, BYR, BZD, CDF, CLF, CLP, COP, CRC, CVE, CZK, DJF, DKK, DOP, DZD, EEK, EGP, ETB, FJD, FKP, GEL, GHS, GIP, GMD, GNF, GTQ, GYD, HKD, HNL, HRK, HTG, HUF, IDR, ILS, INR, IQD, ISK, JEP, JMD, JOD, KES, KGS, KHR, KMF, KWD, KYD, KZT, LAK, LBP, LKR, LRD, LSL, LTL, LVL, LYD, MAD, MDL, MGA, MKD, MMK, MNT, MOP, MRO, MUR, MVR, MWK, MXN, MYR, MZN, NAD, NGN, NIO, NOK, NPR, OMR, PAB, PEN, PGK, PHP, PKR, PLN, PYG, QAR, RON, RSD, RUB, RWF, SAR, SBD, SCR, SDG, SGD, SHP, SLL, SOS, SRD, STD, SVC, SYP, SZL, THB, TJS, TMT, TND, TOP, TRY, TTD, TWD, TZS, UAH, UGX, UYU, UZS, VEF, VND, VUV, WST, XAF, XAG, XAU, XCD, XOF, XPF, YER, ZAR, ZMW, ZWL authorize - \ No newline at end of file + From 8dacf9939c435f5dbcebdf430a04046c1053b5a3 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Thu, 13 Mar 2014 16:14:25 -0400 Subject: [PATCH 035/315] Fix for order not marked complete issue --- .../Bitpay/Bitcoins/Model/PaymentMethod.php | 75 +++++++++++++------ 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php index 1c7ec81..eeadcd7 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php +++ b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php @@ -53,7 +53,7 @@ class Bitpay_Bitcoins_Model_PaymentMethod extends Mage_Payment_Model_Method_Abst /** * Can capture funds online? */ - protected $_canCapture = false; + protected $_canCapture = true; /** * Can capture partial amounts online? @@ -93,7 +93,7 @@ class Bitpay_Bitcoins_Model_PaymentMethod extends Mage_Payment_Model_Method_Abst //protected $_formBlockType = 'bitcoins/form'; //protected $_infoBlockType = 'bitcoins/info'; - function canUseForCurrency($currencyCode) { + function canUseForCurrency($currencyCode) { $currencies = Mage::getStoreConfig('payment/Bitcoins/currencies'); $currencies = array_map('trim', explode(',', $currencies)); return array_search($currencyCode, $currencies) !== false; @@ -144,32 +144,61 @@ 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(); + try { + $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 = Mage::getModel('core/resource_transaction') + ->addObject($invoice) + ->addObject($invoice->getOrder()); - $transactionSave->save(); - $order->addStatusToHistory(Mage_Sales_Model_Order::STATE_COMPLETE); - try { - $order->sendNewOrderEmail(); + $transactionSave->save(); } catch (Exception $e) { Mage::logException($e); } - } else { - Mage::log('Count of InvoiceCollection was zero! Order not invoiced.', null, 'bitpay.log'); } } + + function MarkOrderComplete($order) { + if ($order->hasInvoices()) { + foreach ($order->getInvoiceCollection() as $_eachInvoice) { + try { + $_eachInvoice->setRequestedCaptureCase(Mage_Sales_Model_Order_Invoice::CAPTURE_ONLINE); + $_eachInvoice->capture()->save(); + } catch (Exception $e) { + Mage::logException($e); + } + } + } + + $shipment = $order->prepareShipment(); + if($shipment) { + $shipment->register(); + $order->setIsInProcess(true); + + $transaction_save = Mage::getModel('core/resource_transaction') + ->addObject($shipment) + ->addObject($shipment->getOrder()) + ->save(); + } + + try { + $order->setState('Complete', 'complete', 'Completed by BitPay payments.', false); + $order->save(); + } catch (Exception $e) { + Mage::logException($e); + } + + + } function MarkOrderCancelled($order) { $order->setState(Mage_Sales_Model_Order::STATE_CANCELLED, true)->save(); -} - + } + // given Mage_Core_Model_Abstract, return api-friendly address function ExtractAddress($address) { $options = array(); @@ -178,7 +207,7 @@ function ExtractAddress($address) { if ($address->getCompany()) $options['buyerName'] = $options['buyerName'].' c/o '.$address->getCompany(); - $options['buyerAddress1'] = $address->getStreet1(); + $options['buyerAddress1'] = $address->getStreet1(); $options['buyerAddress2'] = $address->getStreet2(); $options['buyerAddress3'] = $address->getStreet3(); $options['buyerAddress4'] = $address->getStreet4(); @@ -193,11 +222,11 @@ function ExtractAddress($address) { foreach(array('buyerName', 'buyerAddress1', 'buyerAddress2', 'buyerAddress3', 'buyerAddress4', 'buyerCity', 'buyerState', 'buyerZip', 'buyerCountry', 'buyerEmail', 'buyerPhone') as $f) $options[$f] = substr($options[$f], 0, 100); - return $options; + return $options; } function CreateInvoiceAndRedirect($payment, $amount) { - include Mage::getBaseDir('lib').'/bitpay/bp_lib.php'; + include Mage::getBaseDir('lib').'/bitpay/bp_lib.php'; $apiKey = Mage::getStoreConfig('payment/Bitcoins/api_key'); $speed = Mage::getStoreConfig('payment/Bitcoins/speed'); @@ -206,7 +235,7 @@ function CreateInvoiceAndRedirect($payment, $amount) { $options = array( 'currency' => $order->getBaseCurrencyCode(), - 'buyerName' => $order->getCustomerFirstname().' '.$order->getCustomerLastname(), + 'buyerName' => $order->getCustomerFirstname().' '.$order->getCustomerLastname(), 'fullNotifications' => 'true', 'notificationURL' => Mage::getUrl('bitpay_callback'), 'redirectURL' => Mage::getUrl('checkout/onepage/success'), @@ -214,7 +243,7 @@ function CreateInvoiceAndRedirect($payment, $amount) { 'apiKey' => $apiKey, ); - $options += $this->ExtractAddress($order->getShippingAddress()); + $options += $this->ExtractAddress($order->getShippingAddress()); $invoice = bpCreateInvoice($orderId, $amount, array('orderId' => $orderId), $options); $payment->setIsTransactionPending(true); // status will be PAYMENT_REVIEW instead of PROCESSING From 6692a731074f3a29368b3f714b5a1da12404c803 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Thu, 13 Mar 2014 16:16:09 -0400 Subject: [PATCH 036/315] Fix for order not marked complete issue --- .../Bitpay/Bitcoins/controllers/IndexController.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php b/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php index da32957..dc27a48 100644 --- a/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php +++ b/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php @@ -31,7 +31,7 @@ public function checkForPaymentAction() { } // bitpay's IPN lands here - public function indexAction() { + 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'); @@ -55,10 +55,14 @@ public function indexAction() { // update the order if it exists already if ($order->getId()) switch($invoice['status']) { + case 'paid': + $method = Mage::getModel('Bitcoins/paymentMethod'); + $method->MarkOrderPaid($order); + break; case 'confirmed': case 'complete': $method = Mage::getModel('Bitcoins/paymentMethod'); - $method->MarkOrderPaid($order); + $method->MarkOrderComplete($order); break; case 'invalid': $method = Mage::getModel('Bitcoins/paymentMethod'); From 0420a112995a913d9c7e6383e0eec021eb38ab52 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Mon, 17 Mar 2014 11:32:05 -0400 Subject: [PATCH 037/315] Fixed shipment order locking issue --- .../Bitpay/Bitcoins/Model/PaymentMethod.php | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php index eeadcd7..87dceec 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php +++ b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php @@ -174,19 +174,20 @@ function MarkOrderComplete($order) { } } - $shipment = $order->prepareShipment(); - if($shipment) { - $shipment->register(); - $order->setIsInProcess(true); + //$shipment = $order->prepareShipment(); + //if($shipment) { + // $shipment->register(); + // $order->setIsInProcess(true); - $transaction_save = Mage::getModel('core/resource_transaction') - ->addObject($shipment) - ->addObject($shipment->getOrder()) - ->save(); - } + // $transaction_save = Mage::getModel('core/resource_transaction') + // ->addObject($shipment) + // ->addObject($shipment->getOrder()) + // ->save(); + //} try { - $order->setState('Complete', 'complete', 'Completed by BitPay payments.', false); + //$order->setState('Complete', 'complete', 'Completed by BitPay payments.', true); + $order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, 'processing', 'BitPay has confirmed the payment.', true); $order->save(); } catch (Exception $e) { Mage::logException($e); From 5e498edf43cf0cf65ac7230679a10d81c5014a87 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Tue, 18 Mar 2014 11:10:45 -0400 Subject: [PATCH 038/315] Add call to send email & bool option for shipment This change explicitly calls sendNewOrderEmail() to notify customers when their payment is completed. Also, added new boolean parameter to give the merchant the option to turn on programmatic shipment creation instead of just commenting this out completely. --- .../Bitpay/Bitcoins/Model/PaymentMethod.php | 54 +++++++++++++------ 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php index 87dceec..fcf7a1e 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php +++ b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php @@ -88,8 +88,13 @@ class Bitpay_Bitcoins_Model_PaymentMethod extends Mage_Payment_Model_Method_Abst /** * Can save credit card information for future processing? */ - protected $_canSaveCc = false; - + protected $_canSaveCc = false; + + /** + * BitPay - create shipment automatically after completing order? + */ + protected $_bpCreateShipment = false; + //protected $_formBlockType = 'bitcoins/form'; //protected $_infoBlockType = 'bitcoins/info'; @@ -174,30 +179,47 @@ function MarkOrderComplete($order) { } } - //$shipment = $order->prepareShipment(); - //if($shipment) { - // $shipment->register(); - // $order->setIsInProcess(true); - - // $transaction_save = Mage::getModel('core/resource_transaction') - // ->addObject($shipment) - // ->addObject($shipment->getOrder()) - // ->save(); - //} + // If the $_bpCreateShipment option is set to true above, this code will + // programmatically create a shipment for you. By design, this will mark + // the entire order as 'complete'. + if(isset($_bpCreateShipment) && $_bpCreateShipment == true) { + try { + $shipment = $order->prepareShipment(); + if($shipment) { + $shipment->register(); + $order->setIsInProcess(true); + $transaction_save = Mage::getModel('core/resource_transaction') + ->addObject($shipment) + ->addObject($shipment->getOrder()) + ->save(); + } + } catch (Exception $e) { + Mage::log('Error creating shipment', null, 'bitpay.log'); + Mage::logException($e); + } + } try { - //$order->setState('Complete', 'complete', 'Completed by BitPay payments.', true); - $order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, 'processing', 'BitPay has confirmed the payment.', true); + if(isset($_bpCreateShipment) && $_bpCreateShipment == true) { + $order->setState('Complete', 'complete', 'Completed by BitPay payments.', true); + } else { + $order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, 'processing', 'BitPay has confirmed the payment.', false); + $order->sendNewOrderEmail(); + } $order->save(); } catch (Exception $e) { Mage::logException($e); } - } function MarkOrderCancelled($order) { - $order->setState(Mage_Sales_Model_Order::STATE_CANCELLED, true)->save(); + try { + $order->setState(Mage_Sales_Model_Order::STATE_CANCELLED, true)->save(); + } catch (Exception $e) { + Mage::log('Could not cancel order', null, 'bitpay.log'); + Mage::logException($e); + } } // given Mage_Core_Model_Abstract, return api-friendly address From ec453baf1af53014e420f672353f0716a9a6639a Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Fri, 21 Mar 2014 11:41:03 -0400 Subject: [PATCH 039/315] Fix for multiple new order emails bug --- app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php index fcf7a1e..b4bf383 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php +++ b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php @@ -204,7 +204,7 @@ function MarkOrderComplete($order) { $order->setState('Complete', 'complete', 'Completed by BitPay payments.', true); } else { $order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, 'processing', 'BitPay has confirmed the payment.', false); - $order->sendNewOrderEmail(); + if(!$order->getEmailSent()) $order->sendNewOrderEmail(); } $order->save(); } catch (Exception $e) { From b58dbf367f0b1203cd4a1ec1e72803dea6176c25 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Wed, 26 Mar 2014 14:07:51 -0400 Subject: [PATCH 040/315] Add testing performed on 1.8.1.0 and docs updated - Improved README documentation. - Additional testing performed against 1.8.1.0 and installation instructions updated to reflect differences. --- README.md | 98 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 81 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 94e5494..7f71f80 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -©2011-2014 BITPAY, INC. +©2011-2014 BITPAY, INC. Permission is hereby granted to any person obtaining a copy of this software and associated documentation for use and/or modification in association with @@ -12,25 +12,63 @@ 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. +Bitcoin payment module for Magento Community Edition using the bitpay.com service. + Installation ------------ -Copy these files into your Magento directory. +1. Unzip this archive and copy the files to the location of your Magento CE installation on your web server. For Ubuntu-based servers, the default location for website files is the /var/www folder. Your web hosting provider may use a different location for storing your website files so check with them if the /var/www folder does not exist or your Magento files are in an otherwise unknown location. + +Many web hosting accounts have a graphical, web-based control panel for your server. This is the easiest method for copying the BitPay plugin files to your Magenento CE directory. If your provider has one of these graphical control panels, log into your hosting account and move the files using that tool. However, if that is not an option and you can only access your web server using a shell account via SSH, open a new connection and issue these commands: + +
+bitpay@bitpay:~$ unzip magento-plugin-master.zip
+bitpay@bitpay:~$ cd magento-plugin-master
+bitpay@bitpay:~$ cp -R ./* /location/of/your/magento/installation/
+
+ +Note: You may need to have superuser privileges to copy files to /var/www on Ubuntu-based servers. If you receive “Permission denied” errors when using the cp command above, use sudo before the cp command and specify the superuser password when asked: + +
+bitpay@bitpay:~$ sudo cp -R ./* /location/of/your/magento/installation/
+[sudo] password for (username):
+
+ +2. Verify the files have been copied correctly by checking your Magento CE installation folder for one or more of them. You can choose to check for any of the files present in the BitPay plugin archive. The file I’m looking for in this example should be in the /var/www/magento/app/code/community/Bitpay/Bitcoins/Model directory along with the Ipn.php file on my Ubuntu server: + +
+bitpay@bitpay:~$ ls -l /var/www/magento/app/code/community/Bitpay/Bitcoins/Model/
+total 24
+-rw-r--r-- 1 root root  3097 Mar 25 14:06 Ipn.php
+-rw-r--r-- 1 root root 10786 Mar 25 14:06 PaymentMethod.php
+drwxr-xr-x 3 root root  4096 Mar 25 13:54 Resource
+drwxr-xr-x 2 root root  4096 Mar 25 13:54 Source
+
+ +If the files were copied correctly and are present in the directory, you should see the files listed when you issue the ls command. If you do not see any files listed, try the cp command again to retry the copying procedure. However, if you still do not see any files listed or you receive an error copying the files, contact your web hosting support for assistance. + + +Magento CE 1.8.x Installation Tips +---------------------------------- +In some instances for merchants using Magento CE version 1.8.x, the BitPay Bitcoins payment plugin might not appear in the Payment Methods configuration section even though all plugin files have been correctly installed. To resolve this issue, log into your admin control panel and choose the System -> Cache Management configuration screen. Click the check box next to the Configuration cache type and choose the Disable action from the Actions drop-down list box. Click the Submit button to disable this cache. + +Next, click both the Flush Magento Cache and Flush Cache Storage buttons (Clicked "Ok" when the pop-up box is displayed) to remove the stale configuration cache files. + +Finally, log completely out of the administrative control panel and then log back in. The Bitcoins option is now correctly displaying under Payment Methods in the configuration screen. The BitPay plugin parameters are exactly the same on Magento CE 1.8.x as on older Magento CE releases. + Configuration ------------- -NOTE: SSL is required for use of the BitPay plugin for Magento +NOTE: SSL is required for use of the BitPay plugin for Magento CE. 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": -
    -
  1. Verify that the module is enabled. -
  2. Enter your API key. -
  3. 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. -
  4. 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. -
  5. (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. -
+ - Verify that the module is enabled. + - Enter your API key. + - 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. + - 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. + - (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. The default setting is "No". + Usage ----- @@ -40,13 +78,39 @@ The order status in the admin panel will be "Processing" if payment has been con Note: This extension does not provide a means of automatically pulling a current BTC exchange rate for presenting BTC prices to shoppers. + +Troubleshooting +--------------- +The official BitPay support website should always be your first reference for troubleshooting any problems you may encounter: https://support.bitpay.com + +The official Magento Community Edition support website might also be helpful if the problem you are experiencing is not directly related to the payment plugin: https://www.magentocommerce.com/support/ce/ + +Other troubleshooting tips: + +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 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. +4. Check the version of this plugin agains the official plugin repository to ensure you are using + the latest version. Your issue might have been addressed in a newer version! +5. If all else fails, send an email describing your issue *in detail* to support@bitpay.com + + Change Log ---------- -Version 1 - - Initial version, tested against Magento 1.6.0.0 +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 2
+- Now supports API keys instead of SSL files. Tested against 1.7.0.2. -Version 3 - - Now gives the option to show an iframe on the checkout page instead of redirecting to bitpay.com. +Version 3
+- Now gives the option to show an iframe on the checkout page instead of redirecting to bitpay.com. + +Version 4
+- Improved README documentation. +- Additional testing performed against 1.8.1.0 and installation instructions updated to reflect differences. +- Added parameter to automatically create a shipment and mark orders complete (off by default). +- Version incremented, other bug fixes and enhancements (see commit notes). From 1c7b970b887cf6593e76507c7303a96cefa05a36 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Thu, 27 Mar 2014 14:20:01 -0400 Subject: [PATCH 041/315] Cleaned code and added order complete admin opt - Added missing function scope for code cleanliness & form - Added support for new admin option for marking an order complete when complete IPN received --- .../Bitpay/Bitcoins/Model/PaymentMethod.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php index b4bf383..571759b 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php +++ b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php @@ -98,7 +98,7 @@ class Bitpay_Bitcoins_Model_PaymentMethod extends Mage_Payment_Model_Method_Abst //protected $_formBlockType = 'bitcoins/form'; //protected $_infoBlockType = 'bitcoins/info'; - function canUseForCurrency($currencyCode) { + public function canUseForCurrency($currencyCode) { $currencies = Mage::getStoreConfig('payment/Bitcoins/currencies'); $currencies = array_map('trim', explode(',', $currencies)); return array_search($currencyCode, $currencies) !== false; @@ -129,7 +129,7 @@ public function authorize(Varien_Object $payment, $amount) { return $this->CreateInvoiceAndRedirect($payment, $amount); } - function CheckForPayment($payment) { + public function CheckForPayment($payment) { $quoteId = $payment->getOrder()->getQuoteId(); $ipn = Mage::getModel('Bitcoins/ipn'); @@ -145,7 +145,7 @@ function CheckForPayment($payment) { return $this; } - function MarkOrderPaid($order) { + public function MarkOrderPaid($order) { $order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, true)->save(); if (!count($order->getInvoiceCollection())) { @@ -167,7 +167,7 @@ function MarkOrderPaid($order) { } } - function MarkOrderComplete($order) { + public function MarkOrderComplete($order) { if ($order->hasInvoices()) { foreach ($order->getInvoiceCollection() as $_eachInvoice) { try { @@ -200,12 +200,12 @@ function MarkOrderComplete($order) { } try { - if(isset($_bpCreateShipment) && $_bpCreateShipment == true) { + if((isset($_bpCreateShipment) && $_bpCreateShipment == true) || Mage::getStoreConfig('payment/Bitcoins/order_disposition')) { $order->setState('Complete', 'complete', 'Completed by BitPay payments.', true); } else { $order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, 'processing', 'BitPay has confirmed the payment.', false); - if(!$order->getEmailSent()) $order->sendNewOrderEmail(); } + if(!$order->getEmailSent()) $order->sendNewOrderEmail(); $order->save(); } catch (Exception $e) { Mage::logException($e); @@ -213,7 +213,7 @@ function MarkOrderComplete($order) { } - function MarkOrderCancelled($order) { + public function MarkOrderCancelled($order) { try { $order->setState(Mage_Sales_Model_Order::STATE_CANCELLED, true)->save(); } catch (Exception $e) { @@ -223,7 +223,7 @@ function MarkOrderCancelled($order) { } // given Mage_Core_Model_Abstract, return api-friendly address - function ExtractAddress($address) { + public function ExtractAddress($address) { $options = array(); $options['buyerName'] = $address->getName(); @@ -248,7 +248,7 @@ function ExtractAddress($address) { return $options; } - function CreateInvoiceAndRedirect($payment, $amount) { + public function CreateInvoiceAndRedirect($payment, $amount) { include Mage::getBaseDir('lib').'/bitpay/bp_lib.php'; $apiKey = Mage::getStoreConfig('payment/Bitcoins/api_key'); From 85e41b5b964e15726315fc499806ec7467b4fa5c Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Thu, 27 Mar 2014 14:26:44 -0400 Subject: [PATCH 042/315] Formatting, help comments and new IPN complete opt - Added new user-configurable IPN complete handling option for those merchants wishing to set an order as complete automatically and still provides for existing merchants who need the order to remain in a processing state - Improved overall look of the plugin control panel by adding logo & link to our site - Added helpful comments for Transaction Speed and new IPN complete option handling - Formatted XML to standardized spacing with the other plugin files --- .../community/Bitpay/Bitcoins/etc/system.xml | 177 ++++++++++-------- 1 file changed, 95 insertions(+), 82 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/etc/system.xml b/app/code/community/Bitpay/Bitcoins/etc/system.xml index f610b95..cbe21f0 100644 --- a/app/code/community/Bitpay/Bitcoins/etc/system.xml +++ b/app/code/community/Bitpay/Bitcoins/etc/system.xml @@ -1,84 +1,97 @@ - - - - - - 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 + + + + + + 670 + 1 + 1 + 0 +
To log into your merchant account or download the latest version of this plugin, visit our website: https://bitpay.com/
]]>
+ + + + 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 + High: an invoice is confirmed immediately when payment received.
Medium: an invoice is confirmed after 1 block confirmation by the network (~10 mins).
Low: an invoice is confirmed after 6 block confirmations by the 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.]]>
+
+ + + + text + 6 + 1 + 1 + 0 + + + + + text + 7 + 1 + 1 + 0 + + + + + select + adminhtml/system_config_source_yesno + 8 + 1 + 1 + 0 + If you want to automatically set an order's state to complete when BitPay sends a completed payment notification, change this option to "Yes". The default setting of "No" will keep the order in a processing state for you to update as needed. Invoices contained within the order will be paid regardless of this setting. + + +
+
+
+
+
+ From 16c0355b5625b30d0d7faa702464217eb69d48cb Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Mon, 31 Mar 2014 11:09:15 -0400 Subject: [PATCH 043/315] Added new HTTP header for version tracking --- lib/bitpay/bp_lib.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/bitpay/bp_lib.php b/lib/bitpay/bp_lib.php index a46e03f..73ce828 100644 --- a/lib/bitpay/bp_lib.php +++ b/lib/bitpay/bp_lib.php @@ -39,6 +39,7 @@ function bpCurl($url, $apiKey, $post = false) { 'Content-Type: application/json', 'Content-Length: ' . $length, 'Authorization: Basic ' . $uname, + 'X-BitPay-Plugin-Info: magento5', ); curl_setopt($curl, CURLOPT_PORT, $bpconfig['port']); From 3c5667a2a50eef616dfaed31c20f5d8d330a22ef Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Mon, 31 Mar 2014 11:10:20 -0400 Subject: [PATCH 044/315] Updated version info --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 7f71f80..f11adfe 100644 --- a/README.md +++ b/README.md @@ -114,3 +114,6 @@ Change Log - Additional testing performed against 1.8.1.0 and installation instructions updated to reflect differences. - Added parameter to automatically create a shipment and mark orders complete (off by default). - Version incremented, other bug fixes and enhancements (see commit notes). + +Version 5
+- Added new HTTP header for version tracking From 58e1854a5d8d4a5c50f49c6bda3606cbf04599c9 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Fri, 16 May 2014 01:23:40 -0400 Subject: [PATCH 045/315] Removed extra error handler calls --- app/code/community/Bitpay/Bitcoins/Block/Iframe.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/Block/Iframe.php b/app/code/community/Bitpay/Bitcoins/Block/Iframe.php index 4630a62..24bbb4c 100644 --- a/app/code/community/Bitpay/Bitcoins/Block/Iframe.php +++ b/app/code/community/Bitpay/Bitcoins/Block/Iframe.php @@ -75,12 +75,8 @@ public function GetIframeUrl() { //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'); From 6d90c9784cc11962f5b734e61da40871c31f91b8 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Fri, 16 May 2014 01:27:49 -0400 Subject: [PATCH 046/315] Removed extra error handler calls --- app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php index 571759b..08ce5ae 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php +++ b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php @@ -310,8 +310,6 @@ public function getQuoteHash($quoteId) { $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; } From 6f06424c2b89b68eb0827711b9ee976b3d949e4a Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Fri, 16 May 2014 01:29:33 -0400 Subject: [PATCH 047/315] Removed extra error handler calls --- .../community/Bitpay/Bitcoins/controllers/IndexController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php b/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php index dc27a48..cce944a 100644 --- a/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php +++ b/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php @@ -33,7 +33,6 @@ public function checkForPaymentAction() { // 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); From 0b1a57eecaf52b1c8ec7d332fc8261c784966bf6 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Fri, 16 May 2014 11:56:10 -0400 Subject: [PATCH 048/315] Create .htaccess --- lib/bitpay/.htaccess | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 lib/bitpay/.htaccess diff --git a/lib/bitpay/.htaccess b/lib/bitpay/.htaccess new file mode 100644 index 0000000..d107f53 --- /dev/null +++ b/lib/bitpay/.htaccess @@ -0,0 +1,5 @@ + + Order allow,deny + Deny from all + Satisfy All + From e727371880f562be6c5bdac6ee74c4eae6708074 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Mon, 19 May 2014 14:53:11 -0400 Subject: [PATCH 049/315] Updated logo in admin panel --- app/code/community/Bitpay/Bitcoins/etc/system.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Bitcoins/etc/system.xml b/app/code/community/Bitpay/Bitcoins/etc/system.xml index cbe21f0..af7e145 100644 --- a/app/code/community/Bitpay/Bitcoins/etc/system.xml +++ b/app/code/community/Bitpay/Bitcoins/etc/system.xml @@ -9,7 +9,7 @@ 1 1 0 -
To log into your merchant account or download the latest version of this plugin, visit our website: https://bitpay.com/
]]>
+
To log into your merchant account or download the latest version of this plugin, visit our website: https://bitpay.com/
]]>
From 8ebae606ca35c88bcd684bae011c12915a59f5a0 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Mon, 19 May 2014 15:05:57 -0400 Subject: [PATCH 050/315] Add testing performed on 1.9.0.1 and updated logo --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f11adfe..16eebe5 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,8 @@ drwxr-xr-x 2 root root 4096 Mar 25 13:54 Source If the files were copied correctly and are present in the directory, you should see the files listed when you issue the ls command. If you do not see any files listed, try the cp command again to retry the copying procedure. However, if you still do not see any files listed or you receive an error copying the files, contact your web hosting support for assistance. -Magento CE 1.8.x Installation Tips ----------------------------------- +Magento CE 1.8.x - 1.9.x Installation Tips +------------------------------------------ In some instances for merchants using Magento CE version 1.8.x, the BitPay Bitcoins payment plugin might not appear in the Payment Methods configuration section even though all plugin files have been correctly installed. To resolve this issue, log into your admin control panel and choose the System -> Cache Management configuration screen. Click the check box next to the Configuration cache type and choose the Disable action from the Actions drop-down list box. Click the Submit button to disable this cache. Next, click both the Flush Magento Cache and Flush Cache Storage buttons (Clicked "Ok" when the pop-up box is displayed) to remove the stale configuration cache files. @@ -92,7 +92,8 @@ The official Magento Community Edition support website might also be helpful if 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 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. + 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. 4. Check the version of this plugin agains the official plugin repository to ensure you are using the latest version. Your issue might have been addressed in a newer version! 5. If all else fails, send an email describing your issue *in detail* to support@bitpay.com @@ -117,3 +118,8 @@ Change Log Version 5
- Added new HTTP header for version tracking + +Version 6
+- Updated BitPay logo in admin settings +- Tested & validated against latest 1.9.0.1 +- Tested & validated with default one-page checkout settings From 1667dfde1adfdf823ec72f721c49c12d35dbff00 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Tue, 24 Jun 2014 23:32:07 -0400 Subject: [PATCH 051/315] Added duplicate paid/confirmed/complete checks Also added logging in case of duplicate IPNs to inform the merchant in the event they needed to capture that information. --- .../Bitcoins/controllers/IndexController.php | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php b/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php index cce944a..be9e720 100644 --- a/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php +++ b/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php @@ -23,9 +23,9 @@ class Bitpay_Bitcoins_IndexController extends Mage_Core_Controller_Front_Action { public function checkForPaymentAction() { - $params = $this->getRequest()->getParams(); + $params = $this->getRequest()->getParams(); $quoteId = $params['quote']; - $paid = Mage::getModel('Bitcoins/ipn')->GetQuotePaid($quoteId); + $paid = Mage::getModel('Bitcoins/ipn')->GetQuotePaid($quoteId); print json_encode(array('paid' => $paid)); exit(); } @@ -33,7 +33,7 @@ public function checkForPaymentAction() { // bitpay's IPN lands here public function indexAction() { require Mage::getBaseDir('lib').'/bitpay/bp_lib.php'; - $apiKey = Mage::getStoreConfig('payment/Bitcoins/api_key'); + $apiKey = Mage::getStoreConfig('payment/Bitcoins/api_key'); $invoice = bpVerifyNotification($apiKey); if (is_string($invoice)) @@ -52,22 +52,36 @@ public function indexAction() { Mage::getModel('Bitcoins/ipn')->Record($invoice); // update the order if it exists already - if ($order->getId()) + if ($order->getId()) { switch($invoice['status']) { + case 'paid': - $method = Mage::getModel('Bitcoins/paymentMethod'); - $method->MarkOrderPaid($order); + // Mark paid if there is an outstanding total + if ($order->getTotalDue() > 0) { + $method = Mage::getModel('Bitcoins/paymentMethod'); + $method->MarkOrderPaid($order); + } else { + Mage::log('Received a PAID notification from BitPay but there is nothing due on this invoice. Ignoring this IPN.', null, 'bitpay.log'); + } break; - case 'confirmed': - case 'complete': - $method = Mage::getModel('Bitcoins/paymentMethod'); - $method->MarkOrderComplete($order); + + case 'confirmed': + case 'complete': + // Mark confirmed/complete if the order has been paid + if ($order->getTotalDue() <= 0) { + $method = Mage::getModel('Bitcoins/paymentMethod'); + $method->MarkOrderComplete($order); + } else { + Mage::log('Received a ' . $invoice['status'] . ' notification from BitPay but this order is not paid yet. Possible internal error with Magento. Check order status to confirm.', null, 'bitpay.log'); + } break; + case 'invalid': $method = Mage::getModel('Bitcoins/paymentMethod'); $method->MarkOrderCancelled($order); break; - } + } + } } From 89136ebb0aacd8beb0963e7a2b3e17f2f998c70f Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Tue, 24 Jun 2014 23:42:05 -0400 Subject: [PATCH 052/315] Added duplicate paid/confirmed/complete checks Also added extra error handling --- .../Bitpay/Bitcoins/Model/PaymentMethod.php | 86 ++++++++++--------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php index 08ce5ae..5c77499 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php +++ b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php @@ -134,7 +134,7 @@ public function CheckForPayment($payment) { $ipn = Mage::getModel('Bitcoins/ipn'); if (!$ipn->GetQuotePaid($quoteId)) { - Mage::throwException("Order not paid for. Please pay first and then Place your Order."); + Mage::throwException("Order ". $order->getId() ." 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); @@ -148,35 +148,43 @@ public function CheckForPayment($payment) { public function MarkOrderPaid($order) { $order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, true)->save(); - if (!count($order->getInvoiceCollection())) { - try { - $invoice = $order->prepareInvoice() - ->setTransactionId(1) - ->addComment('Invoiced automatically by Bitpay/Bitcoins/controllers/IndexController.php') - ->register() - ->pay(); + if ($order->getTotalDue() > 0) { + if (!count($order->getInvoiceCollection())) { + try { + $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 = Mage::getModel('core/resource_transaction') + ->addObject($invoice) + ->addObject($invoice->getOrder()); - $transactionSave->save(); - } catch (Exception $e) { - Mage::logException($e); + $transactionSave->save(); + } catch (Exception $e) { + Mage::logException($e); + } } + } else { + Mage::log('MarkOrderPaid called but order '. $order->getId() .' does not have a balance due.', null, 'bitpay.log'); } } public function MarkOrderComplete($order) { - if ($order->hasInvoices()) { - foreach ($order->getInvoiceCollection() as $_eachInvoice) { - try { - $_eachInvoice->setRequestedCaptureCase(Mage_Sales_Model_Order_Invoice::CAPTURE_ONLINE); - $_eachInvoice->capture()->save(); - } catch (Exception $e) { - Mage::logException($e); + if ($order->getTotalDue() <= 0) { + if ($order->hasInvoices()) { + foreach ($order->getInvoiceCollection() as $_eachInvoice) { + try { + $_eachInvoice->setRequestedCaptureCase(Mage_Sales_Model_Order_Invoice::CAPTURE_ONLINE); + $_eachInvoice->capture()->save(); + } catch (Exception $e) { + Mage::logException($e); + } } } + } else { + Mage::log('MarkOrderComplete called but order '. $order->getId() .' has an outstanding balance that has not been paid.', null, 'bitpay.log'); } // If the $_bpCreateShipment option is set to true above, this code will @@ -194,7 +202,7 @@ public function MarkOrderComplete($order) { ->save(); } } catch (Exception $e) { - Mage::log('Error creating shipment', null, 'bitpay.log'); + Mage::log('Error creating shipment for order '. $order->getId() .'.', null, 'bitpay.log'); Mage::logException($e); } } @@ -217,7 +225,7 @@ public function MarkOrderCancelled($order) { try { $order->setState(Mage_Sales_Model_Order::STATE_CANCELLED, true)->save(); } catch (Exception $e) { - Mage::log('Could not cancel order', null, 'bitpay.log'); + Mage::log('Could not cancel order '. $order->getId() .'.', null, 'bitpay.log'); Mage::logException($e); } } @@ -234,12 +242,12 @@ public function ExtractAddress($address) { $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(); + $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) @@ -251,19 +259,19 @@ public function ExtractAddress($address) { public 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(); + $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(), + 'currency' => $order->getBaseCurrencyCode(), + 'buyerName' => $order->getCustomerFirstname().' '.$order->getCustomerLastname(), 'fullNotifications' => 'true', - 'notificationURL' => Mage::getUrl('bitpay_callback'), - 'redirectURL' => Mage::getUrl('checkout/onepage/success'), - 'transactionSpeed' => $speed, - 'apiKey' => $apiKey, + 'notificationURL' => Mage::getUrl('bitpay_callback'), + 'redirectURL' => Mage::getUrl('checkout/onepage/success'), + 'transactionSpeed' => $speed, + 'apiKey' => $apiKey, ); $options += $this->ExtractAddress($order->getShippingAddress()); @@ -273,7 +281,7 @@ public function CreateInvoiceAndRedirect($payment, $amount) { 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."); + Mage::throwException("Error creating BitPay 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']); From 9688f94437a3f23b1c3041b9d2683a52f07a7e83 Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Mon, 30 Jun 2014 15:23:29 -0400 Subject: [PATCH 053/315] Small bug fix where it was complaining that was not defined and added a comment on where this error is displayed --- app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php index 5c77499..f6f8c35 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php +++ b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php @@ -134,7 +134,8 @@ public function CheckForPayment($payment) { $ipn = Mage::getModel('Bitcoins/ipn'); if (!$ipn->GetQuotePaid($quoteId)) { - Mage::throwException("Order ". $order->getId() ." not paid for. Please pay first and then Place your Order."); + // This is the error that is displayed to the customer during checkout. + 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); From 0a4fd03d696801eb8c8324fbb85afb4116ed0169 Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Tue, 1 Jul 2014 11:19:50 -0400 Subject: [PATCH 054/315] Updated the log levels for some of the bitpay.log file --- .../community/Bitpay/Bitcoins/Model/Ipn.php | 4 ++-- .../Bitpay/Bitcoins/Model/PaymentMethod.php | 20 +++++++++++-------- .../Bitcoins/controllers/IndexController.php | 4 ++-- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/Model/Ipn.php b/app/code/community/Bitpay/Bitcoins/Model/Ipn.php index 5ad9776..9976991 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/Ipn.php +++ b/app/code/community/Bitpay/Bitcoins/Model/Ipn.php @@ -50,14 +50,14 @@ function GetStatusReceived($quoteId, $statuses) { $quote = Mage::getModel('sales/quote')->load($quoteId, 'entity_id'); if (!$quote) { - Mage::log('quote not found', NULL, 'bitpay.log'); + Mage::log('quote not found', Zend_Log::WARN, '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'); + Mage::log('Could not find quote hash for quote '.$quoteId, Zend_Log::WARN, 'bitpay.log'); return false; } diff --git a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php index f6f8c35..23c4822 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php +++ b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php @@ -108,14 +108,14 @@ public function canUseCheckout() { $secret = Mage::getStoreConfig('payment/Bitcoins/api_key'); if (!$secret or !strlen($secret)) { - Mage::log('Bitpay/Bitcoins: API key not entered', null, 'bitpay.log'); + Mage::log('Bitpay/Bitcoins: API key not entered', Zend_Log::ERR, 'bitpay.log'); return false; } $speed = Mage::getStoreConfig('payment/Bitcoins/speed'); if (!$speed or !strlen($speed)) { - Mage::log('Bitpay/Bitcoins: Transaction Speed invalid', null, 'bitpay.log'); + Mage::log('Bitpay/Bitcoins: Transaction Speed invalid', Zend_Log::ERR, 'bitpay.log'); return false; } @@ -136,6 +136,7 @@ public function CheckForPayment($payment) { if (!$ipn->GetQuotePaid($quoteId)) { // This is the error that is displayed to the customer during checkout. Mage::throwException("Order not paid for. Please pay first and then Place your Order."); + Mage::log('Order not paid for. Please pay first and then Place Your Order.', Zend_Log::CRIT, 'bitpay.log'); } else if (!$ipn->GetQuoteComplete($quoteId)) { // order status will be PAYMENT_REVIEW instead of PROCESSING $payment->setIsTransactionPending(true); @@ -164,11 +165,12 @@ public function MarkOrderPaid($order) { $transactionSave->save(); } catch (Exception $e) { + Mage::log($e->getMessage(), Zend_Log::EMERG, 'bitpay.log'); Mage::logException($e); } } } else { - Mage::log('MarkOrderPaid called but order '. $order->getId() .' does not have a balance due.', null, 'bitpay.log'); + Mage::log('MarkOrderPaid called but order '. $order->getId() .' does not have a balance due.', Zend_Log::WARN, 'bitpay.log'); } } @@ -180,12 +182,13 @@ public function MarkOrderComplete($order) { $_eachInvoice->setRequestedCaptureCase(Mage_Sales_Model_Order_Invoice::CAPTURE_ONLINE); $_eachInvoice->capture()->save(); } catch (Exception $e) { + Mage::log($e->getMessage(), Zend_Log::EMERG, 'bitpay.log'); Mage::logException($e); } } } } else { - Mage::log('MarkOrderComplete called but order '. $order->getId() .' has an outstanding balance that has not been paid.', null, 'bitpay.log'); + Mage::log('MarkOrderComplete called but order '. $order->getId() .' has an outstanding balance that has not been paid.', Zend_Log::WARN, 'bitpay.log'); } // If the $_bpCreateShipment option is set to true above, this code will @@ -203,7 +206,7 @@ public function MarkOrderComplete($order) { ->save(); } } catch (Exception $e) { - Mage::log('Error creating shipment for order '. $order->getId() .'.', null, 'bitpay.log'); + Mage::log('Error creating shipment for order '. $order->getId() .'.', Zend_Log::ERR, 'bitpay.log'); Mage::logException($e); } } @@ -217,6 +220,7 @@ public function MarkOrderComplete($order) { if(!$order->getEmailSent()) $order->sendNewOrderEmail(); $order->save(); } catch (Exception $e) { + Mage::log($e->getMessage(), Zend_Log::EMERG, 'bitpay.log'); Mage::logException($e); } @@ -280,8 +284,8 @@ public function CreateInvoiceAndRedirect($payment, $amount) { $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::log('Error creating bitpay invoice', Zend_Log::CRIT, 'bitpay.log'); + Mage::log($invoice['error'], Zend_Log::CRIT, 'bitpay.log'); Mage::throwException("Error creating BitPay invoice. Please try again or use another payment option."); } else { $invoiceId = Mage::getModel('sales/order_invoice_api')->create($orderId, array()); @@ -302,7 +306,7 @@ public function getOrderPlaceRedirectUrl() { public function getQuoteHash($quoteId) { $quote = Mage::getModel('sales/quote')->load($quoteId, 'entity_id'); if (!$quote) { - Mage::log('getQuoteTimestamp: quote not found', NULL, 'bitpay.log'); + Mage::log('getQuoteTimestamp: quote not found', Zend_Log::ERR, 'bitpay.log'); return false; } diff --git a/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php b/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php index be9e720..af8d3b9 100644 --- a/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php +++ b/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php @@ -37,7 +37,7 @@ public function indexAction() { $invoice = bpVerifyNotification($apiKey); if (is_string($invoice)) - Mage::log("bitpay callback error: $invoice", null, 'bitpay.log'); + Mage::log("bitpay callback error: $invoice", Zend_Log::ERR, 'bitpay.log'); else { // get the order if (isset($invoice['posData']['quoteId'])) { @@ -72,7 +72,7 @@ public function indexAction() { $method = Mage::getModel('Bitcoins/paymentMethod'); $method->MarkOrderComplete($order); } else { - Mage::log('Received a ' . $invoice['status'] . ' notification from BitPay but this order is not paid yet. Possible internal error with Magento. Check order status to confirm.', null, 'bitpay.log'); + Mage::log('Received a ' . $invoice['status'] . ' notification from BitPay but this order is not paid yet. Possible internal error with Magento. Check order status to confirm.', Zend_Log::ERR, 'bitpay.log'); } break; From 5f21a0089fcc9d447398edf5d89109270e83c1f5 Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Tue, 1 Jul 2014 11:53:02 -0400 Subject: [PATCH 055/315] Initial commit of an updated readme file --- README.md | 111 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 80 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 16eebe5..341968d 100644 --- a/README.md +++ b/README.md @@ -17,45 +17,78 @@ Bitcoin payment module for Magento Community Edition using the bitpay.com servic Installation ------------ -1. Unzip this archive and copy the files to the location of your Magento CE installation on your web server. For Ubuntu-based servers, the default location for website files is the /var/www folder. Your web hosting provider may use a different location for storing your website files so check with them if the /var/www folder does not exist or your Magento files are in an otherwise unknown location. - -Many web hosting accounts have a graphical, web-based control panel for your server. This is the easiest method for copying the BitPay plugin files to your Magenento CE directory. If your provider has one of these graphical control panels, log into your hosting account and move the files using that tool. However, if that is not an option and you can only access your web server using a shell account via SSH, open a new connection and issue these commands: - -
+1.  Unzip this archive and copy the files to the location of your
+[Magento CE](http://magento.com/) installation on your web server. For Ubuntu-based servers, the default
+location for website files is the `/var/www` folder. Your web hosting provider may
+use a different location for storing your website files so check with them if the
+`/var/www` folder does not exist or your Magento files are in an otherwise unknown
+location.
+
+Many web hosting accounts have a graphical, web-based control panel for your server.
+This is the easiest method for copying the [BitPay Magento Plugin](https://github.com/bitpay/magento-plugin) files to your Magenento
+CE directory. If your provider has one of these graphical control panels, log into
+your hosting account and move the files using that tool. However, if that is not
+an option and you can only access your web server using a shell account via SSH,
+open a new connection and issue these commands:
+
+```bash
 bitpay@bitpay:~$ unzip magento-plugin-master.zip
 bitpay@bitpay:~$ cd magento-plugin-master
 bitpay@bitpay:~$ cp -R ./* /location/of/your/magento/installation/
-
- -Note: You may need to have superuser privileges to copy files to /var/www on Ubuntu-based servers. If you receive “Permission denied” errors when using the cp command above, use sudo before the cp command and specify the superuser password when asked: +``` +Note: You may need to have superuser privileges to copy files +to `/var/www` on Ubuntu-based servers. If you receive “Permission denied” errors +when using the cp command above, use sudo before the cp command and specify the +superuser password when asked: -
+```bash
 bitpay@bitpay:~$ sudo cp -R ./* /location/of/your/magento/installation/
 [sudo] password for (username):
-
- -2. Verify the files have been copied correctly by checking your Magento CE installation folder for one or more of them. You can choose to check for any of the files present in the BitPay plugin archive. The file I’m looking for in this example should be in the /var/www/magento/app/code/community/Bitpay/Bitcoins/Model directory along with the Ipn.php file on my Ubuntu server: - -
+```
+2.  Verify the files have been copied correctly by checking
+your Magento CE installation folder for one or more of them.  You can choose
+to check for any of the files present in the BitPay plugin archive.  The file
+I’m looking for in this example should be in the `/var/www/magento/app/code/community/Bitpay/Bitcoins/Model`
+directory along with the `Ipn.php` file on my Ubuntu server:
+
+```bash
 bitpay@bitpay:~$ ls -l /var/www/magento/app/code/community/Bitpay/Bitcoins/Model/
 total 24
 -rw-r--r-- 1 root root  3097 Mar 25 14:06 Ipn.php
 -rw-r--r-- 1 root root 10786 Mar 25 14:06 PaymentMethod.php
 drwxr-xr-x 3 root root  4096 Mar 25 13:54 Resource
 drwxr-xr-x 2 root root  4096 Mar 25 13:54 Source
-
+``` -If the files were copied correctly and are present in the directory, you should see the files listed when you issue the ls command. If you do not see any files listed, try the cp command again to retry the copying procedure. However, if you still do not see any files listed or you receive an error copying the files, contact your web hosting support for assistance. +If the files were copied correctly and are present in the directory, you should +see the files listed when you issue the ls command. If you do not see any files +listed, try the cp command again to retry the copying procedure. However, if +you still do not see any files listed or you receive an error copying the files, +contact your web hosting support for assistance. +3. Using [modman](https://github.com/colinmollenhour/modman) you can +install the BitPay Magento Plugin. Once +you have modman installed, run `modman init` if you have not already done so. Next +just run `modman clone https://github.com/bitpay/magento-plugin.git` in the root +of the Magento installation. In this case it is `/var/www/magento`. Magento CE 1.8.x - 1.9.x Installation Tips ------------------------------------------ -In some instances for merchants using Magento CE version 1.8.x, the BitPay Bitcoins payment plugin might not appear in the Payment Methods configuration section even though all plugin files have been correctly installed. To resolve this issue, log into your admin control panel and choose the System -> Cache Management configuration screen. Click the check box next to the Configuration cache type and choose the Disable action from the Actions drop-down list box. Click the Submit button to disable this cache. - -Next, click both the Flush Magento Cache and Flush Cache Storage buttons (Clicked "Ok" when the pop-up box is displayed) to remove the stale configuration cache files. - -Finally, log completely out of the administrative control panel and then log back in. The Bitcoins option is now correctly displaying under Payment Methods in the configuration screen. The BitPay plugin parameters are exactly the same on Magento CE 1.8.x as on older Magento CE releases. - +In some instances for merchants using Magento CE version 1.8.x, the BitPay +Bitcoins payment plugin might not appear in the Payment Methods configuration +section even though all plugin files have been correctly installed. To +resolve this issue, log into your admin control panel and choose the +System -> Cache Management configuration screen. Click the check box next +to the Configuration cache type and choose the Disable action from the Actions +drop-down list box. Click the Submit button to disable this cache. + +Next, click both the Flush Magento Cache and Flush Cache Storage buttons (Clicked +"Ok" when the pop-up box is displayed) to remove the stale configuration cache files. + +Finally, log completely out of the administrative control panel and then log back +in. The Bitcoins option is now correctly displaying under Payment Methods in the +configuration screen. The BitPay plugin parameters are exactly the same on Magento +CE 1.8.x as on older Magento CE releases. Configuration ------------- @@ -65,25 +98,41 @@ Configuration 2. In Admin panel under "System > Configuration > Sales > Payment Methods > Bitcoins": - Verify that the module is enabled. - Enter your API key. - - 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. - - 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. - - (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. The default setting is "No". - + - 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. + - 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. + - (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. The default setting is + "No". 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. +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. - +Note: This extension does not provide a means of automatically pulling a current BTC +exchange rate for presenting BTC prices to shoppers. Troubleshooting --------------- -The official BitPay support website should always be your first reference for troubleshooting any problems you may encounter: https://support.bitpay.com +The official BitPay support website should always be your first reference for +troubleshooting any problems you may encounter: https://support.bitpay.com -The official Magento Community Edition support website might also be helpful if the problem you are experiencing is not directly related to the payment plugin: https://www.magentocommerce.com/support/ce/ +The official Magento Community Edition support website might also be helpful +if the problem you are experiencing is not directly related to the payment +plugin: https://www.magentocommerce.com/support/ce/ Other troubleshooting tips: From ed4a350e26fb6fffc76e954c538872b770a2b7fd Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Tue, 1 Jul 2014 12:11:42 -0400 Subject: [PATCH 056/315] Added a little more information and reformated some of the headings --- README.md | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 341968d..cccd07b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +BitPay Inc Magento Plugin +========================= + ©2011-2014 BITPAY, INC. Permission is hereby granted to any person obtaining a copy of this software @@ -14,10 +17,10 @@ THE SOFTWARE. Bitcoin payment module for Magento Community Edition using the bitpay.com service. +# Installation -Installation ------------- -1. Unzip this archive and copy the files to the location of your +## Download +1. [Download](https://github.com/bitpay/magento-plugin/archive/master.zip) and Unzip this archive and copy the files to the location of your [Magento CE](http://magento.com/) installation on your web server. For Ubuntu-based servers, the default location for website files is the `/var/www` folder. Your web hosting provider may use a different location for storing your website files so check with them if the @@ -66,14 +69,14 @@ listed, try the cp command again to retry the copying procedure. However, if you still do not see any files listed or you receive an error copying the files, contact your web hosting support for assistance. -3. Using [modman](https://github.com/colinmollenhour/modman) you can +## modman +Using [modman](https://github.com/colinmollenhour/modman) you can install the BitPay Magento Plugin. Once you have modman installed, run `modman init` if you have not already done so. Next just run `modman clone https://github.com/bitpay/magento-plugin.git` in the root of the Magento installation. In this case it is `/var/www/magento`. -Magento CE 1.8.x - 1.9.x Installation Tips ------------------------------------------- +# Magento CE 1.8.x - 1.9.x Installation Tips In some instances for merchants using Magento CE version 1.8.x, the BitPay Bitcoins payment plugin might not appear in the Payment Methods configuration section even though all plugin files have been correctly installed. To @@ -90,8 +93,7 @@ in. The Bitcoins option is now correctly displaying under Payment Methods in the configuration screen. The BitPay plugin parameters are exactly the same on Magento CE 1.8.x as on older Magento CE releases. -Configuration -------------- +# Configuration NOTE: SSL is required for use of the BitPay plugin for Magento CE. 1. Create an API key at bitpay.com by clicking My Account > API Access Keys > Add New API Key. @@ -112,8 +114,7 @@ Configuration will be redirected to bitpay.com to pay their order. The default setting is "No". -Usage ------ +# 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 @@ -125,8 +126,7 @@ The order status in the admin panel will be "Processing" if payment has been con Note: This extension does not provide a means of automatically pulling a current BTC exchange rate for presenting BTC prices to shoppers. -Troubleshooting ---------------- +# Troubleshooting The official BitPay support website should always be your first reference for troubleshooting any problems you may encounter: https://support.bitpay.com @@ -140,16 +140,21 @@ plugin: https://www.magentocommerce.com/support/ce/ 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 bitpay.log file for any errors during BitPay payment attempts. If you contact BitPay +3. Check the `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. -4. Check the version of this plugin agains the official plugin repository to ensure you are using + inside your Magento's `var/log/` directory. +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! 5. If all else fails, send an email describing your issue *in detail* to support@bitpay.com +NOTE: When contacting support it will help us is you provide: +* Magento Version +* Other plugins you have installed +* Some configuration settings such as: + * Transaction Speed + * Set order complete with "complete" IPN -Change Log ----------- +# Change Log Version 1
- Initial version, tested against Magento 1.6.0.0 From 2a3888262734284b974221614885308e65cbfb60 Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Tue, 1 Jul 2014 12:35:30 -0400 Subject: [PATCH 057/315] Updated composer.json and added a build.xml file to lint check php files --- .gitignore | 3 +++ build.xml | 13 +++++++++++++ composer.json | 36 +++++++++++++++++++++++++++++------- 3 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 build.xml diff --git a/.gitignore b/.gitignore index 7eb4c07..967e733 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ *.swp lib/bitpay/bp_config.php +bin/ +vendor/ +composer.lock diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..0d3bea3 --- /dev/null +++ b/build.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/composer.json b/composer.json index 4686e66..80b9dc3 100644 --- a/composer.json +++ b/composer.json @@ -1,9 +1,31 @@ { - "name": "bitpay/bitcoins", - "description": "Bitcoin payment module using the bitpay.com service", - "minimum-stability": "dev", - "type": "magento-module", - "require": { - "magento-hackathon/magento-composer-installer": "dev-master" - } + "name": "bitpay/magento-plugin", + "description": "Bitcoin payment module using the bitpay.com service", + "keywords": ["magento","bitcoin"], + "minimum-stability": "dev", + "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": { + "composer/installers": "~1.0" + }, + "require-dev": { + "phing/phing": "*" + }, + "config": { + "bin-dir": "bin" + }, + "extra": { + "branch-alias": { + "dev-master": "6.0.x-dev" + } + }, + "archive": { + "exclude": ["build.xml"] + } } From 1d3cc1c1b481c304cedbc05b217beae33fa42fa5 Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Tue, 1 Jul 2014 17:01:18 -0400 Subject: [PATCH 058/315] This might take care of the issue of double paying --- app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php index 23c4822..9122026 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php +++ b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php @@ -175,6 +175,10 @@ public function MarkOrderPaid($order) { } public function MarkOrderComplete($order) { + /** + * The order has already been invoiced and has already been paid, this + * code leads to having payments applied multiple times. + * if ($order->getTotalDue() <= 0) { if ($order->hasInvoices()) { foreach ($order->getInvoiceCollection() as $_eachInvoice) { @@ -190,6 +194,7 @@ public function MarkOrderComplete($order) { } else { Mage::log('MarkOrderComplete called but order '. $order->getId() .' has an outstanding balance that has not been paid.', Zend_Log::WARN, 'bitpay.log'); } + */ // If the $_bpCreateShipment option is set to true above, this code will // programmatically create a shipment for you. By design, this will mark From 568623b432f3c1b4956a7a0922e9119c3961172e Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Wed, 2 Jul 2014 09:39:21 -0400 Subject: [PATCH 059/315] Working on setting up travis-ci --- .travis.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..abd02a7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: php +php: + - 5.5 + - 5.4 + - 5.3 +install: + - composer install +script: php bin/phing From b40c1b9ff1c5818f2b585ac1f2a9b6df53eae814 Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Wed, 2 Jul 2014 09:56:27 -0400 Subject: [PATCH 060/315] Updated readme to show status of plugin --- README.md | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index cccd07b..653a35b 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,9 @@ BitPay Inc Magento Plugin ========================= -©2011-2014 BITPAY, INC. - -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. +# Status -Bitcoin payment module for Magento Community Edition using the bitpay.com service. +[![Build Status](https://travis-ci.org/bitpay/magento-plugin.svg?branch=master)](https://travis-ci.org/bitpay/magento-plugin) # Installation @@ -177,3 +165,21 @@ NOTE: When contacting support it will help us is you provide: - Updated BitPay logo in admin settings - Tested & validated against latest 1.9.0.1 - Tested & validated with default one-page checkout settings + +# License + +©2011-2014 BITPAY, INC. + +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 for Magento Community Edition using the bitpay.com service. From 46615edab5881181f4d85fc81bb4b28457c34fd9 Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Wed, 2 Jul 2014 11:25:48 -0400 Subject: [PATCH 061/315] Added coveralls status badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 653a35b..f37b3dd 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ BitPay Inc Magento Plugin [![Build Status](https://travis-ci.org/bitpay/magento-plugin.svg?branch=master)](https://travis-ci.org/bitpay/magento-plugin) +[![Coverage Status](https://img.shields.io/coveralls/bitpay/magento-plugin.svg)](https://coveralls.io/r/bitpay/magento-plugin) + # Installation ## Download From b15d2a8330cf036887e8156a7a4ccc551f29e5f9 Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Wed, 2 Jul 2014 11:57:26 -0400 Subject: [PATCH 062/315] Updated to use scrutinizer-ci since coveralls uses some different tools --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f37b3dd..44d21f0 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ BitPay Inc Magento Plugin [![Build Status](https://travis-ci.org/bitpay/magento-plugin.svg?branch=master)](https://travis-ci.org/bitpay/magento-plugin) -[![Coverage Status](https://img.shields.io/coveralls/bitpay/magento-plugin.svg)](https://coveralls.io/r/bitpay/magento-plugin) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/JoshuaEstes/magento-plugin/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/bitpay/magento-plugin/?branch=master) + +[![Code Coverage](https://scrutinizer-ci.com/g/JoshuaEstes/magento-plugin/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/bitpay/magento-plugin/?branch=master) # Installation From 64e239457363a2fd0a94b523713dd882c05168ae Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Wed, 2 Jul 2014 12:02:03 -0400 Subject: [PATCH 063/315] Added coveralls back so all the tools can be played with for builds --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 44d21f0..f446df4 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ BitPay Inc Magento Plugin [![Code Coverage](https://scrutinizer-ci.com/g/JoshuaEstes/magento-plugin/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/bitpay/magento-plugin/?branch=master) +[![Coverage Status](https://img.shields.io/coveralls/bitpay/magento-plugin.svg)](https://coveralls.io/r/bitpay/magento-plugin) + # Installation ## Download From 56428f237dc124397ea9302dbd6c8ed87e6389e1 Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Wed, 2 Jul 2014 12:07:45 -0400 Subject: [PATCH 064/315] Added config file for scrutinizer-ci --- .scrutinizer.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .scrutinizer.yml diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..3cd704d --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,3 @@ +inherit: true +tools: + sensiolabs_security_checker: true From 021a4a8ceb4db3f78adef22dec910f812c60051c Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Wed, 2 Jul 2014 12:11:01 -0400 Subject: [PATCH 065/315] Added coveralls config file --- .coveralls.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .coveralls.yml diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000..37aa5cc --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1 @@ +service-name: travis-ci From 0f0e4ea4925754471e52fda53545ae106370b8cb Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Wed, 2 Jul 2014 12:18:45 -0400 Subject: [PATCH 066/315] Pushed up links to personal repo by mistake --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f446df4..df4de4d 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@ BitPay Inc Magento Plugin [![Build Status](https://travis-ci.org/bitpay/magento-plugin.svg?branch=master)](https://travis-ci.org/bitpay/magento-plugin) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/JoshuaEstes/magento-plugin/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/bitpay/magento-plugin/?branch=master) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/bitpay/magento-plugin/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/bitpay/magento-plugin/?branch=master) -[![Code Coverage](https://scrutinizer-ci.com/g/JoshuaEstes/magento-plugin/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/bitpay/magento-plugin/?branch=master) +[![Code Coverage](https://scrutinizer-ci.com/g/bitpay/magento-plugin/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/bitpay/magento-plugin/?branch=master) [![Coverage Status](https://img.shields.io/coveralls/bitpay/magento-plugin.svg)](https://coveralls.io/r/bitpay/magento-plugin) From cf391ba6303e075b29fed29b2c430e9705335236 Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Wed, 2 Jul 2014 13:15:41 -0400 Subject: [PATCH 067/315] Working on some code clean up, this is the first iteration and just includes code formatting --- .../Bitpay/Bitcoins/Block/Iframe.php | 153 ++-- .../community/Bitpay/Bitcoins/Model/Ipn.php | 162 ++-- .../Bitpay/Bitcoins/Model/PaymentMethod.php | 690 ++++++++++-------- .../Bitpay/Bitcoins/Model/Resource/Ipn.php | 12 +- .../Model/Resource/Ipn/Collection.php | 15 +- .../Bitpay/Bitcoins/Model/Source/Speed.php | 41 +- .../Bitcoins/controllers/IndexController.php | 133 ++-- .../frontend/base/default/layout/bitcoins.xml | 5 +- .../default/template/bitcoins/iframe.phtml | 31 +- lib/bitpay/bp_config_default.php | 21 +- lib/bitpay/bp_lib.php | 287 +++++--- lib/bitpay/bp_options.php | 30 +- 12 files changed, 919 insertions(+), 661 deletions(-) diff --git a/app/code/community/Bitpay/Bitcoins/Block/Iframe.php b/app/code/community/Bitpay/Bitcoins/Block/Iframe.php index 24bbb4c..1d48ea0 100644 --- a/app/code/community/Bitpay/Bitcoins/Block/Iframe.php +++ b/app/code/community/Bitpay/Bitcoins/Block/Iframe.php @@ -19,72 +19,95 @@ * */ -class Bitpay_Bitcoins_Block_Iframe extends Mage_Checkout_Block_Onepage_Payment { - protected function _construct() { - $this->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('checkout/onepage/success'), - '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); - - $invoice = bpCreateInvoice($quoteId, $price, array('quoteId' => $quoteId, 'quoteHash' => $hash), $options); - - 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; +class Bitpay_Bitcoins_Block_Iframe extends Mage_Checkout_Block_Onepage_Payment +{ + + /** + */ + protected function _construct() + { + $this->setTemplate('bitcoins/iframe.phtml'); + parent::_construct(); } - return $invoice['url'].'&view=iframe'; - } + /** + * @return + */ + 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 + * + * @return + */ + public function GetIframeUrl() { + // are they using bitpay? + // @todo refactor this + 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('checkout/onepage/success'), + '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); + + $invoice = bpCreateInvoice($quoteId, $price, array('quoteId' => $quoteId, 'quoteHash' => $hash), $options); + + 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'; + } } diff --git a/app/code/community/Bitpay/Bitcoins/Model/Ipn.php b/app/code/community/Bitpay/Bitcoins/Model/Ipn.php index 9976991..a5516ee 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/Ipn.php +++ b/app/code/community/Bitpay/Bitcoins/Model/Ipn.php @@ -19,74 +19,112 @@ * */ -class Bitpay_Bitcoins_Model_Ipn extends Mage_Core_Model_Abstract { - - function _construct() { - $this->_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', Zend_Log::WARN, 'bitpay.log'); - return false; - } +class Bitpay_Bitcoins_Model_Ipn extends Mage_Core_Model_Abstract +{ - $quoteHash = Mage::getModel('Bitcoins/paymentMethod')->getQuoteHash($quoteId); + /** + */ + function _construct() + { + $this->_init('Bitcoins/ipn'); - if (!$quoteHash) { - Mage::log('Could not find quote hash for quote '.$quoteId, Zend_Log::WARN, 'bitpay.log'); - return false; + return parent::_construct(); } - $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; - } + /** + * @param $invoice + * + * @return + */ + 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(); } - return false; - } - - function GetQuotePaid($quoteId) { - return $this->GetStatusReceived($quoteId, array('paid', 'confirmed', 'complete')); - } + /** + * @param string $quoteId + * @param array $statuses + * + * @return boolean + */ + function GetStatusReceived($quoteId, $statuses) { + if (!$quoteId) + { + return false; + } + + $quote = Mage::getModel('sales/quote')->load($quoteId, 'entity_id'); + + if (!$quote) + { + Mage::log('quote not found', Zend_Log::WARN, 'bitpay.log'); + + return false; + } + + $quoteHash = Mage::getModel('Bitcoins/paymentMethod')->getQuoteHash($quoteId); + + if (!$quoteHash) + { + Mage::log('Could not find quote hash for quote '.$quoteId, Zend_Log::WARN, '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 GetQuoteComplete($quoteId) { - return $this->GetStatusReceived($quoteId, array('confirmed', 'complete')); - } + /** + * @param string $quoteId + * + * @return boolean + */ + function GetQuotePaid($quoteId) + { + return $this->GetStatusReceived($quoteId, array('paid', 'confirmed', 'complete')); + } + /** + * @param string $quoteId + * + * @return boolean + */ + function GetQuoteComplete($quoteId) + { + return $this->GetStatusReceived($quoteId, array('confirmed', 'complete')); + } } - -?> diff --git a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php index 9122026..3d67450 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php +++ b/app/code/community/Bitpay/Bitcoins/Model/PaymentMethod.php @@ -22,314 +22,432 @@ /** * Our test CC module adapter */ -class Bitpay_Bitcoins_Model_PaymentMethod extends Mage_Payment_Model_Method_Abstract { - /** - * unique internal payment method identifier - * - * @var string [a-z0-9_] - */ - protected $_code = 'Bitcoins'; - - /** - * Here are examples of flags that will determine functionality availability - * of this module to be used by frontend and backend. - * - * @see all flags and their defaults in Mage_Payment_Model_Method_Abstract - * - * It is possible to have a custom dynamic logic by overloading - * public function can* for each flag respectively - */ - - /** - * Is this payment method a gateway (online auth/charge) ? - */ - protected $_isGateway = true; - - /** - * Can authorize online? - */ - protected $_canAuthorize = true; - - /** - * Can capture funds online? - */ - protected $_canCapture = true; - - /** - * Can capture partial amounts online? - */ - protected $_canCapturePartial = false; - - /** - * Can refund online? - */ - protected $_canRefund = false; - - /** - * Can void transactions online? - */ - protected $_canVoid = false; - - /** - * Can use this payment method in administration panel? - */ - protected $_canUseInternal = false; - - /** - * Can show this payment method as an option on checkout payment page? - */ - protected $_canUseCheckout = true; - - /** - * Is this payment method suitable for multi-shipping checkout? - */ - protected $_canUseForMultishipping = true; - - /** - * Can save credit card information for future processing? - */ - protected $_canSaveCc = false; - - /** - * BitPay - create shipment automatically after completing order? - */ - protected $_bpCreateShipment = false; - - //protected $_formBlockType = 'bitcoins/form'; - //protected $_infoBlockType = 'bitcoins/info'; - - public function canUseForCurrency($currencyCode) { - $currencies = Mage::getStoreConfig('payment/Bitcoins/currencies'); - $currencies = array_map('trim', explode(',', $currencies)); - return array_search($currencyCode, $currencies) !== false; - } - - public function canUseCheckout() { - $secret = Mage::getStoreConfig('payment/Bitcoins/api_key'); - - if (!$secret or !strlen($secret)) { - Mage::log('Bitpay/Bitcoins: API key not entered', Zend_Log::ERR, 'bitpay.log'); - return false; +class Bitpay_Bitcoins_Model_PaymentMethod extends Mage_Payment_Model_Method_Abstract +{ + + /** + * unique internal payment method identifier + * + * @var string [a-z0-9_] + */ + protected $_code = 'Bitcoins'; + + /** + * Here are examples of flags that will determine functionality availability + * of this module to be used by frontend and backend. + * + * @see all flags and their defaults in Mage_Payment_Model_Method_Abstract + * + * It is possible to have a custom dynamic logic by overloading + * public function can* for each flag respectively + */ + + /** + * Is this payment method a gateway (online auth/charge) ? + */ + protected $_isGateway = true; + + /** + * Can authorize online? + */ + protected $_canAuthorize = true; + + /** + * Can capture funds online? + */ + protected $_canCapture = true; + + /** + * Can capture partial amounts online? + */ + protected $_canCapturePartial = false; + + /** + * Can refund online? + */ + protected $_canRefund = false; + + /** + * Can void transactions online? + */ + protected $_canVoid = false; + + /** + * Can use this payment method in administration panel? + */ + protected $_canUseInternal = false; + + /** + * Can show this payment method as an option on checkout payment page? + */ + protected $_canUseCheckout = true; + + /** + * Is this payment method suitable for multi-shipping checkout? + */ + protected $_canUseForMultishipping = true; + + /** + * Can save credit card information for future processing? + */ + protected $_canSaveCc = false; + + /** + * BitPay - create shipment automatically after completing order? + */ + protected $_bpCreateShipment = false; + + //protected $_formBlockType = 'bitcoins/form'; + //protected $_infoBlockType = 'bitcoins/info'; + + /** + * @param string $currencyCode + * + * @return boolean + */ + public function canUseForCurrency($currencyCode) + { + $currencies = Mage::getStoreConfig('payment/Bitcoins/currencies'); + $currencies = array_map('trim', explode(',', $currencies)); + + return array_search($currencyCode, $currencies) !== false; } - - $speed = Mage::getStoreConfig('payment/Bitcoins/speed'); - if (!$speed or !strlen($speed)) { - Mage::log('Bitpay/Bitcoins: Transaction Speed invalid', Zend_Log::ERR, 'bitpay.log'); - return false; + /** + * @return boolean + */ + public function canUseCheckout() + { + $secret = Mage::getStoreConfig('payment/Bitcoins/api_key'); + + if (!$secret or !strlen($secret)) + { + Mage::log('Bitpay/Bitcoins: API key not entered', Zend_Log::ERR, 'bitpay.log'); + + return false; + } + + $speed = Mage::getStoreConfig('payment/Bitcoins/speed'); + + if (!$speed or !strlen($speed)) + { + Mage::log('Bitpay/Bitcoins: Transaction Speed invalid', Zend_Log::ERR, 'bitpay.log'); + + return false; + } + + return $this->_canUseCheckout; } - - return $this->_canUseCheckout; - } - - public function authorize(Varien_Object $payment, $amount) { - if (!Mage::getStoreConfig('payment/Bitcoins/fullscreen')) - return $this->CheckForPayment($payment); - else - return $this->CreateInvoiceAndRedirect($payment, $amount); - } - - public function CheckForPayment($payment) { - $quoteId = $payment->getOrder()->getQuoteId(); - $ipn = Mage::getModel('Bitcoins/ipn'); - - if (!$ipn->GetQuotePaid($quoteId)) { - // This is the error that is displayed to the customer during checkout. - Mage::throwException("Order not paid for. Please pay first and then Place your Order."); - Mage::log('Order not paid for. Please pay first and then Place Your Order.', Zend_Log::CRIT, 'bitpay.log'); - } else if (!$ipn->GetQuoteComplete($quoteId)) { - // order status will be PAYMENT_REVIEW instead of PROCESSING - $payment->setIsTransactionPending(true); - } else { - $this->MarkOrderPaid($payment->getOrder()); + + /** + * @param Varien_Object $payment + * @param string $amounf + * + * @return + */ + public function authorize(Varien_Object $payment, $amount) + { + if (!Mage::getStoreConfig('payment/Bitcoins/fullscreen')) + { + return $this->CheckForPayment($payment); + } + else + { + return $this->CreateInvoiceAndRedirect($payment, $amount); + } } - return $this; - } - - public function MarkOrderPaid($order) { - $order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, true)->save(); - - if ($order->getTotalDue() > 0) { - if (!count($order->getInvoiceCollection())) { - try { - $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(); - } catch (Exception $e) { - Mage::log($e->getMessage(), Zend_Log::EMERG, 'bitpay.log'); - Mage::logException($e); + /** + * @param $payment + * + * @return Bitpay_Bitcoins_Model_PaymentMethod + */ + public function CheckForPayment($payment) + { + $quoteId = $payment->getOrder()->getQuoteId(); + $ipn = Mage::getModel('Bitcoins/ipn'); + + if (!$ipn->GetQuotePaid($quoteId)) + { + // This is the error that is displayed to the customer during checkout. + Mage::throwException("Order not paid for. Please pay first and then Place your Order."); + Mage::log('Order not paid for. Please pay first and then Place Your Order.', Zend_Log::CRIT, 'bitpay.log'); } - } - } else { - Mage::log('MarkOrderPaid called but order '. $order->getId() .' does not have a balance due.', Zend_Log::WARN, 'bitpay.log'); + else if (!$ipn->GetQuoteComplete($quoteId)) + { + // order status will be PAYMENT_REVIEW instead of PROCESSING + $payment->setIsTransactionPending(true); + } else { + $this->MarkOrderPaid($payment->getOrder()); + } + + return $this; } - } - public function MarkOrderComplete($order) { /** - * The order has already been invoiced and has already been paid, this - * code leads to having payments applied multiple times. + * @param $order * - if ($order->getTotalDue() <= 0) { - if ($order->hasInvoices()) { - foreach ($order->getInvoiceCollection() as $_eachInvoice) { - try { - $_eachInvoice->setRequestedCaptureCase(Mage_Sales_Model_Order_Invoice::CAPTURE_ONLINE); - $_eachInvoice->capture()->save(); - } catch (Exception $e) { + * @return + */ + public function MarkOrderPaid($order) + { + $order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, true)->save(); + + if ($order->getTotalDue() > 0) + { + if (!count($order->getInvoiceCollection())) + { + try + { + $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(); + } + catch (Exception $e) + { + Mage::log($e->getMessage(), Zend_Log::EMERG, 'bitpay.log'); + Mage::logException($e); + } + } + } + else + { + Mage::log('MarkOrderPaid called but order '. $order->getId() .' does not have a balance due.', Zend_Log::WARN, 'bitpay.log'); + } + } + + public function MarkOrderComplete($order) + { + /** + * The order has already been invoiced and has already been paid, this + * code leads to having payments applied multiple times. + * + if ($order->getTotalDue() <= 0) + { + if ($order->hasInvoices()) + { + foreach ($order->getInvoiceCollection() as $_eachInvoice) + { + try + { + $_eachInvoice->setRequestedCaptureCase(Mage_Sales_Model_Order_Invoice::CAPTURE_ONLINE); + $_eachInvoice->capture()->save(); + } + catch (Exception $e) + { + Mage::log($e->getMessage(), Zend_Log::EMERG, 'bitpay.log'); + Mage::logException($e); + } + } + } + } + else + { + Mage::log('MarkOrderComplete called but order '. $order->getId() .' has an outstanding balance that has not been paid.', Zend_Log::WARN, 'bitpay.log'); + } + */ + + // If the $_bpCreateShipment option is set to true above, this code will + // programmatically create a shipment for you. By design, this will mark + // the entire order as 'complete'. + if(isset($_bpCreateShipment) && $_bpCreateShipment == true) + { + try + { + $shipment = $order->prepareShipment(); + if ($shipment) + { + $shipment->register(); + $order->setIsInProcess(true); + $transaction_save = Mage::getModel('core/resource_transaction') + ->addObject($shipment) + ->addObject($shipment->getOrder()) + ->save(); + } + } + catch (Exception $e) + { + Mage::log('Error creating shipment for order '. $order->getId() .'.', Zend_Log::ERR, 'bitpay.log'); + Mage::logException($e); + } + } + + try + { + if((isset($_bpCreateShipment) && $_bpCreateShipment == true) || Mage::getStoreConfig('payment/Bitcoins/order_disposition')) + { + $order->setState('Complete', 'complete', 'Completed by BitPay payments.', true); + } + else + { + $order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, 'processing', 'BitPay has confirmed the payment.', false); + } + + if(!$order->getEmailSent()) + { + $order->sendNewOrderEmail(); + } + + $order->save(); + } + catch (Exception $e) + { Mage::log($e->getMessage(), Zend_Log::EMERG, 'bitpay.log'); Mage::logException($e); - } } - } - } else { - Mage::log('MarkOrderComplete called but order '. $order->getId() .' has an outstanding balance that has not been paid.', Zend_Log::WARN, 'bitpay.log'); + } - */ - // If the $_bpCreateShipment option is set to true above, this code will - // programmatically create a shipment for you. By design, this will mark - // the entire order as 'complete'. - if(isset($_bpCreateShipment) && $_bpCreateShipment == true) { - try { - $shipment = $order->prepareShipment(); - if($shipment) { - $shipment->register(); - $order->setIsInProcess(true); - $transaction_save = Mage::getModel('core/resource_transaction') - ->addObject($shipment) - ->addObject($shipment->getOrder()) - ->save(); + /** + * @param $order + */ + public function MarkOrderCancelled($order) + { + try + { + $order->setState(Mage_Sales_Model_Order::STATE_CANCELLED, true)->save(); + } + catch (Exception $e) + { + Mage::log('Could not cancel order '. $order->getId() .'.', null, 'bitpay.log'); + Mage::logException($e); } - } catch (Exception $e) { - Mage::log('Error creating shipment for order '. $order->getId() .'.', Zend_Log::ERR, 'bitpay.log'); - Mage::logException($e); - } } - try { - if((isset($_bpCreateShipment) && $_bpCreateShipment == true) || Mage::getStoreConfig('payment/Bitcoins/order_disposition')) { - $order->setState('Complete', 'complete', 'Completed by BitPay payments.', true); - } else { - $order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, 'processing', 'BitPay has confirmed the payment.', false); - } - if(!$order->getEmailSent()) $order->sendNewOrderEmail(); - $order->save(); - } catch (Exception $e) { - Mage::log($e->getMessage(), Zend_Log::EMERG, 'bitpay.log'); - Mage::logException($e); - } + /** + * given Mage_Core_Model_Abstract, return api-friendly address + * + * @param $address + * + * @return array + */ + public 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(); - } - - public function MarkOrderCancelled($order) { - try { - $order->setState(Mage_Sales_Model_Order::STATE_CANCELLED, true)->save(); - } catch (Exception $e) { - Mage::log('Could not cancel order '. $order->getId() .'.', null, 'bitpay.log'); - Mage::logException($e); + // 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; } - } - - // given Mage_Core_Model_Abstract, return api-friendly address - public 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; - } - - public 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('checkout/onepage/success'), - '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', Zend_Log::CRIT, 'bitpay.log'); - Mage::log($invoice['error'], Zend_Log::CRIT, 'bitpay.log'); - Mage::throwException("Error creating BitPay 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']); + + /** + * @param $payment + * @param $amount + * + * @return Bitpay_Bitcoins_Model_PaymentMethod + */ + public 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('checkout/onepage/success'), + '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', Zend_Log::CRIT, 'bitpay.log'); + Mage::log($invoice['error'], Zend_Log::CRIT, 'bitpay.log'); + Mage::throwException("Error creating BitPay 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; } - 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', Zend_Log::ERR, 'bitpay.log'); - return false; + /** + * @return string + */ + public function getOrderPlaceRedirectUrl() + { + if (Mage::getStoreConfig('payment/Bitcoins/fullscreen')) + { + return Mage::getSingleton('customer/session')->getRedirectUrl(); + } + else + { + return ''; + } } - - // 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 + + /** + * computes a unique hash determined by the contents of the cart + * + * @param string $quoteId + * + * @return boolean|string + */ + public function getQuoteHash($quoteId) + { + $quote = Mage::getModel('sales/quote')->load($quoteId, 'entity_id'); + if (!$quote) + { + Mage::log('getQuoteTimestamp: quote not found', Zend_Log::ERR, '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 + + return $hash; } - - $hash = base64_encode(hash_hmac('sha256', $description, $quoteId)); - $hash = substr($hash, 0, 30); // fit it in posData maxlen - - return $hash; - } - } -?> diff --git a/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn.php b/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn.php index c192d53..e9fe8c9 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn.php +++ b/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn.php @@ -19,10 +19,10 @@ * */ -class Bitpay_Bitcoins_Model_Resource_Ipn extends Mage_Core_Model_Resource_Db_Abstract { - protected function _construct() { - $this->_init('Bitcoins/ipn', 'id'); - } +class Bitpay_Bitcoins_Model_Resource_Ipn extends Mage_Core_Model_Resource_Db_Abstract +{ + protected function _construct() + { + $this->_init('Bitcoins/ipn', 'id'); + } } - -?> diff --git a/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn/Collection.php b/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn/Collection.php index 871918b..3528162 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn/Collection.php +++ b/app/code/community/Bitpay/Bitcoins/Model/Resource/Ipn/Collection.php @@ -19,10 +19,13 @@ * */ -class Bitpay_Bitcoins_Model_Resource_Ipn_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract { - protected function _construct() { - $this->_init('Bitcoins/ipn'); - } -} +class Bitpay_Bitcoins_Model_Resource_Ipn_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ -?> + /** + */ + protected function _construct() + { + $this->_init('Bitcoins/ipn'); + } +} diff --git a/app/code/community/Bitpay/Bitcoins/Model/Source/Speed.php b/app/code/community/Bitpay/Bitcoins/Model/Source/Speed.php index 145a1c6..9fe9203 100644 --- a/app/code/community/Bitpay/Bitcoins/Model/Source/Speed.php +++ b/app/code/community/Bitpay/Bitcoins/Model/Source/Speed.php @@ -19,22 +19,27 @@ * */ -class Bitpay_Bitcoins_Model_Source_Speed { - public function toOptionArray() { - return array( - array( - 'value' => 'low', - 'label' => 'Low', - ), - array( - 'value' => 'medium', - 'label' => 'Medium', - ), - array( - 'value' => 'high', - 'label' => 'High', - )); - } -} +class Bitpay_Bitcoins_Model_Source_Speed +{ -?> + /** + * @return array + */ + public function toOptionArray() + { + return array( + array( + 'value' => 'low', + 'label' => 'Low', + ), + array( + 'value' => 'medium', + 'label' => 'Medium', + ), + array( + 'value' => 'high', + 'label' => 'High', + ) + ); + } +} diff --git a/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php b/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php index af8d3b9..a6e2536 100644 --- a/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php +++ b/app/code/community/Bitpay/Bitcoins/controllers/IndexController.php @@ -20,71 +20,88 @@ */ // callback controller -class Bitpay_Bitcoins_IndexController extends Mage_Core_Controller_Front_Action { +class Bitpay_Bitcoins_IndexController extends Mage_Core_Controller_Front_Action +{ - public function checkForPaymentAction() { - $params = $this->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'; - $apiKey = Mage::getStoreConfig('payment/Bitcoins/api_key'); - $invoice = bpVerifyNotification($apiKey); - - if (is_string($invoice)) - Mage::log("bitpay callback error: $invoice", Zend_Log::ERR, '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); + /** + */ + public function checkForPaymentAction() + { + $params = $this->getRequest()->getParams(); + $quoteId = $params['quote']; + $paid = Mage::getModel('Bitcoins/ipn')->GetQuotePaid($quoteId); + print json_encode(array('paid' => $paid)); + exit(); + } - // update the order if it exists already - if ($order->getId()) { - switch($invoice['status']) { + /** + * bitpay's IPN lands here + */ + public function indexAction() { + require Mage::getBaseDir('lib').'/bitpay/bp_lib.php'; + $apiKey = Mage::getStoreConfig('payment/Bitcoins/api_key'); + $invoice = bpVerifyNotification($apiKey); - case 'paid': - // Mark paid if there is an outstanding total - if ($order->getTotalDue() > 0) { - $method = Mage::getModel('Bitcoins/paymentMethod'); - $method->MarkOrderPaid($order); - } else { - Mage::log('Received a PAID notification from BitPay but there is nothing due on this invoice. Ignoring this IPN.', null, 'bitpay.log'); + if (is_string($invoice)) + { + Mage::log("bitpay callback error: $invoice", Zend_Log::ERR, 'bitpay.log'); + } + else + { + // get the order + if (isset($invoice['posData']['quoteId'])) + { + $quoteId = $invoice['posData']['quoteId']; + $order = Mage::getModel('sales/order')->load($quoteId, 'quote_id'); } - break; - - case 'confirmed': - case 'complete': - // Mark confirmed/complete if the order has been paid - if ($order->getTotalDue() <= 0) { - $method = Mage::getModel('Bitcoins/paymentMethod'); - $method->MarkOrderComplete($order); - } else { - Mage::log('Received a ' . $invoice['status'] . ' notification from BitPay but this order is not paid yet. Possible internal error with Magento. Check order status to confirm.', Zend_Log::ERR, 'bitpay.log'); + else + { + $orderId = $invoice['posData']['orderId']; + $order = Mage::getModel('sales/order')->loadByIncrementId($orderId); } - break; - case 'invalid': - $method = Mage::getModel('Bitcoins/paymentMethod'); - $method->MarkOrderCancelled($order); - break; - } - } + // 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 'paid': + // Mark paid if there is an outstanding total + if ($order->getTotalDue() > 0) + { + $method = Mage::getModel('Bitcoins/paymentMethod'); + $method->MarkOrderPaid($order); + } + else + { + Mage::log('Received a PAID notification from BitPay but there is nothing due on this invoice. Ignoring this IPN.', null, 'bitpay.log'); + } + break; - } + case 'confirmed': + case 'complete': + // Mark confirmed/complete if the order has been paid + if ($order->getTotalDue() <= 0) + { + $method = Mage::getModel('Bitcoins/paymentMethod'); + $method->MarkOrderComplete($order); + } + else + { + Mage::log('Received a ' . $invoice['status'] . ' notification from BitPay but this order is not paid yet. Possible internal error with Magento. Check order status to confirm.', Zend_Log::ERR, 'bitpay.log'); + } + break; + case 'invalid': + $method = Mage::getModel('Bitcoins/paymentMethod'); + $method->MarkOrderCancelled($order); + break; + } + } + } + } } diff --git a/app/design/frontend/base/default/layout/bitcoins.xml b/app/design/frontend/base/default/layout/bitcoins.xml index e2ae5ad..00d2581 100644 --- a/app/design/frontend/base/default/layout/bitcoins.xml +++ b/app/design/frontend/base/default/layout/bitcoins.xml @@ -1,10 +1,9 @@ - - - \ No newline at end of file + + diff --git a/app/design/frontend/base/default/template/bitcoins/iframe.phtml b/app/design/frontend/base/default/template/bitcoins/iframe.phtml index 7a18f4b..e009b5b 100644 --- a/app/design/frontend/base/default/template/bitcoins/iframe.phtml +++ b/app/design/frontend/base/default/template/bitcoins/iframe.phtml @@ -1,24 +1,33 @@ 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; +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/'); +$url = Mage::getUrl('bitpay_callback/index/checkForPayment/'); + if ($request->getScheme() == 'https') +{ $url = str_replace('http://', 'https://', $url); +} ?> 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..858f869 --- /dev/null +++ b/app/design/frontend/base/default/template/bitpay/form/bitpay.phtml @@ -0,0 +1,19 @@ + + +getMethodCode() ?> + 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..a4c49ee --- /dev/null +++ b/app/design/frontend/base/default/template/bitpay/iframe.phtml @@ -0,0 +1,50 @@ +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..444ac8b --- /dev/null +++ b/app/design/frontend/base/default/template/bitpay/info/default.phtml @@ -0,0 +1,9 @@ + +

escapeHtml($this->getMethod()->getTitle()) ?>

+ +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 4317ca4..0000000 --- a/app/etc/modules/Bitpay_Bitcoins.xml +++ /dev/null @@ -1,36 +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..86c25cc --- /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 index 34e324b..7cb68f3 100644 --- a/build.xml +++ b/build.xml @@ -1,27 +1,8 @@ diff --git a/build/build.properties b/build/build.properties index 8881bae..d10344e 100644 --- a/build/build.properties +++ b/build/build.properties @@ -1,6 +1,6 @@ # The MIT License (MIT) # -# Copyright (c) 2011-2014 BitPay LLC +# 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 diff --git a/build/n98-magerun.yaml b/build/n98-magerun.yaml index 246b2ea..d797295 100644 --- a/build/n98-magerun.yaml +++ b/build/n98-magerun.yaml @@ -1,6 +1,6 @@ # The MIT License (MIT) # -# Copyright (c) 2011-2014 BitPay LLC +# 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 diff --git a/build/phpunit.xml.dist b/build/phpunit.xml.dist index 03a3d1a..72a17db 100644 --- a/build/phpunit.xml.dist +++ b/build/phpunit.xml.dist @@ -45,10 +45,10 @@ - ../app/code/community/Bitpay/Bitcoins/ + ../app/code/community/Bitpay/Core/ ../shell - ../app/code/community/Bitpay/Bitcoins/sql + ../app/code/community/Bitpay/Core/sql diff --git a/build/rulesets/phpmd.xml b/build/rulesets/phpmd.xml index acf46e2..feb5ee7 100644 --- a/build/rulesets/phpmd.xml +++ b/build/rulesets/phpmd.xml @@ -3,7 +3,7 @@ /** * The MIT License (MIT) * - * Copyright (c) 2011-2014 BitPay LLC + * 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 diff --git a/build/travis.properties b/build/travis.properties index cb38dee..a21c8f1 100644 --- a/build/travis.properties +++ b/build/travis.properties @@ -1,6 +1,6 @@ # The MIT License (MIT) # -# Copyright (c) 2011-2014 BitPay LLC +# 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 diff --git a/composer.json b/composer.json index 72607a7..af6ed06 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "bitpay/magento-plugin", "description": "Bitcoin payment module using the bitpay.com service", "keywords": ["magento","bitcoin"], - "minimum-stability": "dev", + "minimum-stability": "stable", "type": "magento-plugin", "homepage": "https://github.com/bitpay/magento-plugin", "license": "MIT", @@ -12,25 +12,27 @@ "source": "https://github.com/bitpay/magento-plugin" }, "require": { - "composer/installers": "~1.0" + "composer/installers": "~1.0", + "bitpay/php-client": "~2.0@dev" }, "require-dev": { - "n98/magerun": "*", - "phpmd/phpmd": "*", - "phpdocumentor/phpdocumentor": "*", - "phing/phing": "*", - "pdepend/pdepend" : "1.1.0", - "squizlabs/php_codesniffer": "*", - "phpunit/phpunit": "*", - "phploc/phploc": "*", - "fzaninotto/faker": "*" + "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": "6.0.x-dev" + "dev-master": "2.0.x-dev" } }, "archive": { diff --git a/docs/MagentoInvoiceSettings.png b/docs/MagentoInvoiceSettings.png new file mode 100644 index 0000000000000000000000000000000000000000..eeb016346a53dbe7b501a7b7bf7908576b9680c8 GIT binary patch literal 40649 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

ir8z1b_oaKUo^|ksuQW$Pnf~JbY`%7$-|NN^zAJ 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

#xap@8 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[$key->getId()] = $key; + + $data = serialize($key); + $encryptedData = \Mage::helper('core')->encrypt($data); + $config = new \Mage_Core_Model_Config(); + $config->saveConfig($key->getId(), $encryptedData); + } + + /** + * @inheritdoc + */ + public function load($id) + { + if (isset($this->_keys[$id])) { + return $this->_keys[$id]; + } + + $entity = \Mage::getStoreConfig($id); + + /** + * Not in database + */ + if (empty($entity)) { + return false; + } + + $decodedEntity = unserialize(\Mage::helper('core')->decrypt($entity)); + + if (empty($decodedEntity)) { + return false; + } + + return $decodedEntity; + } +} diff --git a/lib/bitpay/.htaccess b/lib/bitpay/.htaccess deleted file mode 100644 index d107f53..0000000 --- a/lib/bitpay/.htaccess +++ /dev/null @@ -1,5 +0,0 @@ - - Order allow,deny - Deny from all - Satisfy All - diff --git a/lib/bitpay/bp_config_default.php b/lib/bitpay/bp_config_default.php deleted file mode 100644 index 33df7fa..0000000 --- a/lib/bitpay/bp_config_default.php +++ /dev/null @@ -1,49 +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). - * - * @param string $orderId - * @param string $price - * @param string $posData - * @param array $options - * - * @return array - */ -function bpCreateInvoice($orderId, $price, $posData, $options = array()) -{ - global $bpOptions, $bpconfig; - - $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; - - $post = array(); - $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://'.$bpconfig['hostAndPort'].'/api/invoice/', $options['apiKey'], $post); - - return $response; -} - -/** - * Call from your notification handler to convert $_POST data to an object containing invoice data - * - * @param boolean|string $apiKey - * - * @return string|array - */ -function bpVerifyNotification($apiKey = false) -{ - global $bpOptions, $bpconfig; - - 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']; - - if (!array_key_exists('id', $json)) - { - return 'Cannot find invoice ID'; - } - - return bpGetInvoice($json['id'], $apiKey); -} - -/** - * $options can include ('apiKey') - * - * @param string $invoiceId - * @param boolean|string $apiKey - * - * @return string|array - */ -function bpGetInvoice($invoiceId, $apiKey=false) -{ - global $bpOptions, $bpconfig; - - if (!$apiKey) - { - $apiKey = $bpOptions['apiKey']; - } - - $response = bpCurl('https://'.$bpconfig['hostAndPort'].'/api/invoice/'.$invoiceId, $apiKey); - - if (is_string($response)) - { - return $response; // error - } - - $response['posData'] = json_decode($response['posData'], true); - if($bpOptions['verifyPos']) - { - $response['posData'] = $response['posData']['posData']; - } - - return $response; -} diff --git a/lib/bitpay/bp_options.php b/lib/bitpay/bp_options.php deleted file mode 100644 index edff94f..0000000 --- a/lib/bitpay/bp_options.php +++ /dev/null @@ -1,42 +0,0 @@ -files() + ->in($vendorDir . '/bitpay/php-client/src') + ->in($vendorDir . '/symfony/config/') + ->in($vendorDir . '/symfony/filesystem/') + ->in($vendorDir . '/symfony/dependency-injection/') + ->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'); +$xml->addChild('description'); +$xml->addChild('notes'); +$authorsNode = $xml->addChild('authors'); +$authors = array( + array( + 'Joshua Estes', // Name + 'BitPayJoshua', // 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', '6.0.0'); +$extensionsNode = $xml->addChild('extensions'); +foreach (array('gmp', '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; zip -r %s .', $tmpDistDir, $distFile.'.zip') +); +$process->run(); +$process = new \Symfony\Component\Process\Process( + sprintf('cd %s; tar -czf %s *', $tmpDistDir, $distFile.'.tgz') +); +$process->run(); + +// Cleanup +$filesystem->remove($tmpDistDir); diff --git a/shell/bitpay.php b/shell/bitpay.php deleted file mode 100644 index 4257309..0000000 --- a/shell/bitpay.php +++ /dev/null @@ -1,92 +0,0 @@ -getArg('clean')) { - switch ($clean) { - case 'expired': - $this->cleanExpired(); - break; - default: - echo $this->usageHelp(); - return 1; - break; - } - - return 0; - } - - echo $this->usageHelp(); - } - - /** - * Removes expired IPNs from database and updates order if they are - * open - */ - public function cleanExpired() - { - Mage::helper('bitpay')->clearExpired(); - } - - /** - * Display help on how to use this bad boy - */ - public function usageHelp() - { - $figlet = new Zend_Text_Figlet( - array( - 'justification' => Zend_Text_Figlet::JUSTIFICATION_CENTER - ) - ); - - return <<render('BitPay')} - -Usage: php -f bitpay.php - - --clean Delete all IPN records based on - -List of Statuses: - - expired - -USAGE; - } -} - -$shell = new Bitpay_Shell_Bitpay(); -$shell->run(); diff --git a/tests/Bitpay/Bitcoins/Model/PaymentMethodTest.php b/tests/Bitpay/Bitcoins/Model/PaymentMethodTest.php deleted file mode 100644 index 4040d96..0000000 --- a/tests/Bitpay/Bitcoins/Model/PaymentMethodTest.php +++ /dev/null @@ -1,102 +0,0 @@ -assertTrue($paymentMethod->canUseForCurrency('USD')); - $this->assertFalse($paymentMethod->canUseForCurrency('ASDF')); - } - - public function testCanUseCheckout() - { - $this->markTestIncomplete(); - } - - public function testIsApiKeyConfigured() - { - $this->markTestIncomplete(); - } - - public function testIsTransactionSpeedConfigured() - { - $this->markTestIncomplete(); - } - - public function testAuthorize() - { - $this->markTestIncomplete(); - } - - public function testCheckForPayment() - { - $this->markTestIncomplete(); - } - - public function testInvoiceOrder() - { - $this->markTestIncomplete(); - } - - public function testMarkOrderPaid() - { - $this->markTestIncomplete(); - } - - public function testMarkOrderComplete() - { - $this->markTestIncomplete(); - } - - public function testMarkOrderCancelled() - { - $this->markTestIncomplete(); - } - - public function testExtractAddress() - { - $this->markTestIncomplete(); - } - - public function testCreateInvoiceAndRedirect() - { - $this->markTestIncomplete(); - } - - public function testGetOrderPlaceRedirectUrl() - { - $this->markTestIncomplete(); - } - - public function testGetQuoteHash() - { - $this->markTestIncomplete(); - } -} diff --git a/tests/Bitpay/Bitcoins/Helper/DataTest.php b/tests/Bitpay/Core/Helper/DataTest.php similarity index 65% rename from tests/Bitpay/Bitcoins/Helper/DataTest.php rename to tests/Bitpay/Core/Helper/DataTest.php index c077cb1..536a73c 100644 --- a/tests/Bitpay/Bitcoins/Helper/DataTest.php +++ b/tests/Bitpay/Core/Helper/DataTest.php @@ -1,32 +1,11 @@ getStore()->setConfig('payment/Bitcoins/api_key', null); + $this->assertSame( + 'payment_bitpay.log', + Mage::helper('bitpay')->getLogFile() + ); + } - $this->assertFalse(Mage::helper('bitpay')->hasApiKey()); + public function testDebugData() + { + Mage::helper('bitpay')->debugData('Testing'); } - public function testHasApiKeyTrue() + public function testIsDebugMode() { - Mage::app()->getStore()->setConfig('payment/Bitcoins/api_key', 'ThisIsMyApiKey'); + Mage::app()->getStore()->setConfig('payment/bitpay/debug', null); + $this->assertFalse(Mage::helper('bitpay')->isDebug()); - $this->assertTrue(Mage::helper('bitpay')->hasApiKey()); + 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/Bitcoins/speed', null); + Mage::app()->getStore()->setConfig('payment/bitpay/speed', null); $this->assertFalse(Mage::helper('bitpay')->hasTransactionSpeed()); } public function testHasTransactionSpeedTrue() { - Mage::app()->getStore()->setConfig('payment/Bitcoins/speed', 'low'); + Mage::app()->getStore()->setConfig('payment/bitpay/speed', 'low'); $this->assertTrue(Mage::helper('bitpay')->hasTransactionSpeed()); } - public function testCleanExpired() + /** + * Location where BitPay IPNs will go + */ + public function testGetNotificationUrl() { - // Create a few expired/invalid ipns - $invalidIpn = $this->createInvalidIpn(); - $expiredIpn = $this->createExpiredIpn(); + $this->assertSame( + 'http://www.localhost.com/bitpay/ipn/', + Mage::helper('bitpay')->getNotificationUrl() + ); + } - // Are the IPNs in the database? - $ipn = Mage::getModel('Bitcoins/ipn'); - $dbInvalidIpn = $ipn->load($invalidIpn->getId())->toArray(); - $dbExpiredIpn = $ipn->load($expiredIpn->getId())->toArray(); - $this->assertArrayHasKey('id', $dbInvalidIpn); - $this->assertArrayHasKey('id', $dbExpiredIpn); - unset($dbInvalidIpn, $dbExpiredIpn); + public function testGetRedirectUrl() + { + $this->assertSame( + 'http://www.localhost.com/checkout/onepage/success/', + Mage::helper('bitpay')->getRedirectUrl() + ); + } + + public function testRegisterAutoloader() + { + Mage::helper('bitpay')->registerAutoloader(); + } - // clean them - Mage::helper('bitpay')->cleanExpired(); + public function testGenerateAndSaveKeys() + { + Mage::helper('bitpay')->generateAndSaveKeys(); + } - // check the database and see if they are still there - $ipn = Mage::getModel('Bitcoins/ipn'); - $dbInvalidIpn = $ipn->load($invalidIpn->getId())->toArray(); - $dbExpiredIpn = $ipn->load($expiredIpn->getId())->toArray(); - $this->assertEmpty($dbInvalidIpn); - $this->assertEmpty($dbExpiredIpn); + public function testGetSinKey() + { + Mage::helper('bitpay')->getSinKey(); } private function createInvalidIpn() { - $ipn = new Bitpay_Bitcoins_Model_Ipn(); + $ipn = new Bitpay_Core_Model_Ipn(); $ipn->setData( array( 'quote_id' => '', @@ -115,7 +114,7 @@ private function createInvalidIpn() private function createExpiredIpn() { $order = $this->createOrder(); - $ipn = new Bitpay_Bitcoins_Model_Ipn(); + $ipn = new Bitpay_Core_Model_Ipn(); $ipn->setData( array( 'quote_id' => '', @@ -166,7 +165,7 @@ private function createOrder() 'save_in_address_book' => 0, 'use_for_shipping' => 1, 'street' => array( - self::$faker->streetAddress + self::$faker->streetAddress, ), ); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index b0a1ca1..1879b74 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,30 +1,10 @@ Date: Thu, 16 Oct 2014 14:26:51 -0400 Subject: [PATCH 155/315] Version bump and rebuild since Magento Connect somehow wasn't giving the correct package when using the extension key given --- app/code/community/Bitpay/Core/etc/config.xml | 2 +- scripts/package | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/code/community/Bitpay/Core/etc/config.xml b/app/code/community/Bitpay/Core/etc/config.xml index 877f18d..2046dd9 100644 --- a/app/code/community/Bitpay/Core/etc/config.xml +++ b/app/code/community/Bitpay/Core/etc/config.xml @@ -8,7 +8,7 @@ - 2.0.0 + 2.0.1 diff --git a/scripts/package b/scripts/package index 51eb3c6..c259681 100755 --- a/scripts/package +++ b/scripts/package @@ -11,7 +11,7 @@ date_default_timezone_set('America/New_York'); // Main Office is in Eastern Time /** * Various Configuration Settings */ -$version = '2.0.0'; +$version = '2.0.1'; $vendorDir = __DIR__ . '/../vendor'; $distDir = __DIR__ . '/../build/dist'; $tmpDistDir = $distDir . '/tmp'; // Files will be placed here temporarly so we can zip/tar them. @@ -131,10 +131,10 @@ $xml->asXml($tmpDistDir . '/package.xml'); $filesystem->remove($distFile.'.zip'); $filesystem->remove($distFile.'.tgz'); -$process = new \Symfony\Component\Process\Process( - sprintf('cd %s; zip -r %s .', $tmpDistDir, $distFile.'.zip') -); -$process->run(); +//$process = new \Symfony\Component\Process\Process( +// sprintf('cd %s; zip -r %s .', $tmpDistDir, $distFile.'.zip') +//); +//$process->run(); $process = new \Symfony\Component\Process\Process( sprintf('cd %s; tar -czf %s *', $tmpDistDir, $distFile.'.tgz') ); From cc639055094061f7215b4cb791e072d801d7e669 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Tue, 18 Nov 2014 18:16:17 -0500 Subject: [PATCH 156/315] Update for invalid input stream from IPN post #74 * Performs check to ensure the file_get_contents() function is not disabled and attempts to enable. * Prior to PHP 5.6, php://input couldn't be reliably re-read. This patch reads it once and works with that data via the raw_post_data variable. * https://github.com/bitpay/magento-plugin/issues/74 --- .../Bitpay/Core/controllers/IpnController.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index 490b2fd..b0d8b95 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -19,17 +19,27 @@ class Bitpay_Core_IpnController extends Mage_Core_Controller_Front_Action */ public function indexAction() { + if (!ini_get('allow_url_fopen')) { + ini_set('allow_url_fopen', true); + } + + $raw_post_data = file_get_contents('php://input'); + + if ($raw_post_data === false) { + 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('Incoming IPN from bitpay'), getallheaders(), - file_get_contents('php://input'), + $raw_post_data, ) ); // Magento doesn't seem to have a way to get the Request body - $ipn = json_decode(file_get_contents('php://input')); + $ipn = json_decode($raw_post_data); $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); From 1a315a6ba415164f4f691a214220eec6dacc513b Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Mon, 24 Nov 2014 09:19:12 -0500 Subject: [PATCH 157/315] Updates for release 2.1.0 --- CHANGELOG | 8 ++++++++ composer.json | 2 +- release.txt | 4 ++++ scripts/package | 2 +- 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 release.txt diff --git a/CHANGELOG b/CHANGELOG index a1907a9..300f95a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,11 @@ +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 diff --git a/composer.json b/composer.json index af6ed06..74767b2 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "2.2.x-dev" } }, "archive": { diff --git a/release.txt b/release.txt new file mode 100644 index 0000000..0ce45a1 --- /dev/null +++ b/release.txt @@ -0,0 +1,4 @@ +[ ] Update version in composer.json file +[ ] Update version in `scripts/package` and run script +[ ] Update CHANGELOG file +[ ] Update to Mangento Connect and GitHub diff --git a/scripts/package b/scripts/package index c259681..d9d6822 100755 --- a/scripts/package +++ b/scripts/package @@ -11,7 +11,7 @@ date_default_timezone_set('America/New_York'); // Main Office is in Eastern Time /** * Various Configuration Settings */ -$version = '2.0.1'; +$version = '2.1.0'; $vendorDir = __DIR__ . '/../vendor'; $distDir = __DIR__ . '/../build/dist'; $tmpDistDir = $distDir . '/tmp'; // Files will be placed here temporarly so we can zip/tar them. From 3e68f5579a32842718455b008cdc7f78445afb4c Mon Sep 17 00:00:00 2001 From: Sam Bohler Date: Tue, 9 Dec 2014 12:50:36 -0500 Subject: [PATCH 158/315] Sanitizes the label sent with the Pairing Code to BitPay --- app/code/community/Bitpay/Core/Helper/Data.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Helper/Data.php b/app/code/community/Bitpay/Core/Helper/Data.php index b67a2ab..9f7e223 100644 --- a/app/code/community/Bitpay/Core/Helper/Data.php +++ b/app/code/community/Bitpay/Core/Helper/Data.php @@ -118,11 +118,15 @@ public function sendPairingRequest($pairingCode) sprintf('Sending Pairing Request for SIN "%s"', (string) $sin) ); + // Sanitize label + $label = preg_replace('/[^a-zA-Z0-9 \-\_\.]/', '', Mage::app()->getStore()->getName()); + $label = substr('Magento - '.$label, 0, 59); + $token = $this->getBitpayClient()->createToken( array( 'id' => (string) $sin, 'pairingCode' => $pairingCode, - 'label' => sprintf('[Magento Store] %s', Mage::app()->getStore()->getName()), + 'label' => $label, ) ); From b753942560c39e0c4e6386646d9678d58eca7b9e Mon Sep 17 00:00:00 2001 From: Sam Bohler Date: Thu, 11 Dec 2014 16:52:36 -0500 Subject: [PATCH 159/315] Updated version for tagging, lib version, typos --- CHANGELOG | 4 ++ .../Core/controllers/IndexController.php | 2 - .../Bitpay/Core/controllers/IpnController.php | 2 +- app/code/community/Bitpay/Core/etc/system.xml | 40 ------------------- composer.json | 2 +- scripts/package | 12 +++--- 6 files changed, 12 insertions(+), 50 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 300f95a..658af47 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +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 diff --git a/app/code/community/Bitpay/Core/controllers/IndexController.php b/app/code/community/Bitpay/Core/controllers/IndexController.php index 98263e5..2066c51 100644 --- a/app/code/community/Bitpay/Core/controllers/IndexController.php +++ b/app/code/community/Bitpay/Core/controllers/IndexController.php @@ -21,8 +21,6 @@ public function indexAction() Mage::helper('bitpay')->debugData( $params ); - //$quoteId = $params['quote']; - //$paid = Mage::getModel('bitpay/ipn')->getQuotePaid($quoteId); } $this->loadLayout(); diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index b0d8b95..0f832da 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -107,7 +107,7 @@ public function indexAction() $state = Mage::getStoreConfig(sprintf('payment/bitpay/invoice_%s', $invoice->getStatus())); $order->addStatusToHistory( $state, - sprintf('Incoming IPN status "%s" updateded order state to "%s"', $invoice->getStatus(), $state) + sprintf('Incoming IPN status "%s" updated order state to "%s"', $invoice->getStatus(), $state) )->save(); } } diff --git a/app/code/community/Bitpay/Core/etc/system.xml b/app/code/community/Bitpay/Core/etc/system.xml index df77255..b009e82 100644 --- a/app/code/community/Bitpay/Core/etc/system.xml +++ b/app/code/community/Bitpay/Core/etc/system.xml @@ -167,17 +167,6 @@ 1 1 - @@ -217,26 +206,6 @@ 1 1 - adminhtml/system_config_form_field_heading @@ -248,15 +217,6 @@ 1 1 - - - gmp - bitpay/adminhtml_system_config_form_field_extension - 510 - 1 - 1 - 1 - openssl diff --git a/composer.json b/composer.json index 74767b2..fd5a6ee 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ }, "require": { "composer/installers": "~1.0", - "bitpay/php-client": "~2.0@dev" + "bitpay/php-client": "2.2.*" }, "require-dev": { "symfony/finder": "~2.3", diff --git a/scripts/package b/scripts/package index d9d6822..8fbb675 100755 --- a/scripts/package +++ b/scripts/package @@ -11,11 +11,11 @@ date_default_timezone_set('America/New_York'); // Main Office is in Eastern Time /** * Various Configuration Settings */ -$version = '2.1.0'; +$version = '2.1.1'; $vendorDir = __DIR__ . '/../vendor'; $distDir = __DIR__ . '/../build/dist'; $tmpDistDir = $distDir . '/tmp'; // Files will be placed here temporarly so we can zip/tar them. -$distFile = $distDir . '/Bitpay_Core-'.$version; // Without extension +$distFile = $distDir . '/BitPay_Magento-'.$version; // Without extension require_once $vendorDir . '/autoload.php'; @@ -61,7 +61,7 @@ $filesystem->copy('README.md', sprintf('%s/app/code/community/Bitpay/Core/README * Need to create the package.xml file required by Magento */ $xml = simplexml_load_string(''); -$xml->addChild('name', 'Bitpay_Core'); +$xml->addChild('name', 'BitPay'); $xml->addChild('version', $version); $xml->addChild('stability', 'stable'); $xml->addChild('license', 'MIT') @@ -74,8 +74,8 @@ $xml->addChild('notes'); $authorsNode = $xml->addChild('authors'); $authors = array( array( - 'Joshua Estes', // Name - 'BitPayJoshua', // Magento Connect Username + 'Integrations Team', // Name + 'BitPayInc', // Magento Connect Username 'support@bitpay.com', // Email ), ); @@ -93,7 +93,7 @@ $requiredNode = $xml->addChild('required', 'php'); $requiredNode->addAttribute('php_min', '5.4.0'); $requiredNode->addAttribute('php_max', '6.0.0'); $extensionsNode = $xml->addChild('extensions'); -foreach (array('gmp', 'openssl', 'mcrypt') as $ext) { +foreach (array('openssl', 'mcrypt') as $ext) { $extNode = $extensionsNode->addChild('name', $ext); $extNode->addChild('min'); $extNode->addChild('max'); From 09d6e3f2e6f282a343e0aff13a72eed858568d3a Mon Sep 17 00:00:00 2001 From: Sam Bohler Date: Thu, 11 Dec 2014 17:58:55 -0500 Subject: [PATCH 160/315] Revert package identifier --- scripts/package | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/package b/scripts/package index 8fbb675..5cd9d0a 100755 --- a/scripts/package +++ b/scripts/package @@ -15,7 +15,7 @@ $version = '2.1.1'; $vendorDir = __DIR__ . '/../vendor'; $distDir = __DIR__ . '/../build/dist'; $tmpDistDir = $distDir . '/tmp'; // Files will be placed here temporarly so we can zip/tar them. -$distFile = $distDir . '/BitPay_Magento-'.$version; // Without extension +$distFile = $distDir . '/Bitpay_Core-'.$version; // Without extension require_once $vendorDir . '/autoload.php'; @@ -61,7 +61,7 @@ $filesystem->copy('README.md', sprintf('%s/app/code/community/Bitpay/Core/README * Need to create the package.xml file required by Magento */ $xml = simplexml_load_string(''); -$xml->addChild('name', 'BitPay'); +$xml->addChild('name', 'Bitpay_Core'); $xml->addChild('version', $version); $xml->addChild('stability', 'stable'); $xml->addChild('license', 'MIT') From 9f8c842f9e5a2bb9abb4d28069bce4a35fcb6c60 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Tue, 16 Dec 2014 11:01:01 -0500 Subject: [PATCH 161/315] Added more error handling and debug logging --- lib/Bitpay/Storage/MagentoStorage.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/Bitpay/Storage/MagentoStorage.php b/lib/Bitpay/Storage/MagentoStorage.php index 16e7f74..c9f5a58 100644 --- a/lib/Bitpay/Storage/MagentoStorage.php +++ b/lib/Bitpay/Storage/MagentoStorage.php @@ -27,7 +27,13 @@ public function persist(\Bitpay\KeyInterface $key) $data = serialize($key); $encryptedData = \Mage::helper('core')->encrypt($data); $config = new \Mage_Core_Model_Config(); - $config->saveConfig($key->getId(), $encryptedData); + + 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.'); + } } /** @@ -35,7 +41,7 @@ public function persist(\Bitpay\KeyInterface $key) */ public function load($id) { - if (isset($this->_keys[$id])) { + if (true === isset($id) && true === isset($this->_keys[$id])) { return $this->_keys[$id]; } @@ -44,16 +50,20 @@ public function load($id) /** * Not in database */ - if (empty($entity)) { + 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.'); return false; } $decodedEntity = unserialize(\Mage::helper('core')->decrypt($entity)); - if (empty($decodedEntity)) { + 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 . '.'); return false; } + Mage::helper('bitpay')->debugData('[INFO] Call to MagentoStorage::load($id) with the id of ' . $id . ' successfully decrypted & unserialized the entity ' . $entity . '.'); + return $decodedEntity; } } From 99deff4007ee69779e625ae16892c909759f4785 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Tue, 16 Dec 2014 11:01:37 -0500 Subject: [PATCH 162/315] Formatting --- lib/Bitpay/Storage/MagentoStorage.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Bitpay/Storage/MagentoStorage.php b/lib/Bitpay/Storage/MagentoStorage.php index c9f5a58..20f790b 100644 --- a/lib/Bitpay/Storage/MagentoStorage.php +++ b/lib/Bitpay/Storage/MagentoStorage.php @@ -51,14 +51,14 @@ public function load($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.'); + 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.'); return false; } $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 . '.'); + Mage::helper('bitpay')->debugData('[INFO] Call to MagentoStorage::load($id) with the id of ' . $id . ' could not decrypt & unserialize the entity ' . $entity . '.'); return false; } From 0007eb3ec1d6b90781dead97fce7dd2a5f565011 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Tue, 16 Dec 2014 18:58:37 -0500 Subject: [PATCH 163/315] Rewrite to be more usable and helpful --- scripts/delete | 114 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 7 deletions(-) diff --git a/scripts/delete b/scripts/delete index 695d693..931d83c 100755 --- a/scripts/delete +++ b/scripts/delete @@ -1,10 +1,110 @@ #!/usr/bin/env sh -#### # -# Script that removes old BitPay extension +# (c) 2014 BitPay, Inc. # -rm -vrf app/code/community/Bitpay/Bitcoins -rm -v app/design/frontend/base/default/layout/bitcoins.xml -rm -vrf app/etc/modules/Bitpay_Bitcoins.xml -rm -vrf lib/bitpay -rm -v shell/bitpay.php +# Shell script that locates and removes +# any previously installed BitPay Magento +# plugin files. +# +# Written by Rich Morgan + +#!/bin/bash + +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" + +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 /usr/local/zend/apache2 -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 -n "Is this correct? (y/n) > " +else + 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 -n "Should I default to $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 -vrfd $fullname + else + echo " $filename does not exist - skipping!" + fi + done + + echo -n "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 -n "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 -vrfd $fullname + else + echo " $filename does not exist - skipping!" + fi + done + echo -n "Process complete." + fi +fi From cfe6e132d1716f04b45e9b858fa6d7eb25eecbac Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Wed, 17 Dec 2014 11:03:02 -0500 Subject: [PATCH 164/315] Removed hardcoded debugging values Also added checks to see if files were still present after the attempted cleaning process --- scripts/delete | 68 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/scripts/delete b/scripts/delete index 931d83c..1ffb56c 100755 --- a/scripts/delete +++ b/scripts/delete @@ -8,8 +8,10 @@ # # Written by Rich Morgan -#!/bin/bash - +# 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" @@ -45,11 +47,14 @@ 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 /usr/local/zend/apache2 -name Mage.php -type f | head -n 1 2>/dev/null` +i=`find /var /usr /opt -name Mage.php -type f | head -n 1 2>/dev/null` if [ -e $i ] then @@ -57,13 +62,16 @@ then cd $DIR && cd ../ mage_dir=`pwd` echo "It looks like Magento is installed in the $mage_dir directory." - echo -n "Is this correct? (y/n) > " + 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 -n "Should I default to $DIR ? (y/n) > " + echo "Should I default to $mage_dir ? (y/n) > " fi read answer @@ -77,17 +85,35 @@ then if [ -e $fullname ] then echo " Found $fullname - removing!" - rm -vrfd $fullname + rm $RMOPTS $fullname else echo " $filename does not exist - skipping!" fi done - - echo -n "Process complete." + 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 -n "Full path or QUIT ? > " + echo "Full path or QUIT ? > " read answer if [ $answer = "QUIT" ] then @@ -100,11 +126,31 @@ else if [ -e $fullname ] then echo " Found $fullname - removing!" - rm -vrfd $fullname + rm $RMOPTS $fullname else echo " $filename does not exist - skipping!" fi done - echo -n "Process complete." + 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 From 44534b508a46d7b617ec84f954490ac1bdd7a155 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Wed, 17 Dec 2014 12:43:50 -0500 Subject: [PATCH 165/315] Rename delete to delete.sh --- scripts/{delete => delete.sh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scripts/{delete => delete.sh} (100%) diff --git a/scripts/delete b/scripts/delete.sh similarity index 100% rename from scripts/delete rename to scripts/delete.sh From 7e5e84b7d15377221a23888db3cea8d52f3cdae2 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Wed, 17 Dec 2014 12:55:15 -0500 Subject: [PATCH 166/315] Formatting and adding more information --- README.md | 175 ++++++++++++++++++------------------------------------ 1 file changed, 58 insertions(+), 117 deletions(-) diff --git a/README.md b/README.md index 056981c..4101cb9 100644 --- a/README.md +++ b/README.md @@ -5,130 +5,86 @@ bitpay/magento-plugin [![Build Status](https://travis-ci.org/bitpay/magento-plugin.svg?branch=master)](https://travis-ci.org/bitpay/magento-plugin) -# Brief Description -BitPay has the most powerful bitcoin infrastructure on the planet and is trusted by over 40,000 merchants. +# Description -# Detail Description +Bitcoin is a powerful new peer-to-peer platform for the next generation of financial technology. The decentralized nature of the Bitcoin network allows for a highly resilient value transfer infrastructure, and this allows merchants to gain greater profits. -Bitcoin is a powerful new peer-to-peer platform for the next generation of -financial technology. The decentralized nature of the Bitcoin network allows -for a highly resilient value transfer infrastructure, and this allows merchants -to gain greater profits. +This is because there are little to no fees for transferring Bitcoins from one person to another. Unlike other payment methods, Bitcoin payments cannot be reversed, so once you are paid you can ship! No waiting days for a payment to clear. -This is because there are little to no fees for transferring Bitcoins from one -person to another. Unlike other payment methods, Bitcoin payments cannot be -reversed, so once you are paid you can ship! No waiting days for a payment to -clear. # Requirements -* [Magento](http://magento.com/resources/system-requirements) >= 1.9.0.1 (Older version will work, but we do not test against those) -* [GMP](http://us2.php.net/gmp) You may have to install this as most servers do not come with it. -* [mcrypt](http://us2.php.net/mcrypt) Magento requires this so you're fine -* [OpenSSL](http://us2.php.net/openssl) Must be compiled with PHP -* PHP >= 5.4 +* [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. -# Upgrade From Version 1.x to 2.x -Merchants who have previous been using the BitPay Magento plugin will need to remove -previous plugin. This can be done by using the [scripts/delete](https://github.com/bitpay/magento-plugin/blob/master/scripts/delete) -script before installing the new plugin. +# Upgrade From Plugin Version 1.x to 2.x -# Installation +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). -## Magento Connect Manager +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. -Goto [http://www.magentocommerce.com/magento-connect/bitpay-payment-method.html](http://www.magentocommerce.com/magento-connect/bitpay-payment-method.html) -and click the *Install Now* link which will give you the *Extension Key* needed -for the next step. +```sh +chmod +x delete.sh +./delete.sh +``` -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. +# Installation -All you need to do is paste the extension key and click on the *Install* button. +## Magento Connect Manager -***WARNING*** It is good practice to backup your database before installing -extensions. Please make sure you Create Backups. +Goto [http://www.magentocommerce.com/magento-connect/bitpay-payment-method.html](http://www.magentocommerce.com/magento-connect/bitpay-payment-method.html) and click the *Install Now* link which will give you the *Extension Key* needed for the next step. -## Download +Once you have the key, log into you Magento Store's Admin Panel and navigate to **System > Magento Connect > Magento Connect Manager**. -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. +***NOTE*** It may ask you to log in again using the same credentials that you use to log into the Admin Panel. -***NOTE*** These files can also up uploaded using the *Magento Connect Manager* -that comes with your Magento Store +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. +***WARNING*** It is good practice to backup your database before installing extensions. Please make sure you Create Backups. -## modman -Using [modman](https://github.com/colinmollenhour/modman) you can -install the BitPay Magento Plugin. Once -you have modman installed, run `modman init` if you have not already done so. Next -just run `modman clone https://github.com/bitpay/magento-plugin.git` in the root -of the Magento installation. In this case it is `/var/www/magento`. +## Download + +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. -## Development +***NOTE*** These files can also up uploaded using the *Magento Connect Manager* that comes with your Magento Store -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. +***WARNING*** It is good practice to backup your database before installing extensions. Please make sure you Create Backups. -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. -Once you are done, 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. +## modman -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. +Using [modman](https://github.com/colinmollenhour/modman) you can install the BitPay Magento Plugin. Once you have modman installed, run `modman init` if you have not already done so. Next just run `modman clone https://github.com/bitpay/magento-plugin.git` in the root of the Magento installation. In this case it is `/var/www/magento`. -***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. # 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**. +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. +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*** 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. +***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. +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. +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. -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. # Support @@ -143,30 +99,19 @@ fulfillment. * [Homepage](http://magento.com) * [Documentation](http://docs.magentocommerce.com) -* [Support Forums](https://www.magentocommerce.com/support/ce/) +* [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 for the latest. -5. If all else fails, send an email describing your issue **in detail** to - support@bitpay.com +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 Version (Found at the bottom page in the Administration section) +* 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) @@ -175,9 +120,17 @@ fulfillment. * enabled debugging for this extension and send us `var/log/payment_bitpay.log` * Screen grabs of error message if applicable. + # Contribute -To contribute to this project, please fork and submit a pull request. +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 @@ -185,20 +138,8 @@ 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. +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. From e70b0fa9c3598fd7e0efe2beccfcc96bb698eb40 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Wed, 17 Dec 2014 12:56:54 -0500 Subject: [PATCH 167/315] Cleaned up unused code --- tests/bootstrap.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 1879b74..a2a0e1a 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -6,7 +6,6 @@ if ($mage = realpath(__DIR__.'/../build/magento/app/Mage.php')) { require_once $mage; - //Mage::setIsDeveloperMode(true); Mage::app(); } else { exit('Could not find Mage.php'); From 0ce0cfebcadbe075a4e820f4353e8d00738d0bb9 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Wed, 17 Dec 2014 13:00:20 -0500 Subject: [PATCH 168/315] Fixed incorrect target parameter --- .../template/bitpay/system/config/field/header.phtml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 index 031e411..2076431 100644 --- 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 @@ -11,7 +11,7 @@ ?>

From 14d0351cd05cdc014902c59a0d42d014c5a00a9b Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Wed, 17 Dec 2014 13:00:53 -0500 Subject: [PATCH 169/315] Fixed incorrect target parameter --- .../default/default/template/bitpay/info/default.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/design/adminhtml/default/default/template/bitpay/info/default.phtml b/app/design/adminhtml/default/default/template/bitpay/info/default.phtml index 89dbbf8..ab3dae6 100644 --- a/app/design/adminhtml/default/default/template/bitpay/info/default.phtml +++ b/app/design/adminhtml/default/default/template/bitpay/info/default.phtml @@ -13,6 +13,6 @@ getBitpayInvoiceUrl()): ?>

- View Invoice + View Invoice

From b40d1a60f3dc2d7e69b9ca3b7f09f58180d5908f Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Wed, 17 Dec 2014 13:04:45 -0500 Subject: [PATCH 170/315] Removed unnecessary context switching --- app/design/frontend/base/default/template/bitpay/iframe.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/design/frontend/base/default/template/bitpay/iframe.phtml b/app/design/frontend/base/default/template/bitpay/iframe.phtml index a4c49ee..836804f 100644 --- a/app/design/frontend/base/default/template/bitpay/iframe.phtml +++ b/app/design/frontend/base/default/template/bitpay/iframe.phtml @@ -32,7 +32,7 @@ if ($request->getScheme() == 'https') { //?quote=", + "", { asynchronous: true, evalScripts: true, From 78e3ab0d2c0fa8708c2728144a7a3a6df93665bf Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Wed, 17 Dec 2014 13:06:37 -0500 Subject: [PATCH 171/315] Removed unnecessary context switching --- .../base/default/template/bitpay/info/default.phtml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/design/frontend/base/default/template/bitpay/info/default.phtml b/app/design/frontend/base/default/template/bitpay/info/default.phtml index 444ac8b..905db79 100644 --- a/app/design/frontend/base/default/template/bitpay/info/default.phtml +++ b/app/design/frontend/base/default/template/bitpay/info/default.phtml @@ -3,7 +3,8 @@ * @license Copyright 2011-2014 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ -?> -

escapeHtml($this->getMethod()->getTitle()) ?>

-getChildHtml()?> +echo '

' . $this->escapeHtml($this->getMethod()->getTitle()) . '

'; + +echo $this->getChildHtml(); +?> From 739d702c339e5b607b923ff9db6fdefe10e7568d Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Wed, 17 Dec 2014 13:10:00 -0500 Subject: [PATCH 172/315] Removed unnecessary context switching --- .../default/template/bitpay/form/bitpay.phtml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/app/design/frontend/base/default/template/bitpay/form/bitpay.phtml b/app/design/frontend/base/default/template/bitpay/form/bitpay.phtml index 858f869..c64fa51 100644 --- a/app/design/frontend/base/default/template/bitpay/form/bitpay.phtml +++ b/app/design/frontend/base/default/template/bitpay/form/bitpay.phtml @@ -3,17 +3,14 @@ * @license Copyright 2011-2014 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ -?> -getMethodCode(); + +echo ''; ?> -getMethodCode() ?> - From f35c0e06ca7491fd12a11d8991a96355e0790c0c Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Wed, 17 Dec 2014 13:12:16 -0500 Subject: [PATCH 173/315] Removed unnecessary context switching --- .../bitpay/system/config/field/header.phtml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) 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 index 2076431..72485e3 100644 --- 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 @@ -3,15 +3,16 @@ * @license Copyright 2011-2014 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ -?> -' . + 'BitPay' . + 'Support' . + 'Sign Up' . + 'Login' . + ''; + ?> -
- BitPay - Support - Sign Up - Login -
From f5dc25f23c3cef2d9640d1d3bbf8a69fff5381d0 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Wed, 17 Dec 2014 13:13:03 -0500 Subject: [PATCH 174/315] Escaped single quotes --- .../template/bitpay/system/config/field/header.phtml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 index 72485e3..6c74aed 100644 --- 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 @@ -10,9 +10,9 @@ echo '
' . 'BitPay' . - 'Support' . - 'Sign Up' . - 'Login' . + 'Support' . + 'Sign Up' . + 'Login' . '
'; ?> From b4dc639e197ba8e315da0a4a909237021c7ec279 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Wed, 17 Dec 2014 13:14:50 -0500 Subject: [PATCH 175/315] Removed unnecessary context switching --- .../default/template/bitpay/info/default.phtml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/app/design/adminhtml/default/default/template/bitpay/info/default.phtml b/app/design/adminhtml/default/default/template/bitpay/info/default.phtml index ab3dae6..79122f2 100644 --- a/app/design/adminhtml/default/default/template/bitpay/info/default.phtml +++ b/app/design/adminhtml/default/default/template/bitpay/info/default.phtml @@ -3,16 +3,15 @@ * @license Copyright 2011-2014 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ -?> - -

Ordered with BitPay

-getBitpayInvoiceUrl()): ?> -

- View Invoice -

- +echo '

Ordered with BitPay

'; + +if ($url = $this->getBitpayInvoiceUrl()) { + echo '

View Invoice

'; +} + +?> From 65a68c8f26f4dbccdca3784d282cfdb3bc5544e9 Mon Sep 17 00:00:00 2001 From: Rich Morgan Date: Wed, 17 Dec 2014 13:21:45 -0500 Subject: [PATCH 176/315] Updated information in comments --- app/code/community/Bitpay/Core/etc/system.xml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/app/code/community/Bitpay/Core/etc/system.xml b/app/code/community/Bitpay/Core/etc/system.xml index b009e82..905f108 100644 --- a/app/code/community/Bitpay/Core/etc/system.xml +++ b/app/code/community/Bitpay/Core/etc/system.xml @@ -28,7 +28,7 @@ - https://bitpay.com/api-tokens and put + https://bitpay.com/api-tokens and put the code that was generated in this field. Once you have paired your Mangento store you can begin accepted Bitcoins as payment on your store.]]> @@ -51,8 +51,7 @@ <label>Title</label> <comment> - What your customers will see during their checkout - experience. + What your customers will see during their checkout experience. </comment> <frontend_type>text</frontend_type> <sort_order>20</sort_order> @@ -63,7 +62,7 @@ <network translate="label"> <label>Network</label> <comment> - <![CDATA[You can sign up for a test account at <a href="https://test.bitpay.com">test.bitpay.com</a>.]]> + <![CDATA[You can sign up for a test account at <a href="https://test.bitpay.com" target="_blank">test.bitpay.com</a>.]]> </comment> <frontend_type>select</frontend_type> <source_model>bitpay/network</source_model> @@ -170,8 +169,7 @@ <invoice_paid translate="label"> <label>Paid</label> <comment> - An invoice is considered "paid" when the bitcoin - network sees a transaction. + An invoice is considered "paid" when the Bitcoin network sees a transaction. </comment> <frontend_type>select</frontend_type> <source_model>adminhtml/system_config_source_order_status</source_model> @@ -183,8 +181,7 @@ <invoice_confirmed translate="label"> <label>Confirmed</label> <comment> - A confirmed invoice means that the Bitcoin network - has approved the transaction. + A confirmed invoice means that the Bitcoin network has approved the transaction. </comment> <frontend_type>select</frontend_type> <source_model>adminhtml/system_config_source_order_status</source_model> @@ -196,8 +193,7 @@ <invoice_complete translate="label"> <label>Complete</label> <comment> - Complete invoices mean that you have gotten credit - for the payment in your BitPay merchant account. + Complete invoices mean that the funds for the sale have been confirmed and deposited into your BitPay merchant account. </comment> <frontend_type>select</frontend_type> <source_model>adminhtml/system_config_source_order_status</source_model> @@ -210,7 +206,7 @@ <label>Requirements</label> <frontend_model>adminhtml/system_config_form_field_heading</frontend_model> <comment> - <![CDATA[If you do not meet the requirements, this extension will fail to work.]]> + This extension will not function if you do not meet the system requirements. </comment> <sort_order>500</sort_order> <show_in_default>1</show_in_default> From b9bc6c30ed70ee5c5a947a23792ebd266c944a37 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Wed, 17 Dec 2014 14:02:31 -0500 Subject: [PATCH 177/315] Added extra error handling and debugging --- .../Bitpay/Core/controllers/IpnController.php | 64 +++++++++++++------ 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index 0f832da..827101e 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -19,17 +19,19 @@ class Bitpay_Core_IpnController extends Mage_Core_Controller_Front_Action */ public function indexAction() { - if (!ini_get('allow_url_fopen')) { + if (false === ini_get('allow_url_fopen')) { ini_set('allow_url_fopen', true); } $raw_post_data = file_get_contents('php://input'); - if ($raw_post_data === false) { + if (false === $raw_post_data) { + Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IpnController: 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('Incoming IPN from bitpay'), @@ -39,9 +41,21 @@ public function indexAction() ); // Magento doesn't seem to have a way to get the Request body - $ipn = json_decode($raw_post_data); + $ipn = json_decode($raw_post_data); + + if (true === empty($ipn)) { + Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IpnController: 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->id)) { + Mage::helper('bitpay')->debugData(sprintf('Did not receive order ID in IPN. See IPN "%s" in database.', $mageIpn->getId())); + throw new Exception('Invalid Bitpay IPN 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 @@ -63,16 +77,9 @@ public function indexAction() ) )->save(); - if (empty($ipn->id) || !isset($ipn->posData->id)) { - Mage::helper('bitpay')->debugData( - sprintf('Did not receive order id in IPN. See IPN "%s" in database.', $mageIpn->getId()) - ); - throw new Exception('Invalid Bitpay IPN received.'); - } - $order = Mage::getModel('sales/order')->loadByIncrementId($ipn->posData->id); - if (!$order->getId()) { + if (false === isset($order) || true === empty($order->getId())) { Mage::helper('bitpay')->debugData('Invalid Bitpay IPN received.'); Mage::throwException('Invalid Bitpay IPN received.'); } @@ -84,27 +91,44 @@ public function indexAction() */ $invoice = Mage::getModel('bitpay/method_bitcoin')->fetchInvoice($ipn->id); + if (false === isset($invoice) || true === empty($invoice) { + Mage::helper('bitpay')->debugData('[ERROR] 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 && $invoice->getStatus() != $ipn->status) { - Mage::getModel('bitpay/method_bitcoin')->debugData('IPN status and status from BitPay are different'); - Mage::throwException('There was an error processing the ipn'); + if ($invoice->getStatus() != $ipn->status) { + Mage::getModel('bitpay/method_bitcoin')->debugData('[ERROR] IPN status and status from BitPay are different.'); + Mage::throwException('There was an error processing the IPN - statuses are different.'); } // Does the price match? - if ($invoice && $invoice->getPrice() != $ipn->price) { - Mage::getModel('bitpay/method_bitcoin')>debugData('Price difference'); - Mage::throwException('There was an error processing the ipn'); + if ($invoice->getPrice() != $ipn->price) { + Mage::getModel('bitpay/method_bitcoin')>debugData('[ERROR] IPN price and invoice price are different.'); + Mage::throwException('There was an error processing the IPN - invoice price does not match the IPN price.'); } // Update the order to notifiy that it has been paid - if (in_array($invoice->getStatus(), array('paid', 'confirmed', 'complete'))) { + if (true === in_array($invoice->getStatus(), array('paid', 'confirmed', 'complete'))) { $payment = Mage::getModel('sales/order_payment')->setOrder($order); - $payment->registerCaptureNotification($invoice->getPrice()); - $order->addPayment($payment)->save(); + + if (true === isset($payment) && false === empty($payment)) { + $payment->registerCaptureNotification($invoice->getPrice()); + $order->addPayment($payment)->save(); + } else { + Mage::helper('bitpay')->debugData('[ERROR] Could not create a payment object in the Bitpay IPN controller.'); + Mage::throwException('Could not create a payment object in the Bitpay IPN controller.'); + } } // 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] 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 to in the Bitpay IPN controller.'); + } + $order->addStatusToHistory( $state, sprintf('Incoming IPN status "%s" updated order state to "%s"', $invoice->getStatus(), $state) From 2bf5911860ab35a93ea863158645ab0c072045f5 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Wed, 17 Dec 2014 14:05:33 -0500 Subject: [PATCH 178/315] Removed accidental newlines --- app/code/community/Bitpay/Core/controllers/IpnController.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index 827101e..cf99880 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -4,8 +4,6 @@ * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ - - /** * @route /bitpay/ipn */ From f950bf05a1352b2f0e7c3930741c4facc3f6b044 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Wed, 17 Dec 2014 14:41:42 -0500 Subject: [PATCH 179/315] Added extra error handling and debugging --- .../Bitpay/Core/controllers/IndexController.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/code/community/Bitpay/Core/controllers/IndexController.php b/app/code/community/Bitpay/Core/controllers/IndexController.php index 2066c51..a2fdec9 100644 --- a/app/code/community/Bitpay/Core/controllers/IndexController.php +++ b/app/code/community/Bitpay/Core/controllers/IndexController.php @@ -14,19 +14,19 @@ class Bitpay_Core_IndexController extends Mage_Core_Controller_Front_Action */ public function indexAction() { - $params = $this->getRequest()->getParams(); $paid = false; - if (isset($params['paid'])) { + $params = $this->getRequest()->getParams(); + + if (true === isset($params['paid'])) { Mage::helper('bitpay')->registerAutoloader(); - Mage::helper('bitpay')->debugData( - $params - ); + Mage::helper('bitpay')->debugData($params); + } else { + Mage::helper('bitpay')->debugData('[ERROR] Could not get params from request.'); } $this->loadLayout(); + $this->getResponse()->setHeader('Content-type', 'application/json'); - $this->getResponse()->setBody( - json_encode(array('paid' => $paid)) - ); + $this->getResponse()->setBody(json_encode(array('paid' => $paid))); } } From cf8bcc53fa22cb1228c8b854893baad5778b56a4 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Wed, 17 Dec 2014 14:46:14 -0500 Subject: [PATCH 180/315] Updated to use defined constants --- app/code/community/Bitpay/Core/Model/TransactionSpeed.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/TransactionSpeed.php b/app/code/community/Bitpay/Core/Model/TransactionSpeed.php index 387b37a..f8ddf27 100644 --- a/app/code/community/Bitpay/Core/Model/TransactionSpeed.php +++ b/app/code/community/Bitpay/Core/Model/TransactionSpeed.php @@ -16,9 +16,9 @@ class Bitpay_Core_Model_TransactionSpeed public function toOptionArray() { return array( - array('value' => self::SPEED_LOW, 'label' => Mage::helper('bitpay')->__('Low')), - array('value' => self::SPEED_MEDIUM, 'label' => Mage::helper('bitpay')->__('Medium')), - array('value' => self::SPEED_HIGH, 'label' => Mage::helper('bitpay')->__('High')), + array('value' => self::SPEED_LOW, 'label' => Mage::helper('bitpay')->__(SPEED_LOW)), + array('value' => self::SPEED_MEDIUM, 'label' => Mage::helper('bitpay')->__(SPEED_MEDIUM)), + array('value' => self::SPEED_HIGH, 'label' => Mage::helper('bitpay')->__(SPEED_HIGH)), ); } } From 7c72010beee5bc652a0b60d4008925fc98cb56d5 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Wed, 17 Dec 2014 14:46:55 -0500 Subject: [PATCH 181/315] Fixed missing scope --- app/code/community/Bitpay/Core/Model/TransactionSpeed.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/TransactionSpeed.php b/app/code/community/Bitpay/Core/Model/TransactionSpeed.php index f8ddf27..a8b96db 100644 --- a/app/code/community/Bitpay/Core/Model/TransactionSpeed.php +++ b/app/code/community/Bitpay/Core/Model/TransactionSpeed.php @@ -16,9 +16,9 @@ class Bitpay_Core_Model_TransactionSpeed public function toOptionArray() { return array( - array('value' => self::SPEED_LOW, 'label' => Mage::helper('bitpay')->__(SPEED_LOW)), - array('value' => self::SPEED_MEDIUM, 'label' => Mage::helper('bitpay')->__(SPEED_MEDIUM)), - array('value' => self::SPEED_HIGH, 'label' => Mage::helper('bitpay')->__(SPEED_HIGH)), + array('value' => self::SPEED_LOW, 'label' => Mage::helper('bitpay')->__(self::SPEED_LOW)), + array('value' => self::SPEED_MEDIUM, 'label' => Mage::helper('bitpay')->__(self::SPEED_MEDIUM)), + array('value' => self::SPEED_HIGH, 'label' => Mage::helper('bitpay')->__(self::SPEED_HIGH)), ); } } From 1ccd5b60bf09e4ce855090be28477d44e82dcb14 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Wed, 17 Dec 2014 14:53:36 -0500 Subject: [PATCH 182/315] Updated to use defined constants --- app/code/community/Bitpay/Core/Model/Status.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Status.php b/app/code/community/Bitpay/Core/Model/Status.php index c1f9a6a..b01f2fe 100644 --- a/app/code/community/Bitpay/Core/Model/Status.php +++ b/app/code/community/Bitpay/Core/Model/Status.php @@ -19,12 +19,12 @@ class Bitpay_Core_Model_Status public function toOptionArray() { return array( - array('value' => self::STATUS_NEW, 'label' => Mage::helper('bitpay')->__('New')), - array('value' => self::STATUS_PAID, 'label' => Mage::helper('bitpay')->__('Paid')), - array('value' => self::STATUS_CONFIRMED, 'label' => Mage::helper('bitpay')->__('Confirmed')), - array('value' => self::STATUS_COMPLETE, 'label' => Mage::helper('bitpay')->__('Complete')), - array('value' => self::STATUS_EXPIRED, 'label' => Mage::helper('bitpay')->__('Expired')), - array('value' => self::STATUS_INVALID, 'label' => Mage::helper('bitpay')->__('Invalid')), + array('value' => 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)), ); } } From f2323c659aa0f307cf0eaff1584a996e2e2b1bd1 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Wed, 17 Dec 2014 14:54:57 -0500 Subject: [PATCH 183/315] Ucwords Formatting Fix --- app/code/community/Bitpay/Core/Model/TransactionSpeed.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/TransactionSpeed.php b/app/code/community/Bitpay/Core/Model/TransactionSpeed.php index a8b96db..751fa24 100644 --- a/app/code/community/Bitpay/Core/Model/TransactionSpeed.php +++ b/app/code/community/Bitpay/Core/Model/TransactionSpeed.php @@ -16,9 +16,9 @@ class Bitpay_Core_Model_TransactionSpeed public function toOptionArray() { return array( - array('value' => self::SPEED_LOW, 'label' => Mage::helper('bitpay')->__(self::SPEED_LOW)), - array('value' => self::SPEED_MEDIUM, 'label' => Mage::helper('bitpay')->__(self::SPEED_MEDIUM)), - array('value' => self::SPEED_HIGH, 'label' => Mage::helper('bitpay')->__(self::SPEED_HIGH)), + array('value' => 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)), ); } } From a6b2bac388f2c59b555a0c73b1c7b34977613932 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Wed, 17 Dec 2014 15:42:31 -0500 Subject: [PATCH 184/315] Added extra error handling and debugging Issue #89 opened to complete TODOs. --- .../community/Bitpay/Core/Model/Observer.php | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Observer.php b/app/code/community/Bitpay/Core/Model/Observer.php index b925580..12a4ec3 100644 --- a/app/code/community/Bitpay/Core/Model/Observer.php +++ b/app/code/community/Bitpay/Core/Model/Observer.php @@ -6,61 +6,61 @@ class Bitpay_Core_Model_Observer { - /** + /* + * TODO: Why is this here? */ public function checkForRequest($observer) { } - /** + /* * 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() { - Mage::helper('bitpay')->debugData( - 'cronjob: started' - ); - $apiKey = Mage::getStoreConfig('payment/bitpay/api_key'); - if (empty($apiKey)) { - Mage::helper('bitpay')->debugData( - 'cronjob: Api Key not set.' - ); - - return; // Api Key needs to be set + 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. - * - * If anyone knows of a better way to do this, please let me know */ $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( - 'cronjob: end' - ); + Mage::helper('bitpay')->debugData('[INFO] Bitpay_Core_Model_Observer::updateOrderStates() order status update job finished.'); } /** @@ -69,6 +69,7 @@ public function updateOrderStates() */ public function cleanExpired() { + Mage::helper('bitpay')->debugData('[INFO] Bitpay_Core_Model_Observer::cleanExpired() called.'); Mage::helper('bitpay')->cleanExpired(); } } From 01afabb942af85bd7775713890d61846423624b4 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Wed, 17 Dec 2014 15:44:13 -0500 Subject: [PATCH 185/315] Updated to use defined constants --- app/code/community/Bitpay/Core/Model/Network.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Network.php b/app/code/community/Bitpay/Core/Model/Network.php index 159e267..df8d7fb 100644 --- a/app/code/community/Bitpay/Core/Model/Network.php +++ b/app/code/community/Bitpay/Core/Model/Network.php @@ -18,8 +18,8 @@ class Bitpay_Core_Model_Network public function toOptionArray() { return array( - array('value' => self::NETWORK_LIVENET, 'label' => Mage::helper('bitpay')->__('Livenet')), - array('value' => self::NETWORK_TESTNET, 'label' => Mage::helper('bitpay')->__('Testnet')), + array('value' => self::NETWORK_LIVENET, 'label' => Mage::helper('bitpay')->__(ucwords(self::NETWORK_LIVENET))), + array('value' => self::NETWORK_TESTNET, 'label' => Mage::helper('bitpay')->__(ucwords(self::NETWORK_TESTNET))), ); } } From 369aff6e53c0b2a98f9b26fec029ae8e63ed1a19 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Wed, 17 Dec 2014 15:45:10 -0500 Subject: [PATCH 186/315] Added missing parenthesis --- app/code/community/Bitpay/Core/Model/Status.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Status.php b/app/code/community/Bitpay/Core/Model/Status.php index b01f2fe..83eeb93 100644 --- a/app/code/community/Bitpay/Core/Model/Status.php +++ b/app/code/community/Bitpay/Core/Model/Status.php @@ -19,12 +19,12 @@ class Bitpay_Core_Model_Status public function toOptionArray() { return array( - array('value' => 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)), + array('value' => 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))), ); } } From 1feedfe671538c5e32b50b62e3f46b50b36321a1 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Wed, 17 Dec 2014 15:45:35 -0500 Subject: [PATCH 187/315] Added missing parenthesis --- app/code/community/Bitpay/Core/Model/TransactionSpeed.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/TransactionSpeed.php b/app/code/community/Bitpay/Core/Model/TransactionSpeed.php index 751fa24..c41356e 100644 --- a/app/code/community/Bitpay/Core/Model/TransactionSpeed.php +++ b/app/code/community/Bitpay/Core/Model/TransactionSpeed.php @@ -16,9 +16,9 @@ class Bitpay_Core_Model_TransactionSpeed public function toOptionArray() { return array( - array('value' => 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)), + array('value' => 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))), ); } } From 409eb02c941480364ef28f39443c8ee6731b6b44 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Wed, 17 Dec 2014 15:51:29 -0500 Subject: [PATCH 188/315] Added extra error handling and debugging --- app/code/community/Bitpay/Core/Model/Invoice.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Invoice.php b/app/code/community/Bitpay/Core/Model/Invoice.php index 7733daa..bdb9ff8 100644 --- a/app/code/community/Bitpay/Core/Model/Invoice.php +++ b/app/code/community/Bitpay/Core/Model/Invoice.php @@ -24,18 +24,20 @@ protected function _construct() */ 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(), - //'updated_at' => 'NOW()', 'url' => $invoice->getUrl(), 'pos_data' => $invoice->getPosData(), 'status' => $invoice->getStatus(), 'btc_price' => $invoice->getBtcPrice(), - //'btc_due' => $invoice->getBtcDue(), 'price' => $invoice->getPrice(), 'currency' => $invoice->getCurrency()->getCode(), - //'ex_rates' => $invoice->getExRates(), 'order_id' => $invoice->getOrderId(), 'invoice_time' => $invoice->getInvoiceTime(), 'expiration_time' => $invoice->getExpirationTime(), @@ -43,7 +45,6 @@ public function prepareWithBitpayInvoice($invoice) 'btc_paid' => $invoice->getBtcPaid(), 'rate' => $invoice->getRate(), 'exception_status' => $invoice->getExceptionStatus(), - //'token' => $invoice->getToken(), ) ); @@ -58,6 +59,11 @@ public function prepareWithBitpayInvoice($invoice) */ public function prepateWithOrder($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->getQuoteId(), From c29c10c599da166b6b9cd16ea34a41e2f81dc693 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Wed, 17 Dec 2014 17:02:59 -0500 Subject: [PATCH 189/315] Added extra error handling and debugging --- .../Bitpay/Core/Model/Method/Bitcoin.php | 143 ++++++++++++++---- 1 file changed, 117 insertions(+), 26 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php index 0c20e7b..46810fa 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -12,6 +12,7 @@ class Bitpay_Core_Model_Method_Bitcoin extends Mage_Payment_Model_Method_Abstrac protected $_code = 'bitpay'; protected $_formBlockType = 'bitpay/form_bitpay'; protected $_infoBlockType = 'bitpay/info'; + protected $_isGateway = true; protected $_canAuthorize = true; protected $_canCapture = false; @@ -19,12 +20,14 @@ class Bitpay_Core_Model_Method_Bitcoin extends Mage_Payment_Model_Method_Abstrac protected $_isInitializeNeeded = false; protected $_canFetchTransactionInfo = false; protected $_canManagerRecurringProfiles = false; - //protected $_canUseCheckout = true; - //protected $_canUseForMultishipping = true; - //protected $_canCapturePartial = false; - //protected $_canRefund = false; - //protected $_canVoid = false; + protected $_canUseCheckout = true; + protected $_canUseForMultishipping = true; + protected $_canCapturePartial = false; + protected $_canRefund = false; + protected $_canVoid = false; + protected $_debugReplacePrivateDataKeys = array(); + protected static $_redirectUrl; /** @@ -34,29 +37,41 @@ class Bitpay_Core_Model_Method_Bitcoin extends Mage_Payment_Model_Method_Abstrac */ public function authorize(Varien_Object $payment, $amount) { - $this->debugData('authorizing new order'); + if (false === isset($payment) || false === isset($amount) || true === empty($payment) || true === empty($amount)) { + $this->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.'); + } + + $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.'); + } + $invoice = $this->prepareInvoice($invoice, $payment, $amount); try { $bitpayInvoice = Mage::helper('bitpay')->getBitpayClient()->createInvoice($invoice); } catch (Exception $e) { - $this->debugData($e->getMessage()); + $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::authorize(): ' . $e->getMessage()); $this->debugData( array( Mage::helper('bitpay')->getBitpayClient()->getRequest()->getBody(), Mage::helper('bitpay')->getBitpayClient()->getResponse()->getBody(), ) ); - Mage::throwException('Could not authorize transaction.'); + Mage::throwException('In Bitpay_Core_Model_Method_Bitcoin::authorize(): Could not authorize transaction.'); } self::$_redirectUrl = $bitpayInvoice->getUrl(); + $this->debugData( array( - 'BitPay Invoice created', + '[INFO] BitPay Invoice created', sprintf('Invoice URL: "%s"', $bitpayInvoice->getUrl()), ) ); @@ -67,7 +82,7 @@ public function authorize(Varien_Object $payment, $amount) ->prepateWithOrder($payment->getOrder()) ->save(); - $this->debugData($bitpayInvoice->getId()); + $this->debugData('[INFO] Leaving Bitpay_Core_Model_Method_Bitcoin::authorize(): invoice id ' . $bitpayInvoice->getId()); return $this; } @@ -83,16 +98,18 @@ public function canUseCheckout() { $token = Mage::getStoreConfig('payment/bitpay/token'); - if (empty($token)) { + if (false === isset($token) || true === empty($token)) { /** * Merchant must goto their account and create a pairing code to * enter in. */ - $this->debugData('Magento store does not have a BitPay token.'); + $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; } @@ -104,11 +121,33 @@ public function canUseCheckout() */ 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; } @@ -121,9 +160,12 @@ public function fetchInvoice($id) */ public function extractAddress($address) { - $this->debugData( - sprintf('Extracting addess') - ); + 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['buyerName'] = $address->getName(); @@ -145,7 +187,10 @@ public function extractAddress($address) // 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); + 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; @@ -158,9 +203,7 @@ public function extractAddress($address) */ public function getOrderPlaceRedirectUrl() { - $this->debugData( - 'Customer wants to place the order. Create invoice and redirect user to invoice' - ); + $this->debugData('[INFO] In Bitpay_Core_Model_Method_Bitcoin::getOrderPlaceRedirectUrl(): $_redirectUrl is ' . self::$_redirectUrl); return self::$_redirectUrl; } @@ -176,6 +219,14 @@ 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(Mage::getStoreConfig('payment/bitpay/speed')); $invoice->setNotificationUrl(Mage::getUrl(Mage::getStoreConfig('payment/bitpay/notification_url'))); @@ -195,14 +246,15 @@ private function initializeInvoice() */ 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.'); + } + $invoice->setOrderId($payment->getOrder()->getIncrementId()); - $invoice->setPosData( - json_encode( - array( - 'id' => $payment->getOrder()->getIncrementId(), - ) - ) - ); + $invoice->setPosData(json_encode(array('id' => $payment->getOrder()->getIncrementId()))); $invoice = $this->addCurrencyInfo($invoice, $payment->getOrder()); $invoice = $this->addPriceInfo($invoice, $amount); @@ -220,7 +272,20 @@ private function prepareInvoice($invoice, $payment, $amount) */ 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.'); + } + $buyer->setFirstName($order->getCustomerFirstname()); $buyer->setLastName($order->getCustomerLastname()); $invoice->setBuyer($buyer); @@ -237,7 +302,20 @@ private function addBuyerInfo($invoice, $order) */ 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->getBaseCurrencyCode()); $invoice->setCurrency($currency); @@ -253,7 +331,20 @@ private function addCurrencyInfo($invoice, $order) */ 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); From 89a980773602a9918f56cc8dc5b3cdb89f2dc3c8 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Wed, 17 Dec 2014 17:11:49 -0500 Subject: [PATCH 190/315] Added extra error handling and debugging --- .../Bitpay/Core/Model/Config/PairingCode.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Config/PairingCode.php b/app/code/community/Bitpay/Core/Model/Config/PairingCode.php index 410fd1e..a3dab51 100644 --- a/app/code/community/Bitpay/Core/Model/Config/PairingCode.php +++ b/app/code/community/Bitpay/Core/Model/Config/PairingCode.php @@ -23,25 +23,21 @@ public function save() */ $pairingCode = trim($this->getValue()); - if (empty($pairingCode)) { + if (true === empty($pairingCode)) { return; } - Mage::helper('bitpay')->debugData('Attempting Pair Code'); + 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 Pairing Code "%s"', $e->getMessage()) - ); - Mage::getSingleton('core/session')->addError( - 'There was an error while trying to pair the pairing code. Please try again or enabled debug mode and send the "payment_bitpay.log" file to support.' - ); + 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 try again 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 Code was successful.'); + Mage::getSingleton('core/session')->addSuccess('Pairing with BitPay was successful.'); } } From 701fc2e833638cf70396b06675eef5bc7a883d49 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Wed, 17 Dec 2014 18:11:47 -0500 Subject: [PATCH 191/315] Added extra error handling and debugging --- .../community/Bitpay/Core/Helper/Data.php | 239 ++++++++++++++---- 1 file changed, 187 insertions(+), 52 deletions(-) diff --git a/app/code/community/Bitpay/Core/Helper/Data.php b/app/code/community/Bitpay/Core/Helper/Data.php index 9f7e223..ebaff97 100644 --- a/app/code/community/Bitpay/Core/Helper/Data.php +++ b/app/code/community/Bitpay/Core/Helper/Data.php @@ -21,7 +21,9 @@ class Bitpay_Core_Helper_Data extends Mage_Core_Helper_Abstract */ public function debugData($debugData) { - Mage::getModel('bitpay/method_bitcoin')->debugData($debugData); + if (true === isset($debugData) && false === empty($debugData)) { + Mage::getModel('bitpay/method_bitcoin')->debugData($debugData); + } } /** @@ -70,11 +72,19 @@ public function getRedirectUrl() */ public function registerAutoloader() { - if (null === $this->_autoloaderRegistered) { - require_once Mage::getBaseDir('lib').'/Bitpay/Autoloader.php'; - \Bitpay\Autoloader::register(); - $this->_autoloaderRegistered = true; - $this->debugData('BitPay Autoloader has been registered'); + 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!'); + } } } @@ -84,21 +94,36 @@ public function registerAutoloader() */ public function generateAndSaveKeys() { - $this->debugData('Generating Keys'); - $this->registerAutoloader(); + $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'); - $this->_privateKey->generate(); + + 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'); - $this->_publicKey - ->setPrivateKey($this->_privateKey) - ->generate(); + + 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('Keys persisted to database'); + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::generateAndSaveKeys(): key manager called to persist keypair to database.'); } /** @@ -106,36 +131,62 @@ public function generateAndSaveKeys() */ public function sendPairingRequest($pairingCode) { - $this->debugData( - sprintf('Sending Paring Request with pairing code "%s"', $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(); - $this->debugData( - sprintf('Sending Pairing Request for SIN "%s"', (string) $sin) - ); + 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); + $label = preg_replace('/[^a-zA-Z0-9 ]/', '', Mage::app()->getStore()->getName()); + $label = substr('Magento ' . $label, 0, 59); - $token = $this->getBitpayClient()->createToken( - array( - 'id' => (string) $sin, - 'pairingCode' => $pairingCode, - 'label' => $label, - ) - ); + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::sendPairingRequest(): using the label "' . $label . '".'); - $this->debugData('Token Obtained'); + $token = $this->getBitpayClient()->createToken( + array( + 'id' => (string) $sin, + 'pairingCode' => (string) $pairingCode, + 'label' => (string) $label, + ) + ); + + 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(); - $config->saveConfig('payment/bitpay/token', $token->getToken()); - $this->debugData('Token Persisted persisted to database'); + 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.'); + } } /** @@ -143,53 +194,97 @@ public function sendPairingRequest($pairingCode) */ public function getSinKey() { - if (null !== $this->_sin) { + if (false === empty($this->_sin)) { return $this->_sin; } - $this->debugData('Getting SIN Key'); + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getSinKey(): attempting to get the SIN parameter.'); + + if (true === empty($this->_autoloaderRegistered)) { + $this->registerAutoloader(); + } - $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(); + ->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 (null !== $this->_publicKey) { + 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; } - $this->debugData('Getting Public Key'); + 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 (!$this->_publicKey) { + 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; } - 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 (null !== $this->_privateKey) { + if (false === empty($this->_privateKey)) { + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getPrivateKey(): found an existing private key, returning that.'); return $this->_privateKey; } - $this->debugData('Getting Private Key'); + 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 (!$this->_publicKey) { + 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; } - 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!'); + } } /** @@ -197,10 +292,19 @@ public function getPrivateKey() */ public function getKeyManager() { - if (null == $this->_keyManager) { - $this->registerAutoloader(); - $this->debugData('Creating instance of KeyManager'); + 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; @@ -214,9 +318,19 @@ public function getKeyManager() */ public function getBitpay() { - if (null === $this->_bitpay) { - $this->registerAutoloader(); + if (true === empty($this->_bitpay)) { + if (true === empty($this->_autoloaderRegistered)) { + $this->registerAutoloader(); + } + $this->_bitpay = new Bitpay\Bitpay(array('bitpay' => $this->getBitpayConfig())); + + if (false === isset($this->_bitpay) || true === empty($this->_bitpay)) { + $this->debugData('[ERROR] In Bitpay_Core_Helper_Data::getBitpay(): could not create new BitPay object. Cannot continue!'); + throw new Exception('In Bitpay_Core_Helper_Data::getBitpay(): could not create new BitPay object. Cannot continue!'); + } else { + $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getBitpay(): successfully created new BitPay object.'); + } } return $this->_bitpay; @@ -242,13 +356,23 @@ protected function getBitpayConfig() */ public function getBitpayClient() { - if (null !== $this->_client) { + if (false === empty($this->_client)) { return $this->_client; } - $this->registerAutoloader(); + 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.'); + } + $this->_client->setPublicKey($this->getPublicKey()); $this->_client->setPrivateKey($this->getPrivateKey()); $this->_client->setNetwork($this->getBitpay()->get('network')); @@ -260,8 +384,19 @@ public function getBitpayClient() public function getToken() { - $this->registerAutoloader(); + 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; From faaf54206ae6aedbe1f3e251b462f7f31d868789 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Thu, 18 Dec 2014 21:00:28 -0500 Subject: [PATCH 192/315] Added extra error handling and debugging --- app/code/community/Bitpay/Core/Block/Info.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Block/Info.php b/app/code/community/Bitpay/Core/Block/Info.php index 4e003b1..765d93d 100644 --- a/app/code/community/Bitpay/Core/Block/Info.php +++ b/app/code/community/Bitpay/Core/Block/Info.php @@ -15,11 +15,22 @@ public function _construct() 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 ($bitpayInvoice) { + if (true === isset($bitpayInvoice) && false === empty($bitpayInvoice)) { return $bitpayInvoice->getUrl(); } } From 4e32b0e2a9ecc9fc8259c27531208fd46c64dc07 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Tue, 30 Dec 2014 12:50:03 -0500 Subject: [PATCH 193/315] Added extra error handling and debugging TODO: Finish the iframe implementation... --- .../community/Bitpay/Core/Block/Iframe.php | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/app/code/community/Bitpay/Core/Block/Iframe.php b/app/code/community/Bitpay/Core/Block/Iframe.php index 0d1dd23..4d14734 100644 --- a/app/code/community/Bitpay/Core/Block/Iframe.php +++ b/app/code/community/Bitpay/Core/Block/Iframe.php @@ -2,6 +2,8 @@ /** * @license Copyright 2011-2014 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE + * + * TODO: Finish this iFrame implemenation... :/ */ class Bitpay_Core_Block_Iframe extends Mage_Checkout_Block_Onepage_Payment @@ -27,6 +29,12 @@ public function getIframeUrl() /*** @var Bitpay_Core_Model_PaymentMethod ***/ $method = $this->getQuote()->getPayment()->getMethodInstance(); + + if (false === isset($method) || true === empty($method)) { + Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not obtain an instance of the payment method.'); + throw new Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not obtain an instance of the payment method.'); + } + $options = array_merge( array( 'currency' => $this->getQuote()->getQuoteCurrencyCode(), @@ -37,32 +45,45 @@ public function getIframeUrl() ), $method->extractAddress($this->getQuote()->getShippingAddress()) ); - Mage::helper('bitpay')->debugData($options); + + if (false === isset($options) || true === empty($options)) { + Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not merge the options array.'); + throw new Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not merge the options array.'); + } else { + Mage::helper('bitpay')->debugData($options); + } // 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($this->getQuote()->getGrandTotal(), 4); + if (false === isset($price) || true === empty($price)) { + Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not get the new rounded price.'); + throw new Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not get the new rounded price.'); + } + //serialize info about the quote to detect changes $hash = $method->getQuoteHash($this->getQuote()->getId()); + if (false === isset($hash) || true === empty($hash)) { + Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not get the quote hash.'); + throw new Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not merge the quote hash.'); + } + Mage::helper('bitpay')->registerAutoloader(); + //$invoice = bpCreateInvoice($quoteId, $price, array('quoteId' => $quoteId, 'quoteHash' => $hash), $options); $invoice = array('url' => 'https://test.bitpay.com/invoice?id=5NxFkXcJbCSivtQRJa4kHP'); if (array_key_exists('error', $invoice)) { - Mage::helper('bitpay')->debugData( - array( - 'Error creating bitpay invoice', - $invoice['error'], - ) - ); + Mage::helper('bitpay')->debugData(array('Error creating bitpay invoice', $invoice['error'],)); Mage::throwException("Error creating BitPay invoice. Please try again or use another payment option."); return false; } - return $invoice['url'].'&view=iframe'; + //return $invoice['url'].'&view=iframe'; + return false; } } From 9d9215c0d4f514f12ad0ed7a7a57914f0ad38659 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Tue, 30 Dec 2014 13:02:04 -0500 Subject: [PATCH 194/315] Update CHANGELOG --- CHANGELOG | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 658af47..3aea5ff 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +2.1.2 + Added extra error handling and fixed debugging + 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 From 8ce288db4b0da62351841c46507858deff6fa3e8 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Tue, 30 Dec 2014 13:18:56 -0500 Subject: [PATCH 195/315] Check to see if new order email was sent and to send one if not. References GH Issue #85 --- .../Bitpay/Core/controllers/IpnController.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index cf99880..7aa2daa 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -112,7 +112,16 @@ public function indexAction() if (true === isset($payment) && false === empty($payment)) { $payment->registerCaptureNotification($invoice->getPrice()); - $order->addPayment($payment)->save(); + $order->addPayment($payment); + + // If the customer has not already been notified by email + // send the notification now that there's a new order. + if (!$order->getEmailSent()) { + Mage::helper('bitpay')->debugData('[INFO] Order email not sent so I am calling $order->sendNewOrderEmail() now...'); + $order->sendNewOrderEmail(); + } + + $order->save(); } else { Mage::helper('bitpay')->debugData('[ERROR] Could not create a payment object in the Bitpay IPN controller.'); Mage::throwException('Could not create a payment object in the Bitpay IPN controller.'); From 6ba2157166d9eb0a212eefb697d2015720c1a7ab Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Tue, 30 Dec 2014 13:20:18 -0500 Subject: [PATCH 196/315] Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 3aea5ff..9555da8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ 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 From 1226a523521db2de560871139f73c2e88754e14e Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Tue, 30 Dec 2014 13:24:11 -0500 Subject: [PATCH 197/315] Formatting --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 4101cb9..5723c1d 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This is because there are little to no fees for transferring Bitcoins from one p # Upgrade 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). +***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. @@ -41,20 +41,20 @@ Goto [http://www.magentocommerce.com/magento-connect/bitpay-payment-method.html] 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. +***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. +***WARNING:*** It is good practice to backup your database before installing extensions. Please make sure you Create Backups. ## Download 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. -***NOTE*** These files can also up uploaded using the *Magento Connect Manager* that comes with your Magento Store +***NOTE:*** These files can also up uploaded using the *Magento Connect Manager* that comes with your Magento Store -***WARNING*** It is good practice to backup your database before installing extensions. Please make sure you Create Backups. +***WARNING:*** It is good practice to backup your database before installing extensions. Please make sure you Create Backups. ## modman @@ -70,9 +70,9 @@ Configuration can be done using the Administrator section of your Megento store. 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:*** 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. +***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. @@ -105,11 +105,11 @@ As a merchant, the orders in your Magento store can be treated as any other orde 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. +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: +***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 @@ -129,7 +129,7 @@ Once you have cloned the repository, you will need to run [composer install](htt 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. +***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 From 8ec543dd54b7abf667d85fea2858ebbe40da24af Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 5 Jan 2015 14:43:04 -0500 Subject: [PATCH 198/315] Removed logging code causing exceptions to be thrown when getBody() was called on an invalid invoice object. --- app/code/community/Bitpay/Core/Model/Method/Bitcoin.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php index 46810fa..fdf59c6 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -58,12 +58,6 @@ public function authorize(Varien_Object $payment, $amount) $bitpayInvoice = Mage::helper('bitpay')->getBitpayClient()->createInvoice($invoice); } catch (Exception $e) { $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::authorize(): ' . $e->getMessage()); - $this->debugData( - array( - Mage::helper('bitpay')->getBitpayClient()->getRequest()->getBody(), - Mage::helper('bitpay')->getBitpayClient()->getResponse()->getBody(), - ) - ); Mage::throwException('In Bitpay_Core_Model_Method_Bitcoin::authorize(): Could not authorize transaction.'); } From cea830617baca2c61ae6173a2a1a74947d92dd58 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 5 Jan 2015 15:52:59 -0500 Subject: [PATCH 199/315] Added namespace backslash --- lib/Bitpay/Storage/MagentoStorage.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Bitpay/Storage/MagentoStorage.php b/lib/Bitpay/Storage/MagentoStorage.php index 20f790b..15f8bb2 100644 --- a/lib/Bitpay/Storage/MagentoStorage.php +++ b/lib/Bitpay/Storage/MagentoStorage.php @@ -31,7 +31,7 @@ public function persist(\Bitpay\KeyInterface $key) 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.'); + \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.'); } } @@ -51,18 +51,18 @@ public function load($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.'); + \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.'); return false; } $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 . '.'); + \Mage::helper('bitpay')->debugData('[INFO] Call to MagentoStorage::load($id) with the id of ' . $id . ' could not decrypt & unserialize the entity ' . $entity . '.'); return false; } - Mage::helper('bitpay')->debugData('[INFO] Call to MagentoStorage::load($id) with the id of ' . $id . ' successfully decrypted & unserialized 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; } From f796e0a4f79eaae174dbc5642e2f7b38c57d5fd8 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 5 Jan 2015 15:55:06 -0500 Subject: [PATCH 200/315] Added namespace backslash --- .../community/Bitpay/Core/Block/Iframe.php | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/code/community/Bitpay/Core/Block/Iframe.php b/app/code/community/Bitpay/Core/Block/Iframe.php index 4d14734..2504f59 100644 --- a/app/code/community/Bitpay/Core/Block/Iframe.php +++ b/app/code/community/Bitpay/Core/Block/Iframe.php @@ -23,7 +23,7 @@ protected function _construct() */ public function getIframeUrl() { - if (Mage::getModel('bitpay/ipn')->getQuotePaid($this->getQuote()->getId())) { + if (\Mage::getModel('bitpay/ipn')->getQuotePaid($this->getQuote()->getId())) { return 'paid'; // quote's already paid, so don't show the iframe } @@ -31,7 +31,7 @@ public function getIframeUrl() $method = $this->getQuote()->getPayment()->getMethodInstance(); if (false === isset($method) || true === empty($method)) { - Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not obtain an instance of the payment method.'); + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not obtain an instance of the payment method.'); throw new Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not obtain an instance of the payment method.'); } @@ -39,18 +39,18 @@ public function getIframeUrl() array( 'currency' => $this->getQuote()->getQuoteCurrencyCode(), 'fullNotifications' => 'true', - 'notificationURL' => Mage::getUrl('bitpay/ipn'), - 'redirectURL' => Mage::getUrl('checkout/onepage/success'), - 'transactionSpeed' => Mage::getStoreConfig('payment/bitpay/speed'), + 'notificationURL' => \Mage::getUrl('bitpay/ipn'), + 'redirectURL' => \Mage::getUrl('checkout/onepage/success'), + 'transactionSpeed' => \Mage::getStoreConfig('payment/bitpay/speed'), ), $method->extractAddress($this->getQuote()->getShippingAddress()) ); if (false === isset($options) || true === empty($options)) { - Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not merge the options array.'); + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not merge the options array.'); throw new Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not merge the options array.'); } else { - Mage::helper('bitpay')->debugData($options); + \Mage::helper('bitpay')->debugData($options); } // Mage doesn't round the total until saving and it can have more precision @@ -59,7 +59,7 @@ public function getIframeUrl() $price = round($this->getQuote()->getGrandTotal(), 4); if (false === isset($price) || true === empty($price)) { - Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not get the new rounded price.'); + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not get the new rounded price.'); throw new Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not get the new rounded price.'); } @@ -67,18 +67,18 @@ public function getIframeUrl() $hash = $method->getQuoteHash($this->getQuote()->getId()); if (false === isset($hash) || true === empty($hash)) { - Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not get the quote hash.'); + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not get the quote hash.'); throw new Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not merge the quote hash.'); } - Mage::helper('bitpay')->registerAutoloader(); + \Mage::helper('bitpay')->registerAutoloader(); //$invoice = bpCreateInvoice($quoteId, $price, array('quoteId' => $quoteId, 'quoteHash' => $hash), $options); $invoice = array('url' => 'https://test.bitpay.com/invoice?id=5NxFkXcJbCSivtQRJa4kHP'); if (array_key_exists('error', $invoice)) { - Mage::helper('bitpay')->debugData(array('Error creating bitpay invoice', $invoice['error'],)); - Mage::throwException("Error creating BitPay invoice. Please try again or use another payment option."); + \Mage::helper('bitpay')->debugData(array('Error creating bitpay invoice', $invoice['error'],)); + \Mage::throwException("Error creating BitPay invoice. Please try again or use another payment option."); return false; } From 5c37d24b4a83e88312f91f303135f39e1c7b8252 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 5 Jan 2015 15:55:33 -0500 Subject: [PATCH 201/315] Added namespace backslash --- app/code/community/Bitpay/Core/Block/Info.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/community/Bitpay/Core/Block/Info.php b/app/code/community/Bitpay/Core/Block/Info.php index 765d93d..585e9af 100644 --- a/app/code/community/Bitpay/Core/Block/Info.php +++ b/app/code/community/Bitpay/Core/Block/Info.php @@ -17,18 +17,18 @@ 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.'); + \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.'); + \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'); + $bitpayInvoice = \Mage::getModel('bitpay/invoice')->load($incrementId, 'increment_id'); if (true === isset($bitpayInvoice) && false === empty($bitpayInvoice)) { return $bitpayInvoice->getUrl(); From b2125500e2c85fbe126dd99d362340c3894624e8 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 5 Jan 2015 15:57:07 -0500 Subject: [PATCH 202/315] Added namespace backslash --- app/code/community/Bitpay/Core/Helper/Data.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/code/community/Bitpay/Core/Helper/Data.php b/app/code/community/Bitpay/Core/Helper/Data.php index ebaff97..8eaebc7 100644 --- a/app/code/community/Bitpay/Core/Helper/Data.php +++ b/app/code/community/Bitpay/Core/Helper/Data.php @@ -22,7 +22,7 @@ class Bitpay_Core_Helper_Data extends Mage_Core_Helper_Abstract public function debugData($debugData) { if (true === isset($debugData) && false === empty($debugData)) { - Mage::getModel('bitpay/method_bitcoin')->debugData($debugData); + \Mage::getModel('bitpay/method_bitcoin')->debugData($debugData); } } @@ -31,7 +31,7 @@ public function debugData($debugData) */ public function isDebug() { - return (boolean) Mage::getStoreConfig('payment/bitpay/debug'); + return (boolean) \Mage::getStoreConfig('payment/bitpay/debug'); } /** @@ -41,7 +41,7 @@ public function isDebug() */ public function hasTransactionSpeed() { - $speed = Mage::getStoreConfig('payment/bitpay/speed'); + $speed = \Mage::getStoreConfig('payment/bitpay/speed'); return !empty($speed); } @@ -53,7 +53,7 @@ public function hasTransactionSpeed() */ public function getNotificationUrl() { - return Mage::getUrl(Mage::getStoreConfig('payment/bitpay/notification_url')); + return \Mage::getUrl(\Mage::getStoreConfig('payment/bitpay/notification_url')); } /** @@ -63,7 +63,7 @@ public function getNotificationUrl() */ public function getRedirectUrl() { - return Mage::getUrl(Mage::getStoreConfig('payment/bitpay/redirect_url')); + return \Mage::getUrl(\Mage::getStoreConfig('payment/bitpay/redirect_url')); } /** @@ -73,7 +73,7 @@ public function getRedirectUrl() public function registerAutoloader() { if (true === empty($this->_autoloaderRegistered)) { - $autoloader_filename = Mage::getBaseDir('lib').'/Bitpay/Autoloader.php'; + $autoloader_filename = \Mage::getBaseDir('lib').'/Bitpay/Autoloader.php'; if (true === is_file($autoloader_filename) && true === is_readable($autoloader_filename)) { require_once $autoloader_filename; @@ -154,7 +154,7 @@ public function sendPairingRequest($pairingCode) } // Sanitize label - $label = preg_replace('/[^a-zA-Z0-9 ]/', '', Mage::app()->getStore()->getName()); + $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 . '".'); @@ -346,7 +346,7 @@ protected function getBitpayConfig() return array( 'public_key' => 'payment/bitpay/public_key', 'private_key' => 'payment/bitpay/private_key', - 'network' => Mage::getStoreConfig('payment/bitpay/network'), + 'network' => \Mage::getStoreConfig('payment/bitpay/network'), 'key_storage' => '\\Bitpay\\Storage\\MagentoStorage', ); } @@ -397,7 +397,7 @@ public function getToken() $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getToken(): successfully created new BitPay Token object.'); } - $token->setToken(Mage::getStoreConfig('payment/bitpay/token')); + $token->setToken(\Mage::getStoreConfig('payment/bitpay/token')); return $token; } From 74ec83fb7495f6ea0332caa6e90b1ebb57606190 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 5 Jan 2015 15:57:36 -0500 Subject: [PATCH 203/315] Added namespace backslash --- app/code/community/Bitpay/Core/Model/TransactionSpeed.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/TransactionSpeed.php b/app/code/community/Bitpay/Core/Model/TransactionSpeed.php index c41356e..824c788 100644 --- a/app/code/community/Bitpay/Core/Model/TransactionSpeed.php +++ b/app/code/community/Bitpay/Core/Model/TransactionSpeed.php @@ -16,9 +16,9 @@ class Bitpay_Core_Model_TransactionSpeed public function toOptionArray() { return array( - array('value' => 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))), + array('value' => 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))), ); } } From 5caa1c396266976ccabfc251fb0a8080d5cabe7a Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 5 Jan 2015 15:57:57 -0500 Subject: [PATCH 204/315] Added namespace backslash --- app/code/community/Bitpay/Core/Model/Status.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Status.php b/app/code/community/Bitpay/Core/Model/Status.php index 83eeb93..829b252 100644 --- a/app/code/community/Bitpay/Core/Model/Status.php +++ b/app/code/community/Bitpay/Core/Model/Status.php @@ -19,12 +19,12 @@ class Bitpay_Core_Model_Status public function toOptionArray() { return array( - array('value' => 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))), + array('value' => 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))), ); } } From ff4ab044d5efaabf817eb3bbaabef44f602a10e6 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 5 Jan 2015 15:58:49 -0500 Subject: [PATCH 205/315] Added namespace backslash --- .../community/Bitpay/Core/Model/Observer.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Observer.php b/app/code/community/Bitpay/Core/Model/Observer.php index 12a4ec3..6b0b814 100644 --- a/app/code/community/Bitpay/Core/Model/Observer.php +++ b/app/code/community/Bitpay/Core/Model/Observer.php @@ -20,26 +20,26 @@ public function checkForRequest($observer) */ public function updateOrderStates() { - $apiKey = Mage::getStoreConfig('payment/bitpay/api_key'); + $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.'); + \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.'); + \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(); + $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.'); + \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.'); + \Mage::helper('bitpay')->debugData('[INFO] Bitpay_Core_Model_Observer::updateOrderStates() successfully retrieved existing open orders.'); } /* @@ -60,7 +60,7 @@ public function updateOrderStates() // No? continue } - Mage::helper('bitpay')->debugData('[INFO] Bitpay_Core_Model_Observer::updateOrderStates() order status update job finished.'); + \Mage::helper('bitpay')->debugData('[INFO] Bitpay_Core_Model_Observer::updateOrderStates() order status update job finished.'); } /** @@ -69,7 +69,7 @@ public function updateOrderStates() */ public function cleanExpired() { - Mage::helper('bitpay')->debugData('[INFO] Bitpay_Core_Model_Observer::cleanExpired() called.'); - Mage::helper('bitpay')->cleanExpired(); + \Mage::helper('bitpay')->debugData('[INFO] Bitpay_Core_Model_Observer::cleanExpired() called.'); + \Mage::helper('bitpay')->cleanExpired(); } } From 4f1f20dde2cf292c1340cf5889a1c53b0c12c957 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 5 Jan 2015 15:59:08 -0500 Subject: [PATCH 206/315] Added namespace backslash --- app/code/community/Bitpay/Core/Model/Network.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Network.php b/app/code/community/Bitpay/Core/Model/Network.php index df8d7fb..a77e475 100644 --- a/app/code/community/Bitpay/Core/Model/Network.php +++ b/app/code/community/Bitpay/Core/Model/Network.php @@ -18,8 +18,8 @@ class Bitpay_Core_Model_Network public function toOptionArray() { return array( - array('value' => self::NETWORK_LIVENET, 'label' => Mage::helper('bitpay')->__(ucwords(self::NETWORK_LIVENET))), - array('value' => self::NETWORK_TESTNET, 'label' => Mage::helper('bitpay')->__(ucwords(self::NETWORK_TESTNET))), + array('value' => self::NETWORK_LIVENET, 'label' => \Mage::helper('bitpay')->__(ucwords(self::NETWORK_LIVENET))), + array('value' => self::NETWORK_TESTNET, 'label' => \Mage::helper('bitpay')->__(ucwords(self::NETWORK_TESTNET))), ); } } From 34c4a48175e24d2ca26e19a72b49376b636de511 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 5 Jan 2015 16:00:02 -0500 Subject: [PATCH 207/315] Added namespace backslash --- app/code/community/Bitpay/Core/Model/Invoice.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Invoice.php b/app/code/community/Bitpay/Core/Model/Invoice.php index bdb9ff8..186fccf 100644 --- a/app/code/community/Bitpay/Core/Model/Invoice.php +++ b/app/code/community/Bitpay/Core/Model/Invoice.php @@ -25,8 +25,8 @@ protected function _construct() 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.'); + \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( @@ -60,8 +60,8 @@ public function prepareWithBitpayInvoice($invoice) public function prepateWithOrder($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.'); + \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( From d2f2d865a2f5b6d1e072a03bd4e296e2827f4404 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 5 Jan 2015 16:03:47 -0500 Subject: [PATCH 208/315] Added namespace backslash --- .../Bitpay/Core/Model/Method/Bitcoin.php | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php index fdf59c6..e310bb6 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -39,7 +39,7 @@ public function authorize(Varien_Object $payment, $amount) { if (false === isset($payment) || false === isset($amount) || true === empty($payment) || true === empty($amount)) { $this->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.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::authorize(): missing payment or amount parameters.'); } $this->debugData('[INFO] Bitpay_Core_Model_Method_Bitcoin::authorize(): authorizing new order.'); @@ -49,16 +49,16 @@ public function authorize(Varien_Object $payment, $amount) 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.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::authorize(): could not initialize invoice.'); } $invoice = $this->prepareInvoice($invoice, $payment, $amount); try { - $bitpayInvoice = Mage::helper('bitpay')->getBitpayClient()->createInvoice($invoice); - } catch (Exception $e) { + $bitpayInvoice = \Mage::helper('bitpay')->getBitpayClient()->createInvoice($invoice); + } catch (\Exception $e) { $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::authorize(): ' . $e->getMessage()); - Mage::throwException('In Bitpay_Core_Model_Method_Bitcoin::authorize(): Could not authorize transaction.'); + \Mage::throwException('In Bitpay_Core_Model_Method_Bitcoin::authorize(): Could not authorize transaction.'); } self::$_redirectUrl = $bitpayInvoice->getUrl(); @@ -71,7 +71,7 @@ public function authorize(Varien_Object $payment, $amount) ); // Save BitPay Invoice in database for reference - $mirrorInvoice = Mage::getModel('bitpay/invoice') + $mirrorInvoice = \Mage::getModel('bitpay/invoice') ->prepareWithBitpayInvoice($bitpayInvoice) ->prepateWithOrder($payment->getOrder()) ->save(); @@ -90,7 +90,7 @@ public function authorize(Varien_Object $payment, $amount) */ public function canUseCheckout() { - $token = Mage::getStoreConfig('payment/bitpay/token'); + $token = \Mage::getStoreConfig('payment/bitpay/token'); if (false === isset($token) || true === empty($token)) { /** @@ -117,18 +117,18 @@ 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.'); + 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(); + \Mage::helper('bitpay')->registerAutoloader(); - $client = Mage::helper('bitpay')->getBitpayClient(); + $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.'); + 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.'); } @@ -137,7 +137,7 @@ public function fetchInvoice($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.'); + 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.'); } @@ -156,7 +156,7 @@ 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.'); + 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.'); } @@ -210,21 +210,21 @@ public function getOrderPlaceRedirectUrl() */ private function initializeInvoice() { - Mage::helper('bitpay')->registerAutoloader(); + \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.'); + 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(Mage::getStoreConfig('payment/bitpay/speed')); - $invoice->setNotificationUrl(Mage::getUrl(Mage::getStoreConfig('payment/bitpay/notification_url'))); - $invoice->setRedirectUrl(Mage::getUrl(Mage::getStoreConfig('payment/bitpay/redirect_url'))); + $invoice->setTransactionSpeed(\Mage::getStoreConfig('payment/bitpay/speed')); + $invoice->setNotificationUrl(\Mage::getUrl(\Mage::getStoreConfig('payment/bitpay/notification_url'))); + $invoice->setRedirectUrl(\Mage::getUrl(\Mage::getStoreConfig('payment/bitpay/redirect_url'))); return $invoice; } @@ -242,7 +242,7 @@ 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.'); + 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.'); } @@ -268,7 +268,7 @@ 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.'); + 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.'); } @@ -277,7 +277,7 @@ private function addBuyerInfo($invoice, $order) 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.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::addBuyerInfo(): could not construct new BitPay buyer object.'); } $buyer->setFirstName($order->getCustomerFirstname()); @@ -298,7 +298,7 @@ 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.'); + 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.'); } @@ -307,7 +307,7 @@ private function addCurrencyInfo($invoice, $order) 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.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::addCurrencyInfo(): could not construct new BitPay currency object.'); } $currency->setCode($order->getBaseCurrencyCode()); @@ -327,7 +327,7 @@ 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.'); + 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.'); } @@ -336,7 +336,7 @@ private function addPriceInfo($invoice, $amount) 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.'); + throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::addPriceInfo(): could not construct new BitPay item object.'); } $item->setPrice($amount); From 33f6d8ba20fa13fdcef03e562839e03eb7816380 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 5 Jan 2015 16:04:51 -0500 Subject: [PATCH 209/315] Added namespace backslash --- .../Bitpay/Core/Model/Config/PairingCode.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Config/PairingCode.php b/app/code/community/Bitpay/Core/Model/Config/PairingCode.php index a3dab51..853452c 100644 --- a/app/code/community/Bitpay/Core/Model/Config/PairingCode.php +++ b/app/code/community/Bitpay/Core/Model/Config/PairingCode.php @@ -27,17 +27,17 @@ public function save() return; } - Mage::helper('bitpay')->debugData('[INFO] In Bitpay_Core_Model_Config_PairingCode::save(): attempting to pair with BitPay with pairing code ' . $pairingCode); + \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 try again or enable debug mode and send the "payment_bitpay.log" file to support@bitpay.com for more help.'); + \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 try again 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.'); + \Mage::getSingleton('core/session')->addSuccess('Pairing with BitPay was successful.'); } } From a07c007a294735e15a5218c2b1a5878a4986e9b8 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 5 Jan 2015 16:07:15 -0500 Subject: [PATCH 210/315] Added namespace backslash --- .../Bitpay/Core/controllers/IpnController.php | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index 7aa2daa..b233de0 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -24,13 +24,13 @@ public function indexAction() $raw_post_data = file_get_contents('php://input'); if (false === $raw_post_data) { - Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IpnController: 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')->debugData('[ERROR] In Bitpay_Core_IpnController: 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')->registerAutoloader(); - Mage::helper('bitpay')->debugData( + \Mage::helper('bitpay')->debugData( array( sprintf('Incoming IPN from bitpay'), getallheaders(), @@ -42,22 +42,22 @@ public function indexAction() $ipn = json_decode($raw_post_data); if (true === empty($ipn)) { - Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IpnController: Could not decode the JSON payload from BitPay.'); - throw new Exception('Could not decode the JSON payload from BitPay.'); + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IpnController: 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->id)) { - Mage::helper('bitpay')->debugData(sprintf('Did not receive order ID in IPN. See IPN "%s" in database.', $mageIpn->getId())); - throw new Exception('Invalid Bitpay IPN received - did not receive order ID.'); + \Mage::helper('bitpay')->debugData(sprintf('Did not receive order ID in IPN. See IPN "%s" in database.', $mageIpn->getId())); + throw new \Exception('Invalid Bitpay IPN 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); + \Mage::helper('bitpay')->debugData($ipn); // Log IPN - $mageIpn = Mage::getModel('bitpay/ipn')->addData( + $mageIpn = \Mage::getModel('bitpay/ipn')->addData( array( 'invoice_id' => isset($ipn->id) ? $ipn->id : '', 'url' => isset($ipn->url) ? $ipn->url : '', @@ -75,11 +75,11 @@ public function indexAction() ) )->save(); - $order = Mage::getModel('sales/order')->loadByIncrementId($ipn->posData->id); + $order = \Mage::getModel('sales/order')->loadByIncrementId($ipn->posData->id); if (false === isset($order) || true === empty($order->getId())) { - Mage::helper('bitpay')->debugData('Invalid Bitpay IPN received.'); - Mage::throwException('Invalid Bitpay IPN received.'); + \Mage::helper('bitpay')->debugData('Invalid Bitpay IPN received.'); + \Mage::throwException('Invalid Bitpay IPN received.'); } /** @@ -87,28 +87,28 @@ public function indexAction() * 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); + $invoice = \Mage::getModel('bitpay/method_bitcoin')->fetchInvoice($ipn->id); if (false === isset($invoice) || true === empty($invoice) { - Mage::helper('bitpay')->debugData('[ERROR] 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); + \Mage::helper('bitpay')->debugData('[ERROR] 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] IPN status and status from BitPay are different.'); - Mage::throwException('There was an error processing the IPN - statuses are different.'); + \Mage::getModel('bitpay/method_bitcoin')->debugData('[ERROR] IPN status and status from BitPay are different.'); + \Mage::throwException('There was an error processing the IPN - statuses are different.'); } // Does the price match? if ($invoice->getPrice() != $ipn->price) { - Mage::getModel('bitpay/method_bitcoin')>debugData('[ERROR] IPN price and invoice price are different.'); - Mage::throwException('There was an error processing the IPN - invoice price does not match the IPN price.'); + \Mage::getModel('bitpay/method_bitcoin')>debugData('[ERROR] IPN price and invoice price are different.'); + \Mage::throwException('There was an error processing the IPN - invoice price does not match the IPN price.'); } // Update the order to notifiy that it has been paid if (true === in_array($invoice->getStatus(), array('paid', 'confirmed', 'complete'))) { - $payment = Mage::getModel('sales/order_payment')->setOrder($order); + $payment = \Mage::getModel('sales/order_payment')->setOrder($order); if (true === isset($payment) && false === empty($payment)) { $payment->registerCaptureNotification($invoice->getPrice()); @@ -117,23 +117,23 @@ public function indexAction() // If the customer has not already been notified by email // send the notification now that there's a new order. if (!$order->getEmailSent()) { - Mage::helper('bitpay')->debugData('[INFO] Order email not sent so I am calling $order->sendNewOrderEmail() now...'); + \Mage::helper('bitpay')->debugData('[INFO] Order email not sent so I am calling $order->sendNewOrderEmail() now...'); $order->sendNewOrderEmail(); } $order->save(); } else { - Mage::helper('bitpay')->debugData('[ERROR] Could not create a payment object in the Bitpay IPN controller.'); - Mage::throwException('Could not create a payment object in the Bitpay IPN controller.'); + \Mage::helper('bitpay')->debugData('[ERROR] Could not create a payment object in the Bitpay IPN controller.'); + \Mage::throwException('Could not create a payment object in the Bitpay IPN controller.'); } } // use state as defined by Merchant - $state = Mage::getStoreConfig(sprintf('payment/bitpay/invoice_%s', $invoice->getStatus())); + $state = \Mage::getStoreConfig(sprintf('payment/bitpay/invoice_%s', $invoice->getStatus())); if (false === isset($state) || true === empty($state) { - Mage::helper('bitpay')->debugData('[ERROR] 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 to in the Bitpay IPN controller.'); + \Mage::helper('bitpay')->debugData('[ERROR] 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 to in the Bitpay IPN controller.'); } $order->addStatusToHistory( From 4b71f46ea4cbe7fd75589d743e707a775653ab2e Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 5 Jan 2015 16:07:49 -0500 Subject: [PATCH 211/315] Added namespace backslash --- .../community/Bitpay/Core/controllers/IndexController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/community/Bitpay/Core/controllers/IndexController.php b/app/code/community/Bitpay/Core/controllers/IndexController.php index a2fdec9..1bfbb0e 100644 --- a/app/code/community/Bitpay/Core/controllers/IndexController.php +++ b/app/code/community/Bitpay/Core/controllers/IndexController.php @@ -18,10 +18,10 @@ public function indexAction() $params = $this->getRequest()->getParams(); if (true === isset($params['paid'])) { - Mage::helper('bitpay')->registerAutoloader(); - Mage::helper('bitpay')->debugData($params); + \Mage::helper('bitpay')->registerAutoloader(); + \Mage::helper('bitpay')->debugData($params); } else { - Mage::helper('bitpay')->debugData('[ERROR] Could not get params from request.'); + \Mage::helper('bitpay')->debugData('[ERROR] Could not get params from request.'); } $this->loadLayout(); From 667357b039fe23e02e7e49876d20950f103961f9 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Thu, 8 Jan 2015 16:15:35 -0500 Subject: [PATCH 212/315] Removed bad function call to non-object --- .../Bitpay/Core/controllers/IpnController.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index b233de0..717d90f 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -30,13 +30,7 @@ public function indexAction() \Mage::helper('bitpay')->registerAutoloader(); - \Mage::helper('bitpay')->debugData( - array( - sprintf('Incoming IPN from bitpay'), - getallheaders(), - $raw_post_data, - ) - ); + \Mage::helper('bitpay')->debugData(array(sprintf('Incoming IPN from bitpay'),$raw_post_data,)); // Magento doesn't seem to have a way to get the Request body $ipn = json_decode($raw_post_data); @@ -47,7 +41,7 @@ public function indexAction() } if (true === empty($ipn->id) || false === isset($ipn->posData->id)) { - \Mage::helper('bitpay')->debugData(sprintf('Did not receive order ID in IPN. See IPN "%s" in database.', $mageIpn->getId())); + \Mage::helper('bitpay')->debugData(sprintf('Did not receive order ID in IPN: ', $ipn); throw new \Exception('Invalid Bitpay IPN received - did not receive order ID.'); } From e9f4c9fe2fdc5969ea352e5697a9af9efd55f37b Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Thu, 8 Jan 2015 16:19:41 -0500 Subject: [PATCH 213/315] More informative error message --- .../community/Bitpay/Core/controllers/IndexController.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/controllers/IndexController.php b/app/code/community/Bitpay/Core/controllers/IndexController.php index 1bfbb0e..6e9a0b2 100644 --- a/app/code/community/Bitpay/Core/controllers/IndexController.php +++ b/app/code/community/Bitpay/Core/controllers/IndexController.php @@ -21,12 +21,14 @@ public function indexAction() \Mage::helper('bitpay')->registerAutoloader(); \Mage::helper('bitpay')->debugData($params); } else { - \Mage::helper('bitpay')->debugData('[ERROR] Could not get params from request.'); + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IndexController::indexAction(), Could not get parameters from HTTP request.'); } $this->loadLayout(); $this->getResponse()->setHeader('Content-type', 'application/json'); + + // ? $this->getResponse()->setBody(json_encode(array('paid' => $paid))); } } From 5f95611d495f50dded3b5de6f991ef2018f02403 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Thu, 8 Jan 2015 16:40:41 -0500 Subject: [PATCH 214/315] Added missing parenthesis & updated error msgs --- .../Bitpay/Core/controllers/IpnController.php | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index 717d90f..cce0b7f 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -24,25 +24,25 @@ public function indexAction() $raw_post_data = file_get_contents('php://input'); if (false === $raw_post_data) { - \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IpnController: Could not read from the php://input stream or invalid Bitpay IPN received.'); + \Mage::helper('bitpay')->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('Incoming IPN from bitpay'),$raw_post_data,)); + \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 (true === empty($ipn)) { - \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IpnController: Could not decode the JSON payload from BitPay.'); + \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->id)) { - \Mage::helper('bitpay')->debugData(sprintf('Did not receive order ID in IPN: ', $ipn); - throw new \Exception('Invalid Bitpay IPN received - did not receive order ID.'); + \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; @@ -72,7 +72,7 @@ public function indexAction() $order = \Mage::getModel('sales/order')->loadByIncrementId($ipn->posData->id); if (false === isset($order) || true === empty($order->getId())) { - \Mage::helper('bitpay')->debugData('Invalid Bitpay IPN received.'); + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IpnController::indexAction(), Invalid Bitpay IPN received.'); \Mage::throwException('Invalid Bitpay IPN received.'); } @@ -83,21 +83,21 @@ public function indexAction() */ $invoice = \Mage::getModel('bitpay/method_bitcoin')->fetchInvoice($ipn->id); - if (false === isset($invoice) || true === empty($invoice) { - \Mage::helper('bitpay')->debugData('[ERROR] Could not retrieve the invoice details for the ipn ID of ' . $ipn->id); + 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] IPN status and status from BitPay are different.'); - \Mage::throwException('There was an error processing the IPN - statuses are different.'); + \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] IPN price and invoice price are different.'); - \Mage::throwException('There was an error processing the IPN - invoice price does not match the 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!'); } // Update the order to notifiy that it has been paid @@ -111,13 +111,13 @@ public function indexAction() // If the customer has not already been notified by email // send the notification now that there's a new order. if (!$order->getEmailSent()) { - \Mage::helper('bitpay')->debugData('[INFO] Order email not sent so I am calling $order->sendNewOrderEmail() now...'); + \Mage::helper('bitpay')->debugData('[INFO] In Bitpay_Core_IpnController::indexAction(), Order email not sent so I am calling $order->sendNewOrderEmail() now...'); $order->sendNewOrderEmail(); } $order->save(); } else { - \Mage::helper('bitpay')->debugData('[ERROR] Could not create a payment object in the Bitpay IPN controller.'); + \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.'); } } @@ -125,14 +125,14 @@ public function indexAction() // 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] Could not retrieve the defined state parameter to update this order to in the Bitpay IPN controller.'); + 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 to in the Bitpay IPN controller.'); } $order->addStatusToHistory( $state, - sprintf('Incoming IPN status "%s" updated order state to "%s"', $invoice->getStatus(), $state) + sprintf('[INFO] In Bitpay_Core_IpnController::indexAction(), Incoming IPN status "%s" updated order state to "%s"', $invoice->getStatus(), $state) )->save(); } } From 154679c0adef9a5aaae25a723430a597520ab8db Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Thu, 8 Jan 2015 16:58:50 -0500 Subject: [PATCH 215/315] Added CDATA wrapping for all comments. --- app/code/community/Bitpay/Core/etc/system.xml | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/app/code/community/Bitpay/Core/etc/system.xml b/app/code/community/Bitpay/Core/etc/system.xml index 905f108..86a14ed 100644 --- a/app/code/community/Bitpay/Core/etc/system.xml +++ b/app/code/community/Bitpay/Core/etc/system.xml @@ -28,9 +28,8 @@ <pairing_code translate="label"> <label>Pairing Code</label> <comment> - <![CDATA[Create a pairing code for your account at <a href="https://bitpay.com/api-tokens" target="_blank">https://bitpay.com/api-tokens</a> and put - the code that was generated in this field. Once you have paired your Mangento store you can begin accepted Bitcoins as payment - on your store.]]> + <![CDATA[You must first create a pairing code for your BitPay Merchant account at <a href="https://bitpay.com/api-tokens" target="_blank">https://bitpay.com/api-tokens</a> and copy/paste + that code here. Once you have successfully paired this Magento store with your BitPay Merchant account, you can begin accepting Bitcoins as payment.]]> </comment> <frontend_type>text</frontend_type> <backend_model>bitpay/config_pairingCode</backend_model> @@ -51,7 +50,7 @@ <title translate="label"> <label>Title</label> <comment> - What your customers will see during their checkout experience. + <![CDATA[This is the payment method name your customers will see during checkout.]]> </comment> <frontend_type>text</frontend_type> <sort_order>20</sort_order> @@ -73,7 +72,9 @@ </network> <debug translate="label"> <label>Debug</label> - <comment>By enabling this, it will output more verbose information in log files.</comment> + <comment> + <![CDATA[By enabling this, it will output more verbose information in log files.]]> + </comment> <frontend_type>select</frontend_type> <source_model>adminhtml/system_config_source_yesno</source_model> <sort_order>40</sort_order> @@ -105,7 +106,9 @@ <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>1</show_in_store> - <comment><![CDATA[Determines when a BitPay payment confirmation is sent.<br /><strong>High:</strong> an invoice is confirmed immediately when payment received.<br /><strong>Medium:</strong> an invoice is confirmed after 1 block confirmation by the network (~10 mins).<br /><strong>Low:</strong> an invoice is confirmed after 6 block confirmations by the network (~1 hour).<br />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.]]></comment> + <comment> + <![CDATA[Determines when a BitPay payment confirmation is sent.<br /><strong>High:</strong> an invoice is confirmed immediately when payment received.<br /><strong>Medium:</strong> an invoice is confirmed after 1 block confirmation by the Bitcoin network (~10 mins).<br /><strong>Low:</strong> an invoice is confirmed after 6 block confirmations by the Bitcoin network (~1 hour).<br />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.]]> + </comment> </speed> <mage_settings_heading> <label>Magento Specific Settings</label> @@ -169,7 +172,7 @@ <invoice_paid translate="label"> <label>Paid</label> <comment> - An invoice is considered "paid" when the Bitcoin network sees a transaction. + <![CDATA[An invoice is considered "paid" when the Bitcoin network sees a transaction.]]> </comment> <frontend_type>select</frontend_type> <source_model>adminhtml/system_config_source_order_status</source_model> @@ -181,7 +184,7 @@ <invoice_confirmed translate="label"> <label>Confirmed</label> <comment> - A confirmed invoice means that the Bitcoin network has approved the transaction. + <![CDATA[A confirmed invoice means that the Bitcoin network has approved the transaction.]]> </comment> <frontend_type>select</frontend_type> <source_model>adminhtml/system_config_source_order_status</source_model> @@ -193,7 +196,7 @@ <invoice_complete translate="label"> <label>Complete</label> <comment> - Complete invoices mean that the funds for the sale have been confirmed and deposited into your BitPay merchant account. + <![CDATA[A completed BitPay invoice indicates the payment for the invoice has been fully confirmed by the Bitcoin network and the funds have been deposited into your BitPay Merchant account.]]> </comment> <frontend_type>select</frontend_type> <source_model>adminhtml/system_config_source_order_status</source_model> @@ -206,7 +209,7 @@ <label>Requirements</label> <frontend_model>adminhtml/system_config_form_field_heading</frontend_model> <comment> - This extension will not function if you do not meet the system requirements. + <![CDATA[This extension will not function if you do not meet the system requirements.]]> </comment> <sort_order>500</sort_order> <show_in_default>1</show_in_default> From 789b24b7b20c4e53fe9a7bb2659965a1f665f526 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Thu, 8 Jan 2015 16:59:58 -0500 Subject: [PATCH 216/315] Fixed invalid parameter check. --- app/code/community/Bitpay/Core/controllers/IpnController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index cce0b7f..947a077 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -40,7 +40,7 @@ public function indexAction() throw new \Exception('Could not decode the JSON payload from BitPay.'); } - if (true === empty($ipn->id) || false === isset($ipn->posData->id)) { + 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.'); } From 9a96cd9e7ad05453ba083a131cac8460e0ee1930 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Thu, 8 Jan 2015 17:02:16 -0500 Subject: [PATCH 217/315] Added namespace backslash --- app/code/community/Bitpay/Core/Block/Info.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/community/Bitpay/Core/Block/Info.php b/app/code/community/Bitpay/Core/Block/Info.php index 585e9af..32761bb 100644 --- a/app/code/community/Bitpay/Core/Block/Info.php +++ b/app/code/community/Bitpay/Core/Block/Info.php @@ -18,14 +18,14 @@ public function getBitpayInvoiceUrl() 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.'); + 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.'); + throw new \Exception('In Bitpay_Core_Block_Info::getBitpayInvoiceUrl(): could not obtain the incrementId.'); } $bitpayInvoice = \Mage::getModel('bitpay/invoice')->load($incrementId, 'increment_id'); From dad4e6a1054dce894c6cb6b4a1226963a9dfbab4 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Thu, 8 Jan 2015 17:03:16 -0500 Subject: [PATCH 218/315] Added namespace backslash --- app/code/community/Bitpay/Core/Block/Iframe.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/community/Bitpay/Core/Block/Iframe.php b/app/code/community/Bitpay/Core/Block/Iframe.php index 2504f59..b2cd580 100644 --- a/app/code/community/Bitpay/Core/Block/Iframe.php +++ b/app/code/community/Bitpay/Core/Block/Iframe.php @@ -32,7 +32,7 @@ public function getIframeUrl() if (false === isset($method) || true === empty($method)) { \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not obtain an instance of the payment method.'); - throw new Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not obtain an instance of the payment method.'); + throw new \Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not obtain an instance of the payment method.'); } $options = array_merge( @@ -48,7 +48,7 @@ public function getIframeUrl() if (false === isset($options) || true === empty($options)) { \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not merge the options array.'); - throw new Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not merge the options array.'); + throw new \Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not merge the options array.'); } else { \Mage::helper('bitpay')->debugData($options); } @@ -60,7 +60,7 @@ public function getIframeUrl() if (false === isset($price) || true === empty($price)) { \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not get the new rounded price.'); - throw new Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not get the new rounded price.'); + throw new \Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not get the new rounded price.'); } //serialize info about the quote to detect changes @@ -68,7 +68,7 @@ public function getIframeUrl() if (false === isset($hash) || true === empty($hash)) { \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not get the quote hash.'); - throw new Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not merge the quote hash.'); + throw new \Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not merge the quote hash.'); } \Mage::helper('bitpay')->registerAutoloader(); From 9ae3f0581ce73a6d1a043e8b992510037516d55c Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Thu, 8 Jan 2015 17:09:03 -0500 Subject: [PATCH 219/315] Added error handling and debugging --- .../Block/Adminhtml/System/Config/Form/Field/Extension.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 index 3e0ec96..812836a 100644 --- 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 @@ -15,9 +15,14 @@ class Bitpay_Core_Block_Adminhtml_System_Config_Form_Field_Extension extends Mag */ protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element) { + if (false === isset($element) || true === empty($element)) { + \Mage::helper('bitpay')->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 (in_array($phpExtension, get_loaded_extensions())) { + if (true === in_array($phpExtension, get_loaded_extensions())) { return 'Installed'; } From d7d0cd58517a6de6615eac43bf3fa1127550ef1d Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Thu, 8 Jan 2015 17:10:53 -0500 Subject: [PATCH 220/315] Added error handling and debugging --- .../Core/Block/Adminhtml/System/Config/Form/Field/Header.php | 5 +++++ 1 file changed, 5 insertions(+) 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 index 2b9454a..80ab46f 100644 --- 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 @@ -23,6 +23,11 @@ class Bitpay_Core_Block_Adminhtml_System_Config_Form_Field_Header extends Mage_A */ public function render(Varien_Data_Form_Element_Abstract $element) { + if (false === isset($element) || true === empty($element)) { + \Mage::helper('bitpay')->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(); } } From 8b5ea763f35d5aa507231ee61eae46f3fda09b51 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Thu, 8 Jan 2015 17:21:15 -0500 Subject: [PATCH 221/315] Added error handling and debugging --- app/code/community/Bitpay/Core/Block/Form/Bitpay.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Block/Form/Bitpay.php b/app/code/community/Bitpay/Core/Block/Form/Bitpay.php index 0dce41c..9c52ebf 100644 --- a/app/code/community/Bitpay/Core/Block/Form/Bitpay.php +++ b/app/code/community/Bitpay/Core/Block/Form/Bitpay.php @@ -8,7 +8,15 @@ class Bitpay_Core_Block_Form_Bitpay extends Mage_Payment_Block_Form { protected function _construct() { + $payment_template = 'bitpay/form/bitpay.phtml'; + parent::_construct(); - $this->setTemplate('bitpay/form/bitpay.phtml'); + + if (true === file_exists($payment_template) && true === is_readable($payment_template)) { + $this->setTemplate($payment_template); + } else { + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Form_Bitpay::_construct(): HTML payment template missing or unreadable.'); + throw new \Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): HTML payment template missing or unreadable.'); + } } } From f2166063620c3d9abee35e7a478f0c72c913d8d2 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Thu, 8 Jan 2015 17:25:24 -0500 Subject: [PATCH 222/315] Added namespace backslash --- .../community/Bitpay/Core/Helper/Data.php | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/app/code/community/Bitpay/Core/Helper/Data.php b/app/code/community/Bitpay/Core/Helper/Data.php index 8eaebc7..206bd39 100644 --- a/app/code/community/Bitpay/Core/Helper/Data.php +++ b/app/code/community/Bitpay/Core/Helper/Data.php @@ -83,7 +83,7 @@ public function registerAutoloader() } 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!'); + throw new \Exception('In Bitpay_Core_Helper_Data::registerAutoloader(): autoloader file was not found or is not readable. Cannot continue!'); } } } @@ -104,7 +104,7 @@ public function generateAndSaveKeys() 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!'); + throw new \Exception('In Bitpay_Core_Helper_Data::generateAndSaveKeys(): could not create new Bitpay private key object. Cannot continue!'); } else { $this->_privateKey->generate(); } @@ -113,7 +113,7 @@ public function generateAndSaveKeys() 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!'); + 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) @@ -133,7 +133,7 @@ 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.'); + 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); } @@ -148,7 +148,7 @@ public function sendPairingRequest($pairingCode) 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!'); + 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); } @@ -169,7 +169,7 @@ public function sendPairingRequest($pairingCode) 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!'); + 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.'); } @@ -178,14 +178,14 @@ public function sendPairingRequest($pairingCode) 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!'); + 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.'); + throw new \Exception('In Bitpay_Core_Helper_Data::sendPairingRequest(): token could not be saved to database.'); } } @@ -208,7 +208,7 @@ public function getSinKey() 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!'); + throw new \Exception('In Bitpay_Core_Helper_Data::getSinKey(): could not create new BitPay SinKey object. Cannot continue!'); } $this->_sin @@ -217,7 +217,7 @@ public function getSinKey() 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!'); + throw new \Exception('In Bitpay_Core_Helper_Data::getSinKey(): could not generate a new SIN from the public key. Cannot continue!'); } return $this->_sin; @@ -251,7 +251,7 @@ public function getPublicKey() 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!'); + throw new \Exception('In Bitpay_Core_Helper_Data::getPublicKey(): could not load or generate a new public key. Cannot continue!'); } } @@ -283,7 +283,7 @@ public function getPrivateKey() 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!'); + throw new \Exception('In Bitpay_Core_Helper_Data::getPrivateKey(): could not load or generate a new private key. Cannot continue!'); } } @@ -301,7 +301,7 @@ public function getKeyManager() 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!'); + 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.'); } @@ -327,7 +327,7 @@ public function getBitpay() if (false === isset($this->_bitpay) || true === empty($this->_bitpay)) { $this->debugData('[ERROR] In Bitpay_Core_Helper_Data::getBitpay(): could not create new BitPay object. Cannot continue!'); - throw new Exception('In Bitpay_Core_Helper_Data::getBitpay(): could not create new BitPay object. Cannot continue!'); + throw new \Exception('In Bitpay_Core_Helper_Data::getBitpay(): could not create new BitPay object. Cannot continue!'); } else { $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getBitpay(): successfully created new BitPay object.'); } @@ -368,7 +368,7 @@ public function getBitpayClient() 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!'); + 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.'); } @@ -392,7 +392,7 @@ public function getToken() 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!'); + 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.'); } From 231e948034a23e1e402fb790dcdd355c203ffbf4 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Thu, 8 Jan 2015 17:30:39 -0500 Subject: [PATCH 223/315] Added namespace backslash --- lib/Bitpay/Storage/MagentoStorage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Bitpay/Storage/MagentoStorage.php b/lib/Bitpay/Storage/MagentoStorage.php index 15f8bb2..7368cfb 100644 --- a/lib/Bitpay/Storage/MagentoStorage.php +++ b/lib/Bitpay/Storage/MagentoStorage.php @@ -32,7 +32,7 @@ public function persist(\Bitpay\KeyInterface $key) $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.'); + throw new \Exception('[ERROR] In file lib/Bitpay/Storage/MagentoStorage.php, class MagentoStorage::persist - Could not instantiate a \Mage_Core_Model_Config object.'); } } From c452db1732082dd89c4c8e06b4a34a2b0d5bc2e9 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Thu, 8 Jan 2015 17:33:25 -0500 Subject: [PATCH 224/315] Formatting --- app/etc/modules/Bitpay_Core.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/etc/modules/Bitpay_Core.xml b/app/etc/modules/Bitpay_Core.xml index 86c25cc..896fa96 100644 --- a/app/etc/modules/Bitpay_Core.xml +++ b/app/etc/modules/Bitpay_Core.xml @@ -6,13 +6,13 @@ */ --> <config> - <modules> - <Bitpay_Core> - <active>true</active> - <codePool>community</codePool> - <depends> - <Mage_Payment /> - </depends> - </Bitpay_Core> - </modules> + <modules> + <Bitpay_Core> + <active>true</active> + <codePool>community</codePool> + <depends> + <Mage_Payment /> + </depends> + </Bitpay_Core> + </modules> </config> From 7aea57bed0e5b8276a39a44a7c23ec98e328145f Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 12 Jan 2015 13:28:54 -0500 Subject: [PATCH 225/315] Removed template file check This was causing an unecessary error during checkout. --- app/code/community/Bitpay/Core/Block/Form/Bitpay.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/code/community/Bitpay/Core/Block/Form/Bitpay.php b/app/code/community/Bitpay/Core/Block/Form/Bitpay.php index 9c52ebf..78bd184 100644 --- a/app/code/community/Bitpay/Core/Block/Form/Bitpay.php +++ b/app/code/community/Bitpay/Core/Block/Form/Bitpay.php @@ -11,12 +11,7 @@ protected function _construct() $payment_template = 'bitpay/form/bitpay.phtml'; parent::_construct(); - - if (true === file_exists($payment_template) && true === is_readable($payment_template)) { - $this->setTemplate($payment_template); - } else { - \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Form_Bitpay::_construct(): HTML payment template missing or unreadable.'); - throw new \Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): HTML payment template missing or unreadable.'); - } + + $this->setTemplate($payment_template); } } From 5b60c5f9fb1d64d9037842f8a1a1f21e201a535c Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 12 Jan 2015 15:25:51 -0500 Subject: [PATCH 226/315] Updated version number for latest release And removed unused code. --- scripts/package | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/scripts/package b/scripts/package index 5cd9d0a..1e13252 100755 --- a/scripts/package +++ b/scripts/package @@ -11,7 +11,7 @@ date_default_timezone_set('America/New_York'); // Main Office is in Eastern Time /** * Various Configuration Settings */ -$version = '2.1.1'; +$version = '2.1.2'; $vendorDir = __DIR__ . '/../vendor'; $distDir = __DIR__ . '/../build/dist'; $tmpDistDir = $distDir . '/tmp'; // Files will be placed here temporarly so we can zip/tar them. @@ -32,6 +32,7 @@ $finder ->in($vendorDir . '/symfony/filesystem/') ->in($vendorDir . '/symfony/dependency-injection/') ->exclude('Tests'); + foreach ($finder as $file) { $path = $file->getRelativePathname(); $filesystem->mkdir( @@ -55,6 +56,7 @@ $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 /** @@ -71,7 +73,9 @@ $xml->addChild('extends'); $xml->addChild('summary'); $xml->addChild('description'); $xml->addChild('notes'); + $authorsNode = $xml->addChild('authors'); + $authors = array( array( 'Integrations Team', // Name @@ -79,25 +83,31 @@ $authors = array( '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', '6.0.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'); @@ -126,18 +136,17 @@ foreach ($finder as $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 +// 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; zip -r %s .', $tmpDistDir, $distFile.'.zip') -//); -//$process->run(); + $process = new \Symfony\Component\Process\Process( sprintf('cd %s; tar -czf %s *', $tmpDistDir, $distFile.'.tgz') ); + $process->run(); // Cleanup From 9f060ce259237a23414c5eb7de09b56e110de05c Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Fri, 20 Feb 2015 18:58:17 -0500 Subject: [PATCH 227/315] Create GUIDE.md --- GUIDE.md | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 GUIDE.md diff --git a/GUIDE.md b/GUIDE.md new file mode 100644 index 0000000..90cfed1 --- /dev/null +++ b/GUIDE.md @@ -0,0 +1,77 @@ +# 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 + +* [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. + + +## 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 +``` + + +## Installation + +**From the Magento Connect Manager:** + +Goto [http://www.magentocommerce.com/magento-connect/bitpay-payment-method.html](http://www.magentocommerce.com/magento-connect/bitpay-payment-method.html) and click the *Install Now* link which will give you the *Extension Key* needed for the next step. + +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. + +**NOTE:** These files can also up uploaded using the *Magento Connect Manager* that comes with your Magento Store + +**WARNING:** It is good practice to backup your database before installing extensions. Please make sure you Create Backups. + + +**Using Modman:** + +Using [modman](https://github.com/colinmollenhour/modman) you can install the BitPay Magento Plugin. Once you have modman installed, run `modman init` if you have not already done so. Next just run `modman clone https://github.com/bitpay/magento-plugin.git` in the root of the Magento installation. In this case it is `/var/www/magento`. + + +## 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. From d4889dff971b021f84c07dfa80fad1aab1bf9cd6 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Fri, 20 Feb 2015 19:00:03 -0500 Subject: [PATCH 228/315] Removed install/config info and added to GUIDE --- README.md | 105 ++++++++---------------------------------------------- 1 file changed, 14 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index 5723c1d..ff99137 100644 --- a/README.md +++ b/README.md @@ -1,115 +1,38 @@ -bitpay/magento-plugin -===================== - -# Build Status - -[![Build Status](https://travis-ci.org/bitpay/magento-plugin.svg?branch=master)](https://travis-ci.org/bitpay/magento-plugin) - - # Description -Bitcoin is a powerful new peer-to-peer platform for the next generation of financial technology. The decentralized nature of the Bitcoin network allows for a highly resilient value transfer infrastructure, and this allows merchants to gain greater profits. - -This is because there are little to no fees for transferring Bitcoins from one person to another. Unlike other payment methods, Bitcoin payments cannot be reversed, so once you are paid you can ship! No waiting days for a payment to clear. - - -# Requirements - -* [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. - - -# Upgrade 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 -``` - - -# Installation - -## Magento Connect Manager - -Goto [http://www.magentocommerce.com/magento-connect/bitpay-payment-method.html](http://www.magentocommerce.com/magento-connect/bitpay-payment-method.html) and click the *Install Now* link which will give you the *Extension Key* needed for the next step. - -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. +Bitcoin payment plugin for Magento using the bitpay.com service. -***WARNING:*** It is good practice to backup your database before installing extensions. Please make sure you Create Backups. - - -## Download - -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. - -***NOTE:*** These files can also up uploaded using the *Magento Connect Manager* that comes with your Magento Store - -***WARNING:*** It is good practice to backup your database before installing extensions. Please make sure you Create Backups. - - -## modman - -Using [modman](https://github.com/colinmollenhour/modman) you can install the BitPay Magento Plugin. Once you have modman installed, run `modman init` if you have not already done so. Next just run `modman clone https://github.com/bitpay/magento-plugin.git` in the root of the Magento installation. In this case it is `/var/www/magento`. - - -# 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 +[![Build Status](https://travis-ci.org/bitpay/magento-plugin.svg?branch=master)](https://travis-ci.org/bitpay/magento-plugin) -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. +## 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 +## Support -## BitPay Support +**BitPay Support:** * [GitHub Issues](https://github.com/bitpay/magento-plugin/issues) * Open an issue if you are having issues with this plugin. * [Support](https://support.bitpay.com) * BitPay merchant support documentation -## Magento Support +**Magento Support:** * [Homepage](http://magento.com) * [Documentation](http://docs.magentocommerce.com) * [Community Edition Support Forums](https://www.magentocommerce.com/support/ce/) -# Troubleshooting +## 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. +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: +**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 @@ -121,7 +44,7 @@ As a merchant, the orders in your Magento store can be treated as any other orde * Screen grabs of error message if applicable. -# Contribute +## 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. @@ -129,14 +52,14 @@ Once you have cloned the repository, you will need to run [composer install](htt 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. +**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 +## License The MIT License (MIT) -Copyright (c) 2011-2014 BitPay, Inc. +Copyright (c) 2011-2015 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: From 393a1f2e709e57fdf50a32ce154bda27f303a343 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Thu, 12 Mar 2015 15:03:06 -0400 Subject: [PATCH 229/315] Removed PHP 5.3 build option Added experimental hhvm build option. --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0cec51f..3df5d21 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ # The MIT License (MIT) # -# Copyright (c) 2011-2014 BitPay +# 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 @@ -21,9 +21,10 @@ # THE SOFTWARE. language: php php: + - 5.6 - 5.5 - 5.4 - - 5.3 + - hhvm install: - composer install #script: ./bin/phing -verbose -propertyfile build/travis.properties build-travis @@ -37,4 +38,4 @@ cache: matrix: fast_finish: true allow_failures: - - php: 5.3 + - php: hhvm From b49ed796dd028e02b8284365c3d10d9078ebffc3 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 18 May 2015 21:05:03 -0400 Subject: [PATCH 230/315] Updated copyright year --- app/code/community/Bitpay/Core/Model/Mysql4/Invoice.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Model/Mysql4/Invoice.php b/app/code/community/Bitpay/Core/Model/Mysql4/Invoice.php index 3f40e16..b4ad214 100644 --- a/app/code/community/Bitpay/Core/Model/Mysql4/Invoice.php +++ b/app/code/community/Bitpay/Core/Model/Mysql4/Invoice.php @@ -1,6 +1,6 @@ <?php /** - * @license Copyright 2011-2014 BitPay Inc., MIT License + * @license Copyright 2011-2015 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ From 040354b8e1fbf551f163d84ac58a5e03b54ba050 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 18 May 2015 21:05:19 -0400 Subject: [PATCH 231/315] Updated copyright year --- app/code/community/Bitpay/Core/Model/Mysql4/Ipn.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Model/Mysql4/Ipn.php b/app/code/community/Bitpay/Core/Model/Mysql4/Ipn.php index bd39876..b997234 100644 --- a/app/code/community/Bitpay/Core/Model/Mysql4/Ipn.php +++ b/app/code/community/Bitpay/Core/Model/Mysql4/Ipn.php @@ -1,6 +1,6 @@ <?php /** - * @license Copyright 2011-2014 BitPay Inc., MIT License + * @license Copyright 2011-2015 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ From 18a0d932a6e740add2d0fac9bda99905ac5b0e96 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 18 May 2015 21:06:46 -0400 Subject: [PATCH 232/315] Updated copyright year --- app/code/community/Bitpay/Core/Model/Mysql4/Ipn/Collection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Model/Mysql4/Ipn/Collection.php b/app/code/community/Bitpay/Core/Model/Mysql4/Ipn/Collection.php index 4ce1711..37dda59 100644 --- a/app/code/community/Bitpay/Core/Model/Mysql4/Ipn/Collection.php +++ b/app/code/community/Bitpay/Core/Model/Mysql4/Ipn/Collection.php @@ -1,6 +1,6 @@ <?php /** - * @license Copyright 2011-2014 BitPay Inc., MIT License + * @license Copyright 2011-2015 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ From 6f11bed9d8ba77f0feac2cb7f1b2ddb7454544ed Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 18 May 2015 21:07:33 -0400 Subject: [PATCH 233/315] Updated copyright year --- .../community/Bitpay/Core/Model/Mysql4/Invoice/Collection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Model/Mysql4/Invoice/Collection.php b/app/code/community/Bitpay/Core/Model/Mysql4/Invoice/Collection.php index e86d071..a02ae15 100644 --- a/app/code/community/Bitpay/Core/Model/Mysql4/Invoice/Collection.php +++ b/app/code/community/Bitpay/Core/Model/Mysql4/Invoice/Collection.php @@ -1,6 +1,6 @@ <?php /** - * @license Copyright 2011-2014 BitPay Inc., MIT License + * @license Copyright 2011-2015 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ From ac6116ad3ab511fbd0df8410f612ed8257743ec9 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 18 May 2015 21:07:56 -0400 Subject: [PATCH 234/315] Updated copyright year --- app/code/community/Bitpay/Core/Model/Config/PairingCode.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Model/Config/PairingCode.php b/app/code/community/Bitpay/Core/Model/Config/PairingCode.php index 853452c..16a9cbf 100644 --- a/app/code/community/Bitpay/Core/Model/Config/PairingCode.php +++ b/app/code/community/Bitpay/Core/Model/Config/PairingCode.php @@ -1,6 +1,6 @@ <?php /** - * @license Copyright 2011-2014 BitPay Inc., MIT License + * @license Copyright 2011-2015 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ From 1415831dfa3955aa1d574787160c171309a2000e Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 18 May 2015 21:08:17 -0400 Subject: [PATCH 235/315] Updated copyright year --- app/code/community/Bitpay/Core/Model/Method/Bitcoin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php index e310bb6..e963430 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -1,6 +1,6 @@ <?php /** - * @license Copyright 2011-2014 BitPay Inc., MIT License + * @license Copyright 2011-2015 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ From 1a45aa80d297cee06117110214795f449f304588 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 18 May 2015 21:08:47 -0400 Subject: [PATCH 236/315] Updated copyright year --- app/code/community/Bitpay/Core/Model/Resource/Mysql4/Setup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Model/Resource/Mysql4/Setup.php b/app/code/community/Bitpay/Core/Model/Resource/Mysql4/Setup.php index ab584b8..b91a0fc 100644 --- a/app/code/community/Bitpay/Core/Model/Resource/Mysql4/Setup.php +++ b/app/code/community/Bitpay/Core/Model/Resource/Mysql4/Setup.php @@ -1,6 +1,6 @@ <?php /** - * @license Copyright 2011-2014 BitPay Inc., MIT License + * @license Copyright 2011-2015 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ From fb03e71d0fa3ccf8d25cddbb9a9913babcb7271d Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 18 May 2015 21:09:06 -0400 Subject: [PATCH 237/315] Updated copyright year --- app/code/community/Bitpay/Core/Model/Invoice.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Model/Invoice.php b/app/code/community/Bitpay/Core/Model/Invoice.php index 186fccf..3827f78 100644 --- a/app/code/community/Bitpay/Core/Model/Invoice.php +++ b/app/code/community/Bitpay/Core/Model/Invoice.php @@ -1,6 +1,6 @@ <?php /** - * @license Copyright 2011-2014 BitPay Inc., MIT License + * @license Copyright 2011-2015 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ From aebfd96eff7b4191e1593aa55de04db52e0d55ee Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 18 May 2015 21:09:22 -0400 Subject: [PATCH 238/315] Updated copyright year --- app/code/community/Bitpay/Core/Model/Ipn.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Model/Ipn.php b/app/code/community/Bitpay/Core/Model/Ipn.php index 9c05f7e..f1f6f9d 100644 --- a/app/code/community/Bitpay/Core/Model/Ipn.php +++ b/app/code/community/Bitpay/Core/Model/Ipn.php @@ -1,6 +1,6 @@ <?php /** - * @license Copyright 2011-2014 BitPay Inc., MIT License + * @license Copyright 2011-2015 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ From 4b6094b73875fccb23df27a09e2998a0fb0844d7 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 18 May 2015 21:09:41 -0400 Subject: [PATCH 239/315] Updated copyright year --- app/code/community/Bitpay/Core/Model/Network.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Model/Network.php b/app/code/community/Bitpay/Core/Model/Network.php index a77e475..b5a4aaa 100644 --- a/app/code/community/Bitpay/Core/Model/Network.php +++ b/app/code/community/Bitpay/Core/Model/Network.php @@ -1,6 +1,6 @@ <?php /** - * @license Copyright 2011-2014 BitPay Inc., MIT License + * @license Copyright 2011-2015 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ From 58b9734bcfaae59b7c7d26df1473a1db0858ce5e Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 18 May 2015 21:09:55 -0400 Subject: [PATCH 240/315] Updated copyright year --- app/code/community/Bitpay/Core/Model/Observer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Model/Observer.php b/app/code/community/Bitpay/Core/Model/Observer.php index 6b0b814..6bd9451 100644 --- a/app/code/community/Bitpay/Core/Model/Observer.php +++ b/app/code/community/Bitpay/Core/Model/Observer.php @@ -1,6 +1,6 @@ <?php /** - * @license Copyright 2011-2014 BitPay Inc., MIT License + * @license Copyright 2011-2015 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ From 27909375b5dbd9741edb1424cd1a1256e5a334e5 Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 18 May 2015 21:10:10 -0400 Subject: [PATCH 241/315] Updated copyright year --- app/code/community/Bitpay/Core/Model/Status.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Model/Status.php b/app/code/community/Bitpay/Core/Model/Status.php index 829b252..10cecad 100644 --- a/app/code/community/Bitpay/Core/Model/Status.php +++ b/app/code/community/Bitpay/Core/Model/Status.php @@ -1,6 +1,6 @@ <?php /** - * @license Copyright 2011-2014 BitPay Inc., MIT License + * @license Copyright 2011-2015 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ From 0d68f1da1707621024b440828bec6f76c89f6d1b Mon Sep 17 00:00:00 2001 From: Rich Morgan <ionux@users.noreply.github.com> Date: Mon, 18 May 2015 21:10:20 -0400 Subject: [PATCH 242/315] Update TransactionSpeed.php --- app/code/community/Bitpay/Core/Model/TransactionSpeed.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Model/TransactionSpeed.php b/app/code/community/Bitpay/Core/Model/TransactionSpeed.php index 824c788..a23f752 100644 --- a/app/code/community/Bitpay/Core/Model/TransactionSpeed.php +++ b/app/code/community/Bitpay/Core/Model/TransactionSpeed.php @@ -1,6 +1,6 @@ <?php /** - * @license Copyright 2011-2014 BitPay Inc., MIT License + * @license Copyright 2011-2015 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ From 43ebfece712df6ac6483765f021c8a855fc8da43 Mon Sep 17 00:00:00 2001 From: Sam Bohler <sam@bitpay.com> Date: Mon, 8 Jun 2015 12:22:53 -0400 Subject: [PATCH 243/315] Fixes packaging script and removes dependency on Symfony --- .../community/Bitpay/Core/Helper/Data.php | 60 ++++--------------- composer.json | 2 +- scripts/package | 3 - 3 files changed, 14 insertions(+), 51 deletions(-) diff --git a/app/code/community/Bitpay/Core/Helper/Data.php b/app/code/community/Bitpay/Core/Helper/Data.php index 206bd39..6f1141b 100644 --- a/app/code/community/Bitpay/Core/Helper/Data.php +++ b/app/code/community/Bitpay/Core/Helper/Data.php @@ -101,7 +101,7 @@ public function generateAndSaveKeys() } $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!'); @@ -251,7 +251,7 @@ public function getPublicKey() 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!'); + throw new \Exception('In Bitpay_Core_Helper_Data::getPublicKey(): could not load or generate a new public key. Cannot continue!'); } } @@ -283,7 +283,7 @@ public function getPrivateKey() 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!'); + throw new \Exception('In Bitpay_Core_Helper_Data::getPrivateKey(): could not load or generate a new private key. Cannot continue!'); } } @@ -298,7 +298,7 @@ public function getKeyManager() } $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!'); @@ -310,47 +310,6 @@ public function getKeyManager() return $this->_keyManager; } - /** - * Initialize an instance of Bitpay or return the one that has already - * been created. - * - * @return Bitpay\Bitpay - */ - public function getBitpay() - { - if (true === empty($this->_bitpay)) { - if (true === empty($this->_autoloaderRegistered)) { - $this->registerAutoloader(); - } - - $this->_bitpay = new Bitpay\Bitpay(array('bitpay' => $this->getBitpayConfig())); - - if (false === isset($this->_bitpay) || true === empty($this->_bitpay)) { - $this->debugData('[ERROR] In Bitpay_Core_Helper_Data::getBitpay(): could not create new BitPay object. Cannot continue!'); - throw new \Exception('In Bitpay_Core_Helper_Data::getBitpay(): could not create new BitPay object. Cannot continue!'); - } else { - $this->debugData('[INFO] In Bitpay_Core_Helper_Data::getBitpay(): successfully created new BitPay object.'); - } - } - - return $this->_bitpay; - } - - /** - * Sets up the bitpay container with settings for magento - * - * @return array - */ - protected function getBitpayConfig() - { - return array( - 'public_key' => 'payment/bitpay/public_key', - 'private_key' => 'payment/bitpay/private_key', - 'network' => \Mage::getStoreConfig('payment/bitpay/network'), - 'key_storage' => '\\Bitpay\\Storage\\MagentoStorage', - ); - } - /** * @return Bitpay\Client */ @@ -373,10 +332,17 @@ public function getBitpayClient() $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($this->getBitpay()->get('network')); - $this->_client->setAdapter($this->getBitpay()->get('adapter')); + $this->_client->setNetwork($network); + $this->_client->setAdapter($adapter); $this->_client->setToken($this->getToken()); return $this->_client; diff --git a/composer.json b/composer.json index fd5a6ee..0613e13 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ }, "require": { "composer/installers": "~1.0", - "bitpay/php-client": "2.2.*" + "bitpay/php-client": "^2.2" }, "require-dev": { "symfony/finder": "~2.3", diff --git a/scripts/package b/scripts/package index 1e13252..b7b344b 100755 --- a/scripts/package +++ b/scripts/package @@ -28,9 +28,6 @@ $finder = new \Symfony\Component\Finder\Finder(); $finder ->files() ->in($vendorDir . '/bitpay/php-client/src') - ->in($vendorDir . '/symfony/config/') - ->in($vendorDir . '/symfony/filesystem/') - ->in($vendorDir . '/symfony/dependency-injection/') ->exclude('Tests'); foreach ($finder as $file) { From bd45f405a53d9493c9981b1b5bad7e706bd71521 Mon Sep 17 00:00:00 2001 From: Alex Leitner and Sam Bohler <integrations@bitpay.com> Date: Thu, 4 Jun 2015 11:42:37 -0400 Subject: [PATCH 244/315] Fixes #79 - Stores correct timestamps for invoices and IPNS Bumped version to 2.1.4 --- CHANGELOG | 14 +++++++++----- app/code/community/Bitpay/Core/Model/Invoice.php | 6 +++--- .../Bitpay/Core/controllers/IpnController.php | 8 ++++---- app/code/community/Bitpay/Core/etc/config.xml | 2 +- ...ysql4-install-2.0.0.php => install-2.1.4.php} | 4 ++-- .../sql/bitpay_setup/upgrade-2.1.2-2.1.4.php | 16 ++++++++++++++++ modman | 1 - scripts/package | 2 +- 8 files changed, 36 insertions(+), 17 deletions(-) rename app/code/community/Bitpay/Core/sql/bitpay_setup/{mysql4-install-2.0.0.php => install-2.1.4.php} (96%) create mode 100644 app/code/community/Bitpay/Core/sql/bitpay_setup/upgrade-2.1.2-2.1.4.php diff --git a/CHANGELOG b/CHANGELOG index 9555da8..8da7a03 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,13 @@ +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 + 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 diff --git a/app/code/community/Bitpay/Core/Model/Invoice.php b/app/code/community/Bitpay/Core/Model/Invoice.php index 3827f78..556a931 100644 --- a/app/code/community/Bitpay/Core/Model/Invoice.php +++ b/app/code/community/Bitpay/Core/Model/Invoice.php @@ -39,9 +39,9 @@ public function prepareWithBitpayInvoice($invoice) 'price' => $invoice->getPrice(), 'currency' => $invoice->getCurrency()->getCode(), 'order_id' => $invoice->getOrderId(), - 'invoice_time' => $invoice->getInvoiceTime(), - 'expiration_time' => $invoice->getExpirationTime(), - 'current_time' => $invoice->getCurrentTime(), + 'invoice_time' => intval($invoice->getInvoiceTime() / 1000), + 'expiration_time' => intval($invoice->getExpirationTime() / 1000), + 'current_time' => intval($invoice->getCurrentTime() / 1000), 'btc_paid' => $invoice->getBtcPaid(), 'rate' => $invoice->getRate(), 'exception_status' => $invoice->getExceptionStatus(), diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index 947a077..7b4d142 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -60,9 +60,9 @@ public function indexAction() 'btc_price' => isset($ipn->btcPrice) ? $ipn->btcPrice : '', 'price' => isset($ipn->price) ? $ipn->price : '', 'currency' => isset($ipn->currency) ? $ipn->currency : '', - 'invoice_time' => isset($ipn->invoiceTime) ? $ipn->invoiceTime : '', - 'expiration_time' => isset($ipn->expirationTime) ? $ipn->expirationTime : '', - 'current_time' => isset($ipn->currentTime) ? $ipn->currentTime : '', + '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) : '', 'btc_paid' => isset($ipn->btcPaid) ? $ipn->btcPaid : '', 'rate' => isset($ipn->rate) ? $ipn->rate : '', 'exception_status' => isset($ipn->exceptionStatus) ? $ipn->exceptionStatus : '', @@ -103,7 +103,7 @@ public function indexAction() // Update the order to notifiy that it has been paid if (true === in_array($invoice->getStatus(), array('paid', 'confirmed', 'complete'))) { $payment = \Mage::getModel('sales/order_payment')->setOrder($order); - + if (true === isset($payment) && false === empty($payment)) { $payment->registerCaptureNotification($invoice->getPrice()); $order->addPayment($payment); diff --git a/app/code/community/Bitpay/Core/etc/config.xml b/app/code/community/Bitpay/Core/etc/config.xml index 2046dd9..1c403aa 100644 --- a/app/code/community/Bitpay/Core/etc/config.xml +++ b/app/code/community/Bitpay/Core/etc/config.xml @@ -8,7 +8,7 @@ <config> <modules> <Bitpay_Core> - <version>2.0.1</version> + <version>2.1.4</version> </Bitpay_Core> </modules> diff --git a/app/code/community/Bitpay/Core/sql/bitpay_setup/mysql4-install-2.0.0.php b/app/code/community/Bitpay/Core/sql/bitpay_setup/install-2.1.4.php similarity index 96% rename from app/code/community/Bitpay/Core/sql/bitpay_setup/mysql4-install-2.0.0.php rename to app/code/community/Bitpay/Core/sql/bitpay_setup/install-2.1.4.php index 3425f49..b4b135d 100644 --- a/app/code/community/Bitpay/Core/sql/bitpay_setup/mysql4-install-2.0.0.php +++ b/app/code/community/Bitpay/Core/sql/bitpay_setup/install-2.1.4.php @@ -6,7 +6,7 @@ $this->startSetup(); /** - * IPN Log Table, used to keep track of incoiming IPNs + * 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(); @@ -20,7 +20,7 @@ $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('curent_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)); 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 @@ +<?php +/** + * @license Copyright 2011-2014 BitPay Inc., MIT License + * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE + */ +$this->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/modman b/modman index e12f70b..310f7c0 100644 --- a/modman +++ b/modman @@ -25,4 +25,3 @@ app/design/frontend/base/default/layout/bitpay.xml app/design/frontend/base/defa app/design/frontend/base/default/template/bitpay app/design/frontend/base/default/template/bitpay app/etc/modules/Bitpay_Core.xml app/etc/modules/Bitpay_Core.xml lib/Bitpay lib/Bitpay -lib/Symfony lib/Symfony diff --git a/scripts/package b/scripts/package index b7b344b..4cd1719 100755 --- a/scripts/package +++ b/scripts/package @@ -11,7 +11,7 @@ date_default_timezone_set('America/New_York'); // Main Office is in Eastern Time /** * Various Configuration Settings */ -$version = '2.1.2'; +$version = '2.1.4'; $vendorDir = __DIR__ . '/../vendor'; $distDir = __DIR__ . '/../build/dist'; $tmpDistDir = $distDir . '/tmp'; // Files will be placed here temporarly so we can zip/tar them. From 9421861046bb87d4c3a7b4a302921f7ce0889def Mon Sep 17 00:00:00 2001 From: Sam Bohler <sam@bitpay.com> Date: Tue, 9 Jun 2015 11:27:25 -0400 Subject: [PATCH 245/315] Versioning for 2.1.5 --- CHANGELOG | 5 ++++- app/code/community/Bitpay/Core/etc/config.xml | 2 +- scripts/package | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8da7a03..c8b51ed 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +2.1.5 + Updated bitpay/php-client to 2.2.4 + 2.1.4 Removed Symfony dependency Fixed timestamp logging in database @@ -12,7 +15,7 @@ 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 diff --git a/app/code/community/Bitpay/Core/etc/config.xml b/app/code/community/Bitpay/Core/etc/config.xml index 1c403aa..6320d22 100644 --- a/app/code/community/Bitpay/Core/etc/config.xml +++ b/app/code/community/Bitpay/Core/etc/config.xml @@ -8,7 +8,7 @@ <config> <modules> <Bitpay_Core> - <version>2.1.4</version> + <version>2.1.5</version> </Bitpay_Core> </modules> diff --git a/scripts/package b/scripts/package index 4cd1719..4649608 100755 --- a/scripts/package +++ b/scripts/package @@ -11,7 +11,7 @@ date_default_timezone_set('America/New_York'); // Main Office is in Eastern Time /** * Various Configuration Settings */ -$version = '2.1.4'; +$version = '2.1.5'; $vendorDir = __DIR__ . '/../vendor'; $distDir = __DIR__ . '/../build/dist'; $tmpDistDir = $distDir . '/tmp'; // Files will be placed here temporarly so we can zip/tar them. From 204cbec02f7edd7a5c4eef1756a9cefe0e637b43 Mon Sep 17 00:00:00 2001 From: Sam Bohler <sam@bitpay.com> Date: Wed, 10 Jun 2015 08:08:34 -0400 Subject: [PATCH 246/315] Pegged bitpay/php-client to commit 9027ce67e4b28516ff1ebd1046bdd15c37a7a59f Fixes IPN issues Versioning for 2.1.6 --- CHANGELOG | 4 ++++ app/code/community/Bitpay/Core/etc/config.xml | 2 +- composer.json | 2 +- scripts/package | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c8b51ed..791a124 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +2.1.6 + Pegged bitpay/php-client to commit 9027ce67e4b28516ff1ebd1046bdd15c37a7a59f + Fixes IPN issues + 2.1.5 Updated bitpay/php-client to 2.2.4 diff --git a/app/code/community/Bitpay/Core/etc/config.xml b/app/code/community/Bitpay/Core/etc/config.xml index 6320d22..a1bc31b 100644 --- a/app/code/community/Bitpay/Core/etc/config.xml +++ b/app/code/community/Bitpay/Core/etc/config.xml @@ -8,7 +8,7 @@ <config> <modules> <Bitpay_Core> - <version>2.1.5</version> + <version>2.1.6</version> </Bitpay_Core> </modules> diff --git a/composer.json b/composer.json index 0613e13..42ce0dc 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ }, "require": { "composer/installers": "~1.0", - "bitpay/php-client": "^2.2" + "bitpay/php-client": "dev-master#9027ce67e4b28516ff1ebd1046bdd15c37a7a59f" }, "require-dev": { "symfony/finder": "~2.3", diff --git a/scripts/package b/scripts/package index 4649608..a494d4a 100755 --- a/scripts/package +++ b/scripts/package @@ -11,7 +11,7 @@ date_default_timezone_set('America/New_York'); // Main Office is in Eastern Time /** * Various Configuration Settings */ -$version = '2.1.5'; +$version = '2.1.6'; $vendorDir = __DIR__ . '/../vendor'; $distDir = __DIR__ . '/../build/dist'; $tmpDistDir = $distDir . '/tmp'; // Files will be placed here temporarly so we can zip/tar them. From f6930898951c5d888c7ef4f45c1883beca6340f8 Mon Sep 17 00:00:00 2001 From: Alex Leitner <alex@bitpay.com> Date: Fri, 19 Jun 2015 11:08:16 -0400 Subject: [PATCH 247/315] Fixes #78: MagentoStorage::load returns KeyInterface instance or throws an exception --- CHANGELOG | 3 +++ lib/Bitpay/Storage/MagentoStorage.php | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 791a124..a38bcb9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +Unreleased + MagentoStorage::load returns KeyInterface instance or throws an exception. + 2.1.6 Pegged bitpay/php-client to commit 9027ce67e4b28516ff1ebd1046bdd15c37a7a59f Fixes IPN issues diff --git a/lib/Bitpay/Storage/MagentoStorage.php b/lib/Bitpay/Storage/MagentoStorage.php index 7368cfb..fbff56d 100644 --- a/lib/Bitpay/Storage/MagentoStorage.php +++ b/lib/Bitpay/Storage/MagentoStorage.php @@ -52,14 +52,14 @@ public function load($id) */ 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.'); - return false; + 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 . '.'); - return false; + 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 . '.'); From b247fbcfee89811b101916145cae1c1cb0e92b6d Mon Sep 17 00:00:00 2001 From: Alex Leitner <alex@bitpay.com> Date: Thu, 18 Jun 2015 14:13:29 -0400 Subject: [PATCH 248/315] Fixes #82: IPN handler now does not override manually updated order statuses --- CHANGELOG | 1 + .../Bitpay/Core/controllers/IpnController.php | 32 +++++++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a38bcb9..d7ced8d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ Unreleased 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 diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index 7b4d142..aaa0db7 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -101,7 +101,10 @@ public function indexAction() } // Update the order to notifiy that it has been paid - if (true === in_array($invoice->getStatus(), array('paid', 'confirmed', 'complete'))) { + $transactionSpeed = \Mage::getStoreConfig('payment/bitpay/speed'); + if ($invoice->getStatus() === 'paid' + || ($invoice->getStatus() === 'confirmed' && $transactionSpeed === 'high')) { + $payment = \Mage::getModel('sales/order_payment')->setOrder($order); if (true === isset($payment) && false === empty($payment)) { @@ -116,6 +119,7 @@ public function indexAction() } $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.'); @@ -127,12 +131,28 @@ public function indexAction() 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 to in the Bitpay IPN controller.'); + \Mage::throwException('Could not retrieve the defined state parameter to update this order in the Bitpay IPN controller.'); } - $order->addStatusToHistory( - $state, - sprintf('[INFO] In Bitpay_Core_IpnController::indexAction(), Incoming IPN status "%s" updated order state to "%s"', $invoice->getStatus(), $state) - )->save(); + // 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; + } + + } } From f2a6b69314cafd4ed12a8c02eee75f4992458feb Mon Sep 17 00:00:00 2001 From: Sam Bohler <sam@bitpay.com> Date: Wed, 24 Jun 2015 09:00:44 -0400 Subject: [PATCH 249/315] Versioning for v2.1.7 --- CHANGELOG | 2 ++ app/code/community/Bitpay/Core/etc/config.xml | 2 +- scripts/package | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d7ced8d..dad31ac 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ Unreleased + +2.1.7 MagentoStorage::load returns KeyInterface instance or throws an exception. IPN handler now does not override manually updated order statuses diff --git a/app/code/community/Bitpay/Core/etc/config.xml b/app/code/community/Bitpay/Core/etc/config.xml index a1bc31b..a62a075 100644 --- a/app/code/community/Bitpay/Core/etc/config.xml +++ b/app/code/community/Bitpay/Core/etc/config.xml @@ -8,7 +8,7 @@ <config> <modules> <Bitpay_Core> - <version>2.1.6</version> + <version>2.1.7</version> </Bitpay_Core> </modules> diff --git a/scripts/package b/scripts/package index a494d4a..8c7659a 100755 --- a/scripts/package +++ b/scripts/package @@ -11,7 +11,7 @@ date_default_timezone_set('America/New_York'); // Main Office is in Eastern Time /** * Various Configuration Settings */ -$version = '2.1.6'; +$version = '2.1.7'; $vendorDir = __DIR__ . '/../vendor'; $distDir = __DIR__ . '/../build/dist'; $tmpDistDir = $distDir . '/tmp'; // Files will be placed here temporarly so we can zip/tar them. From a25d5320ba1e2e8bb1d7a2ad69abf600f48db453 Mon Sep 17 00:00:00 2001 From: Alex Leitner <alex@bitpay.com> Date: Fri, 26 Jun 2015 11:05:13 -0400 Subject: [PATCH 250/315] Fixes #81: Customer info now logged for invoice --- CHANGELOG | 1 + .../Bitpay/Core/Model/Method/Bitcoin.php | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index dad31ac..4ac9be4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,5 @@ Unreleased + Customer info now logged for invoice 2.1.7 MagentoStorage::load returns KeyInterface instance or throws an exception. diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php index e963430..d7fb171 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -282,6 +282,46 @@ private function addBuyerInfo($invoice, $order) $buyer->setFirstName($order->getCustomerFirstname()); $buyer->setLastName($order->getCustomerLastname()); + + $address = $order->getBillingAddress(); + + if (!empty($address->getStreet1())) { + $buyer->setAddress( + array( + $address->getStreet1(), + $address->getStreet2(), + $address->getStreet3(), + $address->getStreet4() + ) + ); + } + + if (!empty($address->getRegionCode())) { + $buyer->setState($address->getRegionCode()); + } else if (!empty($address->getRegion())) { + $buyer->setState($address->getRegion()); + } + + if (!empty($address->getCountry())) { + $buyer->setCountry($address->getCountry()); + } + + if (!empty($address->getCity())) { + $buyer->setCity($address->getCity()); + } + + if (!empty($address->getPostcode())) { + $buyer->setZip($address->getPostcode()); + } + + if (!empty($address->getEmail())) { + $buyer->setEmail($address->getEmail()); + } + + if (!empty($address->getTelephone())) { + $buyer->setPhone($address->getTelephone()); + } + $invoice->setBuyer($buyer); return $invoice; From 14fb6302c0561ebe0edce9a00f6877d580a107ea Mon Sep 17 00:00:00 2001 From: Alex Leitner <alex@bitpay.com> Date: Wed, 8 Jul 2015 13:39:12 -0400 Subject: [PATCH 251/315] Fixes #90: Direct Checkout now an option BitPay magento plugin --- .../community/Bitpay/Core/Block/Iframe.php | 71 +++++++------------ .../community/Bitpay/Core/Model/Invoice.php | 8 +-- app/code/community/Bitpay/Core/Model/Ipn.php | 47 ++++++++++++ .../Bitpay/Core/Model/Method/Bitcoin.php | 53 +++++++++++--- .../Core/Model/Resource/Ipn/Collection.php | 44 ++++++++++++ .../Core/controllers/IndexController.php | 15 ++-- .../Bitpay/Core/controllers/IpnController.php | 8 ++- app/code/community/Bitpay/Core/etc/config.xml | 1 + app/code/community/Bitpay/Core/etc/system.xml | 9 +++ .../frontend/base/default/layout/bitpay.xml | 5 ++ .../default/template/bitpay/form/bitpay.phtml | 9 +-- .../base/default/template/bitpay/iframe.phtml | 7 +- 12 files changed, 201 insertions(+), 76 deletions(-) create mode 100644 app/code/community/Bitpay/Core/Model/Resource/Ipn/Collection.php diff --git a/app/code/community/Bitpay/Core/Block/Iframe.php b/app/code/community/Bitpay/Core/Block/Iframe.php index b2cd580..1b62b5d 100644 --- a/app/code/community/Bitpay/Core/Block/Iframe.php +++ b/app/code/community/Bitpay/Core/Block/Iframe.php @@ -23,6 +23,24 @@ protected function _construct() */ 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 } @@ -30,60 +48,23 @@ public function getIframeUrl() /*** @var Bitpay_Core_Model_PaymentMethod ***/ $method = $this->getQuote()->getPayment()->getMethodInstance(); + $amount = $this->getQuote()->getGrandTotal(); + if (false === isset($method) || true === empty($method)) { \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not obtain an instance of the payment method.'); throw new \Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not obtain an instance of the payment method.'); } - $options = array_merge( - array( - 'currency' => $this->getQuote()->getQuoteCurrencyCode(), - 'fullNotifications' => 'true', - 'notificationURL' => \Mage::getUrl('bitpay/ipn'), - 'redirectURL' => \Mage::getUrl('checkout/onepage/success'), - 'transactionSpeed' => \Mage::getStoreConfig('payment/bitpay/speed'), - ), - $method->extractAddress($this->getQuote()->getShippingAddress()) - ); - - if (false === isset($options) || true === empty($options)) { - \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not merge the options array.'); - throw new \Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not merge the options array.'); - } else { - \Mage::helper('bitpay')->debugData($options); - } - - // 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($this->getQuote()->getGrandTotal(), 4); - - if (false === isset($price) || true === empty($price)) { - \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not get the new rounded price.'); - throw new \Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not get the new rounded price.'); - } - - //serialize info about the quote to detect changes - $hash = $method->getQuoteHash($this->getQuote()->getId()); - - if (false === isset($hash) || true === empty($hash)) { - \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not get the quote hash.'); - throw new \Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not merge the quote hash.'); - } - - \Mage::helper('bitpay')->registerAutoloader(); + $bitcoinMethod = \Mage::getModel('bitpay/method_bitcoin'); - //$invoice = bpCreateInvoice($quoteId, $price, array('quoteId' => $quoteId, 'quoteHash' => $hash), $options); - $invoice = array('url' => 'https://test.bitpay.com/invoice?id=5NxFkXcJbCSivtQRJa4kHP'); - - if (array_key_exists('error', $invoice)) { - \Mage::helper('bitpay')->debugData(array('Error creating bitpay invoice', $invoice['error'],)); + try { + $bitcoinMethod->authorize($payment, $amount, true); + } catch (\Exception $e) { + \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): failed with the message: ' . $e->getMessage()); \Mage::throwException("Error creating BitPay invoice. Please try again or use another payment option."); - return false; } - //return $invoice['url'].'&view=iframe'; - return false; + return $bitcoinMethod->getOrderPlaceRedirectUrl(); } } diff --git a/app/code/community/Bitpay/Core/Model/Invoice.php b/app/code/community/Bitpay/Core/Model/Invoice.php index 556a931..af5180b 100644 --- a/app/code/community/Bitpay/Core/Model/Invoice.php +++ b/app/code/community/Bitpay/Core/Model/Invoice.php @@ -57,17 +57,17 @@ public function prepareWithBitpayInvoice($invoice) * @param Mage_Sales_Model_Order $order * @return Bitpay_Core_Model_Invoice */ - public function prepateWithOrder($order) + 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->getQuoteId(), - 'increment_id' => $order->getIncrementId(), + 'quote_id' => $order['quote_id'], + 'increment_id' => $order['increment_id'], ) ); diff --git a/app/code/community/Bitpay/Core/Model/Ipn.php b/app/code/community/Bitpay/Core/Model/Ipn.php index f1f6f9d..2c04c4c 100644 --- a/app/code/community/Bitpay/Core/Model/Ipn.php +++ b/app/code/community/Bitpay/Core/Model/Ipn.php @@ -12,6 +12,53 @@ class Bitpay_Core_Model_Ipn extends Mage_Core_Model_Abstract */ protected function _construct() { + parent::_construct(); $this->_init('bitpay/ipn'); } + + /** + * @param string $quoteId + * @param array $statuses + * + * @return boolean + */ + function GetStatusReceived($quoteId, $statuses) + { + if (!$quoteId) + { + return false; + } + + $quote = Mage::getModel('sales/quote')->load($quoteId, 'entity_id'); + + if (!$quote) + { + Mage::log('quote not found', Zend_Log::WARN, 'bitpay.log'); + return false; + } + + $collection = $this->getCollection(); + + foreach ($collection as $i) + { + if ($quoteId == json_decode($i->pos_data, true)['quoteId']) { + 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 index d7fb171..ccf9f06 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -35,8 +35,24 @@ class Bitpay_Core_Model_Method_Bitcoin extends Mage_Payment_Model_Method_Abstrac * @param float $amount * @return Bitpay_Core_Model_PaymentMethod */ - public function authorize(Varien_Object $payment, $amount) + public function authorize(Varien_Object $payment, $amount, $iframe = false) { + // Check if coming from iframe or submit button + if ((!Mage::getStoreConfig('payment/bitpay/fullscreen') && $iframe === false) + || (Mage::getStoreConfig('payment/bitpay/fullscreen') && $iframe === true)) { + $quoteId = $payment->getOrder()->getQuoteId(); + $ipn = Mage::getModel('bitpay/ipn'); + + if (!$ipn->GetQuotePaid($quoteId)) + { + // This is the error that is displayed to the customer during checkout. + Mage::throwException("Order not paid for. Please pay first and then Place your Order."); + Mage::log('Order not paid for. Please pay first and then Place Your Order.', Zend_Log::CRIT, Mage::helper('bitpay')->getLogFile()); + } + + return $this; + } + if (false === isset($payment) || false === isset($amount) || true === empty($payment) || true === empty($amount)) { $this->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.'); @@ -61,7 +77,7 @@ public function authorize(Varien_Object $payment, $amount) \Mage::throwException('In Bitpay_Core_Model_Method_Bitcoin::authorize(): Could not authorize transaction.'); } - self::$_redirectUrl = $bitpayInvoice->getUrl(); + self::$_redirectUrl = (Mage::getStoreConfig('payment/bitpay/fullscreen')) ? $bitpayInvoice->getUrl(): $bitpayInvoice->getUrl().'&view=iframe'; $this->debugData( array( @@ -70,10 +86,13 @@ public function authorize(Varien_Object $payment, $amount) ) ); + $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) - ->prepateWithOrder($payment->getOrder()) + ->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()); @@ -200,6 +219,7 @@ public function getOrderPlaceRedirectUrl() $this->debugData('[INFO] In Bitpay_Core_Model_Method_Bitcoin::getOrderPlaceRedirectUrl(): $_redirectUrl is ' . self::$_redirectUrl); return self::$_redirectUrl; + } /** @@ -247,12 +267,22 @@ private function prepareInvoice($invoice, $payment, $amount) $this->debugData('[INFO] In Bitpay_Core_Model_Method_Bitcoin::prepareInvoice(): entered function with good invoice, payment and amount parameters.'); } - $invoice->setOrderId($payment->getOrder()->getIncrementId()); - $invoice->setPosData(json_encode(array('id' => $payment->getOrder()->getIncrementId()))); + $quote = Mage::getSingleton('checkout/session')->getQuote(); + $order = \Mage::getModel('sales/order')->load($quote->getId(), 'quote_id'); - $invoice = $this->addCurrencyInfo($invoice, $payment->getOrder()); + if (Mage::getStoreConfig('payment/bitpay/fullscreen')) { + $invoice->setOrderId($order->getIncrementId()); + $invoice->setPosData(json_encode(array('orderId' => $order->getIncrementId()))); + } else { + $invoice->setOrderId($quote->getId()); + $invoice->setPosData(json_encode(array('quoteId' => $quote->getId()))); + $convertQuote = Mage::getSingleton('sales/convert_quote'); + $order = $convertQuote->toOrder($quote); + } + + $invoice = $this->addCurrencyInfo($invoice, $order); $invoice = $this->addPriceInfo($invoice, $amount); - $invoice = $this->addBuyerInfo($invoice, $payment->getOrder()); + $invoice = $this->addBuyerInfo($invoice, $order); return $invoice; } @@ -280,10 +310,17 @@ private function addBuyerInfo($invoice, $order) throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::addBuyerInfo(): could not construct new BitPay buyer object.'); } + $buyer->setFirstName($order->getCustomerFirstname()); $buyer->setLastName($order->getCustomerLastname()); - $address = $order->getBillingAddress(); + + if (Mage::getStoreConfig('payment/bitpay/fullscreen')) { + $address = $order->getBillingAddress(); + } else { + $quote = Mage::getSingleton('checkout/session')->getQuote(); + $address = $quote->getBillingAddress(); + } if (!empty($address->getStreet1())) { $buyer->setAddress( 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 @@ +<?php + +/** + * 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. + */ + +class Bitpay_Core_Model_Resource_Ipn_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +{ + + /** + */ + protected function _construct() + { + parent::_construct(); + $this->_init('bitpay/ipn'); + } + + public function delete() + { + foreach ($this->getItems() as $item) { + $item->delete(); + } + } +} diff --git a/app/code/community/Bitpay/Core/controllers/IndexController.php b/app/code/community/Bitpay/Core/controllers/IndexController.php index 6e9a0b2..1907672 100644 --- a/app/code/community/Bitpay/Core/controllers/IndexController.php +++ b/app/code/community/Bitpay/Core/controllers/IndexController.php @@ -14,21 +14,16 @@ class Bitpay_Core_IndexController extends Mage_Core_Controller_Front_Action */ public function indexAction() { - $paid = false; - $params = $this->getRequest()->getParams(); - - if (true === isset($params['paid'])) { - \Mage::helper('bitpay')->registerAutoloader(); - \Mage::helper('bitpay')->debugData($params); - } else { - \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IndexController::indexAction(), Could not get parameters from HTTP request.'); - } + $params = $this->getRequest()->getParams(); + $quoteId = $params['quote']; + \Mage::helper('bitpay')->registerAutoloader(); + \Mage::helper('bitpay')->debugData($params); + $paid = Mage::getModel('bitpay/ipn')->GetQuotePaid($quoteId); $this->loadLayout(); $this->getResponse()->setHeader('Content-type', 'application/json'); - // ? $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 index aaa0db7..ce9681a 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -69,7 +69,13 @@ public function indexAction() ) )->save(); - $order = \Mage::getModel('sales/order')->loadByIncrementId($ipn->posData->id); + + // 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->getId())) { \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IpnController::indexAction(), Invalid Bitpay IPN received.'); diff --git a/app/code/community/Bitpay/Core/etc/config.xml b/app/code/community/Bitpay/Core/etc/config.xml index a62a075..e27b6f3 100644 --- a/app/code/community/Bitpay/Core/etc/config.xml +++ b/app/code/community/Bitpay/Core/etc/config.xml @@ -118,6 +118,7 @@ <notification_url>bitpay/ipn</notification_url> <redirect_url>checkout/onepage/success</redirect_url> <speed>medium</speed> + <fullscreen>0</fullscreen> <invoice_new>new</invoice_new> <invoice_paid>processing</invoice_paid> <invoice_confirmed>processing</invoice_confirmed> diff --git a/app/code/community/Bitpay/Core/etc/system.xml b/app/code/community/Bitpay/Core/etc/system.xml index 86a14ed..274c9e0 100644 --- a/app/code/community/Bitpay/Core/etc/system.xml +++ b/app/code/community/Bitpay/Core/etc/system.xml @@ -98,6 +98,15 @@ <show_in_website>1</show_in_website> <show_in_store>1</show_in_store> </redirect_url> + <fullscreen translate="label"> + <label>Redirect Checkout</label> + <frontend_type>select</frontend_type> + <source_model>adminhtml/system_config_source_yesno</source_model> + <sort_order>2</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>0</show_in_store> + </fullscreen> <speed translate="label"> <label>Transaction Speed</label> <frontend_type>select</frontend_type> diff --git a/app/design/frontend/base/default/layout/bitpay.xml b/app/design/frontend/base/default/layout/bitpay.xml index eac80cb..f90422b 100644 --- a/app/design/frontend/base/default/layout/bitpay.xml +++ b/app/design/frontend/base/default/layout/bitpay.xml @@ -6,4 +6,9 @@ */ --> <layout> + <checkout_onepage_review> + <reference name="checkout.onepage.review.info.items.after"> + <block name="iframe" type="bitpay/iframe"/> + </reference> + </checkout_onepage_review> </layout> diff --git a/app/design/frontend/base/default/template/bitpay/form/bitpay.phtml b/app/design/frontend/base/default/template/bitpay/form/bitpay.phtml index c64fa51..87091e0 100644 --- a/app/design/frontend/base/default/template/bitpay/form/bitpay.phtml +++ b/app/design/frontend/base/default/template/bitpay/form/bitpay.phtml @@ -9,8 +9,9 @@ */ $_code = $this->getMethodCode(); - -echo '<ul class="form-list" id="payment_form_' . $_code . '" style="display:none;">' . - '<li>You will be transfered to <a href="https://bitpay.com" target="_blank">BitPay</a> to complete your purchase when using this payment method.</li>' . - '</ul>'; +if (Mage::getStoreConfig('payment/bitpay/fullscreen')) { + echo '<ul class="form-list" id="payment_form_' . $_code . '" style="display:none;">' . + '<li>You will be transfered to <a href="https://bitpay.com" target="_blank">BitPay</a> to complete your purchase when using this payment method.</li>' . + '</ul>'; +} ?> diff --git a/app/design/frontend/base/default/template/bitpay/iframe.phtml b/app/design/frontend/base/default/template/bitpay/iframe.phtml index 836804f..fd7fb94 100644 --- a/app/design/frontend/base/default/template/bitpay/iframe.phtml +++ b/app/design/frontend/base/default/template/bitpay/iframe.phtml @@ -37,14 +37,13 @@ new PeriodicalExecuter(function() { asynchronous: true, evalScripts: true, onComplete: function(request, json) { - data = request.responseText.evalJSON(); + var data = request.responseText.evalJSON(); if (data.paid) { - buttons = $$("button.btn-checkout"); - buttons.each(function(btn) { btn.click(); }) + review.save(); } } } - ) + ); }, 5); //]]> </script> From d649fdd7caff9efddb852ad160704cd5203846cb Mon Sep 17 00:00:00 2001 From: System Administrator <root@Samuels-MacBook-Pro.local> Date: Wed, 8 Jul 2015 14:47:35 -0400 Subject: [PATCH 252/315] Versioning for 2.1.8 --- CHANGELOG | 3 +++ app/code/community/Bitpay/Core/etc/config.xml | 2 +- scripts/package | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4ac9be4..9c2fe77 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ Unreleased + +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. diff --git a/app/code/community/Bitpay/Core/etc/config.xml b/app/code/community/Bitpay/Core/etc/config.xml index e27b6f3..a84aa6f 100644 --- a/app/code/community/Bitpay/Core/etc/config.xml +++ b/app/code/community/Bitpay/Core/etc/config.xml @@ -8,7 +8,7 @@ <config> <modules> <Bitpay_Core> - <version>2.1.7</version> + <version>2.1.8</version> </Bitpay_Core> </modules> diff --git a/scripts/package b/scripts/package index 8c7659a..68e3e9a 100755 --- a/scripts/package +++ b/scripts/package @@ -11,7 +11,7 @@ date_default_timezone_set('America/New_York'); // Main Office is in Eastern Time /** * Various Configuration Settings */ -$version = '2.1.7'; +$version = '2.1.8'; $vendorDir = __DIR__ . '/../vendor'; $distDir = __DIR__ . '/../build/dist'; $tmpDistDir = $distDir . '/tmp'; // Files will be placed here temporarly so we can zip/tar them. From cc72b715454f550b610de256ecc687d127b718c7 Mon Sep 17 00:00:00 2001 From: James Hawkins <jamesh@providentmetals.com> Date: Mon, 13 Jul 2015 10:34:25 -0500 Subject: [PATCH 253/315] Creating is_empty function PHP 5.4 does not allow objects in the empty() function. --- .../Bitpay/Core/Model/Method/Bitcoin.php | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php index ccf9f06..5c543fe 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -322,7 +322,7 @@ private function addBuyerInfo($invoice, $order) $address = $quote->getBillingAddress(); } - if (!empty($address->getStreet1())) { + if (!$this->is_empty($address->getStreet1())) { $buyer->setAddress( array( $address->getStreet1(), @@ -333,29 +333,29 @@ private function addBuyerInfo($invoice, $order) ); } - if (!empty($address->getRegionCode())) { + if (!$this->is_empty($address->getRegionCode())) { $buyer->setState($address->getRegionCode()); - } else if (!empty($address->getRegion())) { + } else if (!$this->is_empty($address->getRegion())) { $buyer->setState($address->getRegion()); } - if (!empty($address->getCountry())) { + if (!$this->is_empty($address->getCountry())) { $buyer->setCountry($address->getCountry()); } - if (!empty($address->getCity())) { + if (!$this->is_empty($address->getCity())) { $buyer->setCity($address->getCity()); } - if (!empty($address->getPostcode())) { + if (!$this->is_empty($address->getPostcode())) { $buyer->setZip($address->getPostcode()); } - if (!empty($address->getEmail())) { + if (!$this->is_empty($address->getEmail())) { $buyer->setEmail($address->getEmail()); } - if (!empty($address->getTelephone())) { + if (!$this->is_empty($address->getTelephone())) { $buyer->setPhone($address->getTelephone()); } @@ -364,6 +364,16 @@ private function addBuyerInfo($invoice, $order) return $invoice; } + /** + * Checks if string is empty - fix for PHP 5.4 + * + * @param $str + * @return boolean + */ + private function is_empty($str){ + return empty($str); + } + /** * Adds currency information to the invoice * From 09696e9049f44055568a1554e862ba1744eca24e Mon Sep 17 00:00:00 2001 From: James Hawkins <jamesh@providentmetals.com> Date: Mon, 13 Jul 2015 16:27:30 -0500 Subject: [PATCH 254/315] Update IpnController.php Fixing additional php 5.4 error --- app/code/community/Bitpay/Core/controllers/IpnController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index ce9681a..bc77f79 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -77,7 +77,7 @@ public function indexAction() $order = \Mage::getModel('sales/order')->load($ipn->posData->quoteId, 'quote_id'); } - if (false === isset($order) || true === empty($order->getId())) { + if (false === isset($order) || true === $this->is_empty($order->getId())) { \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IpnController::indexAction(), Invalid Bitpay IPN received.'); \Mage::throwException('Invalid Bitpay IPN received.'); } From 12ca4c371f61268fe8532054258f5cf01316cbf3 Mon Sep 17 00:00:00 2001 From: James Hawkins <jamesh@providentmetals.com> Date: Tue, 14 Jul 2015 09:04:39 -0500 Subject: [PATCH 255/315] Storing objects in variables Moving to null and empty string checks --- .../Bitpay/Core/Model/Method/Bitcoin.php | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php index 5c543fe..553faad 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -321,11 +321,12 @@ private function addBuyerInfo($invoice, $order) $quote = Mage::getSingleton('checkout/session')->getQuote(); $address = $quote->getBillingAddress(); } - - if (!$this->is_empty($address->getStreet1())) { + + $street = $address->getStreet1(); + if (null !== $street && '' !== $street) { $buyer->setAddress( array( - $address->getStreet1(), + $street, $address->getStreet2(), $address->getStreet3(), $address->getStreet4() @@ -333,30 +334,37 @@ private function addBuyerInfo($invoice, $order) ); } - if (!$this->is_empty($address->getRegionCode())) { - $buyer->setState($address->getRegionCode()); - } else if (!$this->is_empty($address->getRegion())) { - $buyer->setState($address->getRegion()); + $region = $address->getRegion(); + $regioncode = $address->getRegionCode(); + if (null !== $regioncode && '' !== $regioncode) { + $buyer->setState($regioncode); + } else if (null !== $region && '' !== $region) { + $buyer->setState($region); } - if (!$this->is_empty($address->getCountry())) { - $buyer->setCountry($address->getCountry()); + $country = $address->getCountry(); + if (null !== $country && '' !== $country) { + $buyer->setCountry($country); } - if (!$this->is_empty($address->getCity())) { - $buyer->setCity($address->getCity()); + $city = $address->getCity(); + if (null !== $city && '' !== $city) { + $buyer->setCity($city); } - if (!$this->is_empty($address->getPostcode())) { - $buyer->setZip($address->getPostcode()); + $postcode = $address->getPostcode(); + if (null !== $postcode && '' !== $postcode) { + $buyer->setZip($postcode); } - if (!$this->is_empty($address->getEmail())) { - $buyer->setEmail($address->getEmail()); + $email = $address->getEmail(); + if (null !== $email && '' !== $email) { + $buyer->setEmail($email); } - - if (!$this->is_empty($address->getTelephone())) { - $buyer->setPhone($address->getTelephone()); + + $telephone = $address->getTelephone(); + if (null !== $telephone && '' !== $telephone) { + $buyer->setPhone($telephone); } $invoice->setBuyer($buyer); @@ -364,16 +372,6 @@ private function addBuyerInfo($invoice, $order) return $invoice; } - /** - * Checks if string is empty - fix for PHP 5.4 - * - * @param $str - * @return boolean - */ - private function is_empty($str){ - return empty($str); - } - /** * Adds currency information to the invoice * From 84e7977238d5d6cafe954e3cf778f324693172d7 Mon Sep 17 00:00:00 2001 From: James Hawkins <jamesh@providentmetals.com> Date: Tue, 14 Jul 2015 09:06:13 -0500 Subject: [PATCH 256/315] Storing object in variable --- app/code/community/Bitpay/Core/controllers/IpnController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index bc77f79..a7ae8a7 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -77,7 +77,8 @@ public function indexAction() $order = \Mage::getModel('sales/order')->load($ipn->posData->quoteId, 'quote_id'); } - if (false === isset($order) || true === $this->is_empty($order->getId())) { + $orderId = $order->getId(); + if (false === isset($order) || (true === empty($orderId))) { \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_IpnController::indexAction(), Invalid Bitpay IPN received.'); \Mage::throwException('Invalid Bitpay IPN received.'); } From 1d12be67e913cdeda44f5e5f8a63453a5c222d9d Mon Sep 17 00:00:00 2001 From: James Hawkins <jamesh@providentmetals.com> Date: Tue, 14 Jul 2015 09:18:19 -0500 Subject: [PATCH 257/315] Cleaning up error checking First checking for a valid order, then checking for a valid order id --- .../community/Bitpay/Core/controllers/IpnController.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index a7ae8a7..2e457e6 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -77,8 +77,13 @@ public function indexAction() $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($order) || (true === empty($orderId))) { + 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.'); } From 55d3f0fbe513342607b630db4384c2c1339aba48 Mon Sep 17 00:00:00 2001 From: Sam Bohler <sam@bitpay.com> Date: Thu, 16 Jul 2015 06:13:34 -0400 Subject: [PATCH 258/315] Versioning for 2.1.9 --- CHANGELOG | 3 +++ app/code/community/Bitpay/Core/etc/config.xml | 2 +- scripts/package | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9c2fe77..183786a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ Unreleased +2.1.9 + Fixed code incompatible with PHP 5.4 + 2.1.8 Customer info now logged for invoice Added direct checkout feature diff --git a/app/code/community/Bitpay/Core/etc/config.xml b/app/code/community/Bitpay/Core/etc/config.xml index a84aa6f..db0946b 100644 --- a/app/code/community/Bitpay/Core/etc/config.xml +++ b/app/code/community/Bitpay/Core/etc/config.xml @@ -8,7 +8,7 @@ <config> <modules> <Bitpay_Core> - <version>2.1.8</version> + <version>2.1.9</version> </Bitpay_Core> </modules> diff --git a/scripts/package b/scripts/package index 68e3e9a..d21502e 100755 --- a/scripts/package +++ b/scripts/package @@ -11,7 +11,7 @@ date_default_timezone_set('America/New_York'); // Main Office is in Eastern Time /** * Various Configuration Settings */ -$version = '2.1.8'; +$version = '2.1.9'; $vendorDir = __DIR__ . '/../vendor'; $distDir = __DIR__ . '/../build/dist'; $tmpDistDir = $distDir . '/tmp'; // Files will be placed here temporarly so we can zip/tar them. From a70999249dac577ab408a92589f0e74cdc5cda44 Mon Sep 17 00:00:00 2001 From: Sam Bohler <sam@bitpay.com> Date: Wed, 26 Aug 2015 15:04:38 -0400 Subject: [PATCH 259/315] Fixes #98: Pulls currency code from instead of --- app/code/community/Bitpay/Core/Model/Method/Bitcoin.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php index 553faad..2dedafb 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -38,7 +38,7 @@ class Bitpay_Core_Model_Method_Bitcoin extends Mage_Payment_Model_Method_Abstrac public function authorize(Varien_Object $payment, $amount, $iframe = false) { // Check if coming from iframe or submit button - if ((!Mage::getStoreConfig('payment/bitpay/fullscreen') && $iframe === false) + if ((!Mage::getStoreConfig('payment/bitpay/fullscreen') && $iframe === false) || (Mage::getStoreConfig('payment/bitpay/fullscreen') && $iframe === true)) { $quoteId = $payment->getOrder()->getQuoteId(); $ipn = Mage::getModel('bitpay/ipn'); @@ -62,7 +62,7 @@ public function authorize(Varien_Object $payment, $amount, $iframe = false) // 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.'); @@ -219,7 +219,7 @@ public function getOrderPlaceRedirectUrl() $this->debugData('[INFO] In Bitpay_Core_Model_Method_Bitcoin::getOrderPlaceRedirectUrl(): $_redirectUrl is ' . self::$_redirectUrl); return self::$_redirectUrl; - + } /** @@ -395,7 +395,7 @@ private function addCurrencyInfo($invoice, $order) throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::addCurrencyInfo(): could not construct new BitPay currency object.'); } - $currency->setCode($order->getBaseCurrencyCode()); + $currency->setCode($order->getOrderCurrencyCode()); $invoice->setCurrency($currency); return $invoice; From 5f494f3763371a0a382370d8510f470b6f3ea3b2 Mon Sep 17 00:00:00 2001 From: Paul Daigle <paul@bitpay.com> Date: Fri, 28 Aug 2015 15:19:51 -0400 Subject: [PATCH 260/315] bump package version --- scripts/package | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/package b/scripts/package index d21502e..720e3cd 100755 --- a/scripts/package +++ b/scripts/package @@ -11,7 +11,7 @@ date_default_timezone_set('America/New_York'); // Main Office is in Eastern Time /** * Various Configuration Settings */ -$version = '2.1.9'; +$version = '2.1.10'; $vendorDir = __DIR__ . '/../vendor'; $distDir = __DIR__ . '/../build/dist'; $tmpDistDir = $distDir . '/tmp'; // Files will be placed here temporarly so we can zip/tar them. From 3f9b79622cf801830132a42e456b186e01abf61c Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte <kleetus@users.noreply.github.com> Date: Fri, 6 Nov 2015 10:15:08 -0500 Subject: [PATCH 261/315] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index ff99137..36bfe8f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +# 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. From 4c4ff8810e12e979f38b74f6fb58b5f8d0072dc2 Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte <kleetus@users.noreply.github.com> Date: Wed, 18 Nov 2015 14:55:56 -0500 Subject: [PATCH 262/315] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 36bfe8f..605393c 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ To get up and running with our plugin quickly, see the GUIDE here: https://githu **BitPay Support:** +* Last Cart Version Tested: 1.9.0.1 * [GitHub Issues](https://github.com/bitpay/magento-plugin/issues) * Open an issue if you are having issues with this plugin. * [Support](https://support.bitpay.com) From fcd44e0a0839ee9af5fcb4638f6d6ba7ae3d7728 Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte <kleetus@users.noreply.github.com> Date: Wed, 18 Nov 2015 14:56:33 -0500 Subject: [PATCH 263/315] Update GUIDE.md --- GUIDE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/GUIDE.md b/GUIDE.md index 90cfed1..0c72817 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -6,6 +6,7 @@ You must have a BitPay merchant account to use this plugin. It's free to [sign- ## Server Requirements +* Last Cart Version Tested: 1.9.0.1 * [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. From e991500dc1383b0bf0349cfbd2f082a6017845bf Mon Sep 17 00:00:00 2001 From: James Walpole <jameswalpole@users.noreply.github.com> Date: Fri, 19 Feb 2016 09:25:41 -0500 Subject: [PATCH 264/315] Update Support Link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 605393c..0e4fb9c 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ To get up and running with our plugin quickly, see the GUIDE here: https://githu * Last Cart Version Tested: 1.9.0.1 * [GitHub Issues](https://github.com/bitpay/magento-plugin/issues) * Open an issue if you are having issues with this plugin. -* [Support](https://support.bitpay.com) +* [Support](https://help.bitpay.com) * BitPay merchant support documentation **Magento Support:** From 75184d74c2ea8d5109ec3d5092da7ebf11d2f04e Mon Sep 17 00:00:00 2001 From: spofa <shepcom@live.de> Date: Tue, 8 Mar 2016 22:42:16 +0800 Subject: [PATCH 265/315] add getLogeFile function add getLogeFile function --- app/code/community/Bitpay/Core/Helper/Data.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/code/community/Bitpay/Core/Helper/Data.php b/app/code/community/Bitpay/Core/Helper/Data.php index 6f1141b..6c0a463 100644 --- a/app/code/community/Bitpay/Core/Helper/Data.php +++ b/app/code/community/Bitpay/Core/Helper/Data.php @@ -367,4 +367,12 @@ public function getToken() return $token; } + + /** + * @return string + */ + public function getLogFile() + { + return "payment_bitpay.log"; + } } From e7811a71c57f5df977e7d015621b048f9ee762b5 Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte <chrisk@bitpay.com> Date: Fri, 27 May 2016 16:02:48 -0400 Subject: [PATCH 266/315] Repaired redirected checkout BitPay invoice amount calculation - If shopping cart user's currency is not the same as the Magento configured base currency and the type of BitPay invoice is a redirected checkout (non iFrame), then the $amount (invoice amount) passed to Bitcoin.php authorize() will be in the base currency and not the user's currency. A BitPay invoice is then created based on the user's currency and the amount, which is wrong. - The Bitcoin.php authorize function now recomputes the amount in the event that the amount is not correct. - Updated the README and GUIDE to show latest Magento version checked. --- GUIDE.md | 2 +- README.md | 2 +- .../community/Bitpay/Core/Model/Method/Bitcoin.php | 14 +++++++++----- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/GUIDE.md b/GUIDE.md index 0c72817..d59156c 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -6,7 +6,7 @@ You must have a BitPay merchant account to use this plugin. It's free to [sign- ## Server Requirements -* Last Cart Version Tested: 1.9.0.1 +* Last Cart Version Tested: 1.9.2.1 * [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. diff --git a/README.md b/README.md index 0e4fb9c..0a79ce9 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ To get up and running with our plugin quickly, see the GUIDE here: https://githu **BitPay Support:** -* Last Cart Version Tested: 1.9.0.1 +* Last Cart Version Tested: 1.9.2.1 * [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) diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php index 2dedafb..93538ee 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -37,6 +37,15 @@ class Bitpay_Core_Model_Method_Bitcoin extends Mage_Payment_Model_Method_Abstrac */ public function authorize(Varien_Object $payment, $amount, $iframe = false) { + if (false === isset($payment) || false === isset($amount) || true === empty($payment) || true === empty($amount)) { + $this->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.'); + } + + if ($iframe === false) { + $amount = $payment->getOrder()->getQuote()->getGrandTotal(); + } + // Check if coming from iframe or submit button if ((!Mage::getStoreConfig('payment/bitpay/fullscreen') && $iframe === false) || (Mage::getStoreConfig('payment/bitpay/fullscreen') && $iframe === true)) { @@ -53,11 +62,6 @@ public function authorize(Varien_Object $payment, $amount, $iframe = false) return $this; } - if (false === isset($payment) || false === isset($amount) || true === empty($payment) || true === empty($amount)) { - $this->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.'); - } - $this->debugData('[INFO] Bitpay_Core_Model_Method_Bitcoin::authorize(): authorizing new order.'); // Create BitPay Invoice From 69565a2a4905aee8bcd6b95e3a2a7c52dd87b4f9 Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte <chrisk@bitpay.com> Date: Fri, 27 May 2016 16:15:38 -0400 Subject: [PATCH 267/315] Bumped package script hard-coded version. --- scripts/package | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/package b/scripts/package index 720e3cd..53ab261 100755 --- a/scripts/package +++ b/scripts/package @@ -11,7 +11,7 @@ date_default_timezone_set('America/New_York'); // Main Office is in Eastern Time /** * Various Configuration Settings */ -$version = '2.1.10'; +$version = '2.1.11'; $vendorDir = __DIR__ . '/../vendor'; $distDir = __DIR__ . '/../build/dist'; $tmpDistDir = $distDir . '/tmp'; // Files will be placed here temporarly so we can zip/tar them. From d0a51527700da0444db167538e87e7bf03e31cd7 Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte <ckleeschulte@rentpath.com> Date: Sat, 9 Jul 2016 17:08:17 -0400 Subject: [PATCH 268/315] Iframe invoice will create order - For merchants using an iframe BitPay invoice (set "Redirect Checkout" to "No") - Before this change: - When a user confirms the payment method, the BitPay invoice will be displayed and the Magento order will not yet be created. - The Magento order is only created if the user pays the invoice, leaves the checkout browser window open, and BitPay is able to see the user's transaction and send the IPN successfully to the Magento server. - The problem is when the customer sends a bitcoin transaction, but their transaction isn't relayed because the fee is too low or the network is under high load. - Under this condition, the customer has paid, yet they have no order with the merchant. The merchant has a hard time serving their customer under these conditions. After this change: - When the user confirms their payment method, the BitPay invoice will NOT be displayed until the user clicks "Place Order". - This will guarantee that an order is created BEFORE the customer is able to pay the BitPay invoice. - This will allow the merchant to self-serve their customer by being able to find their customer in their dashboard and issue a refund or apply the transaction. --- .../Bitpay/Core/Model/Method/Bitcoin.php | 14 +--- .../base/default/template/bitpay/iframe.phtml | 75 ++++++++++++++----- 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php index 93538ee..59255c9 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -46,19 +46,11 @@ public function authorize(Varien_Object $payment, $amount, $iframe = false) $amount = $payment->getOrder()->getQuote()->getGrandTotal(); } - // Check if coming from iframe or submit button + // This means that this authorize method was called from a Magento checkout controller + // and not the iframe.phtml template while this plugin is in non-redirected checkout mode, + // therefore we shouldn't create a new invoice, we should just return the model if ((!Mage::getStoreConfig('payment/bitpay/fullscreen') && $iframe === false) || (Mage::getStoreConfig('payment/bitpay/fullscreen') && $iframe === true)) { - $quoteId = $payment->getOrder()->getQuoteId(); - $ipn = Mage::getModel('bitpay/ipn'); - - if (!$ipn->GetQuotePaid($quoteId)) - { - // This is the error that is displayed to the customer during checkout. - Mage::throwException("Order not paid for. Please pay first and then Place your Order."); - Mage::log('Order not paid for. Please pay first and then Place Your Order.', Zend_Log::CRIT, Mage::helper('bitpay')->getLogFile()); - } - return $this; } diff --git a/app/design/frontend/base/default/template/bitpay/iframe.phtml b/app/design/frontend/base/default/template/bitpay/iframe.phtml index fd7fb94..2536f5d 100644 --- a/app/design/frontend/base/default/template/bitpay/iframe.phtml +++ b/app/design/frontend/base/default/template/bitpay/iframe.phtml @@ -1,6 +1,6 @@ -<?php +<?php /** - * @license Copyright 2011-2014 BitPay Inc., MIT License + * @license Copyright 2011-2016 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ @@ -18,7 +18,7 @@ switch($url) { echo 'Error creating invoice. Please try again or try another payment solution.'; break; default: - echo '<iframe src="'.$url.'" style="width:500px; height:150px; overflow:hidden; border:none; margin:auto; display:block;" scrolling="no" allowtransparency="true" frameborder="0"> </iframe>'; + echo '<iframe class="bitpay_invoice_iframe" src="'.$url.'" style="width:500px; height:150px; overflow:hidden; border:none; margin:auto; display:none;" scrolling="no" allowtransparency="true" frameborder="0"> </iframe>'; break; } $quoteId = $this->getQuote()->getId(); @@ -27,23 +27,64 @@ $url = Mage::getUrl('bitpay/index/index/'); if ($request->getScheme() == 'https') { $url = str_replace('http://', 'https://', $url); } + ?> <script type="text/javascript"> //<![CDATA[ -new PeriodicalExecuter(function() { - new Ajax.Request( - "<?php echo $url . '?quote=' . $quoteId; ?>", - { - asynchronous: true, - evalScripts: true, - onComplete: function(request, json) { - var data = request.responseText.evalJSON(); - if (data.paid) { - review.save(); - } - } +if ($$('iframe.bitpay_invoice_iframe').length > 0) { + var bpListener = { + nextStep: function(transport) { + if (transport && transport.responseText) { + try{ + response = eval('(' + transport.responseText + ')'); + } + catch (e) { + response = {}; + } + if (response.success) { + $$('iframe.bitpay_invoice_iframe')[0].setStyle({display:'block'}); + $$('button.btn-checkout')[0].setStyle({display:'none'}); + var ipnPoller = new PeriodicalExecuter(function() { + new Ajax.Request("<?php echo $url . '?quote=' . $quoteId; ?>", + { + asynchronous: true, + evalScripts: true, + onComplete: function(request, json) { + var data = request.responseText.evalJSON(); + if (data.paid) { + ipnPoller.stop(); + review.nextStep(transport); + } + } + }); + }, 5); + } else { + var msg = response.error_messages; + if (typeof(msg)=='object') { + msg = msg.join("\n"); + } + if (msg) { + alert(msg); + } + } + if (response.update_section) { + $('checkout-'+response.update_section.name+'-load').update(response.update_section.html); } - ); -}, 5); + if (response.goto_section) { + checkout.gotoSection(response.goto_section, true); + } + } + } + }; + var reviewCheck = new PeriodicalExecuter(function() { + if (review) { + reviewCheck.stop(); + var originalSaveHandler = review.onSave; + review.onSave = bpListener.nextStep.bindAsEventListener(review); + } + }, 1); +} //]]> </script> + + From ca747284baf6cf286df8de6eee8f3698674cc6f3 Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte <ckleeschulte@rentpath.com> Date: Sat, 9 Jul 2016 20:16:46 -0400 Subject: [PATCH 269/315] Helper scripts - Added an ipn helper script to post ipns --- scripts/config.json | 11 +++ scripts/send_ipn_for_last_order_created.js | 97 ++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 scripts/config.json create mode 100644 scripts/send_ipn_for_last_order_created.js 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/send_ipn_for_last_order_created.js b/scripts/send_ipn_for_last_order_created.js new file mode 100644 index 0000000..bb442c0 --- /dev/null +++ b/scripts/send_ipn_for_last_order_created.js @@ -0,0 +1,97 @@ +'use-strict' + +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; i<convertedKeys.length; i++) { + var rowValue = rows[0][fields[i].name]; + if (convertedKeys[i] === 'status') { + data[convertedKeys[i]] = config.status; + } else if (convertedKeys[i].match(timeRegExp)) { + data[convertedKeys[i]] = rowValue * 1000; + } + else { + data[convertedKeys[i]] = rowValue; + } + } + data.buyerFields = {}; + data.url = 'https://test.bitpay.com:443/invoice?id=' + rows[0].id; + data.posData = '{\"quoteId\":\"' + rows[0].quote_id.toString() + '\"}'; + data.btcPaid = data.btcPrice; + data.btcDue = '0.000000'; + var jsonPayload = JSON.stringify(data); + curl_args[5] = format(curl_args[5], {length: jsonPayload.length}); + curl_args[11] = jsonPayload; + connection.end(); + send(curl_args); +} + +function convertNames(names) { + var ret = []; + if (!names || typeof names !== 'object' || names.length === 0) { + return ret; + } + + for (var j = 0; j < names.length; j++) { + var name = names[j].name; + var converted = name[0]; + var i = 1; + while (i < (name.length - 1)) { + if (name[i] === '_') { + converted += name[i+1].toUpperCase(); + i = i+2; + } else { + converted += name[i]; + i++; + } + } + converted += name[name.length-1]; + ret.push(converted); + } + + return ret; +} + +postIpn(); From 04800164e614aa5bff66c23006442307b84b06a7 Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte <chrisk@bitpay.com> Date: Mon, 18 Jul 2016 14:23:28 -0400 Subject: [PATCH 270/315] Added changelog entries. --- CHANGELOG | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 183786a..37cb388 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,12 @@ Unreleased +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 From 9c1ed7fdbe3ddafb0cfdc0b90bd3b3f65890d502 Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte <chrisk@bitpay.com> Date: Tue, 6 Sep 2016 18:07:17 -0400 Subject: [PATCH 271/315] Unified order creation to the same place as redirected check out. - Redirected check out and iframe checkout share the same order creation logic. - The order will be created during saveOrder call to backend. - The difference between the two will be injection of client-side javascript during payment type selection. - if BitPay/Bitcoin, client-side JS will be inserted into page to swap the out the next step after order save is complete - in all cases, Magento will create the invoice and pass the client a redirect_url - the iframe invoice will receive the redirect url and build an iframe with the src being the bitpay url. - the redirected check out will not have any new client side JS, so the next step will be to redirect from Magento. --- .../community/Bitpay/Core/Block/Iframe.php | 24 ++---------------- app/code/community/Bitpay/Core/Model/Ipn.php | 11 ++++---- .../Bitpay/Core/Model/Method/Bitcoin.php | 25 +++---------------- .../Core/controllers/IndexController.php | 15 +++++++---- .../base/default/template/bitpay/iframe.phtml | 18 +++++++------ scripts/package | 2 +- scripts/send_ipn_for_last_order_created.js | 4 +-- 7 files changed, 35 insertions(+), 64 deletions(-) diff --git a/app/code/community/Bitpay/Core/Block/Iframe.php b/app/code/community/Bitpay/Core/Block/Iframe.php index 1b62b5d..b4312f7 100644 --- a/app/code/community/Bitpay/Core/Block/Iframe.php +++ b/app/code/community/Bitpay/Core/Block/Iframe.php @@ -3,7 +3,6 @@ * @license Copyright 2011-2014 BitPay Inc., MIT License * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE * - * TODO: Finish this iFrame implemenation... :/ */ class Bitpay_Core_Block_Iframe extends Mage_Checkout_Block_Onepage_Payment @@ -24,6 +23,7 @@ protected function _construct() public function getIframeUrl() { + //by this time, we MUST have an order already created if (!($quote = Mage::getSingleton('checkout/session')->getQuote()) or !($payment = $quote->getPayment()) or !($paymentMethod = $payment->getMethod()) @@ -45,26 +45,6 @@ public function getIframeUrl() return 'paid'; // quote's already paid, so don't show the iframe } - /*** @var Bitpay_Core_Model_PaymentMethod ***/ - $method = $this->getQuote()->getPayment()->getMethodInstance(); - - $amount = $this->getQuote()->getGrandTotal(); - - if (false === isset($method) || true === empty($method)) { - \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not obtain an instance of the payment method.'); - throw new \Exception('In Bitpay_Core_Block_Iframe::getIframeUrl(): Could not obtain an instance of the payment method.'); - } - - $bitcoinMethod = \Mage::getModel('bitpay/method_bitcoin'); - - try { - $bitcoinMethod->authorize($payment, $amount, true); - } catch (\Exception $e) { - \Mage::helper('bitpay')->debugData('[ERROR] In Bitpay_Core_Block_Iframe::getIframeUrl(): failed with the message: ' . $e->getMessage()); - \Mage::throwException("Error creating BitPay invoice. Please try again or use another payment option."); - return false; - } - - return $bitcoinMethod->getOrderPlaceRedirectUrl(); + return 'bitpay'; } } diff --git a/app/code/community/Bitpay/Core/Model/Ipn.php b/app/code/community/Bitpay/Core/Model/Ipn.php index 2c04c4c..7b81368 100644 --- a/app/code/community/Bitpay/Core/Model/Ipn.php +++ b/app/code/community/Bitpay/Core/Model/Ipn.php @@ -29,19 +29,20 @@ function GetStatusReceived($quoteId, $statuses) return false; } - $quote = Mage::getModel('sales/quote')->load($quoteId, 'entity_id'); + $order = \Mage::getModel('sales/order')->load($quoteId, 'quote_id'); - if (!$quote) - { - Mage::log('quote not found', Zend_Log::WARN, 'bitpay.log'); + 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(); $collection = $this->getCollection(); foreach ($collection as $i) { - if ($quoteId == json_decode($i->pos_data, true)['quoteId']) { + if ($orderId == json_decode($i->pos_data, true)['orderId']) { if (in_array($i->status, $statuses)) { return true; } diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php index 59255c9..4aefa76 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -42,17 +42,7 @@ public function authorize(Varien_Object $payment, $amount, $iframe = false) throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::authorize(): missing payment or amount parameters.'); } - if ($iframe === false) { - $amount = $payment->getOrder()->getQuote()->getGrandTotal(); - } - - // This means that this authorize method was called from a Magento checkout controller - // and not the iframe.phtml template while this plugin is in non-redirected checkout mode, - // therefore we shouldn't create a new invoice, we should just return the model - if ((!Mage::getStoreConfig('payment/bitpay/fullscreen') && $iframe === false) - || (Mage::getStoreConfig('payment/bitpay/fullscreen') && $iframe === true)) { - return $this; - } + $amount = $payment->getOrder()->getQuote()->getGrandTotal(); $this->debugData('[INFO] Bitpay_Core_Model_Method_Bitcoin::authorize(): authorizing new order.'); @@ -82,7 +72,7 @@ public function authorize(Varien_Object $payment, $amount, $iframe = false) ) ); - $quote = Mage::getSingleton('checkout/session')->getQuote(); + $quote = \Mage::getSingleton('checkout/session')->getQuote(); $order = \Mage::getModel('sales/order')->load($quote->getId(), 'quote_id'); // Save BitPay Invoice in database for reference @@ -266,15 +256,8 @@ private function prepareInvoice($invoice, $payment, $amount) $quote = Mage::getSingleton('checkout/session')->getQuote(); $order = \Mage::getModel('sales/order')->load($quote->getId(), 'quote_id'); - if (Mage::getStoreConfig('payment/bitpay/fullscreen')) { - $invoice->setOrderId($order->getIncrementId()); - $invoice->setPosData(json_encode(array('orderId' => $order->getIncrementId()))); - } else { - $invoice->setOrderId($quote->getId()); - $invoice->setPosData(json_encode(array('quoteId' => $quote->getId()))); - $convertQuote = Mage::getSingleton('sales/convert_quote'); - $order = $convertQuote->toOrder($quote); - } + $invoice->setOrderId($order->getIncrementId()); + $invoice->setPosData(json_encode(array('orderId' => $order->getIncrementId()))); $invoice = $this->addCurrencyInfo($invoice, $order); $invoice = $this->addPriceInfo($invoice, $amount); diff --git a/app/code/community/Bitpay/Core/controllers/IndexController.php b/app/code/community/Bitpay/Core/controllers/IndexController.php index 1907672..f954ddb 100644 --- a/app/code/community/Bitpay/Core/controllers/IndexController.php +++ b/app/code/community/Bitpay/Core/controllers/IndexController.php @@ -14,16 +14,21 @@ class Bitpay_Core_IndexController extends Mage_Core_Controller_Front_Action */ public function indexAction() { - $params = $this->getRequest()->getParams(); - $quoteId = $params['quote']; \Mage::helper('bitpay')->registerAutoloader(); \Mage::helper('bitpay')->debugData($params); - $paid = Mage::getModel('bitpay/ipn')->GetQuotePaid($quoteId); - $this->loadLayout(); + $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'); - $this->getResponse()->setBody(json_encode(array('paid' => $paid))); + return $this->getResponse()->setBody(json_encode(array('paid' => $paid))); } } diff --git a/app/design/frontend/base/default/template/bitpay/iframe.phtml b/app/design/frontend/base/default/template/bitpay/iframe.phtml index 2536f5d..3320d2e 100644 --- a/app/design/frontend/base/default/template/bitpay/iframe.phtml +++ b/app/design/frontend/base/default/template/bitpay/iframe.phtml @@ -18,7 +18,7 @@ switch($url) { echo 'Error creating invoice. Please try again or try another payment solution.'; break; default: - echo '<iframe class="bitpay_invoice_iframe" src="'.$url.'" style="width:500px; height:150px; overflow:hidden; border:none; margin:auto; display:none;" scrolling="no" allowtransparency="true" frameborder="0"> </iframe>'; + echo '<div class="bitpay_invoice_div" style="display:none; width:100%;"></div>'; break; } $quoteId = $this->getQuote()->getId(); @@ -31,7 +31,7 @@ if ($request->getScheme() == 'https') { ?> <script type="text/javascript"> //<![CDATA[ -if ($$('iframe.bitpay_invoice_iframe').length > 0) { +if ($$('div.bitpay_invoice_div').length > 0) { var bpListener = { nextStep: function(transport) { if (transport && transport.responseText) { @@ -42,17 +42,21 @@ if ($$('iframe.bitpay_invoice_iframe').length > 0) { response = {}; } if (response.success) { - $$('iframe.bitpay_invoice_iframe')[0].setStyle({display:'block'}); $$('button.btn-checkout')[0].setStyle({display:'none'}); + var invoice_div = $$('div.bitpay_invoice_div')[0]; + invoice_div.setStyle({display:'block'}); + invoice_div.innerHTML = '<iframe class="bitpay_invoice_iframe" src="' + response.redirect + '" style="width:500px; height:150px; overflow:hidden; border:none; display:block; margin:auto; scrolling="no" allowtransparency="true" frameborder="0"></iframe>'; var ipnPoller = new PeriodicalExecuter(function() { new Ajax.Request("<?php echo $url . '?quote=' . $quoteId; ?>", - { - asynchronous: true, - evalScripts: true, - onComplete: function(request, json) { + { + asynchronous: true, + evalScripts: true, + onComplete: function(request) { var data = request.responseText.evalJSON(); if (data.paid) { ipnPoller.stop(); + response.redirect = null; + transport.responseText = JSON.stringify(response); review.nextStep(transport); } } diff --git a/scripts/package b/scripts/package index 53ab261..e362694 100755 --- a/scripts/package +++ b/scripts/package @@ -11,7 +11,7 @@ date_default_timezone_set('America/New_York'); // Main Office is in Eastern Time /** * Various Configuration Settings */ -$version = '2.1.11'; +$version = '2.1.12'; $vendorDir = __DIR__ . '/../vendor'; $distDir = __DIR__ . '/../build/dist'; $tmpDistDir = $distDir . '/tmp'; // Files will be placed here temporarly so we can zip/tar them. diff --git a/scripts/send_ipn_for_last_order_created.js b/scripts/send_ipn_for_last_order_created.js index bb442c0..b52db24 100644 --- a/scripts/send_ipn_for_last_order_created.js +++ b/scripts/send_ipn_for_last_order_created.js @@ -1,5 +1,3 @@ -'use-strict' - 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)'; @@ -58,7 +56,7 @@ function processRows(err, rows, fields) { } data.buyerFields = {}; data.url = 'https://test.bitpay.com:443/invoice?id=' + rows[0].id; - data.posData = '{\"quoteId\":\"' + rows[0].quote_id.toString() + '\"}'; + data.posData = '{\"orderId\":\"' + rows[0].order_id.toString() + '\"}'; data.btcPaid = data.btcPrice; data.btcDue = '0.000000'; var jsonPayload = JSON.stringify(data); From d6f71f8cc9592e28ed3b63dfbaf7b9cfc89cfdeb Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte <chrisk@bitpay.com> Date: Wed, 7 Sep 2016 10:34:03 -0400 Subject: [PATCH 272/315] Removed unneeded comment. --- app/code/community/Bitpay/Core/Block/Iframe.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Block/Iframe.php b/app/code/community/Bitpay/Core/Block/Iframe.php index b4312f7..327894b 100644 --- a/app/code/community/Bitpay/Core/Block/Iframe.php +++ b/app/code/community/Bitpay/Core/Block/Iframe.php @@ -23,7 +23,6 @@ protected function _construct() public function getIframeUrl() { - //by this time, we MUST have an order already created if (!($quote = Mage::getSingleton('checkout/session')->getQuote()) or !($payment = $quote->getPayment()) or !($paymentMethod = $payment->getMethod()) From 4dfa531842fdf9cb68a99e4185847caba3709b88 Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte <chrisk@bitpay.com> Date: Wed, 7 Sep 2016 17:55:12 -0400 Subject: [PATCH 273/315] Added check for orderId. --- app/code/community/Bitpay/Core/Model/Ipn.php | 6 ++++++ scripts/package | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Model/Ipn.php b/app/code/community/Bitpay/Core/Model/Ipn.php index 7b81368..428c9c9 100644 --- a/app/code/community/Bitpay/Core/Model/Ipn.php +++ b/app/code/community/Bitpay/Core/Model/Ipn.php @@ -38,6 +38,12 @@ function GetStatusReceived($quoteId, $statuses) $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) diff --git a/scripts/package b/scripts/package index e362694..8efdcd9 100755 --- a/scripts/package +++ b/scripts/package @@ -11,7 +11,7 @@ date_default_timezone_set('America/New_York'); // Main Office is in Eastern Time /** * Various Configuration Settings */ -$version = '2.1.12'; +$version = '2.1.13'; $vendorDir = __DIR__ . '/../vendor'; $distDir = __DIR__ . '/../build/dist'; $tmpDistDir = $distDir . '/tmp'; // Files will be placed here temporarly so we can zip/tar them. From 957c84e1714957ef20a8ad3abddb7f21715bd496 Mon Sep 17 00:00:00 2001 From: "P.H. Poorthuis" <pieter@MacBook-Air-van-PH.local> Date: Mon, 12 Dec 2016 14:09:20 +0100 Subject: [PATCH 274/315] Switched from user currency to magento shop currency to prevent fraud messages --- app/code/community/Bitpay/Core/Model/Method/Bitcoin.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php index 4aefa76..85619e9 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -42,7 +42,8 @@ public function authorize(Varien_Object $payment, $amount, $iframe = false) throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::authorize(): missing payment or amount parameters.'); } - $amount = $payment->getOrder()->getQuote()->getGrandTotal(); + // 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.'); @@ -374,7 +375,9 @@ private function addCurrencyInfo($invoice, $order) throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::addCurrencyInfo(): could not construct new BitPay currency object.'); } - $currency->setCode($order->getOrderCurrencyCode()); + //$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; From d281a3463f1cc7ed5927da5414386bde0389cc96 Mon Sep 17 00:00:00 2001 From: Pieter Poorthuis <pieter@bitpay.com> Date: Thu, 15 Dec 2016 11:40:17 +0100 Subject: [PATCH 275/315] Delete release.txt --- release.txt | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 release.txt diff --git a/release.txt b/release.txt deleted file mode 100644 index 0ce45a1..0000000 --- a/release.txt +++ /dev/null @@ -1,4 +0,0 @@ -[ ] Update version in composer.json file -[ ] Update version in `scripts/package` and run script -[ ] Update CHANGELOG file -[ ] Update to Mangento Connect and GitHub From 5d936704f80bd62623e2dff976e997c7a46d678e Mon Sep 17 00:00:00 2001 From: "P.H. Poorthuis" <pieter@MacBook-Air-van-PH.local> Date: Thu, 15 Dec 2016 15:00:06 +0100 Subject: [PATCH 276/315] Updated GUIDE & README --- GUIDE.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GUIDE.md b/GUIDE.md index d59156c..275b1d7 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -6,7 +6,7 @@ You must have a BitPay merchant account to use this plugin. It's free to [sign- ## Server Requirements -* Last Cart Version Tested: 1.9.2.1 +* Last Cart Version Tested: 1.9.3.1 * [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. diff --git a/README.md b/README.md index 0a79ce9..2d30b02 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ To get up and running with our plugin quickly, see the GUIDE here: https://githu **BitPay Support:** -* Last Cart Version Tested: 1.9.2.1 +* Last Cart Version Tested: 1.9.3.1 * [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) From 8193bdf366aa9470b830edd219960207f1743e8e Mon Sep 17 00:00:00 2001 From: Pieter Poorthuis <pieter@bitpay.com> Date: Thu, 15 Dec 2016 15:11:24 +0100 Subject: [PATCH 277/315] Update CHANGELOG --- CHANGELOG | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 37cb388..debf998 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ -Unreleased - +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 From 592fd53cc3b61899d812531d4fb2bcc8a56ac517 Mon Sep 17 00:00:00 2001 From: Gopinath <gopinath.c@innoppl.com> Date: Tue, 24 Jan 2017 17:37:58 +0530 Subject: [PATCH 278/315] Order status issue --- .../community/Bitpay/Core/Model/Observer.php | 131 +++++---- .../Bitpay/Core/Model/Order/Payment.php | 223 +++++++++++++++ .../Bitpay/Core/controllers/IpnController.php | 8 +- app/code/community/Bitpay/Core/etc/config.xml | 261 +++++++++--------- 4 files changed, 434 insertions(+), 189 deletions(-) create mode 100644 app/code/community/Bitpay/Core/Model/Order/Payment.php diff --git a/app/code/community/Bitpay/Core/Model/Observer.php b/app/code/community/Bitpay/Core/Model/Observer.php index 6bd9451..067079f 100644 --- a/app/code/community/Bitpay/Core/Model/Observer.php +++ b/app/code/community/Bitpay/Core/Model/Observer.php @@ -4,72 +4,83 @@ * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE */ -class Bitpay_Core_Model_Observer -{ - /* - * TODO: Why is this here? - */ - public function checkForRequest($observer) - { - } +class Bitpay_Core_Model_Observer { - /* - * 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'); + public function implementOrderStatus($e) { + $order = $e -> getOrder(); + $paymentCode = $order -> getPayment() -> getMethodInstance() -> getCode(); + Mage::log('sales_order_place_after--------------:'); + Mage::log('$paymentCode:' . $paymentCode); + if ($paymentCode == 'bitpay') { + $order -> setState(Mage_Sales_Model_Order::STATE_NEW, true); + $order -> save(); + } - 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.'); - } + // Mage::log('$order = $event->getOrder();' . $order -> getState()); + } - /* - * 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(); + /* + * TODO: Why is this here? + */ + public function checkForRequest($observer) { + } - 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.'); - } + /* + * 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'); - /* - * 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; + 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.'); + } - // TODO: - // Does the order need to be updated? - // Yes? Update Order Status - // No? continue - } + /* + * 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(); - \Mage::helper('bitpay')->debugData('[INFO] Bitpay_Core_Model_Observer::updateOrderStates() order status update job finished.'); - } + 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(); + } - /** - * 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(); - } } 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..c4278a9 --- /dev/null +++ b/app/code/community/Bitpay/Core/Model/Order/Payment.php @@ -0,0 +1,223 @@ +<?php +class Bitpay_Core_Model_Order_Payment extends Mage_Sales_Model_Order_Payment { + +protected function _authorize($isOnline, $amount) + { + // check for authorization amount to be equal to grand total + $this->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 + $states = $order->getConfig()->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; + } + } + ?> \ No newline at end of file diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index 2e457e6..f9afb68 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -101,10 +101,10 @@ public function indexAction() } // Does the status match? - if ($invoice->getStatus() != $ipn->status) { + /* 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) { @@ -114,8 +114,8 @@ public function indexAction() // Update the order to notifiy that it has been paid $transactionSpeed = \Mage::getStoreConfig('payment/bitpay/speed'); - if ($invoice->getStatus() === 'paid' - || ($invoice->getStatus() === 'confirmed' && $transactionSpeed === 'high')) { + if ($ipn->status === 'paid' + || ($ipn->status === 'confirmed' && $transactionSpeed === 'high')) { $payment = \Mage::getModel('sales/order_payment')->setOrder($order); diff --git a/app/code/community/Bitpay/Core/etc/config.xml b/app/code/community/Bitpay/Core/etc/config.xml index db0946b..c75f139 100644 --- a/app/code/community/Bitpay/Core/etc/config.xml +++ b/app/code/community/Bitpay/Core/etc/config.xml @@ -1,131 +1,142 @@ <?xml version="1.0"?> <!-- /** - * @license Copyright 2011-2014 BitPay Inc., MIT License - * @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE - */ - --> +* @license Copyright 2011-2014 BitPay Inc., MIT License +* @see https://github.com/bitpay/magento-plugin/blob/master/LICENSE +*/ +--> <config> - <modules> - <Bitpay_Core> - <version>2.1.9</version> - </Bitpay_Core> - </modules> + <modules> + <Bitpay_Core> + <version>2.1.9</version> + </Bitpay_Core> + </modules> + <frontend> + <routers> + <bitpay> + <use>standard</use> + <args> + <module>Bitpay_Core</module> + <frontName>bitpay</frontName> + </args> + </bitpay> + </routers> + <layout> + <updates> + <bitpay> + <file>bitpay.xml</file> + </bitpay> + </updates> + </layout> + <translate> + <modules> + <bitpay> + <files> + <default>Bitpay_Core.csv</default> + </files> + </bitpay> + </modules> + </translate> + </frontend> + <admin> + <routers> + <adminhtml> + <args> + <modules> + <bitpay before="Mage_Adminhtml">Bitpay_Core_Adminhtml</bitpay> + </modules> + </args> + </adminhtml> + </routers> + </admin> + <global> + <blocks> + <bitpay> + <class>Bitpay_Core_Block</class> + </bitpay> + </blocks> + <helpers> + <bitpay> + <class>Bitpay_Core_Helper</class> + </bitpay> + </helpers> + <models> + <bitpay> + <class>Bitpay_Core_Model</class> + <resourceModel>bitpay_mysql4</resourceModel> - <frontend> - <routers> - <bitpay> - <use>standard</use> - <args> - <module>Bitpay_Core</module> - <frontName>bitpay</frontName> - </args> - </bitpay> - </routers> - <layout> - <updates> - <bitpay> - <file>bitpay.xml</file> - </bitpay> - </updates> - </layout> - <translate> - <modules> - <bitpay> - <files> - <default>Bitpay_Core.csv</default> - </files> - </bitpay> - </modules> - </translate> - </frontend> - - <admin> - <routers> - <adminhtml> - <args> - <modules> - <bitpay before="Mage_Adminhtml">Bitpay_Core_Adminhtml</bitpay> - </modules> - </args> - </adminhtml> - </routers> - </admin> - - <global> - <blocks> - <bitpay> - <class>Bitpay_Core_Block</class> - </bitpay> - </blocks> - <helpers> - <bitpay> - <class>Bitpay_Core_Helper</class> - </bitpay> - </helpers> - - <models> - <bitpay> - <class>Bitpay_Core_Model</class> - <resourceModel>bitpay_mysql4</resourceModel> - </bitpay> - <bitpay_mysql4> - <class>Bitpay_Core_Model_Mysql4</class> - <entities> - <invoice> - <table>bitpay_invoices</table> - </invoice> - <ipn> - <table>bitpay_ipns</table> - </ipn> - </entities> - </bitpay_mysql4> - </models> - - <resources> - <bitpay_write> - <connection> - <use>core_write</use> - </connection> - </bitpay_write> - <bitpay_read> - <connection> - <use>core_read</use> - </connection> - </bitpay_read> - <bitpay_setup> - <setup> - <module>Bitpay_Core</module> - <class>Bitpay_Core_Model_Resource_Mysql4_Setup</class> - </setup> - <connection> - <use>core_setup</use> - </connection> - </bitpay_setup> - </resources> - </global> - - <default> - <payment> - <bitpay> - <model>bitpay/method_bitcoin</model> - <order_status>new</order_status> - <payment_action>authorize</payment_action> - <active>0</active> - <title>Bitcoin - testnet - 0 - bitpay/ipn - checkout/onepage/success - medium - 0 - new - processing - processing - complete - canceled - canceled - - - + + + Bitpay_Core_Model_Mysql4 + + + bitpay_invoices
+
+ + bitpay_ipns
+
+
+
+ + + Bitpay_Core_Model_Order_Payment + + + + + + + + singleton + Bitpay_Core_Model_Observer + implementOrderStatus + + + + + + + + core_write + + + + + core_read + + + + + Bitpay_Core + Bitpay_Core_Model_Resource_Mysql4_Setup + + + core_setup + + + + + + + + bitpay/method_bitcoin + new + authorize + 0 + Bitcoin + testnet + 0 + bitpay/ipn + checkout/onepage/success + medium + 0 + new + processing + processing + complete + canceled + canceled + + + From 2f9b734a4660a6a7b283eb809262e94d7ca845c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dro=C5=BCd=C5=BCal?= Date: Thu, 22 Feb 2018 13:58:24 +0100 Subject: [PATCH 279/315] IPN creates after payment a second invoice in database instead of update invoice --- CHANGELOG | 2 ++ .../Bitpay/Core/controllers/IpnController.php | 16 +++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index debf998..3a77c5f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +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 diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index f9afb68..e6ea69a 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -116,13 +116,15 @@ public function indexAction() $transactionSpeed = \Mage::getStoreConfig('payment/bitpay/speed'); if ($ipn->status === 'paid' || ($ipn->status === 'confirmed' && $transactionSpeed === 'high')) { - - $payment = \Mage::getModel('sales/order_payment')->setOrder($order); - - if (true === isset($payment) && false === empty($payment)) { - $payment->registerCaptureNotification($invoice->getPrice()); - $order->addPayment($payment); - + + 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); // If the customer has not already been notified by email // send the notification now that there's a new order. if (!$order->getEmailSent()) { From 51ee0cda2c6e4cc378c6beb1b834d97d2340b46d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dro=C5=BCd=C5=BCal?= Date: Mon, 26 Feb 2018 11:47:26 +0100 Subject: [PATCH 280/315] Added missing methods in the Magento version below 1.9: _isSameCurrency, getStatusStatuses, addStatusfilter. --- CHANGELOG | 2 + .../Bitpay/Core/Model/Order/Payment.php | 61 +++++++++++++++++-- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3a77c5f..0dbc3cc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +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 diff --git a/app/code/community/Bitpay/Core/Model/Order/Payment.php b/app/code/community/Bitpay/Core/Model/Order/Payment.php index c4278a9..16153bb 100644 --- a/app/code/community/Bitpay/Core/Model/Order/Payment.php +++ b/app/code/community/Bitpay/Core/Model/Order/Payment.php @@ -9,7 +9,7 @@ protected function _authorize($isOnline, $amount) if (!$isSameCurrency || !$this->_isCaptureFinal($amount)) { $this->setIsFraudDetected(true); } - + // update totals $amount = $this->_formatAmount($amount, true); $this->setBaseAmountAuthorized($amount); @@ -60,7 +60,7 @@ protected function _authorize($isOnline, $amount) return $this; } - public function registerCaptureNotification($amount, $skipFraudDetection = false) + public function registerCaptureNotification($amount, $skipFraudDetection = false) { $this->_generateTransactionId(Mage_Sales_Model_Order_Payment_Transaction::TYPE_CAPTURE, $this->getAuthorizationTransaction() @@ -125,7 +125,7 @@ public function registerCaptureNotification($amount, $skipFraudDetection = false } - public function place() + public function place() { Mage::dispatchEvent('sales_order_payment_place_start', array('payment' => $this)); $order = $this->getOrder(); @@ -193,7 +193,15 @@ public function place() $orderStatus = $order->getConfig()->getStateDefaultStatus($orderState); } else { // check if $orderStatus has assigned a state - $states = $order->getConfig()->getStatusStates($orderStatus); + 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); } @@ -218,6 +226,49 @@ public function place() 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; } - ?> \ No newline at end of file +} +?> From 33928948eb8c8e0949c269010972b78683e8c4ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dro=C5=BCd=C5=BCal?= Date: Wed, 7 Mar 2018 12:16:14 +0100 Subject: [PATCH 281/315] display "Invoice price must be at least" error --- app/code/community/Bitpay/Core/Model/Method/Bitcoin.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php index 85619e9..52980a7 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -58,9 +58,14 @@ public function authorize(Varien_Object $payment, $amount, $iframe = false) $invoice = $this->prepareInvoice($invoice, $payment, $amount); try { - $bitpayInvoice = \Mage::helper('bitpay')->getBitpayClient()->createInvoice($invoice); + $bitpayInvoice = \Mage::helper('bitpay')->getBitpayClient()->createInvoice($invoice); } catch (\Exception $e) { - $this->debugData('[ERROR] In Bitpay_Core_Model_Method_Bitcoin::authorize(): ' . $e->getMessage()); + $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.'); } From 673bc5ad5f34ca88ea59a2c49f2f1594ce8acc7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dro=C5=BCd=C5=BCal?= Date: Thu, 8 Mar 2018 10:46:25 +0100 Subject: [PATCH 282/315] Non-debug logging fix --- .../community/Bitpay/Core/Helper/Data.php | 20 +++++++++++++++++++ .../community/Bitpay/Core/Model/Observer.php | 2 -- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/code/community/Bitpay/Core/Helper/Data.php b/app/code/community/Bitpay/Core/Helper/Data.php index 6c0a463..6d5680f 100644 --- a/app/code/community/Bitpay/Core/Helper/Data.php +++ b/app/code/community/Bitpay/Core/Helper/Data.php @@ -21,7 +21,20 @@ class Bitpay_Core_Helper_Data extends Mage_Core_Helper_Abstract */ public function debugData($debugData) { + //log information about the environment + $phpVersion = explode('-', phpversion())[0]; + $extendedDebugData = array( + '[PHP version] ' . $phpVersion, + '[Magento version] ' . \Mage::getVersion(), + '[BitPay plugin version] ' . $this->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); } } @@ -166,6 +179,8 @@ public function sendPairingRequest($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!'); @@ -375,4 +390,9 @@ 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/Observer.php b/app/code/community/Bitpay/Core/Model/Observer.php index 067079f..ead6c2b 100644 --- a/app/code/community/Bitpay/Core/Model/Observer.php +++ b/app/code/community/Bitpay/Core/Model/Observer.php @@ -9,8 +9,6 @@ class Bitpay_Core_Model_Observer { public function implementOrderStatus($e) { $order = $e -> getOrder(); $paymentCode = $order -> getPayment() -> getMethodInstance() -> getCode(); - Mage::log('sales_order_place_after--------------:'); - Mage::log('$paymentCode:' . $paymentCode); if ($paymentCode == 'bitpay') { $order -> setState(Mage_Sales_Model_Order::STATE_NEW, true); $order -> save(); From 18c6f7b300ec1e45bffbad205c8b3cc36dc354af Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Mon, 12 Mar 2018 14:23:52 +0100 Subject: [PATCH 283/315] checkForRequest - this function is not used anywhere --- app/code/community/Bitpay/Core/Model/Observer.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Observer.php b/app/code/community/Bitpay/Core/Model/Observer.php index ead6c2b..8400cd7 100644 --- a/app/code/community/Bitpay/Core/Model/Observer.php +++ b/app/code/community/Bitpay/Core/Model/Observer.php @@ -17,12 +17,6 @@ public function implementOrderStatus($e) { // Mage::log('$order = $event->getOrder();' . $order -> getState()); } - /* - * TODO: Why is this here? - */ - public function checkForRequest($observer) { - } - /* * 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 From ed00d715d8b0591ec571c96b9e064fa2f525bbac Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Mon, 12 Mar 2018 14:24:08 +0100 Subject: [PATCH 284/315] add api key --- app/code/community/Bitpay/Core/etc/config.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/community/Bitpay/Core/etc/config.xml b/app/code/community/Bitpay/Core/etc/config.xml index c75f139..8b6f90a 100644 --- a/app/code/community/Bitpay/Core/etc/config.xml +++ b/app/code/community/Bitpay/Core/etc/config.xml @@ -124,6 +124,7 @@ authorize 0 Bitcoin + test testnet 0 bitpay/ipn From c97216582ad101c1e7860fdd83e6a96e0ae87ba1 Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Tue, 13 Mar 2018 11:31:22 +0100 Subject: [PATCH 285/315] failure page configurations --- app/code/community/Bitpay/Core/etc/system.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/code/community/Bitpay/Core/etc/system.xml b/app/code/community/Bitpay/Core/etc/system.xml index 274c9e0..0bb34b2 100644 --- a/app/code/community/Bitpay/Core/etc/system.xml +++ b/app/code/community/Bitpay/Core/etc/system.xml @@ -98,6 +98,14 @@ 1 1 + + + text + 60 + 1 + 1 + 1 + select From 23db274908fd694f3adba93139b9f0202bd1b377 Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Tue, 13 Mar 2018 11:31:29 +0100 Subject: [PATCH 286/315] failure page configurations --- app/code/community/Bitpay/Core/etc/config.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/code/community/Bitpay/Core/etc/config.xml b/app/code/community/Bitpay/Core/etc/config.xml index 8b6f90a..2829978 100644 --- a/app/code/community/Bitpay/Core/etc/config.xml +++ b/app/code/community/Bitpay/Core/etc/config.xml @@ -93,6 +93,15 @@ + + + + singleton + bitpay_core_model_observer + redirectToCartIfExpired + + + @@ -129,6 +138,7 @@ 0 bitpay/ipn checkout/onepage/success + checkout/cart1 medium 0 new From 76b82f8c7f8665012ae2193915548825ab9cf47c Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Tue, 13 Mar 2018 11:32:10 +0100 Subject: [PATCH 287/315] expired invoice - wrong redirect --- .../Bitpay/Core/Model/Method/Bitcoin.php | 3 ++ .../community/Bitpay/Core/Model/Observer.php | 44 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php index 52980a7..0547689 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -54,6 +54,9 @@ public function authorize(Varien_Object $payment, $amount, $iframe = false) $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); diff --git a/app/code/community/Bitpay/Core/Model/Observer.php b/app/code/community/Bitpay/Core/Model/Observer.php index 8400cd7..63631e7 100644 --- a/app/code/community/Bitpay/Core/Model/Observer.php +++ b/app/code/community/Bitpay/Core/Model/Observer.php @@ -74,5 +74,49 @@ 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(); + } + } + } + } } From 849890d660fcacf9e5336bcb7f048fd4b4d36604 Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Tue, 13 Mar 2018 11:34:30 +0100 Subject: [PATCH 288/315] configuration page --- app/code/community/Bitpay/Core/etc/config.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/etc/config.xml b/app/code/community/Bitpay/Core/etc/config.xml index 2829978..c20cd77 100644 --- a/app/code/community/Bitpay/Core/etc/config.xml +++ b/app/code/community/Bitpay/Core/etc/config.xml @@ -138,7 +138,7 @@ 0 bitpay/ipn checkout/onepage/success - checkout/cart1 + checkout/cart medium 0 new From f3f82944b85994d263f693b4c537f0e07074fef1 Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Wed, 14 Mar 2018 09:58:20 +0100 Subject: [PATCH 289/315] issue 126 - Magento marketplace compatibility --- app/code/community/Bitpay/Core/Model/Order/Payment.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Model/Order/Payment.php b/app/code/community/Bitpay/Core/Model/Order/Payment.php index 16153bb..fd5a945 100644 --- a/app/code/community/Bitpay/Core/Model/Order/Payment.php +++ b/app/code/community/Bitpay/Core/Model/Order/Payment.php @@ -271,4 +271,3 @@ private function addStatusFilter($collectionObj, $status) return $collectionObj; } } -?> From d2351ea220f678a8cea2888bc50918777889c9e2 Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Thu, 15 Mar 2018 14:17:44 +0100 Subject: [PATCH 290/315] iframre redirect --- app/design/frontend/base/default/template/bitpay/iframe.phtml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/design/frontend/base/default/template/bitpay/iframe.phtml b/app/design/frontend/base/default/template/bitpay/iframe.phtml index 3320d2e..50143d8 100644 --- a/app/design/frontend/base/default/template/bitpay/iframe.phtml +++ b/app/design/frontend/base/default/template/bitpay/iframe.phtml @@ -56,8 +56,6 @@ if ($$('div.bitpay_invoice_div').length > 0) { if (data.paid) { ipnPoller.stop(); response.redirect = null; - transport.responseText = JSON.stringify(response); - review.nextStep(transport); } } }); From daf46c7a5f7f895da0515c18868113735aac57df Mon Sep 17 00:00:00 2001 From: Pieter Poorthuis Date: Fri, 16 Mar 2018 12:39:19 +0100 Subject: [PATCH 291/315] Update for v2.1.18 --- CHANGELOG | 8 ++++++++ GUIDE.md | 2 +- LICENSE | 2 +- README.md | 4 ++-- app/code/community/Bitpay/Core/etc/config.xml | 4 ++-- composer.json | 5 ++--- scripts/package | 6 +++++- 7 files changed, 21 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0dbc3cc..5472f8d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,11 @@ +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 diff --git a/GUIDE.md b/GUIDE.md index 275b1d7..8995913 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -6,7 +6,7 @@ You must have a BitPay merchant account to use this plugin. It's free to [sign- ## Server Requirements -* Last Cart Version Tested: 1.9.3.1 +* 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. diff --git a/LICENSE b/LICENSE index 328b352..e6a7a65 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2011-2014 BitPay, Inc. +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 diff --git a/README.md b/README.md index 2d30b02..43af0ae 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ To get up and running with our plugin quickly, see the GUIDE here: https://githu **BitPay Support:** -* Last Cart Version Tested: 1.9.3.1 +* 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) @@ -65,7 +65,7 @@ If you encounter any issues or implement any updates or changes, please open an The MIT License (MIT) -Copyright (c) 2011-2015 BitPay, Inc. +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: diff --git a/app/code/community/Bitpay/Core/etc/config.xml b/app/code/community/Bitpay/Core/etc/config.xml index c20cd77..6e0bd45 100644 --- a/app/code/community/Bitpay/Core/etc/config.xml +++ b/app/code/community/Bitpay/Core/etc/config.xml @@ -1,14 +1,14 @@ - 2.1.9 + 2.1.18 diff --git a/composer.json b/composer.json index 42ce0dc..c33070e 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "bitpay/magento-plugin", "description": "Bitcoin payment module using the bitpay.com service", - "keywords": ["magento","bitcoin"], + "keywords": ["magento","bitcoin", "bitcoin cash"], "minimum-stability": "stable", "type": "magento-plugin", "homepage": "https://github.com/bitpay/magento-plugin", @@ -12,8 +12,7 @@ "source": "https://github.com/bitpay/magento-plugin" }, "require": { - "composer/installers": "~1.0", - "bitpay/php-client": "dev-master#9027ce67e4b28516ff1ebd1046bdd15c37a7a59f" + "bitpay/php-client": "~2.2" }, "require-dev": { "symfony/finder": "~2.3", diff --git a/scripts/package b/scripts/package index 8efdcd9..fe49432 100755 --- a/scripts/package +++ b/scripts/package @@ -11,7 +11,7 @@ date_default_timezone_set('America/New_York'); // Main Office is in Eastern Time /** * Various Configuration Settings */ -$version = '2.1.13'; +$version = '2.1.18'; $vendorDir = __DIR__ . '/../vendor'; $distDir = __DIR__ . '/../build/dist'; $tmpDistDir = $distDir . '/tmp'; // Files will be placed here temporarly so we can zip/tar them. @@ -143,7 +143,11 @@ $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 From b2d2fe92133413e8a8cb781ef98026974488c1a2 Mon Sep 17 00:00:00 2001 From: Pieter Poorthuis Date: Fri, 16 Mar 2018 14:49:44 +0100 Subject: [PATCH 292/315] Updated package info for Magento marketplace compatibility --- scripts/package | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/package b/scripts/package index fe49432..3d3fbd1 100755 --- a/scripts/package +++ b/scripts/package @@ -67,9 +67,9 @@ $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'); -$xml->addChild('description'); -$xml->addChild('notes'); +$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'); From f43c3bac6cb33e240c64477b94b779035ba7a633 Mon Sep 17 00:00:00 2001 From: Pieter Poorthuis Date: Mon, 19 Mar 2018 14:55:39 +0100 Subject: [PATCH 293/315] Simplified plugin configuration, improved error messages --- .../Bitpay/Core/Model/Config/PairingCode.php | 2 +- app/code/community/Bitpay/Core/etc/config.xml | 10 +- app/code/community/Bitpay/Core/etc/system.xml | 123 +++++++++--------- 3 files changed, 70 insertions(+), 65 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Config/PairingCode.php b/app/code/community/Bitpay/Core/Model/Config/PairingCode.php index 16a9cbf..4526fc2 100644 --- a/app/code/community/Bitpay/Core/Model/Config/PairingCode.php +++ b/app/code/community/Bitpay/Core/Model/Config/PairingCode.php @@ -33,7 +33,7 @@ public function save() \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 try again or enable debug mode and send the "payment_bitpay.log" file to support@bitpay.com for more help.'); + \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; } diff --git a/app/code/community/Bitpay/Core/etc/config.xml b/app/code/community/Bitpay/Core/etc/config.xml index 6e0bd45..50e5a2c 100644 --- a/app/code/community/Bitpay/Core/etc/config.xml +++ b/app/code/community/Bitpay/Core/etc/config.xml @@ -132,17 +132,17 @@ new authorize 0 - Bitcoin - test - testnet + Bitcoin and Bitcoin Cash + test + livenet 0 bitpay/ipn checkout/onepage/success - checkout/cart + checkout/cart medium 0 new - processing + pending processing complete canceled diff --git a/app/code/community/Bitpay/Core/etc/system.xml b/app/code/community/Bitpay/Core/etc/system.xml index 0bb34b2..0451d35 100644 --- a/app/code/community/Bitpay/Core/etc/system.xml +++ b/app/code/community/Bitpay/Core/etc/system.xml @@ -28,8 +28,8 @@ - https://bitpay.com/api-tokens and copy/paste - that code here. Once you have successfully paired this Magento store with your BitPay Merchant account, you can begin accepting Bitcoins as payment.]]> + https://bitpay.com/api-tokens -> Add New Token -> Add Token and copy/paste + the 7 character pairing code here.]]> text bitpay/config_pairingCode @@ -38,6 +38,15 @@ 1 1 + + + select + adminhtml/system_config_source_yesno + 2 + 1 + 1 + 1 + select @@ -106,27 +115,66 @@ 1 1 - - - select - adminhtml/system_config_source_yesno - 2 - 1 - 1 - 0 - - - - select - bitpay/transactionSpeed + + + + + 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.]]>
+ adminhtml/system_config_form_field_heading @@ -178,50 +226,7 @@ 0 validate-number - - - adminhtml/system_config_form_field_heading - 100 - 1 - 1 - 1 - - - - - - - select - adminhtml/system_config_source_order_status - 120 - 1 - 1 - 1 - - - - - - - select - adminhtml/system_config_source_order_status - 130 - 1 - 1 - 1 - - - - - - - select - adminhtml/system_config_source_order_status - 140 - 1 - 1 - 1 - + adminhtml/system_config_form_field_heading From c6e69e05e40e0d33e4a29ba7c1f92039717e16cd Mon Sep 17 00:00:00 2001 From: Pieter Poorthuis Date: Mon, 9 Apr 2018 16:42:34 +0200 Subject: [PATCH 294/315] Update for v2.1.19 --- CHANGELOG | 6 ++++++ app/code/community/Bitpay/Core/etc/config.xml | 2 +- scripts/package | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5472f8d..0777cfa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +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) diff --git a/app/code/community/Bitpay/Core/etc/config.xml b/app/code/community/Bitpay/Core/etc/config.xml index 50e5a2c..ce7c490 100644 --- a/app/code/community/Bitpay/Core/etc/config.xml +++ b/app/code/community/Bitpay/Core/etc/config.xml @@ -8,7 +8,7 @@ - 2.1.18 + 2.1.19 diff --git a/scripts/package b/scripts/package index 3d3fbd1..5242af9 100755 --- a/scripts/package +++ b/scripts/package @@ -11,7 +11,7 @@ date_default_timezone_set('America/New_York'); // Main Office is in Eastern Time /** * Various Configuration Settings */ -$version = '2.1.18'; +$version = '2.1.19'; $vendorDir = __DIR__ . '/../vendor'; $distDir = __DIR__ . '/../build/dist'; $tmpDistDir = $distDir . '/tmp'; // Files will be placed here temporarly so we can zip/tar them. @@ -95,7 +95,7 @@ $xml->addChild('dependencies'); $requiredNode = $xml->addChild('required', 'php'); $requiredNode->addAttribute('php_min', '5.4.0'); -$requiredNode->addAttribute('php_max', '6.0.0'); +$requiredNode->addAttribute('php_max', '7.2.0'); $extensionsNode = $xml->addChild('extensions'); From f16cc16bf6cd136b9f0421ef5597ab44794eb41c Mon Sep 17 00:00:00 2001 From: Pieter Poorthuis Date: Tue, 10 Apr 2018 12:49:55 +0200 Subject: [PATCH 295/315] Updated link to Magento Marketplace --- GUIDE.md | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/GUIDE.md b/GUIDE.md index 8995913..4464485 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -12,24 +12,11 @@ You must have a BitPay merchant account to use this plugin. It's free to [sign- * [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. - -## 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 -``` - - ## Installation **From the Magento Connect Manager:** -Goto [http://www.magentocommerce.com/magento-connect/bitpay-payment-method.html](http://www.magentocommerce.com/magento-connect/bitpay-payment-method.html) and click the *Install Now* link which will give you the *Extension Key* needed for the next step. +Goto [https://marketplace.magento.com/bitpay-bitpay-core.html](https://marketplace.magento.com/bitpay-bitpay-core.html) and click the *Install Now* link which will give you the *Extension Key* needed for the next step. Once you have the key, log into you Magento Store's Admin Panel and navigate to **System > Magento Connect > Magento Connect Manager**. @@ -76,3 +63,14 @@ You are also able to configure how BitPay's IPN (Instant Payment Notifications) 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 +``` From c54c521c071399d85be97c13de935047e58e188a Mon Sep 17 00:00:00 2001 From: Pieter Poorthuis Date: Wed, 25 Apr 2018 10:22:14 +0200 Subject: [PATCH 296/315] Update Magento connect procedure --- GUIDE.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/GUIDE.md b/GUIDE.md index 4464485..3e6fe5c 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -14,9 +14,11 @@ You must have a BitPay merchant account to use this plugin. It's free to [sign- ## Installation -**From the Magento Connect Manager:** +**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 *Install Now* link which will give you the *Extension Key* needed for the next step. +* 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**. @@ -31,16 +33,8 @@ All you need to do is paste the extension key and click on the *Install* button. 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. -**NOTE:** These files can also up uploaded using the *Magento Connect Manager* that comes with your Magento Store - **WARNING:** It is good practice to backup your database before installing extensions. Please make sure you Create Backups. - -**Using Modman:** - -Using [modman](https://github.com/colinmollenhour/modman) you can install the BitPay Magento Plugin. Once you have modman installed, run `modman init` if you have not already done so. Next just run `modman clone https://github.com/bitpay/magento-plugin.git` in the root of the Magento installation. In this case it is `/var/www/magento`. - - ## 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**. From d8f4a04c1ed30d3863aaf558680e2276d0a210b9 Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Thu, 10 May 2018 12:01:49 +0200 Subject: [PATCH 297/315] fix issue 141 --- app/code/community/Bitpay/Core/Model/Invoice.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Invoice.php b/app/code/community/Bitpay/Core/Model/Invoice.php index af5180b..ed6a4e1 100644 --- a/app/code/community/Bitpay/Core/Model/Invoice.php +++ b/app/code/community/Bitpay/Core/Model/Invoice.php @@ -39,9 +39,9 @@ public function prepareWithBitpayInvoice($invoice) 'price' => $invoice->getPrice(), 'currency' => $invoice->getCurrency()->getCode(), 'order_id' => $invoice->getOrderId(), - 'invoice_time' => intval($invoice->getInvoiceTime() / 1000), - 'expiration_time' => intval($invoice->getExpirationTime() / 1000), - 'current_time' => intval($invoice->getCurrentTime() / 1000), + 'invoice_time' => intval(date_format($invoice->getInvoiceTime(), 'U') / 1000), + 'expiration_time' => intval(date_format($invoice->getExpirationTime(), 'U') / 1000), + 'current_time' => intval(date_format($invoice->getCurrentTime(), 'U') / 1000), 'btc_paid' => $invoice->getBtcPaid(), 'rate' => $invoice->getRate(), 'exception_status' => $invoice->getExceptionStatus(), From 8b4680d91ffb4c87de8cb3647d4c157438ce556b Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Fri, 18 May 2018 12:24:03 +0200 Subject: [PATCH 298/315] issue 142 --- app/code/community/Bitpay/Core/Model/Invoice.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Invoice.php b/app/code/community/Bitpay/Core/Model/Invoice.php index ed6a4e1..3fa9424 100644 --- a/app/code/community/Bitpay/Core/Model/Invoice.php +++ b/app/code/community/Bitpay/Core/Model/Invoice.php @@ -28,23 +28,21 @@ public function prepareWithBitpayInvoice($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(), - 'btc_price' => $invoice->getBtcPrice(), 'price' => $invoice->getPrice(), 'currency' => $invoice->getCurrency()->getCode(), 'order_id' => $invoice->getOrderId(), 'invoice_time' => intval(date_format($invoice->getInvoiceTime(), 'U') / 1000), 'expiration_time' => intval(date_format($invoice->getExpirationTime(), 'U') / 1000), 'current_time' => intval(date_format($invoice->getCurrentTime(), 'U') / 1000), - 'btc_paid' => $invoice->getBtcPaid(), - 'rate' => $invoice->getRate(), 'exception_status' => $invoice->getExceptionStatus(), + 'transactionCurrency' => $invoice->getTransactionCurrency() ) ); From c6c08c5cd77591a5641af9f1f7c554833ad8d56d Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Fri, 18 May 2018 12:24:14 +0200 Subject: [PATCH 299/315] issue 143 --- .../Bitpay/Core/controllers/IpnController.php | 32 +------------------ 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index e6ea69a..693d575 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -57,15 +57,13 @@ public function indexAction() 'url' => isset($ipn->url) ? $ipn->url : '', 'pos_data' => json_encode($ipn->posData), 'status' => isset($ipn->status) ? $ipn->status : '', - 'btc_price' => isset($ipn->btcPrice) ? $ipn->btcPrice : '', '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) : '', - 'btc_paid' => isset($ipn->btcPaid) ? $ipn->btcPaid : '', - 'rate' => isset($ipn->rate) ? $ipn->rate : '', 'exception_status' => isset($ipn->exceptionStatus) ? $ipn->exceptionStatus : '', + 'transactionCurrency' => isset($ipn->transactionCurrency) ? $ipn->transactionCurrency : '' ) )->save(); @@ -112,34 +110,6 @@ public function indexAction() \Mage::throwException('There was an error processing the IPN - invoice price does not match the IPN price. Rejecting this IPN!'); } - // Update the order to notifiy that it has been paid - $transactionSpeed = \Mage::getStoreConfig('payment/bitpay/speed'); - if ($ipn->status === 'paid' - || ($ipn->status === 'confirmed' && $transactionSpeed === 'high')) { - - 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); - // If the customer has not already been notified by email - // send the notification now that there's a new order. - 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(); - } - - $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.'); - } - } - // use state as defined by Merchant $state = \Mage::getStoreConfig(sprintf('payment/bitpay/invoice_%s', $invoice->getStatus())); From 281e70b975f0473fbfb03f52ac2109d9b0d29c5b Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Fri, 18 May 2018 12:24:22 +0200 Subject: [PATCH 300/315] issue 144 --- app/code/community/Bitpay/Core/Model/Method/Bitcoin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php index 0547689..1672b43 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -237,7 +237,7 @@ private function initializeInvoice() } $invoice->setFullNotifications(true); - $invoice->setTransactionSpeed(\Mage::getStoreConfig('payment/bitpay/speed')); + $invoice->setTransactionSpeed('medium'); $invoice->setNotificationUrl(\Mage::getUrl(\Mage::getStoreConfig('payment/bitpay/notification_url'))); $invoice->setRedirectUrl(\Mage::getUrl(\Mage::getStoreConfig('payment/bitpay/redirect_url'))); From 4114c21999c43bb3ec68fb23121e2f2febcc4799 Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Thu, 24 May 2018 12:51:37 +0200 Subject: [PATCH 301/315] issue 145 - disable OFAC sanctioned countries --- .../Bitpay/Core/Model/SpecificCountry.php | 27 +++++++++++++++++++ app/code/community/Bitpay/Core/etc/system.xml | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 app/code/community/Bitpay/Core/Model/SpecificCountry.php diff --git a/app/code/community/Bitpay/Core/Model/SpecificCountry.php b/app/code/community/Bitpay/Core/Model/SpecificCountry.php new file mode 100644 index 0000000..49c6fa0 --- /dev/null +++ b/app/code/community/Bitpay/Core/Model/SpecificCountry.php @@ -0,0 +1,27 @@ +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/etc/system.xml b/app/code/community/Bitpay/Core/etc/system.xml index 0bb34b2..d4c0d23 100644 --- a/app/code/community/Bitpay/Core/etc/system.xml +++ b/app/code/community/Bitpay/Core/etc/system.xml @@ -148,7 +148,7 @@ multiselect 77 - adminhtml/system_config_source_country + bitpay/specificCountry 1 1 0 From 6a371901568635aca04e9be9048326d13eeab04f Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Fri, 25 May 2018 10:29:54 +0200 Subject: [PATCH 302/315] issue 150 stop populating buyer fields (except buyerEmail) --- .../Bitpay/Core/Model/Method/Bitcoin.php | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php index 1672b43..9a8586b 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -176,25 +176,10 @@ public 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) { + 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); From cb886b8f09cf089d1ea860248ad79c9af63dfa70 Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Mon, 28 May 2018 14:37:52 +0200 Subject: [PATCH 303/315] issue 148 --- app/code/community/Bitpay/Core/Model/Observer.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/code/community/Bitpay/Core/Model/Observer.php b/app/code/community/Bitpay/Core/Model/Observer.php index 63631e7..799155d 100644 --- a/app/code/community/Bitpay/Core/Model/Observer.php +++ b/app/code/community/Bitpay/Core/Model/Observer.php @@ -100,8 +100,14 @@ public function redirectToCartIfExpired(Varien_Event_Observer $observer) } //check if order is pending if($order->getStatus() != 'pending') + { + $order->cancel(); + $order->setState(Mage_Sales_Model_Order::STATE_CANCELED, true, 'Cancel Transaction.'); + $order->setStatus("canceled"); + $order->save(); return; - + } + //check if invoice for order exist in bitpay_invoices table $bitpayInvoice = \Mage::getModel('bitpay/invoice')->load($order->getIncrementId(), 'increment_id'); $bitpayInvoiceData = $bitpayInvoice->getData(); From de0d707ee459c2cf465430036690444f1d97ed9c Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Mon, 4 Jun 2018 15:32:23 +0200 Subject: [PATCH 304/315] issue 150 --- .../community/Bitpay/Core/controllers/IpnController.php | 6 ++++++ app/code/community/Bitpay/Core/etc/system.xml | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index 693d575..18d20f5 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -118,6 +118,12 @@ public function indexAction() \Mage::throwException('Could not retrieve the defined state parameter to update this order in the Bitpay IPN controller.'); } + $order_confirmation = \Mage::getStoreConfig('payment/bitpay/order_confirmation'); + if($order_confirmation == '1') + { + $order->sendNewOrderEmail(); + } + // Check if status should be updated switch ($order->getStatus()) { case Mage_Sales_Model_Order::STATE_CANCELED: diff --git a/app/code/community/Bitpay/Core/etc/system.xml b/app/code/community/Bitpay/Core/etc/system.xml index d4c0d23..5275ece 100644 --- a/app/code/community/Bitpay/Core/etc/system.xml +++ b/app/code/community/Bitpay/Core/etc/system.xml @@ -127,6 +127,15 @@ 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 From 9d684867c1efee64c3ac90fbfde2ba475dbd765a Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Thu, 7 Jun 2018 10:50:19 +0200 Subject: [PATCH 305/315] fix issues 148, 150 --- .../Bitpay/Core/Model/Method/Bitcoin.php | 3 ++- .../community/Bitpay/Core/Model/Observer.php | 4 --- .../Bitpay/Core/controllers/IpnController.php | 26 ++++++++++++++----- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php index 9a8586b..23ef8e9 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -59,7 +59,7 @@ public function authorize(Varien_Object $payment, $amount, $iframe = false) $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) { @@ -251,6 +251,7 @@ private function prepareInvoice($invoice, $payment, $amount) $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); diff --git a/app/code/community/Bitpay/Core/Model/Observer.php b/app/code/community/Bitpay/Core/Model/Observer.php index 799155d..1dac437 100644 --- a/app/code/community/Bitpay/Core/Model/Observer.php +++ b/app/code/community/Bitpay/Core/Model/Observer.php @@ -101,10 +101,6 @@ public function redirectToCartIfExpired(Varien_Event_Observer $observer) //check if order is pending if($order->getStatus() != 'pending') { - $order->cancel(); - $order->setState(Mage_Sales_Model_Order::STATE_CANCELED, true, 'Cancel Transaction.'); - $order->setStatus("canceled"); - $order->save(); return; } diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index 18d20f5..e56ac35 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -34,7 +34,12 @@ public function indexAction() // 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.'); @@ -118,12 +123,6 @@ public function indexAction() \Mage::throwException('Could not retrieve the defined state parameter to update this order in the Bitpay IPN controller.'); } - $order_confirmation = \Mage::getStoreConfig('payment/bitpay/order_confirmation'); - if($order_confirmation == '1') - { - $order->sendNewOrderEmail(); - } - // Check if status should be updated switch ($order->getStatus()) { case Mage_Sales_Model_Order::STATE_CANCELED: @@ -142,7 +141,20 @@ public function indexAction() )->save(); break; } + + if($ipn->status == 'expired') + { + $order->cancel(); + $order->setState(Mage_Sales_Model_Order::STATE_CANCELED, true, 'Cancel Transaction.'); + $order->setStatus("canceled"); + $order->save(); + } + $order_confirmation = \Mage::getStoreConfig('payment/bitpay/order_confirmation'); + if($order_confirmation == '1') + { + $order->sendNewOrderEmail(); + } } } From 9b96fd558139d39909ffdab637c9e8876613257e Mon Sep 17 00:00:00 2001 From: Pieter Poorthuis Date: Thu, 7 Jun 2018 15:54:13 +0200 Subject: [PATCH 306/315] Stop adding buyer address info to BitPay invoice --- .../Bitpay/Core/Model/Method/Bitcoin.php | 45 ------------------- 1 file changed, 45 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php index 23ef8e9..4d97d2b 100644 --- a/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php +++ b/app/code/community/Bitpay/Core/Model/Method/Bitcoin.php @@ -284,11 +284,6 @@ private function addBuyerInfo($invoice, $order) throw new \Exception('In Bitpay_Core_Model_Method_Bitcoin::addBuyerInfo(): could not construct new BitPay buyer object.'); } - - $buyer->setFirstName($order->getCustomerFirstname()); - $buyer->setLastName($order->getCustomerLastname()); - - if (Mage::getStoreConfig('payment/bitpay/fullscreen')) { $address = $order->getBillingAddress(); } else { @@ -296,51 +291,11 @@ private function addBuyerInfo($invoice, $order) $address = $quote->getBillingAddress(); } - $street = $address->getStreet1(); - if (null !== $street && '' !== $street) { - $buyer->setAddress( - array( - $street, - $address->getStreet2(), - $address->getStreet3(), - $address->getStreet4() - ) - ); - } - - $region = $address->getRegion(); - $regioncode = $address->getRegionCode(); - if (null !== $regioncode && '' !== $regioncode) { - $buyer->setState($regioncode); - } else if (null !== $region && '' !== $region) { - $buyer->setState($region); - } - - $country = $address->getCountry(); - if (null !== $country && '' !== $country) { - $buyer->setCountry($country); - } - - $city = $address->getCity(); - if (null !== $city && '' !== $city) { - $buyer->setCity($city); - } - - $postcode = $address->getPostcode(); - if (null !== $postcode && '' !== $postcode) { - $buyer->setZip($postcode); - } - $email = $address->getEmail(); if (null !== $email && '' !== $email) { $buyer->setEmail($email); } - $telephone = $address->getTelephone(); - if (null !== $telephone && '' !== $telephone) { - $buyer->setPhone($telephone); - } - $invoice->setBuyer($buyer); return $invoice; From a973c6943a8fe0e002c54a1197b145eb6d664abe Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Tue, 12 Jun 2018 15:11:27 +0200 Subject: [PATCH 307/315] fix invoices --- .../Bitpay/Core/controllers/IpnController.php | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index e56ac35..4ac7598 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -114,6 +114,26 @@ public function indexAction() \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!'); } + + $transactionSpeed = 'medium'; + if ($ipn->status === 'paid' + || ($ipn->status === 'confirmed' && $transactionSpeed === 'high')) { + + 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->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.'); + } + } // use state as defined by Merchant $state = \Mage::getStoreConfig(sprintf('payment/bitpay/invoice_%s', $invoice->getStatus())); From 9feef77c9bc62ef66fb342f2b9a714f4949087d0 Mon Sep 17 00:00:00 2001 From: Pieter Poorthuis Date: Thu, 14 Jun 2018 14:19:53 +0200 Subject: [PATCH 308/315] Send order confirmation when paid IPN comes in Only send order confirmation when paid IPN comes in. Make sure to send the order confirmation only once. --- .../Bitpay/Core/controllers/IpnController.php | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index 4ac7598..45b0a98 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -115,26 +115,6 @@ public function indexAction() \Mage::throwException('There was an error processing the IPN - invoice price does not match the IPN price. Rejecting this IPN!'); } - $transactionSpeed = 'medium'; - if ($ipn->status === 'paid' - || ($ipn->status === 'confirmed' && $transactionSpeed === 'high')) { - - 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->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.'); - } - } - // use state as defined by Merchant $state = \Mage::getStoreConfig(sprintf('payment/bitpay/invoice_%s', $invoice->getStatus())); @@ -169,11 +149,29 @@ public function indexAction() $order->setStatus("canceled"); $order->save(); } + + if ($ipn->status === 'paid') { + // 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); + } - $order_confirmation = \Mage::getStoreConfig('payment/bitpay/order_confirmation'); - if($order_confirmation == '1') - { - $order->sendNewOrderEmail(); + 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' && !$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(); + } + $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.'); + } } } From 8c6c7c02fe9b5671ef48f981d92ccd99f1428f12 Mon Sep 17 00:00:00 2001 From: Pieter Poorthuis Date: Thu, 14 Jun 2018 15:27:30 +0200 Subject: [PATCH 309/315] Added logging to IpnController --- .../Bitpay/Core/controllers/IpnController.php | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index 45b0a98..ecfb632 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -151,20 +151,30 @@ public function indexAction() } 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()); + 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' && !$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(); + 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(); } From c5d656374272f86093d51699ecc9d0772d1c1b68 Mon Sep 17 00:00:00 2001 From: Pieter Poorthuis Date: Thu, 14 Jun 2018 15:31:40 +0200 Subject: [PATCH 310/315] Update for v2.1.20 --- app/code/community/Bitpay/Core/etc/config.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/code/community/Bitpay/Core/etc/config.xml b/app/code/community/Bitpay/Core/etc/config.xml index ce7c490..d8406cc 100644 --- a/app/code/community/Bitpay/Core/etc/config.xml +++ b/app/code/community/Bitpay/Core/etc/config.xml @@ -8,7 +8,7 @@ - 2.1.19 + 2.1.20 @@ -135,6 +135,7 @@ Bitcoin and Bitcoin Cash test livenet + 1 0 bitpay/ipn checkout/onepage/success @@ -144,9 +145,9 @@ new pending processing - complete + processing canceled - canceled + pending From ff760487fd89aecfaafcc49f24242698ba7781d4 Mon Sep 17 00:00:00 2001 From: Pieter Poorthuis Date: Thu, 14 Jun 2018 15:32:42 +0200 Subject: [PATCH 311/315] Version bump to 2.1.20 --- scripts/package | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/package b/scripts/package index 5242af9..fa55a52 100755 --- a/scripts/package +++ b/scripts/package @@ -11,7 +11,7 @@ date_default_timezone_set('America/New_York'); // Main Office is in Eastern Time /** * Various Configuration Settings */ -$version = '2.1.19'; +$version = '2.1.20'; $vendorDir = __DIR__ . '/../vendor'; $distDir = __DIR__ . '/../build/dist'; $tmpDistDir = $distDir . '/tmp'; // Files will be placed here temporarly so we can zip/tar them. From 036b237ad979ac4fb0abeb70f571fd4c3f9086eb Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 28 Aug 2018 14:03:12 +1200 Subject: [PATCH 312/315] Fixes #154 - fix modman with miss files --- modman | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/modman b/modman index 310f7c0..43f0a58 100644 --- a/modman +++ b/modman @@ -20,8 +20,21 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -app/code/community/Bitpay/Core app/code/community/Bitpay/Core -app/design/frontend/base/default/layout/bitpay.xml app/design/frontend/base/default/layout/bitpay.xml -app/design/frontend/base/default/template/bitpay app/design/frontend/base/default/template/bitpay -app/etc/modules/Bitpay_Core.xml app/etc/modules/Bitpay_Core.xml -lib/Bitpay lib/Bitpay +# 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 From cee7301bb0cd44bd4d8e2befc6497b0d1f85970f Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Tue, 13 Nov 2018 15:02:20 +0100 Subject: [PATCH 313/315] timestamp fix --- app/code/community/Bitpay/Core/Model/Invoice.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/community/Bitpay/Core/Model/Invoice.php b/app/code/community/Bitpay/Core/Model/Invoice.php index 3fa9424..c1c41b9 100644 --- a/app/code/community/Bitpay/Core/Model/Invoice.php +++ b/app/code/community/Bitpay/Core/Model/Invoice.php @@ -28,7 +28,7 @@ public function prepareWithBitpayInvoice($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(), @@ -38,9 +38,9 @@ public function prepareWithBitpayInvoice($invoice) 'price' => $invoice->getPrice(), 'currency' => $invoice->getCurrency()->getCode(), 'order_id' => $invoice->getOrderId(), - 'invoice_time' => intval(date_format($invoice->getInvoiceTime(), 'U') / 1000), - 'expiration_time' => intval(date_format($invoice->getExpirationTime(), 'U') / 1000), - 'current_time' => intval(date_format($invoice->getCurrentTime(), 'U') / 1000), + '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() ) From 2e4a92dea558d46bb847f7504e2d80e4d3d4b6fa Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Tue, 13 Nov 2018 15:02:46 +0100 Subject: [PATCH 314/315] update isactive --- .../Bitpay/Core/controllers/IpnController.php | 66 ++++++++++++------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index 4ac7598..a8a7ef9 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -97,7 +97,15 @@ public function indexAction() * 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); @@ -115,26 +123,6 @@ public function indexAction() \Mage::throwException('There was an error processing the IPN - invoice price does not match the IPN price. Rejecting this IPN!'); } - $transactionSpeed = 'medium'; - if ($ipn->status === 'paid' - || ($ipn->status === 'confirmed' && $transactionSpeed === 'high')) { - - 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->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.'); - } - } - // use state as defined by Merchant $state = \Mage::getStoreConfig(sprintf('payment/bitpay/invoice_%s', $invoice->getStatus())); @@ -169,11 +157,39 @@ public function indexAction() $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); + } - $order_confirmation = \Mage::getStoreConfig('payment/bitpay/order_confirmation'); - if($order_confirmation == '1') - { - $order->sendNewOrderEmail(); + 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.'); + } } } From 60fd24936c90d51a89b3ca6c7f150f7d2eeef3d3 Mon Sep 17 00:00:00 2001 From: Damian Bzdon Date: Tue, 13 Nov 2018 15:46:16 +0100 Subject: [PATCH 315/315] update isactive --- app/code/community/Bitpay/Core/controllers/IpnController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/community/Bitpay/Core/controllers/IpnController.php b/app/code/community/Bitpay/Core/controllers/IpnController.php index a8a7ef9..7c58d60 100644 --- a/app/code/community/Bitpay/Core/controllers/IpnController.php +++ b/app/code/community/Bitpay/Core/controllers/IpnController.php @@ -105,7 +105,7 @@ public function indexAction() $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); @@ -193,4 +193,4 @@ public function indexAction() } } -} +} \ No newline at end of file