Skip to content
← Back to Functions
Code

Invoice Line Builder

Builds invoice-ready line items from cart/order records.

Function signature

ogBuildInvoiceLines(order_lines = array(), options = array())

Categories

  • Ecommerce Workflows

Parameters

order_linesValidated order line rows used to build invoice totals.optionsOptional documented policy controls for this helper.

Return value

Public-safe status string returned by the function for explicit controller branching or logging.

  • success
  • message
  • data

Compatibility

Existing function name, slug, path, and call order preserved; advertised metadata corrected to the actual source behavior.

Minimum PHP version: 7.4

Security notes

Use caller-owned allowlists and context-specific escaping; validate admin actions, export fields, privacy plans, cache keys, templates, settings, routes, and ecommerce policies before production use.

Code

<?php

/*
 * Copyright (c) 2026 Jeffery L. Paris <jparis@phpog.com>.
 * Free for personal and internal use. Paid project use requires visible credit
 * to Jeffery L. Paris. Corporate use requires a paid license fee unless a
 * separate written license states otherwise.
 */

/**
 * Builds invoice-ready line snapshots from order/cart lines.
 *
 * Money values are handled as integer minor units. The function returns a plan
 * and does not write invoice records by itself.
 *
 * @param array $order_lines Order lines with title, quantity, unit price, discounts, and tax values.
 * @param array $options Optional invoice formatting and currency options.
 * @return array Invoice lines with totals and warnings.
 */
function ogBuildInvoiceLines($order_lines = array(), $options = array()) {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array()
	);

	if (!is_array($order_lines)) {
		$result['message'] = 'Order lines must be an array.';
		return $result;
	}

	if (!is_array($options)) {
		$options = array();
	}

	$currency = 'USD';
	if (!empty($options['currency'])) {
		$currency = strtoupper(trim((string)$options['currency']));
	}

	$invoice_lines = array();
	$subtotal_minor_units = 0;
	$discount_minor_units = 0;
	$tax_minor_units = 0;
	$warnings = array();

	foreach ($order_lines as $index => $line) {
		if (!is_array($line)) {
			$warnings[] = 'Skipped non-array line at index ' . (string)$index . '.';
			continue;
		}

		$title = 'Line Item';
		if (!empty($line['title'])) {
			$title = trim((string)$line['title']);
		}

		$quantity = 1;
		if (!empty($line['quantity'])) {
			$quantity = (int)$line['quantity'];
		}
		if ($quantity < 1) {
			$quantity = 1;
		}

		$unit_price_minor_units = 0;
		if (!empty($line['unit_price_minor_units'])) {
			$unit_price_minor_units = (int)$line['unit_price_minor_units'];
		}

		$line_discount_minor_units = 0;
		if (!empty($line['discount_minor_units'])) {
			$line_discount_minor_units = (int)$line['discount_minor_units'];
		}

		$line_tax_minor_units = 0;
		if (!empty($line['tax_minor_units'])) {
			$line_tax_minor_units = (int)$line['tax_minor_units'];
		}

		$line_subtotal = $unit_price_minor_units * $quantity;
		$line_total = $line_subtotal - $line_discount_minor_units + $line_tax_minor_units;
		if ($line_total < 0) {
			$line_total = 0;
		}

		$subtotal_minor_units += $line_subtotal;
		$discount_minor_units += $line_discount_minor_units;
		$tax_minor_units += $line_tax_minor_units;

		$invoice_lines[] = array(
			'title' => $title,
			'quantity' => $quantity,
			'unit_price_minor_units' => $unit_price_minor_units,
			'subtotal_minor_units' => $line_subtotal,
			'discount_minor_units' => $line_discount_minor_units,
			'tax_minor_units' => $line_tax_minor_units,
			'total_minor_units' => $line_total,
			'currency' => $currency
		);
	}

	$grand_total = $subtotal_minor_units - $discount_minor_units + $tax_minor_units;
	if ($grand_total < 0) {
		$grand_total = 0;
	}

	$result['success'] = true;
	$result['message'] = 'Invoice lines built.';
	$result['data'] = array(
		'lines' => $invoice_lines,
		'subtotal_minor_units' => $subtotal_minor_units,
		'discount_minor_units' => $discount_minor_units,
		'tax_minor_units' => $tax_minor_units,
		'grand_total_minor_units' => $grand_total,
		'currency' => $currency,
		'warnings' => $warnings
	);

	return $result;
}