Magento 1.9.0.1 CE – Programmatically Authorize and Capture

ce-1.9.0.1payment

I need to authorize, do some 3rd party API calls, and based on what returns from those calls, either capture or cancel the authorization.

Currently I'm using just authorize.net, but I'd like for this to be possible for as many payment gateways as possible. I have authorize.net set to Authorize Only. My observer runs on the sales_order_payment_place_end event.

Here's what I'm doing in my observer (not abstracted yet, still need proof of concept):

if(/*API calls return ok*/)
{
    $observer->getPayment()->getMethod_instance()->capture($observer->getPayment(),$observer->getPayment()->getAmount());
}
else
{
    Mage::dispatchEvent('Mainstreethost_MicrosOrder_Failure');
    $observer->getPayment()->getMethod_instance()->cancel($observer->getPayment());
}

When I call capture(), it checks to see if the payment was pre authorized, which is what we're trying to accomplish:

public function capture(Varien_Object $payment, $amount)
{
    if ($amount <= 0) {
        Mage::throwException(Mage::helper('paygate')->__('Invalid amount for capture.'));
    }
    $this->_initCardsStorage($payment);
    if ($this->_isPreauthorizeCapture($payment)) {
        $this->_preauthorizeCapture($payment, $amount);
    } else if ($this->isPartialAuthorization($payment)) {
        $this->_partialAuthorization($payment, $amount, self::REQUEST_TYPE_AUTH_CAPTURE);
    } else {
        $this->_place($payment, $amount, self::REQUEST_TYPE_AUTH_CAPTURE);
    }
    $payment->setSkipTransactionCreation(true);
    return $this;
}

_isPreauthorizedCapture() tries to assign $lastTransaction:

protected function _isPreauthorizeCapture($payment)
{
    if ($this->getCardsStorage()->getCardsCount() <= 0) {
        return false;
    }
    foreach($this->getCardsStorage()->getCards() as $card) {
        $lastTransaction = $payment->getTransaction($card->getLastTransId());
        if (!$lastTransaction
            || $lastTransaction->getTxnType() != Mage_Sales_Model_Order_Payment_Transaction::TYPE_AUTH
        ) {
            return false;
        }
    }
    return true;
}

$lastTransaction always returns false. This is because $payment->getTransaction() eventually calls _lookupTransaction():

protected function _lookupTransaction($txnId, $txnType = false)
{
    if (!$txnId) {
        if ($txnType && $this->getId()) {
            $collection = Mage::getModel('sales/order_payment_transaction')->getCollection()
                ->setOrderFilter($this->getOrder())
                ->addPaymentIdFilter($this->getId())
                ->addTxnTypeFilter($txnType)
                ->setOrder('created_at', Varien_Data_Collection::SORT_ORDER_DESC)
                ->setOrder('transaction_id', Varien_Data_Collection::SORT_ORDER_DESC);
            foreach ($collection as $txn) {
                $txn->setOrderPaymentObject($this);
                $this->_transactionsLookup[$txn->getTxnId()] = $txn;
                return $txn;
            }
        }
        return false;
    }
    if (isset($this->_transactionsLookup[$txnId])) {
        return $this->_transactionsLookup[$txnId];
    }
    $txn = Mage::getModel('sales/order_payment_transaction')
        ->setOrderPaymentObject($this)
        ->loadByTxnId($txnId);
    if ($txn->getId()) {
        $this->_transactionsLookup[$txnId] = $txn;
    } else {
        $this->_transactionsLookup[$txnId] = false;
    }
    return $this->_transactionsLookup[$txnId];
}

Now, this method is called twice, once for authorization, and once when I call capture() in the observer. The authorization call will skip to this line:

$txn = Mage::getModel('sales/order_payment_transaction')
        ->setOrderPaymentObject($this)
        ->loadByTxnId($txnId);

And return a transaction object with null data. Why does this happen?

Because the method loadObjectByTxnId() in Mage_Sales_Model_Resource_Order_Payment_Transaction assigns $data to false:

public function loadObjectByTxnId(Mage_Sales_Model_Order_Payment_Transaction $transaction, $orderId, $paymentId, 
    $txnId)
{
    $select = $this->_getLoadByUniqueKeySelect($orderId, $paymentId, $txnId);
    $data   = $this->_getWriteAdapter()->fetchRow($select);
    $transaction->setData($data);
    $this->unserializeFields($transaction);
    $this->_afterLoad($transaction);
}

$data is what hydrates the $txn class object in _lookupTransaction()! In case you're wondering, this is what the sql query actually is:

SELECT `sales_payment_transaction`.* FROM `sales_payment_transaction` WHERE (order_id = '38') AND (payment_id = '38') AND (txn_id = '2217720876')

That table is always empty by the way.

But wait! There's so much more going awry!

On the second call (capture call) to _lookupTransaction(), this if gets tripped:

if (isset($this->_transactionsLookup[$txnId])) {
    return $this->_transactionsLookup[$txnId];
}

But that value is false, meaning that _isPreauthorizeCapture() returns false, and then capture() throws an error, saying I need to have the credit card number.

Best Answer

The solution I came up with was that I was not creating an invoice and registering it before trying to capture/void. I also had to handle the transaction and the order states:

$invoice = Mage::getModel('sales/Service_Order', $this->getOrder())->prepareInvoice();

if($isCapture)
{
    $invoice->setRequestedCaptureCase(Mage_Sales_Model_Order_Invoice::CAPTURE_ONLINE);
    $invoice->register();
}
else
{
    $invoice->void();
}

$transactionSave = Mage::getModel('core/Resource_Transaction')
            ->addObject($invoice)
            ->addObject($invoice->getOrder());
$transactionSave->save();


if ($isCapture)
{
    $order->setStatus(Mage_Sales_Model_Order::STATE_CLOSED);
}
else
{
    $order->setState(Mage_Sales_Model_Order::STATE_CANCELED);
    $order->setStatus(Mage_Sales_Model_Order::STATE_CANCELED);
}

$order->save();
Related Topic