Skip to content
intermediate Phase 49 · Order Lifecycle

Credit Memo

Refund creation, partial refund, refund items, and offline/online refund processing

45m
0 problems
Topic Progress 0%

Credit Memo Creation

Credit Memo Service

namespace Magento\Sales\Model\Service;

class CreditmemoService
{
    public function __construct(
        private \Magento\Sales\Api\CreditmemoRepositoryInterface $creditmemoRepository,
        private \Magento\Sales\Api\Data\CreditmemoInterfaceFactory $creditmemoFactory
    ) {}

    public function createCreditmemo(
        \Magento\Sales\Api\Data\OrderInterface $order,
        array $items = [],
        bool $offline = true
    ): \Magento\Sales\Api\Data\CreditmemoInterface {
        $creditmemo = $this->creditmemoFactory->create();
        $creditmemo->setOrderId($order->getEntityId());
        $creditmemo->setStoreId($order->getStoreId());

        // Add items
        if (empty($items)) {
            // Refund all invoiced items
            foreach ($order->getItems() as $item) {
                $this->addItemToCreditmemo($creditmemo, $item);
            }
        } else {
            // Partial refund
            foreach ($items as $itemId => $qty) {
                $item = $order->getItemById($itemId);
                if ($item) {
                    $this->addItemToCreditmemo($creditmemo, $item, $qty);
                }
            }
        }

        // Calculate totals
        $creditmemo->collectTotals();

        // Set refund type
        $creditmemo->setOffline($offline);

        $this->creditmemoRepository->save($creditmemo);

        return $creditmemo;
    }
}

Credit Memo Items

private function addItemToCreditmemo(
    \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo,
    \Magento\Sales\Api\Data\OrderItemInterface $orderItem,
    float $qty = null
): void {
    $creditmemoItem = $this->creditmemoItemFactory->create();
    $creditmemoItem->setOrderItem($orderItem);
    $creditmemoItem->setQty($qty ?? $orderItem->getQtyInvoiced());

    $creditmemo->addItem($creditmemoItem);
}

Partial Refund

Partial Refund Implementation

// Refund specific items only
$items = [
    1 => 1,  // item_id 1, refund qty 1
    3 => 2,  // item_id 3, refund qty 2
];

$creditmemo = $this->creditmemoService->createCreditmemo($order, $items);

// Check refund status
foreach ($order->getItems() as $item) {
    $refundedQty = $item->getQtyRefunded();
    $remaining = $item->getQtyInvoiced() - $refundedQty;

    if ($remaining > 0) {
        echo "Item {$item->getId()}: {$remaining} can still be refunded\n";
    }
}

Multiple Credit Memos

// First refund - items 1 and 2
$creditmemo1 = $this->creditmemoService->createCreditmemo(
    $order,
    [1 => 1, 2 => 1],
    true // offline
);

// Second refund - remaining items
$creditmemo2 = $this->creditmemoService->createCreditmemo(
    $order,
    [2 => 1], // remaining qty
    false // online (gateway refund)
);

Refund Eligibility

// Check if order can be refunded
if ($order->canCreditmemo()) {
    // Refund is possible
}

// Check remaining refundable qty
foreach ($order->getItems() as $item) {
    $refundable = $item->getQtyInvoiced() - $item->getQtyRefunded();
    if ($refundable > 0) {
        // Item can still be refunded
    }
}

Offline vs Online Refund

Offline Refund

// Offline refund - no gateway interaction
$creditmemo = $this->creditmemoService->createCreditmemo(
    $order,
    [],
    true // offline = true
);

// Manually mark as refunded
$creditmemo->setState(\Magento\Sales\Model\Order\Creditmemo::STATE_REFUNDED);
$this->creditmemoRepository->save($creditmemo);

Online Refund (Gateway)

// Online refund - call payment gateway
$creditmemo = $this->creditmemoService->createCreditmemo(
    $order,
    [],
    false // offline = false
);

// Process through payment method
$payment = $order->getPayment();
$method = $this->paymentMethodPool->getMethod($payment->getMethod());

if ($method->canRefund()) {
    $method->refund($payment, $creditmemo->getGrandTotal());
}

// Save with refund transaction
$creditmemo->setState(\Magento\Sales\Model\Order\Creditmemo::STATE_REFUNDED);
$this->creditmemoRepository->save($creditmemo);

Refund Amounts

// Credit memo amounts
$creditmemo->setSubtotal($refundSubtotal);
$creditmemo->setBaseSubtotal($refundSubtotal);
$creditmemo->setAdjustment($adjustmentFee);
$creditmemo->setBaseAdjustment($adjustmentFee);
$creditmemo->setGrandTotal($refundTotal);
$creditmemo->setBaseGrandTotal($refundTotal);

Refund Item Management

Item-Level Refunds

// Get refundable items
$refundableItems = [];
foreach ($order->getItems() as $item) {
    $refundable = $item->getQtyInvoiced() - $item->getQtyRefunded();
    if ($refundable > 0) {
        $refundableItems[$item->getId()] = [
            'name' => $item->getName(),
            'sku' => $item->getSku(),
            'refundable_qty' => $refundable,
            'price' => $item->getPrice(),
            'row_total' => $item->getRowTotal(),
        ];
    }
}

Refund Totals Calculation

// Calculate refund totals
$subtotal = 0;
$tax = 0;
$shipping = 0;

foreach ($creditmemo->getItems() as $item) {
    $subtotal += $item->getRowTotal();
    $tax += $item->getTaxAmount();
}

// Add shipping refund if applicable
if ($creditmemo->getShippingAmount() > 0) {
    $shipping = $creditmemo->getShippingAmount();
}

$grandTotal = $subtotal + $tax + $shipping - $creditmemo->getAdjustment();

$creditmemo->setSubtotal($subtotal);
$creditmemo->setTaxAmount($tax);
$creditmemo->setShippingAmount($shipping);
$creditmemo->setGrandTotal($grandTotal);

Quiz

1. What is the difference between offline and online refund?

Question 1 options

2. How do you create a partial refund?

Question 2 options

3. What determines if an item can be refunded?

Question 3 options

Flashcards

Question

Credit memo purpose?

Answer

Records refund of order items and totals

Question

Offline vs online refund?

Answer

Offline: no gateway. Online: processes through payment method

Question

Refundable qty formula?

Answer

qty_invoiced - qty_refunded

Question

canCreditmemo()?

Answer

Returns true if order has refundable items

Revision Notes

Key Takeaways

  • 1. Credit memos record refunds for invoiced items
  • 2. Partial refunds specify item IDs and quantities
  • 3. Offline refunds don't interact with payment gateway
  • 4. Online refunds process through the payment method for fund return
  • 5. Refundable quantity = qty_invoiced - qty_refunded

Interview Tips

  • Explain the difference between offline and online refunds
  • Describe how partial refunds affect order item quantities
  • Discuss refund eligibility checks before processing

Cheat Sheet

Credit Memo:
  Full: refund all invoiced items
  Partial: specific item_id => qty

Refund Types:
  Offline: no gateway interaction
  Online: processes through payment method

Eligibility:
  canCreditmemo() → true if items refundable
  Refundable = qty_invoiced - qty_refunded

Totals:
  subtotal + tax + shipping - adjustment = grand_total