Skip to content
← Back to Functions
Code

Customer Lifetime Value Summary

Summarizes order count, revenue, refunds, average order, and first/last purchase.

Function signature

ogBuildCustomerLifetimeValue(orders = array(), options = array())

Categories

  • Ecommerce Workflows

Parameters

ordersOrder history rows used for customer lifetime-value calculation.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.
 */

/**
 * Summarizes customer lifetime value from order records.
 *
 * Money is calculated in integer minor units. Unpaid/cancelled orders are skipped
 * unless the caller supplies different paid statuses.
 *
 * @param array $orders Order records for one customer.
 * @param array $options Paid-status and refund handling options.
 * @return array Customer value summary.
 */
function ogBuildCustomerLifetimeValue($orders = array(), $options = array()) {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array()
	);

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

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

	$paid_statuses = array('paid', 'captured', 'settled', 'completed');
	if (!empty($options['paid_statuses']) && is_array($options['paid_statuses'])) {
		$paid_statuses = $options['paid_statuses'];
	}

	$order_count = 0;
	$gross_minor_units = 0;
	$refund_minor_units = 0;
	$first_purchase_at = 0;
	$last_purchase_at = 0;

	foreach ($orders as $order) {
		if (!is_array($order)) {
			continue;
		}

		$status = '';
		if (!empty($order['payment_status'])) {
			$status = strtolower(trim((string)$order['payment_status']));
		}

		if (!in_array($status, $paid_statuses, true)) {
			continue;
		}

		$total = 0;
		if (!empty($order['total_minor_units'])) {
			$total = (int)$order['total_minor_units'];
		}

		$refund = 0;
		if (!empty($order['refund_minor_units'])) {
			$refund = (int)$order['refund_minor_units'];
		}

		$purchase_time = 0;
		if (!empty($order['purchased_at'])) {
			if (is_numeric($order['purchased_at'])) {
				$purchase_time = (int)$order['purchased_at'];
			} else {
				$purchase_time = strtotime((string)$order['purchased_at']);
			}
		}

		$order_count++;
		$gross_minor_units += $total;
		$refund_minor_units += $refund;

		if ($purchase_time > 0) {
			if ($first_purchase_at == 0 || $purchase_time < $first_purchase_at) {
				$first_purchase_at = $purchase_time;
			}
			if ($purchase_time > $last_purchase_at) {
				$last_purchase_at = $purchase_time;
			}
		}
	}

	$net_minor_units = $gross_minor_units - $refund_minor_units;
	if ($net_minor_units < 0) {
		$net_minor_units = 0;
	}

	$average_order_minor_units = 0;
	if ($order_count > 0) {
		$average_order_minor_units = (int)floor($net_minor_units / $order_count);
	}

	$result['success'] = true;
	$result['message'] = 'Customer lifetime value summarized.';
	$result['data'] = array(
		'order_count' => $order_count,
		'gross_minor_units' => $gross_minor_units,
		'refund_minor_units' => $refund_minor_units,
		'net_minor_units' => $net_minor_units,
		'average_order_minor_units' => $average_order_minor_units,
		'first_purchase_at' => $first_purchase_at,
		'last_purchase_at' => $last_purchase_at
	);

	return $result;
}