Merge pull request #1 from jaafit/master

Pull iFrame changes
This commit is contained in:
bitpay 2012-10-31 20:39:44 -07:00
commit 43a795a355
14 changed files with 309 additions and 103 deletions

View File

@ -25,12 +25,13 @@ Configuration
b. Enter your API key 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. 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. 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 Usage
----- -----
When a shopping 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). Upon receiving their order, the system takes the shopper to a bitpay.com invoice where the user is presented with bitcoin payment instructions. Once payment is received, a link is presented to the shopper that will take them to their "My Account" page. 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.
In your Admin control panel, you can see the invoices associated with each order made with Bitcoins. The invoice will tell you whether payment has been received. 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.
@ -41,3 +42,6 @@ Version 1
Version 2 Version 2
- Now supports API keys instead of SSL files. Tested against 1.7.0.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.

View File

@ -0,0 +1,50 @@
<?php
class Bitpay_Bitcoins_Block_Iframe extends Mage_Checkout_Block_Onepage_Payment
{
protected function _construct()
{
$this->setTemplate('bitcoins/iframe.phtml');
parent::_construct();
}
// create an invoice and return the url so that iframe.phtml can display it
public function GetIframeUrl()
{
if (Mage::getStoreConfig('payment/Bitcoins/fullscreen'))
return 'disabled';
include '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($quote->getId()))
return 'paid'; // quote's already paid, so don't show the iframe
$options = array(
'currency' => $quote->getQuoteCurrencyCode(),
#'buyerName' => $order->getCustomerFirstname().' '.$order->getCustomerLastname(),
'fullNotifications' => 'true',
'notificationURL' => Mage::getUrl('bitpay_callback'),
'redirectURL' => Mage::getUrl('customer/account'),
'transactionSpeed' => $speed,
'apiKey' => $apiKey,
);
$invoice = bpCreateInvoice($quoteId, $quote->getGrandTotal(), array('quoteId' => $quoteId), $options);
if (array_key_exists('error', $invoice))
{
Mage::log('Error creating bitpay invoice');
Mage::log($invoice['error']);
Mage::throwException("Error creating bit-pay invoice. Please try again or use another payment option.");
return false;
}
return $invoice['url'].'&view=iframe';
}
}
?>

View File

@ -0,0 +1,52 @@
<?php
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)
->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)
{
$collection = $this->getCollection()->AddFilter('quote_id', $quoteId);
foreach($collection as $i)
if (in_array($i->getStatus(), $statuses))
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'));
}
}
?>

View File

