3

The Root Problem

The root of all this is actually pretty straightforward - the store has free downloadable software products, and they need invoices to be created for them when they're ordered. I've turned on this option for the Zero Subtotal payment method, and the invoices are being created as expected. However, emails weren't being sent for them, as they would for any other invoice. To fix this, I made a simple event observer on the sales_order_invoice_save_after event, which just checks to see if $order->getPayment()->getMethodInstance()->getCode() == 'free', and if so, call $invoice->sendEmail(). It now sends the emails.

However, for other payment methods, the invoice email has a list of the downloadable files with their links included with the Item's other options. For emails sent by the method described above, the title for this shows up (e.g. "Downloads", in our case), but that's all. If I do exactly the same order again, but change the product's price to $1 and pay for it, it works fine.

My Futile Attempts

For reference, I'm on Magento Enterprise 1.12.0.2.

First, I thought maybe the email was just being generated and sent too early in the process, before the links had been made active. To try and fix that, I tried changing the event that the observer was called for. I didn't think to save a list of ones I tried, but I did pretty much anything related to the payment or invoice that seemed relevant, and all produced the same result - no worse (the email was still sent, etc), but no better (still missing the downloadable links).

Second, I thought maybe I'd just be a little more aggressive with it, and actually make the links active myself right before sending the email, using this:

function make_links_available($order) {  
  foreach ($order->getAllItems() as $item) {
    if (
        $item->getProductType() == Mage_Downloadable_Model_Product_Type::TYPE_DOWNLOADABLE ||
        $item->getRealProductType() == Mage_Downloadable_Model_Product_Type::TYPE_DOWNLOADABLE
       ) {
      $linkPurchased = Mage::getResourceModel('downloadable/link_purchased_item_collection')
        ->addFieldToFilter('order_item_id', array('in' => array($item->getId()) ) );
      foreach ($linkPurchased as $link) {
        if ($link->getStatus() != Mage_Downloadable_Model_Link_Purchased_Item::LINK_STATUS_EXPIRED) {
          $link->setStatus(Mage_Downloadable_Model_Link_Purchased_Item::LINK_STATUS_AVAILABLE)->save();
        }          
      }
    }
  }
}

I actually scavanged this from another script I wrote some time ago, and it could probably be done better, but for the moment I was just trying to see if that was the problem in the first place before worrying about that. Sadly though, it doesn't seen to have helped. The result is the same as before, no better, no worse.

Lastly, I've started exploring the templates and whatnot to try and find out for sure what might be causing the problem, rather than making (hopefully educated) guesses, before wasting more time chasing red herrings. The template used in that section is contained in template/email/order/items.phtml, where it looks over $_order->getAllItems(), calling $this->getItemHtml($_item) on each item.

I looked for the template that looked like it could be responsible for the actual HTML of the link itself, thinking a clue as to what's causing it could be nearby (some condition not being met, etc). I only found two candidates:

  • app/design/frontend/base/default/template/downloadable/email/order/items/order/downloadable.phtml
  • app/design/frontend/base/default/template/downloadable/email/order/items/invoice/downloadable.phtml

The relevant parts here appear to be the same, though:

<?php if ($links = $this->getLinks()->getPurchasedItems()): ?>
<dl style="margin:0; padding:0;">
  <dt><strong><em><?php echo $this->getLinksTitle() ?></em></strong></dt>
  <?php foreach ($links as $link): ?>
<dd style="margin:0; padding:0 0 0 9px;">
  <?php echo $this->escapeHtml($link->getLinkTitle()); ?>&nbsp;
  (<a href="<?php echo $this->getPurchasedLinkUrl($link) ?>" style="color:#1E7EC8;"><?php echo Mage::helper('downloadable')->__('download') ?></a>)
  </dd>
  <?php endforeach; ?>
</dl>
<?php endif; ?>

The title returned by $this->getLinksTitle() does appear in the emails, so ($links = $this->getLinks()->getPurchasedItems()) is true, but the foreach doesn't run - there appear to be no actual links returned by getPurchasedItems(). I would have thought that would be taken care of when I was trying to force them to be set to active, but maybe that "Purchased" is more literal than I had thought? I would have thought that having an invoice, regardless of the price or total, would have been enough but I could be wrong.

In any case, at the moment I'm still digging, but thought I'd post this all so far in the hopes that someone has done this before and already has the answer here. Any help or insight is appreciated, even if it's just a nudge in the right direction.

Edit: I've found that if I load the order after everything's done (as in, I go through the process normally) and run a script that just loads the order, gets the invoice, and calls sendEmail() on it, the email works - everything looks correct, and the links are included. I'm currently thinking maybe it really is a timing issue. I could probably just have a cronjob that, for these affected invoices, runs every minute and calls sendEmail() on them, but that seems horribly sloppy.

3

Especially after the additional information in the "Edit" of my question, I suspected I just needed to be doing what I already was doing, but later in the process. I modified Magento's dispatchEvent() to dump the name and information of every event fired, and did a free order to see what the latest event fired was that would still have the order or invoice passed to it, hoping that it would happen after the links are activated or whatever needed to be done to them.

It turns out that was a good idea. The first candidate I saw was the event sales_order_invoice_save_commit_after, and it worked immediately. I did add additional code to my observer so that emails aren't sent for any later calls to this (for example, invoices are saved when a comment is left on them, so without this, a new invoice email would be sent every time a comment is made on the invoice:

if($invoice->getEmailSent())
    return;

Other than that though, that was it - just setting it on another event. The trick was to find out which one.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.