1

I am using the magento soap api v2. Through the api I can successfully create credit memos and they show up fine in the magento backend. They aren't showing up in my payment processors sandbox, and I fear that is because they are "offline refunds".

In the magento interface, when you create a credit memo on an order, it's an "offline refund". You have to create the credit memo on the invoice for the refund to be an "online refund" and actually go through to your processor.

So I headed over to the documentation and saw that there is no argument to pass for the invoice number. You can only create credit memos for an order. So maybe I missed it and there is a way to make one on an invoice. Or maybe I can pass another argument through the api to force an online refund?

QUESTION Is there a way to do online refunds (credit memo) through the api?

3

Natively, the Magento API doesn't support online refunds. You have to create a local version of it which implements some methods which are normally only called when raising credit memos in the admin.

If you don't want to read the logic behind these changes and just need the solution, scroll down to the TL;DR section further below.

First, let's override the Credit memo API create action. Assuming you have a custom sales module for your overrides, you'll add this rewrite to its config:

app/code/local/Mycompany/Sales/etc/config.xml:

<models>
    <sales>
        <rewrite>
            <order_creditmemo_api>Mycompany_Sales_Model_Order_Creditmemo_Api</order_creditmemo_api>
        </rewrite>
    </sales>
</models>

Then, let's extend that model and its create method:

app/code/local/Mycompany/Sales/Model/Order/Creditmemo/Api.php

<?php
class Mycompany_Sales_Model_Order_Creditmemo_Api extends Mage_Sales_Model_Order_Creditmemo_Api
{
    public function create($orderIncrementId, $creditmemoData = null, $comment = null, $notifyCustomer = false,
                           $includeComment = false, $refundToStoreCreditAmount = null)
    {
        /** @var $order Mage_Sales_Model_Order */
        $order = Mage::getModel('sales/order')->load($orderIncrementId, 'increment_id');
        if (!$order->getId()) {
            $this->_fault('order_not_exists');
        }
        if (!$order->canCreditmemo()) {
            $this->_fault('cannot_create_creditmemo');
        }
        $creditmemoData = $this->_prepareCreateData($creditmemoData);

        /** @var $service Mage_Sales_Model_Service_Order */
        $service = Mage::getModel('sales/service_order', $order);
        /** @var $creditmemo Mage_Sales_Model_Order_Creditmemo */
        $creditmemo = $service->prepareCreditmemo($creditmemoData);

        // refund to Store Credit
        if ($refundToStoreCreditAmount) {
            // check if refund to Store Credit is available
            if ($order->getCustomerIsGuest()) {
                $this->_fault('cannot_refund_to_storecredit');
            }
            $refundToStoreCreditAmount = max(
                0,
                min($creditmemo->getBaseCustomerBalanceReturnMax(), $refundToStoreCreditAmount)
            );
            if ($refundToStoreCreditAmount) {
                $refundToStoreCreditAmount = $creditmemo->getStore()->roundPrice($refundToStoreCreditAmount);
                $creditmemo->setBaseCustomerBalanceTotalRefunded($refundToStoreCreditAmount);
                $refundToStoreCreditAmount = $creditmemo->getStore()->roundPrice(
                    $refundToStoreCreditAmount*$order->getStoreToOrderRate()
                );
                // this field can be used by customer balance observer
                $creditmemo->setBsCustomerBalTotalRefunded($refundToStoreCreditAmount);
                // setting flag to make actual refund to customer balance after credit memo save
                $creditmemo->setCustomerBalanceRefundFlag(true);
            }
        }
        $creditmemo->setPaymentRefundDisallowed(true)->register();
        // add comment to creditmemo
        if (!empty($comment)) {
            $creditmemo->addComment($comment, $notifyCustomer);
        }
        try {
            Mage::getModel('core/resource_transaction')
                ->addObject($creditmemo)
                ->addObject($order)
                ->save();
            // send email notification
            $creditmemo->sendEmail($notifyCustomer, ($includeComment ? $comment : ''));
        } catch (Mage_Core_Exception $e) {
            $this->_fault('data_invalid', $e->getMessage());
        }
        return $creditmemo->getIncrementId();
    }
}

