Skip to content
← Back to Functions
Code

RFM Customer Segmenter

Segments customers using recency, frequency, and monetary scores.

Function signature

ogSegmentCustomersRfm(customers = array(), options = array())

Categories

  • Ecommerce Workflows

Parameters

customersCustomer rows with recency, frequency, and monetary fields.optionsOptional scoring thresholds. Recognized keys: `reference_time`.

Return value

Short public-safe status message.

  • segments
  • skipped

Compatibility

Existing function name and call order preserved; metadata signature corrected to source.

Minimum PHP version: 7.4

Security notes

Validate request method, identity, permissions, and caller-owned allowlists before use; keep secrets and internal paths out of public output.

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.
 */

/**
 * Segments customers using recency, frequency, and monetary scores.
 *
 * @param array $customers Customer rows with recency, frequency, and monetary fields.
 * @param array $options Optional scoring thresholds.
 * @return array Customer rows with RFM scores and segment labels.
 */
function ogSegmentCustomersRfm($customers = array(), $options = array()) {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array()
	);

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

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

	$reference_time = time();
	if (!empty($options['reference_time'])) {
		$reference_time = (int)$options['reference_time'];
	}

	$segments = array();
	$skipped = 0;
	foreach ($customers as $customer) {
		if (!is_array($customer)) {
			$skipped++;
			continue;
		}

		$last_order_time = 0;
		if (!empty($customer['last_order_at'])) {
			$parsed_time = strtotime((string)$customer['last_order_at']);
			if ($parsed_time !== false) {
				$last_order_time = $parsed_time;
			}
		}

		$order_count = 0;
		if (!empty($customer['order_count']) && is_numeric($customer['order_count'])) {
			$order_count = (int)$customer['order_count'];
		}

		$revenue_minor_units = 0;
		if (!empty($customer['revenue_minor_units']) && is_numeric($customer['revenue_minor_units'])) {
			$revenue_minor_units = (int)$customer['revenue_minor_units'];
		}

		if ($last_order_time <= 0) {
			$skipped++;
			continue;
		}

		$recency_days = (int)floor(($reference_time - $last_order_time) / 86400);
		$recency_score = 1;
		if ($recency_days <= 30) {
			$recency_score = 5;
		} elseif ($recency_days <= 90) {
			$recency_score = 4;
		} elseif ($recency_days <= 180) {
			$recency_score = 3;
		} elseif ($recency_days <= 365) {
			$recency_score = 2;
		}

		$frequency_score = 1;
		if ($order_count >= 20) {
			$frequency_score = 5;
		} elseif ($order_count >= 10) {
			$frequency_score = 4;
		} elseif ($order_count >= 5) {
			$frequency_score = 3;
		} elseif ($order_count >= 2) {
			$frequency_score = 2;
		}

		$monetary_score = 1;
		if ($revenue_minor_units >= 100000) {
			$monetary_score = 5;
		} elseif ($revenue_minor_units >= 50000) {
			$monetary_score = 4;
		} elseif ($revenue_minor_units >= 20000) {
			$monetary_score = 3;
		} elseif ($revenue_minor_units >= 5000) {
			$monetary_score = 2;
		}

		$total_score = $recency_score + $frequency_score + $monetary_score;
		$label = 'Needs attention';
		if ($total_score >= 13) {
			$label = 'Champion';
		} elseif ($total_score >= 10) {
			$label = 'Loyal';
		} elseif ($total_score >= 7) {
			$label = 'Promising';
		}

		$segments[] = array(
			'customer' => $customer,
			'recency_days' => $recency_days,
			'recency_score' => $recency_score,
			'frequency_score' => $frequency_score,
			'monetary_score' => $monetary_score,
			'total_score' => $total_score,
			'segment' => $label
		);
	}

	$result['success'] = true;
	$result['message'] = 'RFM segmentation completed.';
	$result['data'] = array('segments' => $segments, 'skipped' => $skipped);

	return $result;
}