@ -72,6 +72,9 @@ class Bitpay_Bitcoins_Model_PaymentMethod extends Mage_Payment_Model_Method_Abst
*/ */
protected $_canSaveCc = false; protected $_canSaveCc = false;
//protected $_formBlockType = 'bitcoins/form';
//protected $_infoBlockType = 'bitcoins/info';
function canUseForCurrency($currencyCode) function canUseForCurrency($currencyCode)
{ {
$currencies = Mage::getStoreConfig('payment/Bitcoins/currencies'); $currencies = Mage::getStoreConfig('payment/Bitcoins/currencies');
@ -79,12 +82,6 @@ class Bitpay_Bitcoins_Model_PaymentMethod extends Mage_Payment_Model_Method_Abst
return array_search($currencyCode, $currencies) !== false; return array_search($currencyCode, $currencies) !== false;
} }
function debuglog($contents)
{
file_put_contents('lib/bitpay/log.txt', "\n".date('m-d H:i:s').": ", FILE_APPEND);
file_put_contents('lib/bitpay/log.txt', $contents, FILE_APPEND);
}
public function canUseCheckout() public function canUseCheckout()
{ {
@ -106,6 +103,32 @@ class Bitpay_Bitcoins_Model_PaymentMethod extends Mage_Payment_Model_Method_Abst
} }
public function authorize(Varien_Object $payment, $amount) 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);
}
return $this;
}
function CreateInvoiceAndRedirect($payment, $amount)
{ {
include 'lib/bitpay/bp_lib.php'; include 'lib/bitpay/bp_lib.php';
@ -115,7 +138,6 @@ class Bitpay_Bitcoins_Model_PaymentMethod extends Mage_Payment_Model_Method_Abst
$order = $payment->getOrder(); $order = $payment->getOrder();
$orderId = $order->getIncrementId(); $orderId = $order->getIncrementId();
$options = array( $options = array(
//'physical' => ,
'currency' => $order->getBaseCurrencyCode(), 'currency' => $order->getBaseCurrencyCode(),
'buyerName' => $order->getCustomerFirstname().' '.$order->getCustomerLastname(), 'buyerName' => $order->getCustomerFirstname().' '.$order->getCustomerLastname(),
'fullNotifications' => 'true', 'fullNotifications' => 'true',
@ -124,17 +146,14 @@ class Bitpay_Bitcoins_Model_PaymentMethod extends Mage_Payment_Model_Method_Abst
'transactionSpeed' => $speed, 'transactionSpeed' => $speed,
'apiKey' => $apiKey, 'apiKey' => $apiKey,
); );
//$this->debuglog($options); $invoice = bpCreateInvoice($orderId, $amount, array('orderId' => $orderId), $options);
$invoice = bpCreateInvoice($orderId, $amount, $orderId, $options);
//$this->debuglog($invoice);
$payment->setIsTransactionPending(true); // status will be PAYMENT_REVIEW instead of PROCESSING $payment->setIsTransactionPending(true); // status will be PAYMENT_REVIEW instead of PROCESSING
if (array_key_exists('error', $invoice)) if (array_key_exists('error', $invoice))
{ {
Mage::log('Error creating bitpay invoice'); Mage::log('Error creating bitpay invoice');
$this->debuglog('error creating bitpay invoice'); Mage::log($invoice['error']);
$this->debuglog($invoice['error']);
Mage::throwException("Error creating bit-pay invoice. Please try again or use another payment option."); Mage::throwException("Error creating bit-pay invoice. Please try again or use another payment option.");
} }
else else
@ -148,8 +167,10 @@ class Bitpay_Bitcoins_Model_PaymentMethod extends Mage_Payment_Model_Method_Abst
public function getOrderPlaceRedirectUrl() public function getOrderPlaceRedirectUrl()
{ {
$url = Mage::getSingleton('customer/session')->getRedirectUrl(); if (Mage::getStoreConfig('payment/Bitcoins/fullscreen'))
return $url; return Mage::getSingleton('customer/session')->getRedirectUrl();
else
return '';
} }
} }

View File

@ -0,0 +1,11 @@
<?php
class Bitpay_Bitcoins_Model_Resource_Ipn extends Mage_Core_Model_Resource_Db_Abstract
{
protected function _construct()
{
$this->_init('Bitcoins/ipn', 'id');
}
}
?>

View File

@ -0,0 +1,11 @@
<?php
class Bitpay_Bitcoins_Model_Resource_Ipn_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract
{
protected function _construct()
{
$this->_init('Bitcoins/ipn');
}
}
?>

View File

@ -1,51 +1,47 @@
<? <?
// callback controller
class Bitpay_Bitcoins_IndexController extends Mage_Core_Controller_Front_Action { class Bitpay_Bitcoins_IndexController extends Mage_Core_Controller_Front_Action {
function debuglog($contents) // bitpay's IPN lands here
{
file_put_contents('lib/bitpay/log.txt', "\n".date('m-d H:i:s').": ", FILE_APPEND);
file_put_contents('lib/bitpay/log.txt', $contents, FILE_APPEND);
}
public function indexAction() { public function indexAction() {
require 'lib/bitpay/bp_lib.php'; require 'lib/bitpay/bp_lib.php';
$apiKey = Mage::getStoreConfig('payment/Bitcoins/api_key'); $apiKey = Mage::getStoreConfig('payment/Bitcoins/api_key');
$response = bpVerifyNotification($apiKey); $invoice = bpVerifyNotification($apiKey);
if (is_string($response))
$this->debuglog("bitpay callback error: $response"); if (is_string($invoice))
Mage::log("bitpay callback error: $invoice");
else { else {
$orderId = $response['posData']; // 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); $order = Mage::getModel('sales/order')->loadByIncrementId($orderId);
$this->debuglog($response['status']); }
switch($response['status']) // save the ipn so that we can find it when the user clicks "Place Order"
{ Mage::getModel('Bitcoins/ipn')->Record($invoice);
case 'paid':
$order->setState($order::STATE_PROCESSING, true)->save();
break;
// update the order if it exists already
if ($order->getId())
switch($invoice['status']) {
case 'confirmed': case 'confirmed':
case 'complete': case 'complete':
$invoices = $order->getInvoiceCollection(); foreach($order->getInvoiceCollection() as $i)
foreach($invoices as $i)
$i->pay() $i->pay()
->save(); ->save();
$order->setState($order::STATE_PROCESSING, true) $order->setState($order::STATE_PROCESSING, true)
->save(); ->save();
break; break;
}
// (bit-pay.com does not send expired notifications as of this release)
case 'expired':
// could set invoices to canceled
break;
} }
} }
} }
}
?> ?>