To create an online refund, you need to raise your credit memo against an invoice. As the API call only offers to raise credit memos against an order, the simplest way is to get the invoice from the order. We'll leave a fallback to the previous behaviour for edge cases where you cannot get invoices from an order.

To do so, let's replace:

$creditmemo = $service->prepareCreditmemo($creditmemoData);

by:

$invoices = $order->getInvoiceCollection();
if ($invoices) {
    $creditmemo = $service->prepareInvoiceCreditmemo($invoices->getFirstItem(), $creditmemoData);
} else {
    $creditmemo = $service->prepareCreditmemo($creditmemoData);
}

When registering the credit memo, there's a safeguard that prevents refunding from happening. Let's remove it:

$creditmemo->setPaymentRefundDisallowed(true)->register();

Becomes:

$creditmemo->register();

Because we attached that credit memo to an invoice, we'll need to save the invoice as well. Let's replace the following:

Mage::getModel('core/resource_transaction')
    ->addObject($creditmemo)
    ->addObject($order)
    ->save();

by this:

$transactionSave = Mage::getModel('core/resource_transaction')
    ->addObject($creditmemo)
    ->addObject($order);
if ($creditmemo->getInvoice()) {
    $transactionSave->addObject($creditmemo->getInvoice());
}
$transactionSave->save();

Now you should be all set. Your final override should look like that:

TL;DR

<?php
class Mycompany_Sales_Model_Order_Creditmemo_Api extends Mage_Sales_Model_Order_Creditmemo_Api
{
    public function create($orderIncrementId, $creditmemoData = null, $comment = null, $notifyCustomer = false,
                           $includeComment = false, $refundToStoreCreditAmount = null)
    {
        /** @var $order Mage_Sales_Model_Order */
        $order = Mage::getModel('sales/order')->load($orderIncrementId, 'increment_id');
        if (!$order->getId()) {
            $this->_fault('order_not_exists');
        }
        if (!$order->canCreditmemo()) {
            $this->_fault('cannot_create_creditmemo');
        }
        $creditmemoData = $this->_prepareCreateData($creditmemoData);

        /** @var $service Mage_Sales_Model_Service_Order */
        $service = Mage::getModel('sales/service_order', $order);
        /** @var $creditmemo Mage_Sales_Model_Order_Creditmemo */
        $invoices = $order->getInvoiceCollection();
        if ($invoices) {
            $creditmemo = $service->prepareInvoiceCreditmemo($invoices->getFirstItem(), $creditmemoData);
        } else {
            $creditmemo = $service->prepareCreditmemo($creditmemoData);
        }

        // refund to Store Credit
        if ($refundToStoreCreditAmount) {
            // check if refund to Store Credit is available
            if ($order->getCustomerIsGuest()) {
                $this->_fault('cannot_refund_to_storecredit');
            }
            $refundToStoreCreditAmount = max(
                0,
                min($creditmemo->getBaseCustomerBalanceReturnMax(), $refundToStoreCreditAmount)
            );
            if ($refundToStoreCreditAmount) {
                $refundToStoreCreditAmount = $creditmemo->getStore()->roundPrice($refundToStoreCreditAmount);
                $creditmemo->setBaseCustomerBalanceTotalRefunded($refundToStoreCreditAmount);
                $refundToStoreCreditAmount = $creditmemo->getStore()->roundPrice(
                    $refundToStoreCreditAmount*$order->getStoreToOrderRate()
                );
                // this field can be used by customer balance observer
                $creditmemo->setBsCustomerBalTotalRefunded($refundToStoreCreditAmount);
                // setting flag to make actual refund to customer balance after credit memo save
                $creditmemo->setCustomerBalanceRefundFlag(true);
            }
        }
        $creditmemo->register();
        // add comment to creditmemo
        if (!empty($comment)) {
            $creditmemo->addComment($comment, $notifyCustomer);
        }
        try {
            $transactionSave = Mage::getModel('core/resource_transaction')
                ->addObject($creditmemo)
                ->addObject($order);
            if ($creditmemo->getInvoice()) {
                $transactionSave->addObject($creditmemo->getInvoice());
            }
            $transactionSave->save();
            // send email notification
            $creditmemo->sendEmail($notifyCustomer, ($includeComment ? $comment : ''));
        } catch (Mage_Core_Exception $e) {
            $this->_fault('data_invalid', $e->getMessage());
        }
        return $creditmemo->getIncrementId();
    }
}
0

