Skip to content
← Back to Functions
Code

Abandoned Cart Detector

Finds carts inactive beyond a threshold and classifies recovery eligibility.

Function signature

ogDetectAbandonedCarts(carts = array(), options = array())

Categories

  • Ecommerce Workflows

Parameters

cartsCart rows reviewed for abandoned-cart recovery eligibility.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.
 */

/**
 * Finds carts inactive beyond a threshold and classifies recovery eligibility.
 *
 * The function respects consent and cooldown metadata supplied by the caller.
 * It does not send recovery emails by itself.
 *
 * @param array $carts Cart records with last_activity_at, customer_email, and consent fields.
 * @param array $options Threshold and cooldown settings.
 * @return array Abandoned cart candidates and skipped carts.
 */
function ogDetectAbandonedCarts($carts = array(), $options = array()) {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array()
	);

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

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

	$inactive_seconds = 3600;
	if (!empty($options['inactive_seconds'])) {
		$inactive_seconds = (int)$options['inactive_seconds'];
	}
	if ($inactive_seconds < 900) {
		$inactive_seconds = 900;
	}

	$cooldown_seconds = 86400;
	if (!empty($options['cooldown_seconds'])) {
		$cooldown_seconds = (int)$options['cooldown_seconds'];
	}
	if ($cooldown_seconds < 3600) {
		$cooldown_seconds = 3600;
	}

	$now = time();
	$abandoned = array();
	$skipped = array();

	foreach ($carts as $cart) {
		if (!is_array($cart)) {
			continue;
		}

		$cart_id = '';
		if (!empty($cart['cart_id'])) {
			$cart_id = (string)$cart['cart_id'];
		}

		$last_activity_at = 0;
		if (!empty($cart['last_activity_at'])) {
			if (is_numeric($cart['last_activity_at'])) {
				$last_activity_at = (int)$cart['last_activity_at'];
			} else {
				$last_activity_at = strtotime((string)$cart['last_activity_at']);
			}
		}

		$email = '';
		if (!empty($cart['customer_email'])) {
			$email = trim((string)$cart['customer_email']);
		}

		$has_consent = false;
		if (!empty($cart['marketing_consent'])) {
			$has_consent = true;
		}

		$last_recovery_sent_at = 0;
		if (!empty($cart['last_recovery_sent_at'])) {
			if (is_numeric($cart['last_recovery_sent_at'])) {
				$last_recovery_sent_at = (int)$cart['last_recovery_sent_at'];
			} else {
				$last_recovery_sent_at = strtotime((string)$cart['last_recovery_sent_at']);
			}
		}

		$reason = '';
		if ($last_activity_at <= 0) {
			$reason = 'Missing last activity time.';
		} elseif (($now - $last_activity_at) < $inactive_seconds) {
			$reason = 'Cart is not old enough.';
		} elseif (empty($email) || filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
			$reason = 'Missing valid customer email.';
		} elseif (!$has_consent) {
			$reason = 'Customer has not consented to recovery email.';
		} elseif ($last_recovery_sent_at > 0 && ($now - $last_recovery_sent_at) < $cooldown_seconds) {
			$reason = 'Recovery cooldown is still active.';
		}

		if (empty($reason)) {
			$abandoned[] = array(
				'cart_id' => $cart_id,
				'customer_email' => $email,
				'last_activity_at' => $last_activity_at,
				'inactive_seconds' => $now - $last_activity_at
			);
		} else {
			$skipped[] = array(
				'cart_id' => $cart_id,
				'reason' => $reason
			);
		}
	}

	$result['success'] = true;
	$result['message'] = 'Abandoned cart scan completed.';
	$result['data'] = array(
		'abandoned_carts' => $abandoned,
		'skipped_carts' => $skipped,
		'inactive_seconds' => $inactive_seconds,
		'cooldown_seconds' => $cooldown_seconds
	);

	return $result;
}