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?
2. How do you create a partial refund?
3. What determines if an item can be refunded?
Flashcards
Question
Credit memo purpose?
Click to reveal answer
Answer
Records refund of order items and totals
Question
Offline vs online refund?
Click to reveal answer
Answer
Offline: no gateway. Online: processes through payment method
Question
Refundable qty formula?
Click to reveal answer
Answer
qty_invoiced - qty_refunded
Question
canCreditmemo()?
Click to reveal answer
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