' . esc_html__( 'Everything on this screen is measured on your own store.', 'agentic-commerce-for-woocommerce' ) . ' '
. esc_html__( 'Connecting adds your catalog to AI shopping surfaces, the reconciliation view, the product feed and ChatGPT Ads — but the numbers here work without it.', 'agentic-commerce-for-woocommerce' )
. ' ' . esc_html__( 'Connect your store →', 'agentic-commerce-for-woocommerce' ) . ''
);
}
self::stats( $bots, $orders );
self::readiness( $connected, $bots, $orders );
Xpay_Admin::render_account_shortcuts();
Xpay_UI::close();
}
/**
* Hero — carries the headline number and, beside it, the qualifier.
*
* @param array $orders Attribution rollup.
* @return void
*/
private static function hero( $orders ) {
$has = $orders['attributed'] > 0;
Xpay_UI::hero(
array(
'eyebrow' => __( 'LAST 30 DAYS', 'agentic-commerce-for-woocommerce' ),
'title' => $has
? __( 'AI assistants are sending you orders', 'agentic-commerce-for-woocommerce' )
: __( 'Watching for AI-driven orders', 'agentic-commerce-for-woocommerce' ),
'sub' => $has
? sprintf(
/* translators: 1: number of attributed orders, 2: total orders in window. */
__( 'We traced %1$d of your %2$d recent orders back to an AI assistant. The share is shown next to every number on this page, so you always know how much of the picture we can account for.', 'agentic-commerce-for-woocommerce' ),
(int) $orders['attributed'],
(int) $orders['orders']
)
: __( 'No order in the last 30 days carried a signal we could trace to an AI assistant. That is a real answer, not a missing one — attribution only fires when a shopper actually arrives from one.', 'agentic-commerce-for-woocommerce' ),
'figure' => $has ? Xpay_UI::money( $orders['revenue'] ) : '',
'figure_label' => $has ? __( 'AI-attributed revenue', 'agentic-commerce-for-woocommerce' ) : '',
)
);
}
/**
* The four tiles. Every one carries a qualifier; Xpay_UI::stat enforces it.
*
* @param array $bots Bot rollup.
* @param array $orders Attribution rollup.
* @return void
*/
private static function stats( $bots, $orders ) {
echo '
';
// 1. AI crawler visits — purely local, always available.
Xpay_UI::stat(
__( 'AI assistant visits', 'agentic-commerce-for-woocommerce' ),
$bots['total'] ? number_format_i18n( $bots['total'] ) : '',
$bots['total']
? sprintf(
/* translators: %d: number of distinct AI bots. */
_n( '%d assistant', '%d assistants', count( $bots['by_bot'] ), 'agentic-commerce-for-woocommerce' ),
count( $bots['by_bot'] )
)
: __( 'nothing seen in 30 days', 'agentic-commerce-for-woocommerce' ),
$bots['total'] ? 'pass' : 'low',
__( 'None yet', 'agentic-commerce-for-woocommerce' )
);
// 2. Attributed orders — the number that must never appear unqualified.
Xpay_UI::stat(
__( 'AI-attributed orders', 'agentic-commerce-for-woocommerce' ),
$orders['orders'] ? number_format_i18n( $orders['attributed'] ) : '',
null === $orders['match_rate']
? __( 'no orders in this window', 'agentic-commerce-for-woocommerce' )
: sprintf(
$orders['truncated']
/* translators: %s: percentage of orders attributed. */
? __( '%s%% of your newest 500 orders', 'agentic-commerce-for-woocommerce' )
/* translators: %s: percentage of orders attributed. */
: __( '%s%% of all orders', 'agentic-commerce-for-woocommerce' ),
number_format_i18n( $orders['match_rate'], 1 )
),
$orders['attributed'] > 0 ? 'pass' : 'low',
__( 'No orders yet', 'agentic-commerce-for-woocommerce' )
);
// 3. Realtime shoppers — a bot fetching a page ON BEHALF OF a shopper is
// the closest local proxy we have for AI demand, and unlike an AI-vs-human
// ratio it needs no denominator we cannot honestly supply.
$realtime = isset( $bots['by_class']['realtime'] ) ? (int) $bots['by_class']['realtime'] : 0;
Xpay_UI::stat(
__( 'Fetched for a shopper', 'agentic-commerce-for-woocommerce' ),
$realtime ? number_format_i18n( $realtime ) : '',
$realtime
? __( 'someone was asking an assistant about you', 'agentic-commerce-for-woocommerce' )
: __( 'only crawlers so far, no live shoppers', 'agentic-commerce-for-woocommerce' ),
$realtime ? 'pass' : 'low',
__( 'None yet', 'agentic-commerce-for-woocommerce' )
);
// 4. ChatGPT Ads spend — connection-gated, so it says so.
Xpay_UI::stat(
__( 'ChatGPT Ads spend', 'agentic-commerce-for-woocommerce' ),
'',
Xpay_Plugin::is_connected()
? __( 'no ads account linked', 'agentic-commerce-for-woocommerce' )
: __( 'connect your store first', 'agentic-commerce-for-woocommerce' ),
'low',
__( 'Not connected', 'agentic-commerce-for-woocommerce' )
);
echo '
';
if ( $bots['by_bot'] ) {
self::bot_breakdown( $bots );
}
}
/**
* Which assistants, specifically. This is the screen's activation moment for
* a merchant who has never connected anything.
*
* @param array $bots Bot rollup.
* @return void
*/
private static function bot_breakdown( $bots ) {
Xpay_UI::section(
__( 'Which AI assistants read your store', 'agentic-commerce-for-woocommerce' ),
__( 'Counted on your own server over the last 30 days. Never sent anywhere.', 'agentic-commerce-for-woocommerce' )
);
echo '';
echo '| ' . esc_html__( 'Assistant', 'agentic-commerce-for-woocommerce' ) . ' | ';
echo '' . esc_html__( 'What it is doing', 'agentic-commerce-for-woocommerce' ) . ' | ';
echo '' . esc_html__( 'Visits', 'agentic-commerce-for-woocommerce' ) . ' | ';
echo '
';
$why = array(
'realtime' => __( 'Fetching a page for a shopper, right now', 'agentic-commerce-for-woocommerce' ),
'search' => __( 'Indexing you for answer results', 'agentic-commerce-for-woocommerce' ),
'training' => __( 'Collecting data for model training', 'agentic-commerce-for-woocommerce' ),
'crawler' => __( 'General crawling', 'agentic-commerce-for-woocommerce' ),
);
$classes = self::bot_classes();
$i = 0;
foreach ( $bots['by_bot'] as $token => $n ) {
if ( ++$i > 12 ) {
break;
}
$class = isset( $classes[ $token ] ) ? $classes[ $token ] : 'crawler';
echo '| ' . esc_html( $token ) . ' | ';
echo '' . esc_html( isset( $why[ $class ] ) ? $why[ $class ] : $why['crawler'] ) . ' | ';
echo '' . esc_html( number_format_i18n( $n ) ) . ' |
';
}
echo '
';
}
/**
* Map token => class from the rollup we stored at capture time.
*
* @return array
*/
private static function bot_classes() {
$data = get_option( Xpay_Local_Stats::BOT_OPTION, array() );
$out = array();
if ( is_array( $data ) ) {
foreach ( $data as $tokens ) {
if ( ! is_array( $tokens ) ) {
continue;
}
foreach ( $tokens as $token => $row ) {
if ( isset( $row['c'] ) ) {
$out[ $token ] = (string) $row['c'];
}
}
}
}
return $out;
}
/**
* Setup completeness. Each failing row states the diagnosis in plain English
* and offers exactly one action — never a bare red dot.
*
* @param bool $connected Connection state.
* @param array $bots Bot rollup.
* @param array $orders Attribution rollup.
* @return void
*/
private static function readiness( $connected, $bots, $orders ) {
$checks = array();
$checks[] = $connected
? array( 'pass', __( 'Store connected', 'agentic-commerce-for-woocommerce' ), __( 'Your catalog is syncing to AI shopping surfaces.', 'agentic-commerce-for-woocommerce' ), '', '' )
: array( 'med', __( 'Store not connected', 'agentic-commerce-for-woocommerce' ), __( 'Local measurement works without this. Connecting adds catalog publishing, the product feed, reconciliation and ads.', 'agentic-commerce-for-woocommerce' ), Xpay_Admin::settings_url(), __( 'Connect', 'agentic-commerce-for-woocommerce' ) );
$checks[] = $bots['total'] > 0
? array( 'pass', __( 'AI assistants are reaching your store', 'agentic-commerce-for-woocommerce' ), sprintf( /* translators: %d: visit count. */ __( '%d visits in the last 30 days.', 'agentic-commerce-for-woocommerce' ), (int) $bots['total'] ), '', '' )
: array( 'med', __( 'No AI assistant seen yet', 'agentic-commerce-for-woocommerce' ), __( 'Either none has visited, or something is blocking them. Check that your robots.txt allows AI crawlers and that the store is publicly reachable.', 'agentic-commerce-for-woocommerce' ), Xpay_Admin::url( '-tracking' ), __( 'Check', 'agentic-commerce-for-woocommerce' ) );
$has_https = is_ssl() || 0 === strpos( (string) home_url(), 'https://' );
$checks[] = $has_https
? array( 'pass', __( 'Served over HTTPS', 'agentic-commerce-for-woocommerce' ), __( 'Required before AI shopping surfaces will honour your discovery files.', 'agentic-commerce-for-woocommerce' ), '', '' )
: array( 'high', __( 'Store is not on HTTPS', 'agentic-commerce-for-woocommerce' ), __( 'AI shopping surfaces ignore discovery files served over plain HTTP. This one blocks everything else.', 'agentic-commerce-for-woocommerce' ), '', '' );
if ( class_exists( 'Xpay_Robots' ) && method_exists( 'Xpay_Robots', 'physical_robots_exists' ) && Xpay_Robots::physical_robots_exists() ) {
$checks[] = array( 'med', __( 'A physical robots.txt is overriding WordPress', 'agentic-commerce-for-woocommerce' ), __( 'Our AI-crawler allowlist cannot apply. Delete the file, or edit it to allow GPTBot, ClaudeBot, PerplexityBot and OAI-SearchBot.', 'agentic-commerce-for-woocommerce' ), '', '' );
} elseif ( ! get_option( 'blog_public' ) ) {
// ⛔ Xpay_Robots::append_agent_allows() returns early and writes NOTHING
// when the site is set to discourage search engines. Asserting an
// allowlist here would be a green tick over a `Disallow: /`.
$checks[] = array( 'high', __( 'Search engines are being discouraged', 'agentic-commerce-for-woocommerce' ), __( 'WordPress is telling every crawler to stay out, so no AI allowlist is published and none of your discovery files will be read. Settings → Reading → uncheck "Discourage search engines".', 'agentic-commerce-for-woocommerce' ), admin_url( 'options-reading.php' ), __( 'Fix', 'agentic-commerce-for-woocommerce' ) );
} else {
$checks[] = array( 'pass', __( 'AI crawlers are allowed', 'agentic-commerce-for-woocommerce' ), __( 'Your robots.txt names the AI shopping crawlers explicitly.', 'agentic-commerce-for-woocommerce' ), '', '' );
}
$checks[] = $orders['attributed'] > 0
? array( 'pass', __( 'Orders are being attributed', 'agentic-commerce-for-woocommerce' ), sprintf( /* translators: %s: percentage. */ __( 'We can account for %s%% of your recent orders.', 'agentic-commerce-for-woocommerce' ), number_format_i18n( (float) $orders['match_rate'], 1 ) ), Xpay_Admin::url( '-revenue' ), __( 'View', 'agentic-commerce-for-woocommerce' ) )
: array( 'low', __( 'No AI-attributed orders yet', 'agentic-commerce-for-woocommerce' ), __( 'Attribution fires when a shopper arrives from an assistant. Until one does, this stays empty — which is a genuine result, not a fault.', 'agentic-commerce-for-woocommerce' ), '', '' );
$done = count(
array_filter(
$checks,
static function ( $c ) {
return 'pass' === $c[0];
}
)
);
$score = (int) round( ( $done / max( 1, count( $checks ) ) ) * 100 );
Xpay_UI::section(
__( 'Setup', 'agentic-commerce-for-woocommerce' ),
__( 'What has to be true for AI shoppers to find, read and buy from your store.', 'agentic-commerce-for-woocommerce' )
);
echo '';
Xpay_UI::ring( $score, sprintf( /* translators: 1: passed, 2: total. */ __( '%1$d of %2$d checks passing', 'agentic-commerce-for-woocommerce' ), $done, count( $checks ) ) );
echo '
' . esc_html(
sprintf(
/* translators: 1: passed count, 2: total count. */
__( '%1$d of %2$d checks passing. Anything below that is not passing tells you exactly what to do about it.', 'agentic-commerce-for-woocommerce' ),
$done,
count( $checks )
)
) . '
';
echo '
';
echo '';
foreach ( $checks as $c ) {
Xpay_UI::check( $c[0], $c[1], $c[2], $c[3], $c[4] );
}
echo '
';
}
}