Sadly, I could not find a way to do this through the API. I resorted to coding it outside of the api, but this does end up running the "online refund" and uses the payment hooks to do the refund through my payment processor.

        $service = Mage::getModel('sales/service_order', $order);
        $creditmemo = $service->prepareInvoiceCreditmemo($invoice, $salesOrderCreditmemoData)->register();
        $transactionSave = Mage::getModel('core/resource_transaction')->addObject($creditmemo)->addObject($order)->addObject($invoice);
        $transactionSave->save();
        $creditMemoId=$creditmemo->getIncrementId();

In this code, $order is a mage order object, ala $order = Mage::getModel('sales/order')->loadByIncrementId($increment_id); and $invoice is an invoice object, and $salesOrderCreditmemoData is an array of product_line_ids => quantity. Working this out has saved my bacon, unfortunately I now have a project that is 95% api and 5% this. Hey, it works.

0

I am having difficulty implementing the solution that you have created. I copied /app/code/core/Mage/Sales/Model/Order/Creditmemo/Api.php to /app/code/local/Mage/Sales/Model/Order/Creditmemo/Api.php and modified

    /** @var $service Mage_Sales_Model_Service_Order */
    $service = Mage::getModel('sales/service_order', $order);
    /** @var $creditmemo Mage_Sales_Model_Order_Creditmemo  */
    $creditmemo = $service->prepareCreditmemo($creditmemoData);

to the following

/** @var $service Mage_Sales_Model_Service_Order */
    $service = Mage::getModel('sales/service_order', $order);
    /** @var $creditmemo Mage_Sales_Model_Order_Creditmemo */
    $creditmemo = $service->prepareInvoiceCreditmemo($invoice, $salesOrderCreditmemoData)->register();
    $transactionSave = Mage::getModel('core/resource_transaction')->addObject($creditmemo)->addObject($order)->addObject($invoice);
    $transactionSave->save();
    $creditMemoId=$creditmemo->getIncrementId();

I am now receiving the following response from my API request

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
  <SOAP-ENV:Fault>
     <faultcode>SOAP-ENV:Server</faultcode>
     <faultstring>Call to a member function getOrder() on a non-object</faultstring>
  </SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Please forgive my ignorance I am new to Magento API.

  • try replacing $salesOrderCreditmemoData with $creditmemoData. In my code, I had that array of lineIds and qtys called $salesOrderCreditmemoData but if you're trying to fix the api I think you need $creditmemoData. Where did your getOrder() function come from? Did I post that? Because I have a helper function in my code named getOrder() but it's not relevant to this problem and I didn't post it....but it seems like a logical name so maybe you have a problem with your getOrder function? – RightClick Jul 9 '15 at 14:05
  • I replaced $salesOrderCreditmemoData with $creditmemoData . getOrder() comes from a helper function and as you mentioned it doesn't look like it should have and relevance. Still no luck though...same error. :( – The Smart Dude Jul 10 '15 at 2:20
  • Just to give you a little background on what I did.... I was using the api for my project, I'm building a streamlined interface because my customers are complete idiots and would mess up their site royally if I don't really guide them. It looks like your approach is to fix the api, which is probably the correct way to do it. I didn't attempt that, rather, in my new interface, I didn't use the api for this one part, and instead used the code below. I already had the $salesOrderCreditmemoData object loaded with data. This code worked for me but where you're trying to use it, i don't know. – RightClick Jul 10 '15 at 14:35

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.