View File

@ -10,44 +10,51 @@
</args> </args>
</bitpay_callback> </bitpay_callback>
</routers> </routers>
<layout>
<updates>
<bitcoins>
<file>bitcoins.xml</file>
</bitcoins>
</updates>
</layout>
</frontend> </frontend>
<modules> <modules>
<Bitpay_Bitcoins> <Bitpay_Bitcoins>
<!-- declare module's version information for database updates --> <!-- for database updates -->
<version>0.1.0</version> <version>1.0.0</version>
</Bitpay_Bitcoins> </Bitpay_Bitcoins>
</modules> </modules>
<global> <global>
<!-- IMPORTANT: if you use your own namespace (i.e. Bitpay) you also have to declare blocks group for new module. See topic: http://www.magentocommerce.com/boards/viewthread/22416/#t102732 -->
<blocks> <blocks>
<Bitcoins> <bitcoins>
<class>Bitpay_Bitcoins_Block</class> <class>Bitpay_Bitcoins_Block</class>
</Bitcoins> </bitcoins>
</blocks> </blocks>
<!-- declare model group for new module -->
<models> <models>
<!-- model group alias to be used in Mage::getModel('Bitcoins/...') -->
<Bitcoins> <Bitcoins>
<!-- base class name for the model group -->
<class>Bitpay_Bitcoins_Model</class> <class>Bitpay_Bitcoins_Model</class>
<resourceModel>Bitcoins_resource</resourceModel>
</Bitcoins> </Bitcoins>
<Bitcoins_resource>
<class>Bitpay_Bitcoins_Model_Resource</class>
<entities>
<ipn>
<table>bitpay_ipns</table>
</ipn>
</entities>
</Bitcoins_resource>
</models> </models>
<!-- declare resource setup for new module -->
<resources> <resources>
<!-- resource identifier --> <Bitcoins_setup> <!-- keep this uppercase or you'll get duplicate errors -->
<Bitcoins_setup>
<!-- specify that this resource is a setup resource and used for upgrades -->
<setup> <setup>
<!-- which module to look for install/upgrade files in --> <!-- which module to look for install/upgrade files in -->
<module>Bitpay_Bitcoins</module> <module>Bitpay_Bitcoins</module>
</setup> </setup>
<!-- specify database connection for this resource -->
<connection> <connection>
<!-- do not create new connection, use predefined core setup connection -->
<use>core_setup</use> <use>core_setup</use>
</connection> </connection>
</Bitcoins_setup> </Bitcoins_setup>
@ -64,25 +71,16 @@
</resources> </resources>
</global> </global>
<!-- declare default configuration values for this module -->
<default> <default>
<!-- 'payment' configuration section (tab) -->
<payment> <payment>
<!-- 'Bitcoins' configuration group (fieldset) -->
<Bitcoins> <Bitcoins>
<!-- by default this payment method is active -->
<active>1</active> <active>1</active>
<!-- model to handle logic for this payment method -->
<model>Bitcoins/paymentMethod</model> <model>Bitcoins/paymentMethod</model>
<!-- default title for payment checkout page and order view page -->
<title>Bitcoins</title> <title>Bitcoins</title>
<speed>low</speed> <speed>low</speed>
<fullscreen>0</fullscreen>
<currencies>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</currencies> <currencies>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</currencies>
<payment_action>authorize</payment_action> <payment_action>authorize</payment_action>
</Bitcoins> </Bitcoins>
</payment> </payment>
</default> </default>

View File

