It’s rather stupid to have the PDF version of invoices on back-end and not the same invoices available on fronted. Why to maintain both the PDF and HTML versions while, at least in Italy, the customer document and the merchant document should be identical?

I like PDFs because a customer can easly save them, print them and send them to other people. So I look for a way to have on Magento customer dashboard (or order list) the same PDF invoice version I can get on administration side.

I was unable to find a ready to use solution even if many commercial modules seem to provide this feature. But is it so hard to do? No it is no! Here the steps!

A module is needed

Ok, this is the worst part: if we need a module, we need to understand Magento modules and particularly the controller concept. I’m not able to deeply explain that, I’m a new bye of Magento, so let me to only show what I did.

I declared a module named Pdf adding a XML to the app/etc/modules: this is required to have the module code loaded by Magento. The file must be named Pdf.xml and it’s content:

<?xml version="1.0"?>
<config>
    <modules>
        <Pdf>
            <active>true</active>
            <codePool>local</codePool>
        </Pdf>
    </modules>
</config>

The I created a controller to have an URL to be called to generate the PDF invoice. The URL will be “/pdf/index/invoice/order_id/x/”. The first part “pdf” identify the module that manage this kind of request, the “index” part is the main controller, the “invoice” part is the specific requested action. The “order_id” and “x” are “parameters used to identify the order for which I’ll print out the invoices.

Yes, I made this thing to print out all invoices for a single order, it’s simpler.

The URL to generate a PDF invoice will be used later in a theme modification so there will be a link for the customer to download the invoice.

Now the hardest part: code the module and write a correct config.xml.

The module home will be “/app/code/local/Pdf”, remember that the module folder name must the same same as the name of the XML seen before. There are many conventions in Magento that a coder must follow and this is a very good thing: keep things clear, uniform and coherent is very important (and usually forgot by PHP coders).

Inside that folder, the sub folder “etc” will contain this config.xml file which declare the controller (and so tells Magento who call when the above URL is used):

<?xml version="1.0"?>
<config>
    <modules>
        <Pdf>
            <version>1.0.0</version>
        </Pdf>
    </modules>

    <frontend>
        <routers>
            <pdf>
                <use>standard</use>
                <args>
                    <module>Pdf</module>
                    <frontName>pdf</frontName>
                </args>
            </pdf>
        </routers>
    </frontend>
</config>

Now, believe me, the URL “/pdf/index/invoices/order_id/x/” will be addressed to our Pdf module. More details with examples on controller can be found here.

The controller

The controller is finally a piece of code that must be named and placed in the right way. Its name will be “IndexController.php”. The “Index” part, as you can note, matches the “index” part of the URL. If one wants to have the URL like “/pdf/batman/invoices/order_id/x/”, the controller will be named “BatmanController.php”.

The controller file must be placed inside our module folder and subfolder “controllers”: /app/code/local/Pdf/controllers”.

The controller is where the PDF invoice is generated and served. It’s code is:

class Pdf_IndexController extends Mage_Core_Controller_Front_Action {        

    public function invoicesAction() {
        $orderId = (int) $this->getRequest()->getParam('order_id');
        $order = Mage::getModel('sales/order')->load($orderId);

        if ($this->_canViewOrder($order)) {
            $invoices = Mage::getResourceModel('sales/order_invoice_collection')
                    ->setOrderFilter($order->getId())
                    ->load();
            if ($invoices->getSize() > 0) {
                $pdf = Mage::getModel('sales/order_pdf_invoice')->getPdf($invoices);

                return $this->_prepareDownloadResponse(
                    'invoice'.Mage::getSingleton('core/date')->date('Y-m-d_H-i-s').'.pdf', $pdf->render(),
                    'application/pdf'
                );

            }
        }
    }

    protected function _canViewOrder($order)
    {
        $customerId = Mage::getSingleton('customer/session')->getCustomerId();
        $availableStates = Mage::getSingleton('sales/order_config')->getVisibleOnFrontStates();
        if ($order->getId() && $order->getCustomerId() && ($order->getCustomerId() == $customerId)
            && in_array($order->getState(), $availableStates, $strict = true)
            ) {
            return true;
        }
        return false;
    }
 }

Note the name of the controller class, which is prefixed with the module name, “Pdf”. The URL part “invoices” is matched by the internal “invoicesAction” method and uses the “PDF generator model” to create the same PDF as in the back-end:

