Welcome to the blog Agency news and insights

Dec 15
By Jamie Saunders

Capturing Magento invoices en-mass

I have recently completed a new order batch processing module for one of our online fashion retailer clients. The first deliverable for the module was a mass capturing action for invoices, in user terms a new drop down element added to the ‘Actions’ dropdown box within the Sales -> Order grid page. Whilst browsing the Magento Community forum I noticed adding this sort of functionality is quite sought after but with no real decent solution documented. It’s important to highlight that here we will be creating files within a module!

Step 1

The first step will be to create your Grid.php file within your module at this location:

app/code/local/C3Media/Capture/Block/Sales/Order/Grid.php

This file needs to extend the Mage_Adminhtnl_Block_Sales_Order_Grid it will contain a protected function called _prepareMassaction() which will add our new drop down element for the capture action. This is done by calling the addItem function. An example is clearly shown below.

class C3Media_OrderBatchProcessing_Block_Sales_Order_Grid extends Mage_Adminhtml_Block_Sales_Order_Grid
{
    protected function _prepareMassaction()
    {
        parent::_prepareMassaction();
 
        $this->getMassactionBlock()->addItem('Capture', array(
	                'label' => Mage::helper('sales')->__('Capture'),
			'url' 	  => $this->getUrl('*/sales_order/massCapture'),
	 ));
    }
 
}

The url is the most important argument within the addItem() method. This is where we are linking to once the submit button is pressed after selecting capture from the drop down box. This will be explained further in the next step.

Step 2

The next step in our Capture module, would be to create the controller which will contain our module instructions. The location within your module should look something similar to this:

app/code/local/C3Media/Capture/Block/Sales/Order/Grid.php

This controller will contain all our actions and will need to reference specific methods from the core controller so again we will tell Magento that we are extending one of its classes shown below:

class C3Media_Capture_Adminhtml_Sales_Capture extends Mage_Adminhtml_Sales_OrderController
{

Step 3

Moving on, we shall now create a new function within the controller called massCaptureAction(). This function is what the addItem() url argument references too. So once the capture option is selected within our drop down box anything within this function is executed. Lets start creating this function and get some order_ids.

/**
* Mass capture selected orders.
*/
public function massCaptureAction()
{
	// Get orderIds from form POST.
	$orderIds = $this->getRequest()->getPost('order_ids', array());

$orderIds is an array so we need to loop through each element of that array and create an object using our order id reference.

// Loop through orders individually.
for ($i=0; $iload($orderIds[$i]);

Step 4

Now we will shall carry out the logic needed to successfully capture an invoice securely and correctly (the Magento way). We start by checking if an order can be invoiced. I have chosen to use PHP exceptions to handle my errors as this keeps the code consistent alongside Magentos.

try
{
	// Check if order can be invoiced
	if (!$order->canInvoice())
	{

Step 5

If the order cannot be invoiced we still need to check if there are any invoices that may have been created manually through the Magento admin panel. So lets check if the order has any associated invoices at all. If there are invoices we will capture them and if there are no invoices per order we will throw an error.

To do this is easy we first check if the order has any invoices and loop through them checking if the invoices state is open. If the invoice state is open which will be labeled by Magento as “Pending”, the script will capture the invoice.

// If cannot invoice order, check if there are any pending invoices.
if ($order->hasInvoices())
{
	// Loop through the invoices.
	foreach($order->getInvoiceCollection() as $invoice)
	{
		// If invoice state is equal to open (Pending) then capture the invoice else throw an error.
		if ($invoice->getState() == Mage_Sales_Model_Order_Invoice::STATE_OPEN)
		{
			$this->captureInvoice($invoice);
		}
		else
		{
			$this->_getSession()->ddError($this->__('Order # '.$order->getIncrementId().': The order does not allow creating an invoice'));
		}
	}
}

If the order can be invoiced simply capture the invoice by first creating one to capture as shown below. The invoice is captured by calling the method captureInvoice() which I will discuss in a later step.

else
{
    // Capture invoice
    $invoice = Mage::getModel('sales/service_order', $order)->prepareInvoice(array());
    $invoice->register();
    $this->captureInvoice($invoice);
}

Step 6

Relating to step 4. We first try to see if the order can be invoiced, If it can’t we throw an exception (error). In my example I have implemented catching for Magentos errors and catching any other errors.

}
// Catch Magento errors.
catch (Mage_Core_Exception $e)
{
	$this->_getSession()->addError($e->getMessage());
}
// Catch other errors.
catch (Exception $e)
{
	$this->_getSession()->addError($this->__('Order # '.$order->getIncrementId().': Unable to save the invoice.'));
	Mage::logException($e);
}

You may also notice on the last line the logging of the exception, whilst not entirely necessary it may be useful for debugging purposes. You should be able to locate the log at this destination:

var/log/exception.log

Last but not least after we have done any processing we want to be redirected back to the Sales -> Order grid page, therefore a redirect is needed as shown below.

    }
    // Redirect back to sales -> order page.
    $this->_redirect('*/*/');
}

Step 7

So so far we have created our massCaptureAction() function which will determine if the invoice can be captured or not. The only thing we need to do now is create the captureInvoice() function that we reference when capturing. This function should be private and will require the invoice object passed as a parameter.

	/**
	* Capture any invoice.
	*/
	private function captureInvoice($invoice)
	{
        // If no products add an error.
        if (!$invoice->getTotalQty())
        {
            $this->_getSession()->addError($this->__('Order # '.$invoice->getOrder()->getIncrementId().': Cannot create an invoice without products.'));

Our first step is to check if the invoice has any products associated with it. If there are no found products we throw an error else we try and setup certain fundamentals required to capture an invoice.

}
else
{
          // Set capture case to online and register the invoice.
          $invoice->setRequestedCaptureCase('online');
 
  	  // Try and send the customer notification email.
	  try
	  {
      	        $invoice->sendEmail(true);
    	        $invoice->setEmailSent(true);
   		$invoice->getOrder()->setCustomerNoteNotify(true); 
	  }
	  // Catch exceptions.
	  catch (Exception $e)
	  {
	  	Mage::logException($e);
	  	$this->_getSession()->addError('Order # '.$invoice->getOrder()->getIncrementId().': '. $this->__('Unable to send the invoice email.'));
	  }

We set the requested capture case to online because we should be capturing from an external resource such as Paypal or Worldpay. Setting up the email notifications is pretty self-explanatory so I wont go into too much detail here and you should have seen exceptions used in earlier steps.

Step 8

Capturing the invoice, yes this is the final step, again not much explaining needed here. We simply tell Magento to capture the invoice from a resource and and provide the user with a success message before the eventual redirect.

          // Capture invoice.
    	  $invoice->getOrder()->setIsInProcess(true);  
    	  $invoice->capture();
 
    	  // Go grab order from external resource and capture (etc. paypal, worldpay).
    	  $transactionSave = Mage::getModel('core/resource_transaction')
          	->addObject($invoice)
        	->addObject($invoice->getOrder());
    	  $transactionSave->save();
 
    	  // Success message.
    	  $this->_getSession()->addSuccess($this->__('Order # '.$invoice->getOrder()->getIncrementId().': The invoice for order has been captured.'));
    }	
}

In summary this procedure was tailored for a certain clients needs, it could be easily altered to suit your needs. If you have any questions please feel free to leave a comment. We will be releasing this along with other functionality as part of an extension early 2012.

  • Sander

    Hello, is the extension available somewhere?

    • http://www.c3media.co.uk Timothy Willis

      We’ve submitted this as an extension on Magento Connect and are waiting for it to be put live. I will update you as soon as this happens.