# woo-postnl/5.9.12/src/Rest_API/V4/Label/Eligibility.php

PostNL for WooCommerce, version 5.9.12. 157 lines.

- Page: https://pluginprobe.com/plugins/woo-postnl/5.9.12/code/src/Rest_API/V4/Label/Eligibility.php
- Raw: https://pluginprobe.com/plugins/woo-postnl/5.9.12/raw/src/Rest_API/V4/Label/Eligibility.php
- Modified: 2026-08-31T09:58:16+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/woo-postnl/5.9.12/code/src/Rest_API/V4/Label/Eligibility.php#L10-L20`.

```php
<?php
/**
 * Class Rest_API\V4\Label\Eligibility file.
 *
 * @package PostNLWooCommerce\Rest_API\V4\Label
 */

declare( strict_types = 1 );

namespace PostNLWooCommerce\Rest_API\V4\Label;

use PostNLWooCommerce\Helper\Product_Mapper\V4_Mapper;
use PostNLWooCommerce\Utils;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Pure decision logic for whether an order is a shipment the V4 label service
 * handles — a domestic NL parcel, a domestic NL letterbox (mailbox parcel), or
 * an EU/ROW international parcel from NL/BE. Kept free of WooCommerce and
 * Order\Base so the gate — the highest-risk part of the flow — can be asserted
 * in isolation.
 *
 * @since   6.0.0
 * @package PostNLWooCommerce\Rest_API\V4\Label
 */
class Eligibility {

	/**
	 * Resolve the V4 mapper result for an order's product combination.
	 *
	 * The selected options are passed through so a service-bearing combination
	 * that keeps product 3085 (e.g. insured) resolves to a services row (or an
	 * unknown combination) and is rejected by is_eligible(), rather than
	 * silently masquerading as the base parcel.
	 *
	 * @param string $origin       Origin country (store base).
	 * @param string $destination  Shipping zone (NL|BE|EU|ROW).
	 * @param bool   $is_pickup    Whether a pickup point was selected.
	 * @param array  $backend_raw  Raw backend feature flags ('yes' strings).
	 * @param string $product_code Legacy resolved product code.
	 * @return array V4_Mapper::map() result.
	 */
	public static function resolve_mapped( string $origin, string $destination, bool $is_pickup, array $backend_raw, string $product_code ): array {
		return V4_Mapper::map(
			array(
				'origin'              => $origin,
				'destination'         => $destination,
				'flow'                => $is_pickup ? 'pickup_points' : 'delivery_day',
				'options'             => array_keys( Utils::get_selected_label_features( $backend_raw ) ),
				'legacy_product_code' => $product_code,
			)
		);
	}

	/**
	 * Decide whether the collected signals describe a shipment the V4 service
	 * handles — a domestic NL or EU/ROW international parcel (single- or
	 * multi-collo), or a domestic NL letterbox (mailbox parcel 2928).
	 *
	 * @param array $signals {
	 *     Signal set assembled by Service::gather_signals().
	 *
	 *     @type int    $num_labels          Collo count (>= 1).
	 *     @type bool   $is_delivery_day     A delivery-day option was selected.
	 *     @type bool   $is_pickup           A pickup point was selected.
	 *     @type bool   $has_return          A return label/barcode is involved.
	 *     @type string $delivery_type       'Standard' or 'Evening'.
	 *     @type string $origin              Origin country.
	 *     @type string $destination         Shipping zone.
	 *     @type array  $mapped              V4_Mapper::map() result.
	 * }
	 * @return bool
	 */
	public static function is_eligible( array $signals ): bool {
		// Multi-collo (num_labels > 1) is supported via multiple request items;
		// only a missing/invalid collo count is rejected here.
		if ( (int) ( $signals['num_labels'] ?? 1 ) < 1 ) {
			return false;
		}

		if ( ! empty( $signals['is_delivery_day'] ) || ! empty( $signals['is_pickup'] ) ) {
			return false;
		}

		if ( ! empty( $signals['has_return'] ) ) {
			return false;
		}

		if ( 'Standard' !== ( $signals['delivery_type'] ?? 'Standard' ) ) {
			return false;
		}

		$origin      = (string) ( $signals['origin'] ?? '' );
		$destination = (string) ( $signals['destination'] ?? '' );

		// Domestic NL (tasks 18-19) or an EU/ROW international parcel from NL/BE
		// (task 20). NL<->BE cross-border stays on legacy for now.
		$is_domestic      = ( 'NL' === $origin && 'NL' === $destination );
		$is_international = in_array( $origin, array( 'NL', 'BE' ), true )
			&& in_array( $destination, array( 'EU', 'ROW' ), true );

		if ( ! $is_domestic && ! $is_international ) {
			return false;
		}

		// The mapper is authoritative: for a combination it marks as having a V4
		// equivalent, its services array is the full V4 representation of every
		// selected option, so no separate product-option gate is needed. Only a
		// pickup DeliveryLocation is excluded here — that variant lands separately.
		$mapped        = $signals['mapped'] ?? array();
		$shipment_type = (string) ( $mapped['shipmentType'] ?? '' );

		if ( empty( $mapped['has_v4_equivalent'] ) || ! empty( $mapped['deliveryLocation'] ) ) {
			return false;
		}

		// Letterbox (mailbox parcel 2928) is a domestic-NL-only variant. The 48h
		// variant (2948) never reaches here: it collapses onto the same 'letterbox'
		// option key but keeps product code 2948, so the mapper's product-code
		// mismatch guard drops it to has_v4_equivalent = false above. A parcel may
		// be domestic or EU/ROW international.
		if ( 'letterbox' === $shipment_type ) {
			return $is_domestic;
		}

		return 'parcel' === $shipment_type;
	}

	/**
	 * Resolve the mapper's service placeholders into concrete request values.
	 *
	 * The matrix stores insuredValue as the '<order_total>' placeholder — a misnomer
	 * kept for now to match V4_Mapper; the value substituted is the item subtotal, not
	 * the order total. It is replaced here with the order's insured amount. All other
	 * flags (deliveryConfirmation, statedAddressOnly, returnWhenNotHome) pass through
	 * unchanged. minimalAgeCheck would too, but no matrix row emits it yet.
	 *
	 * The insured amount must remain the order item subtotal (WC_Order::get_subtotal),
	 * matching the value the legacy Shipping\Client puts in the Amounts block. Do not
	 * switch it to the order total — that would add tax/shipping and diverge from V1.
	 *
	 * @param array $mapped_services Services array from V4_Mapper::map().
	 * @param float $insured_value   Amount to insure (order item subtotal).
	 * @return array Concrete service flags for Request_Builder.
	 */
	public static function resolve_services( array $mapped_services, float $insured_value ): array {
		if ( array_key_exists( 'insuredValue', $mapped_services ) ) {
			$mapped_services['insuredValue'] = $insured_value;
		}

		return $mapped_services;
	}
}

```