$pdf = Mage::getModel('sales/order_pdf_invoice')->getPdf($invoices);

The only custom part I added is a control over the relation between the requested order to print and and cutomer, _canViewOrder(), that I copied from another core controller

Display the PDF invoice link

Now that we have a “service” to get the invoice in PDF format, we need to add a link somewhere on customer area. I decided to modify the “my orders” panel where there is the customer order list.

The involved file is the piece of the template which list the customer orders. The base file is:

/app/design/frontend/base/default/template/sales/order/history.phtml

I noted that my themes does not redefine this file so I copied it on my theme folder using the same folder structure (starting from “template”): that operation override completely this template part. Then I modified it adding a link to the PDF printing service:

Original:

...
<span><a href="<?php echo $this->getViewUrl($_order) ?>"><?php echo $this->__('View Order') ?></a>
    <?php /*<span>|</span><a href="<?php echo $this->getTrackUrl($_order) ?>"><?php echo $this->__('Track Order') ?></a>&nbsp;*/ ?>
    <?php if ($this->helper('sales/reorder')->canReorder($_order)) : ?>
    <span>|</span> <a href="<?php echo $this->getReorderUrl($_order) ?>"><?php echo $this->__('Reorder') ?></a>
    <?php endif ?>
</span>
...

Modified:

...
<span><a href="<?php echo $this->getViewUrl($_order) ?>"><?php echo $this->__('View Order') ?></a>
    <?php /*<span>|</span><a href="<?php echo $this->getTrackUrl($_order) ?>"><?php echo $this->__('Track Order') ?></a>&nbsp;*/ ?>
    <?php if ($this->helper('sales/reorder')->canReorder($_order)) : ?>
    <span>|</span> <a href="<?php echo $this->getReorderUrl($_order) ?>"><?php echo $this->__('Reorder') ?></a>
    <?php endif ?>
    <span>|</span>
    <a href="<?php echo $this->getUrl('pdf/index/invoices', array('order_id' => $_order->getId())) ?>">Invoices (PDF)</a>
</span>
...

That’s all folks! Now you don’t need to pay for PDF invoices on frontend anymore!

Conclusions

I agree, it’s not so simple, but neither too hard to accomplish. Magento is clearly complex and it’s complexity has negative effects on simple thing and positive effects on complex things. My next experiment will be the modification of the PDF invoice generator, which is full PHP code, trying to change it without rewrite it!

Similar Posts

16 Comments

  1. Hi,

    People who get 404 errors don’t have <? php before the .php file content. I encountered the same problem but searched for a solution, there was told to paste <? php before the code. Everything worked like a charm after that!

    BR

    Ruud

  2. Hi Stefano,

    Found the solution of my problem.
    In the controller file I need to start with <?php and then the class.
    In your (and Magento's examples) the controller starts direct with class.

    Kind regards,

    Bjorn

  3. Hi Stefano,

    Thank you for this great tutorial. Unfortunately I’m getting an error I can’t solve.
    When I hit the “Invoice (PDF)” link, I’m getting the following error: “Controller file was loaded but class does not exist”. I checked everything three times (at least), but can’t find the mistake. Could you help me out with this code? I’m using Magento 1.7.0.2. Could it be a version problem?

    Kind regards,

    Bjorn

  4. I found the 404 error issue. Don’t name the controller file “BatmanController.php”. Instead, name it IndexDontroller.php

    That will fix your issue ;)

  5. I should add that before showing the PDF link it is necessary to generate invoice for the order via admin zone. If invoice for order has not been generated then a 404 page is shown.

    I have created a helper in addition and before showing the link I check if there is an invoice for the order. If invoice generated then echo the link if no then doesn’t echo the link.

  6. Hello,

    I’m having the same problem.
    I modified the abstract.php and invoice.php in my magento installation and now I want to provide these invoices to my customers in their account dashboard.

    I’ve followed every step in this tutorial but when I go to the url I keep getting the error 404.

    In magento I can see the module Pdf and it’s active.

    What am I doing wrong?

    I would really appreciatie your help on this.

    (I’m using Magento 1.7)

    1. Can you zip and send me your code? I’ve re-checked the tutorial and it seems correct compared to the code I have on my site (and it’s running).

      What version of Magento are you using?

Leave a Reply