@ -14,7 +14,7 @@
<label>Enabled</label> <label>Enabled</label>
<frontend_type>select</frontend_type> <frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_yesno</source_model> <source_model>adminhtml/system_config_source_yesno</source_model>
<sort_order>1</sort_order> <sort_order>0</sort_order>
<show_in_default>1</show_in_default> <show_in_default>1</show_in_default>
<show_in_website>1</show_in_website> <show_in_website>1</show_in_website>
<show_in_store>0</show_in_store> <show_in_store>0</show_in_store>
@ -29,10 +29,20 @@
<show_in_store>0</show_in_store> <show_in_store>0</show_in_store>
</title> </title>
<fullscreen translate="label">
<label>Fullscreen invoice</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>
<api_key translate="label"> <api_key translate="label">
<label>API key</label> <label>API key</label>
<frontend_type>text</frontend_type> <frontend_type>text</frontend_type>
<sort_order>2</sort_order> <sort_order>3</sort_order>
<show_in_default>1</show_in_default> <show_in_default>1</show_in_default>
<show_in_website>1</show_in_website> <show_in_website>1</show_in_website>
<show_in_store>0</show_in_store> <show_in_store>0</show_in_store>
@ -49,7 +59,7 @@
</speed> </speed>
<currencies translate="label"> <currencies translate="label">
<label>Currencies accepted by bit-pay.com</label> <label>Currencies accepted by bitpay.com</label>
<frontend_type>text</frontend_type> <frontend_type>text</frontend_type>
<sort_order>6</sort_order> <sort_order>6</sort_order>
<show_in_default>1</show_in_default> <show_in_default>1</show_in_default>

View File

@ -0,0 +1,26 @@
<?php
$installer = $this;
$installer->startSetup();
$installer->run("
CREATE TABLE IF NOT EXISTS `bitpay_ipns` (
`id` int(10) unsigned NOT NULL auto_increment,
`quote_id` int(10) unsigned default NULL,
`order_id` int(10) unsigned default NULL,
`invoice_id` varchar(200) NOT NULL,
`url` varchar(400) NOT NULL,
`status` varchar(20) NOT NULL,
`btc_price` decimal(16,8) NOT NULL,
`price` decimal(16,8) NOT NULL,
`currency` varchar(10) NOT NULL,
`invoice_time` int(11) unsigned NOT NULL,
`expiration_time` int(11) unsigned NOT NULL,
`current_time` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `quote_id` (`quote_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 ;
");
$installer->endSetup();

View File

@ -0,0 +1,10 @@
<?xml version="1.0"?>
<layout>
<checkout_onepage_review>
<reference name="checkout.onepage.review.info.items.after">
<block name="iframe" type="bitcoins/iframe"/>
</reference>
</checkout_onepage_review>
</layout>

View File

@ -0,0 +1,13 @@
<?php
$url = $this->GetIframeUrl();
switch($url){
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 '<iframe src="'.$url.'" width="600" height="150"> </iframe>'; break;
}
?>

View File

@ -62,10 +62,10 @@ function bpCreateInvoice($orderId, $price, $posData, $options = array()) {
$options = array_merge($bpOptions, $options); // $options override any options found in bp_options.php $options = array_merge($bpOptions, $options); // $options override any options found in bp_options.php
$options['posData'] = '{"posData": "' . $posData . '"'; $pos = array('posData' => $posData);
if ($bpOptions['verifyPos']) // if desired, a hash of the POS data is included to verify source in the callback if ($bpOptions['verifyPos'])
$options['posData'].= ', "hash": "' . crypt($posData, $options['apiKey']).'"'; $pos['hash'] = crypt(serialize($posData), $options['apiKey']);
$options['posData'].= '}'; $options['posData'] = json_encode($pos);
$options['orderID'] = $orderId; $options['orderID'] = $orderId;
$options['price'] = $price; $options['price'] = $price;
@ -102,7 +102,7 @@ function bpVerifyNotification($apiKey = false) {
return 'no posData'; return 'no posData';
$posData = json_decode($json['posData'], true); $posData = json_decode($json['posData'], true);
if($bpOptions['verifyPos'] and $posData['hash'] != crypt($posData['posData'], $apiKey)) if($bpOptions['verifyPos'] and $posData['hash'] != crypt(serialize($posData['posData']), $apiKey))
return 'authentication failed (bad hash)'; return 'authentication failed (bad hash)';
$json['posData'] = $posData['posData']; $json['posData'] = $posData['posData'];
@ -119,6 +119,8 @@ function bpGetInvoice($invoiceId, $apiKey=false) {
if (is_string($response)) if (is_string($response))
return $response; // error return $response; // error
$response['posData'] = json_decode($response['posData'], true); $response['posData'] = json_decode($response['posData'], true);
$response['posData'] = $response['posData']['posData'];
return $response; return $response;
} }

View File

@ -11,6 +11,8 @@ $bpOptions['SSLkey'] = '';
$bpOptions['secret'] = ''; $bpOptions['secret'] = '';
$bpOptions['verifyPos'] = true;
$bpOptions['notificationEmail'] = ''; $bpOptions['notificationEmail'] = '';
$bpOptions['notificationURL'] = ''; $bpOptions['notificationURL'] = '';