- Invoice Overview
- When Invoices Are Created
- Automatic vs Manual Invoice Creation
- Invoice Creation Methods
- Invoice States and Lifecycle
- Payment Method Impact
- Admin Panel Invoice Creation
- Programmatic Invoice Creation
- API Invoice Creation
- Invoice Types
- Configuration Settings
- Best Practices
- Troubleshooting
An invoice is a commercial document that:
- Records the sale of products/services
- Requests payment from the customer
- Enables shipping (invoiced items can be shipped)
- Allows refunds (credit memos require invoices)
- Tracks payment capture for online payments
Order (Intent to Purchase) → Invoice (Request for Payment) → Payment (Money Collection)
↓ ↓
Items can be shipped Order completion
- Financial record of the transaction
- Legal document for accounting purposes
- Prerequisite for shipping and refunds
- Payment capture trigger for some gateways
// Payment methods that auto-create invoices
$autoInvoicePaymentMethods = [
'paypal_standard', // PayPal Standard
'stripe_payments', // Stripe (when set to capture)
'braintree', // Braintree (capture mode)
'authorizenet_directpost', // Authorize.Net (capture)
'cashondelivery' // Cash on Delivery
];When: Immediately after order placement and successful payment Trigger: Payment capture success
When: Immediately after order placement Reason: No online payment processing required Configuration: Usually enabled by default for COD
// Offline methods that may auto-invoice
$offlinePaymentMethods = [
'checkmo', // Check/Money Order
'banktransfer', // Bank Transfer
'purchaseorder' // Purchase Order
];When: Based on configuration settings Control: Admin setting "Payment Action"
When: After admin manually captures payment Examples: Credit cards set to "Authorize Only" Process: Order → Manual Invoice → Payment Capture
When: After payment confirmation Examples: Bank transfers, checks Process: Order → Payment Received → Manual Invoice
When: Admin wants to invoice specific items Use Cases: Partial shipments, backordered items Process: Select items → Create partial invoice
Stores → Configuration → Sales → Payment Methods → [Payment Method] → Payment Action
// Payment action configurations
$paymentActions = [
'authorize_capture' => 'Authorize and Capture (Auto-Invoice)',
'authorize' => 'Authorize Only (Manual Invoice)',
'order' => 'Order (Manual Invoice)'
];<!-- payment method configuration -->
<config>
<default>
<payment>
<stripe_payments>
<payment_action>authorize_capture</payment_action>
<automatic_invoice>1</automatic_invoice>
</stripe_payments>
</payment>
</default>
</config>- Better cash flow control
- Inventory management (don't capture until ready to ship)
- Fraud prevention (review orders before capturing)
- Partial fulfillment handling
// Triggered during order payment processing
class PaymentCapture
{
public function capturePayment($order, $payment)
{
// Process payment
$result = $this->gateway->capture($payment);
if ($result->isSuccessful()) {
// Auto-create invoice if configured
if ($this->shouldAutoInvoice($payment->getMethod())) {
$this->createInvoiceForOrder($order);
}
}
}
private function shouldAutoInvoice($paymentMethod)
{
return $this->scopeConfig->isSetFlag(
"payment/{$paymentMethod}/automatic_invoice"
);
}
}Navigation: Sales → Orders → Select Order → Invoice
use Magento\Sales\Model\Service\InvoiceService;
use Magento\Sales\Model\Order\Email\Sender\InvoiceSender;
$invoice = $this->invoiceService->prepareInvoice($order);
$invoice->register();
$order->save();REST API: POST /rest/V1/order/{orderId}/invoice
GraphQL: Custom mutation for invoice creation
// Invoice state constants
const STATE_OPEN = 1; // Invoice created, not paid
const STATE_PAID = 2; // Invoice paid/captured
const STATE_CANCELED = 3; // Invoice canceledOrder Placed → Invoice Created (STATE_OPEN) → Payment Captured → Invoice Paid (STATE_PAID)
↓ ↓
Can be canceled Can create credit memo
↓ ↓
Invoice Canceled (STATE_CANCELED) Refund process
public function getInvoiceStateTransitions()
{
return [
'new_order' => [
'can_create_invoice' => true,
'auto_invoice' => 'depends_on_payment_method'
],
'invoice_created' => [
'state' => Invoice::STATE_OPEN,
'can_capture' => true,
'can_cancel' => true
],
'payment_captured' => [
'state' => Invoice::STATE_PAID,
'can_ship' => true,
'can_refund' => true
]
];
}// Immediate invoice creation
'payment_action' => 'authorize_capture'- Invoice: Created automatically
- Payment: Captured immediately
- Inventory: Reserved immediately
- Use Case: Standard online payments
// Manual invoice creation required
'payment_action' => 'authorize'- Invoice: Manual creation required
- Payment: Authorized, captured later
- Inventory: Reserved after invoice
- Use Case: Fraud prevention, custom workflows
// No payment processing
'payment_action' => 'order'- Invoice: Manual creation
- Payment: Processed offline
- Inventory: Managed manually
- Use Case: Offline payments, B2B orders
// Stripe configuration example
'stripe_payments' => [
'payment_action' => 'authorize_capture', // Auto-invoice
'automatic_invoice' => true,
'can_capture' => true,
'can_void' => true
]// PayPal auto-invoices on successful payment
'paypal_standard' => [
'payment_action' => 'sale', // Auto-invoice
'skip_order_review_step' => false
]// Manual invoice creation
'checkmo' => [
'payment_action' => 'order', // Manual invoice
'automatic_invoice' => false
]Admin Panel → Sales → Orders → Select Order
Order View → Invoice Button (top right)
Invoice Options:
- Items to Invoice: Select specific products
- Quantities: Specify invoice quantities
- Capture Online: Capture payment immediately
- Comments: Add internal/customer comments
- Email: Send invoice email to customer
<!-- Invoice creation form -->
<form id="invoice_form" action="{{invoice_save_url}}" method="post">
<!-- Items selection -->
<table class="data-table">
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th>Qty Ordered</th>
<th>Qty to Invoice</th>
</tr>
</thead>
<tbody>
<tr>
<td>Sample Product</td>
<td>$50.00</td>
<td>2</td>
<td><input type="number" name="invoice[items][1][qty]" value="2" max="2"/></td>
</tr>
</tbody>
</table>
<!-- Invoice options -->
<div class="invoice-options">
<input type="checkbox" name="invoice[capture_case]" value="online" checked/>
<label>Capture Online</label>
<input type="checkbox" name="invoice[send_email]" value="1" checked/>
<label>Send Email Copy of Invoice</label>
<textarea name="invoice[comment_text]" placeholder="Comments"></textarea>
</div>
<button type="submit" class="submit-invoice">Submit Invoice</button>
</form><?php
namespace Vendor\Module\Service;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Model\Service\InvoiceService;
use Magento\Sales\Model\Order\Email\Sender\InvoiceSender;
use Magento\Framework\DB\Transaction;
class InvoiceCreationService
{
private $orderRepository;
private $invoiceService;
private $invoiceSender;
private $transaction;
public function __construct(
OrderRepositoryInterface $orderRepository,
InvoiceService $invoiceService,
InvoiceSender $invoiceSender,
Transaction $transaction
) {
$this->orderRepository = $orderRepository;
$this->invoiceService = $invoiceService;
$this->invoiceSender = $invoiceSender;
$this->transaction = $transaction;
}
/**
* Create full invoice for order
*/
public function createFullInvoice($orderId, $captureOnline = true, $sendEmail = true)
{
try {
$order = $this->orderRepository->get($orderId);
// Check if order can be invoiced
if (!$order->canInvoice()) {
throw new \Exception('Order cannot be invoiced');
}
// Prepare invoice
$invoice = $this->invoiceService->prepareInvoice($order);
if (!$invoice->getTotalQty()) {
throw new \Exception('Cannot create invoice without products');
}
// Set capture mode
if ($captureOnline) {
$invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::CAPTURE_ONLINE);
} else {
$invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::CAPTURE_OFFLINE);
}
// Register invoice
$invoice->register();
// Add comment
$invoice->addComment('Invoice created programmatically', false, false);
// Save invoice and order
$transactionSave = $this->transaction
->addObject($invoice)
->addObject($invoice->getOrder());
$transactionSave->save();
// Send email
if ($sendEmail) {
$this->invoiceSender->send($invoice);
}
return $invoice;
} catch (\Exception $e) {
throw new \Exception('Invoice creation failed: ' . $e->getMessage());
}
}
/**
* Create partial invoice
*/
public function createPartialInvoice($orderId, $invoiceData, $captureOnline = true)
{
$order = $this->orderRepository->get($orderId);
// Prepare invoice with specific items
$invoice = $this->invoiceService->prepareInvoice($order, $invoiceData);
if ($captureOnline) {
$invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::CAPTURE_ONLINE);
} else {
$invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::CAPTURE_OFFLINE);
}
$invoice->register();
// Save
$transactionSave = $this->transaction
->addObject($invoice)
->addObject($invoice->getOrder());
$transactionSave->save();
return $invoice;
}
}// Create full invoice with online capture
$invoice = $invoiceService->createFullInvoice(12345, true, true);
// Create partial invoice
$invoiceData = [
1 => 2, // Item ID 1, quantity 2
2 => 1 // Item ID 2, quantity 1
];
$partialInvoice = $invoiceService->createPartialInvoice(12345, $invoiceData, false);// Create invoice via REST API
const createInvoice = async (orderId, invoiceData) => {
const response = await fetch(`/rest/V1/order/${orderId}/invoice`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${adminToken}`
},
body: JSON.stringify({
capture: true, // Online capture
items: invoiceData.items,
comment: {
comment: 'Invoice created via API',
is_visible_on_front: false
},
notify: true // Send email
})
});
return await response.json();
};
// Usage
const invoiceData = {
items: [
{ order_item_id: 1, qty: 2 },
{ order_item_id: 2, qty: 1 }
]
};
createInvoice(12345, invoiceData);# Custom GraphQL mutation (requires implementation)
mutation createInvoice($orderId: Int!, $invoiceData: InvoiceInput!) {
createInvoice(order_id: $orderId, invoice_data: $invoiceData) {
invoice {
id
increment_id
state
grand_total
}
success
message
}
}// Invoice entire order
$invoice = $this->invoiceService->prepareInvoice($order);- All items in the order
- Complete order total
- Single transaction
// Invoice specific items/quantities
$invoiceData = [
'item_id_1' => 2, // Quantity to invoice
'item_id_2' => 1
];
$invoice = $this->invoiceService->prepareInvoice($order, $invoiceData);- Selected items only
- Specific quantities
- Multiple invoices possible
// Online payment capture
$invoice->setRequestedCaptureCase(Invoice::CAPTURE_ONLINE);- Processes payment immediately
- Updates payment status
- Captures authorized amount
// Offline/manual capture
$invoice->setRequestedCaptureCase(Invoice::CAPTURE_OFFLINE);- No payment processing
- Manual capture required
- Used for offline payments
Stores → Configuration → Sales → Sales → Invoice and Packing Slip Design
- Logo: Company logo on invoices
- Address: Company address information
- Invoice Comments: Default comments
- Email Settings: Invoice email templates
// Payment method specific settings
<config>
<default>
<payment>
<stripe_payments>
<payment_action>authorize_capture</payment_action>
<automatic_invoice>1</automatic_invoice>
<invoice_email>1</invoice_email>
</stripe_payments>
<checkmo>
<payment_action>order</payment_action>
<automatic_invoice>0</automatic_invoice>
</checkmo>
</payment>
</default>
</config>// Configure automatic invoice creation based on order status
public function configureAutoInvoice()
{
return [
'processing' => [
'auto_invoice' => true,
'capture_online' => true,
'send_email' => true
],
'pending_payment' => [
'auto_invoice' => false,
'wait_for_payment' => true
]
];
}public function determineInvoiceCreationTiming($order)
{
$paymentMethod = $order->getPayment()->getMethod();
// Immediate invoicing for secure payments
$immediateInvoiceMethods = [
'paypal_standard',
'stripe_payments',
'cashondelivery'
];
// Delayed invoicing for review
$delayedInvoiceMethods = [
'checkmo',
'banktransfer',
'purchaseorder'
];
if (in_array($paymentMethod, $immediateInvoiceMethods)) {
return 'immediate';
} elseif (in_array($paymentMethod, $delayedInvoiceMethods)) {
return 'manual_review';
}
return 'default_flow';
}public function createPartialInvoiceForShipment($order, $shipmentItems)
{
// Only invoice items that are being shipped
$invoiceData = [];
foreach ($shipmentItems as $item) {
$invoiceData[$item->getOrderItemId()] = $item->getQty();
}
// Create invoice for shipped items only
$invoice = $this->invoiceService->prepareInvoice($order, $invoiceData);
$invoice->setRequestedCaptureCase(Invoice::CAPTURE_ONLINE);
$invoice->register();
return $invoice;
}public function createInvoiceWithErrorHandling($orderId)
{
try {
$order = $this->orderRepository->get($orderId);
// Validate order state
if (!$order->canInvoice()) {
throw new \Exception("Order {$orderId} cannot be invoiced. Current state: " . $order->getState());
}
// Check payment method capability
if (!$order->getPayment()->getMethodInstance()->canCapture()) {
throw new \Exception("Payment method does not support capture");
}
$invoice = $this->createFullInvoice($orderId);
// Log success
$this->logger->info("Invoice created successfully", [
'order_id' => $orderId,
'invoice_id' => $invoice->getId(),
'amount' => $invoice->getGrandTotal()
]);
return $invoice;
} catch (\Exception $e) {
$this->logger->error("Invoice creation failed", [
'order_id' => $orderId,
'error' => $e->getMessage()
]);
throw $e;
}
}Causes:
- Order already fully invoiced
- Order canceled or on hold
- Payment not authorized
Solution:
public function diagnoseInvoiceIssue($order)
{
$issues = [];
if ($order->getState() === Order::STATE_CANCELED) {
$issues[] = 'Order is canceled';
}
if ($order->getState() === Order::STATE_HOLDED) {
$issues[] = 'Order is on hold';
}
if ($order->getTotalInvoiced() >= $order->getGrandTotal()) {
$issues[] = 'Order already fully invoiced';
}
if (!$order->getPayment()->canCapture()) {
$issues[] = 'Payment cannot be captured';
}
return $issues;
}Check:
- Payment method configuration
- Payment action setting
- Automatic invoice setting
Debug:
public function debugAutoInvoice($paymentMethod)
{
$config = $this->scopeConfig->getValue("payment/{$paymentMethod}");
return [
'payment_action' => $config['payment_action'] ?? 'not_set',
'automatic_invoice' => $config['automatic_invoice'] ?? 'not_set',
'can_capture' => $this->paymentMethodInstance->canCapture()
];
}Common Problems:
- Tax calculations
- Discount distributions
- Shipping allocations
Solution:
public function validatePartialInvoiceAmounts($order, $invoiceData)
{
$calculatedTotal = 0;
foreach ($invoiceData as $itemId => $qty) {
$orderItem = $order->getItemById($itemId);
$itemTotal = ($orderItem->getPrice() * $qty);
$calculatedTotal += $itemTotal;
}
// Add proportional tax and shipping
$proportionalTax = $this->calculateProportionalTax($order, $invoiceData);
$proportionalShipping = $this->calculateProportionalShipping($order, $invoiceData);
$expectedTotal = $calculatedTotal + $proportionalTax + $proportionalShipping;
return $expectedTotal;
}Invoice Creation in Magento 2:
- Automatically - For "Authorize and Capture" payment methods
- Manually - For "Authorize Only" or offline payment methods
- On-demand - When admin creates them for specific business needs
- Payment capture success (automatic)
- Admin action (manual)
- API calls (programmatic)
- Order status changes (configured)
- Order must be in valid state (not canceled/closed)
- Payment must be authorized (for online methods)
- Items must be available for invoicing
- Configure payment actions appropriately
- Use partial invoices for complex fulfillment
- Implement proper error handling
- Monitor invoice creation logs
- Send email notifications to customers
Invoice creation is crucial for order fulfillment as it enables shipping, refunds, and proper financial record-keeping in Magento 2's order lifecycle.