{
	"$schema": "https://playground.wordpress.net/blueprint-schema.json",
	"meta": {
		"title": "TrustLens — Customer Trust Intelligence for WooCommerce",
		"description": "Explore the TrustLens dashboard with a populated demo store: 14 customers across all six risk segments, sample chargebacks, card-testing defense events, and signal-impact bars on every customer profile."
	},
	"landingPage": "/wp-admin/admin.php?page=trustlens",
	"preferredVersions": {
		"php": "8.0",
		"wp": "latest"
	},
	"phpExtensionBundles": [
		"kitchen-sink"
	],
	"features": {
		"networking": true
	},
	"steps": [
		{
			"step": "installPlugin",
			"pluginData": {
				"resource": "wordpress.org/plugins",
				"slug": "woocommerce"
			}
		},
		{
			"step": "installPlugin",
			"pluginData": {
				"resource": "wordpress.org/plugins",
				"slug": "trustlens"
			}
		},
		{
			"step": "installTheme",
			"themeData": {
				"resource": "wordpress.org/themes",
				"slug": "storefront"
			}
		},
		{
			"step": "activateTheme",
			"themeFolderName": "storefront"
		},
		{
			"step": "setSiteOptions",
			"options": {
				"blogname": "TrustLens Demo Store",
				"blogdescription": "Customer trust intelligence for WooCommerce — see your shoppers clearly",
				"permalink_structure": "/%postname%/",
				"timezone_string": "UTC",
				"woocommerce_task_list_hidden": "yes",
				"woocommerce_task_list_complete": "yes",
				"woocommerce_admin_notices": [],
				"woocommerce_default_country": "US:CA",
				"woocommerce_currency": "USD",
				"trustlens_welcome_summary_sent": true
			}
		},
		{
			"step": "runPHP",
			"code": "<?php require '/wordpress/wp-load.php'; delete_transient('_wc_activation_redirect'); update_option('woocommerce_onboarding_profile', array('completed' => true)); update_user_meta(1, 'trustlens_review_notice_dismissed', true); update_user_meta(1, 'trustlens_review_notice_remind_later', time() + YEAR_IN_SECONDS); if ( class_exists('WC_Install') ) { WC_Install::create_pages(); } if ( class_exists('TrustLens_Install') ) { TrustLens_Install::create_tables(); TrustLens_Install::create_options(); } flush_rewrite_rules(); ?>"
		},
		{
			"step": "runPHP",
			"code": "<?php\nrequire '/wordpress/wp-load.php';\nglobal $wpdb;\n$customers_table = $wpdb->prefix . 'trustlens_customers';\n$now             = current_time('mysql', true);\n$days_ago        = function($d) { return gmdate('Y-m-d H:i:s', time() - $d * DAY_IN_SECONDS); };\n$customers = array(\n\tarray('e' => 'sarah.chen@demo.test',     'name' => 'Sarah Chen',       'score' => 97, 'seg' => 'vip',      'orders' => 32, 'value' => 4280.00, 'refunds' => 1, 'rvalue' => 89.00,  'rate' => 3.12,  'first' => $days_ago(720), 'last' => $days_ago(2)),\n\tarray('e' => 'james.rivera@demo.test',   'name' => 'James Rivera',     'score' => 93, 'seg' => 'vip',      'orders' => 18, 'value' => 2150.00, 'refunds' => 0, 'rvalue' => 0.00,   'rate' => 0.00,  'first' => $days_ago(540), 'last' => $days_ago(8)),\n\tarray('e' => 'elena.marsh@demo.test',    'name' => 'Elena Marsh',      'score' => 88, 'seg' => 'vip',      'orders' => 14, 'value' => 1820.00, 'refunds' => 1, 'rvalue' => 62.00,  'rate' => 7.14,  'first' => $days_ago(420), 'last' => $days_ago(11)),\n\tarray('e' => 'mateo.kim@demo.test',      'name' => 'Mateo Kim',        'score' => 81, 'seg' => 'trusted',  'orders' => 11, 'value' => 1390.00, 'refunds' => 1, 'rvalue' => 75.00,  'rate' => 9.09,  'first' => $days_ago(310), 'last' => $days_ago(4)),\n\tarray('e' => 'priya.shah@demo.test',     'name' => 'Priya Shah',       'score' => 76, 'seg' => 'trusted',  'orders' => 8,  'value' => 920.00,  'refunds' => 1, 'rvalue' => 110.00, 'rate' => 12.50, 'first' => $days_ago(240), 'last' => $days_ago(6)),\n\tarray('e' => 'aiko.tanaka@demo.test',    'name' => 'Aiko Tanaka',      'score' => 71, 'seg' => 'trusted',  'orders' => 9,  'value' => 1180.00, 'refunds' => 1, 'rvalue' => 130.00, 'rate' => 11.11, 'first' => $days_ago(260), 'last' => $days_ago(12)),\n\tarray('e' => 'oliver.brooks@demo.test',  'name' => 'Oliver Brooks',    'score' => 62, 'seg' => 'normal',   'orders' => 6,  'value' => 580.00,  'refunds' => 1, 'rvalue' => 95.00,  'rate' => 16.67, 'first' => $days_ago(180), 'last' => $days_ago(14)),\n\tarray('e' => 'nora.fischer@demo.test',   'name' => 'Nora Fischer',     'score' => 55, 'seg' => 'normal',   'orders' => 4,  'value' => 420.00,  'refunds' => 1, 'rvalue' => 110.00, 'rate' => 25.00, 'first' => $days_ago(95),  'last' => $days_ago(20)),\n\tarray('e' => 'k.morales@demo.test',      'name' => 'Kai Morales',      'score' => 38, 'seg' => 'caution',  'orders' => 7,  'value' => 690.00,  'refunds' => 3, 'rvalue' => 285.00, 'rate' => 42.86, 'first' => $days_ago(160), 'last' => $days_ago(3)),\n\tarray('e' => 'rj.albright@demo.test',    'name' => 'R.J. Albright',    'score' => 33, 'seg' => 'caution',  'orders' => 5,  'value' => 540.00,  'refunds' => 2, 'rvalue' => 215.00, 'rate' => 40.00, 'first' => $days_ago(130), 'last' => $days_ago(9)),\n\tarray('e' => 't.delacruz@demo.test',     'name' => 'Tatiana DelaCruz', 'score' => 24, 'seg' => 'risk',     'orders' => 9,  'value' => 1240.00, 'refunds' => 5, 'rvalue' => 720.00, 'rate' => 55.56, 'first' => $days_ago(280), 'last' => $days_ago(5)),\n\tarray('e' => 'm.alvarado@demo.test',     'name' => 'Marcus Alvarado',  'score' => 18, 'seg' => 'risk',     'orders' => 6,  'value' => 810.00,  'refunds' => 4, 'rvalue' => 560.00, 'rate' => 66.67, 'first' => $days_ago(150), 'last' => $days_ago(1)),\n\tarray('e' => 'fraud.ring.01@demo.test',  'name' => 'A. Henderson',     'score' => 6,  'seg' => 'critical', 'orders' => 4,  'value' => 980.00,  'refunds' => 3, 'rvalue' => 880.00, 'rate' => 75.00, 'first' => $days_ago(45),  'last' => $days_ago(7)),\n\tarray('e' => 'fraud.ring.02@demo.test',  'name' => 'A. Henderson (alt)','score' => 4, 'seg' => 'critical', 'orders' => 3,  'value' => 720.00,  'refunds' => 3, 'rvalue' => 720.00, 'rate' => 100.0, 'first' => $days_ago(38),  'last' => $days_ago(10))\n);\nforeach ($customers as $c) {\n\t$hash = function_exists('wstl_get_email_hash') ? wstl_get_email_hash($c['e']) : hash('sha256', $c['e']);\n\t$wpdb->insert($customers_table, array(\n\t\t'email_hash'         => $hash,\n\t\t'customer_email'     => $c['e'],\n\t\t'customer_type'      => 'user',\n\t\t'trust_score'        => $c['score'],\n\t\t'segment'            => $c['seg'],\n\t\t'total_orders'       => $c['orders'],\n\t\t'total_order_value'  => $c['value'],\n\t\t'total_refunds'      => $c['refunds'],\n\t\t'total_refund_value' => $c['rvalue'],\n\t\t'full_refunds'       => max(0, $c['refunds'] - 1),\n\t\t'partial_refunds'    => min(1, $c['refunds']),\n\t\t'return_rate'        => $c['rate'],\n\t\t'first_order_date'   => $c['first'],\n\t\t'last_order_date'    => $c['last'],\n\t\t'score_updated_at'   => $now,\n\t\t'created_at'         => $c['first'],\n\t\t'updated_at'         => $now\n\t));\n}\n?>"
		},
		{
			"step": "runPHP",
			"code": "<?php\nrequire '/wordpress/wp-load.php';\nglobal $wpdb;\n$t = $wpdb->prefix . 'trustlens_customers';\n// Apply state modifiers that make the demo concrete.\n$wpdb->update($t, array('total_disputes' => 2, 'disputes_lost' => 2, 'is_blocked' => 1, 'admin_notes' => 'Auto-blocked after 2 lost chargebacks. Shipping address shared with fraud.ring.02@.', 'linked_accounts' => 1), array('customer_email' => 'fraud.ring.01@demo.test'));\n$wpdb->update($t, array('total_disputes' => 2, 'disputes_lost' => 2, 'linked_accounts' => 1, 'admin_notes' => 'Linked to fraud.ring.01@ via shipping address + payment method.'), array('customer_email' => 'fraud.ring.02@demo.test'));\n$wpdb->update($t, array('total_disputes' => 1, 'disputes_lost' => 1, 'admin_notes' => 'Disputed a $240 order as \"product unacceptable\" two weeks after delivery.'), array('customer_email' => 'm.alvarado@demo.test'));\n$wpdb->update($t, array('is_allowlisted' => 1, 'admin_notes' => 'Allowlisted — long-standing VIP. Score locked at 100; signals do not lower it.'), array('customer_email' => 'sarah.chen@demo.test'));\n$wpdb->update($t, array('coupon_then_refund' => 2, 'first_order_coupons' => 3, 'admin_notes' => 'Used WELCOME15 on 3 different accounts before refunding. Coupon abuse suspected.'), array('customer_email' => 'rj.albright@demo.test'));\n?>"
		},
		{
			"step": "runPHP",
			"code": "<?php\nrequire '/wordpress/wp-load.php';\nglobal $wpdb;\n$signals_table = $wpdb->prefix . 'trustlens_signals';\n$lookup = function($email) use ($wpdb) {\n\treturn $wpdb->get_var($wpdb->prepare(\"SELECT email_hash FROM {$wpdb->prefix}trustlens_customers WHERE customer_email = %s\", $email));\n};\n$signals = array(\n\tarray('sarah.chen@demo.test',    'returns',         15,  'Excellent return history: 3% rate over 32 orders'),\n\tarray('sarah.chen@demo.test',    'orders',          12,  '32 completed orders, no cancellations'),\n\tarray('sarah.chen@demo.test',    'account_age',     15,  '720 days since first order'),\n\tarray('james.rivera@demo.test',  'returns',         15,  'Zero refunds across 18 orders'),\n\tarray('james.rivera@demo.test',  'orders',          10,  '18 completed orders'),\n\tarray('james.rivera@demo.test',  'account_age',     15,  '540 days since first order'),\n\tarray('elena.marsh@demo.test',   'returns',         12,  'Low return rate: 7% over 14 orders'),\n\tarray('elena.marsh@demo.test',   'account_age',     15,  '420 days since first order'),\n\tarray('k.morales@demo.test',     'returns',         -15, 'Elevated return rate: 43%'),\n\tarray('k.morales@demo.test',     'orders',          -5,  '1 cancelled order this month'),\n\tarray('rj.albright@demo.test',   'coupons',         -20, 'Coupon-then-refund pattern: 2 incidents'),\n\tarray('rj.albright@demo.test',   'coupons',         -15, '3 first-order coupons across linked accounts'),\n\tarray('t.delacruz@demo.test',    'returns',         -30, 'High return rate: 56% on $1240 lifetime value'),\n\tarray('t.delacruz@demo.test',    'returns',         -10, 'Full-refund ratio 80% — wardrobing risk'),\n\tarray('m.alvarado@demo.test',    'returns',         -25, 'Return rate 67% on 6 orders'),\n\tarray('m.alvarado@demo.test',    'chargebacks',     -15, '1 lost dispute in last 90 days'),\n\tarray('fraud.ring.01@demo.test', 'chargebacks',     -40, '2 lost disputes in 45 days'),\n\tarray('fraud.ring.01@demo.test', 'linked_accounts', -25, 'Linked to 1 other high-risk account by shipping address + payment method'),\n\tarray('fraud.ring.01@demo.test', 'returns',         -20, 'Return rate 75% on 4 orders'),\n\tarray('fraud.ring.02@demo.test', 'chargebacks',     -40, '2 lost disputes in 38 days'),\n\tarray('fraud.ring.02@demo.test', 'linked_accounts', -25, 'Linked to fraud.ring.01@')\n);\nforeach ($signals as $s) {\n\t$hash = $lookup($s[0]);\n\tif ( ! $hash ) { continue; }\n\t$wpdb->insert($signals_table, array(\n\t\t'email_hash'    => $hash,\n\t\t'module_id'     => $s[1],\n\t\t'signal_score'  => $s[2],\n\t\t'signal_reason' => $s[3],\n\t\t'created_at'    => current_time('mysql', true)\n\t));\n}\n?>"
		},
		{
			"step": "runPHP",
			"code": "<?php\nrequire '/wordpress/wp-load.php';\nglobal $wpdb;\n$fingerprints_table = $wpdb->prefix . 'trustlens_fingerprints';\n$lookup = function($email) use ($wpdb) {\n\treturn $wpdb->get_var($wpdb->prepare(\"SELECT email_hash FROM {$wpdb->prefix}trustlens_customers WHERE customer_email = %s\", $email));\n};\n// Linked-accounts pair: both fraud.ring rows share a shipping-address fingerprint.\n$ring1 = $lookup('fraud.ring.01@demo.test');\n$ring2 = $lookup('fraud.ring.02@demo.test');\n$shared_addr = hash('sha256', 'demo_shared_address|421 Mockingbird Ln|Boise|ID|83702|US');\n$shared_pay  = hash('sha256', 'demo_shared_payment|stripe|***4242');\nforeach (array($ring1, $ring2) as $hash) {\n\tif ( ! $hash ) { continue; }\n\t$wpdb->insert($fingerprints_table, array(\n\t\t'email_hash'       => $hash,\n\t\t'fingerprint_type' => 'shipping_address',\n\t\t'fingerprint_hash' => $shared_addr,\n\t\t'first_seen'       => gmdate('Y-m-d H:i:s', time() - 38 * DAY_IN_SECONDS),\n\t\t'last_seen'        => gmdate('Y-m-d H:i:s', time() - 7  * DAY_IN_SECONDS),\n\t\t'times_seen'       => 4,\n\t\t'country_code'     => 'US'\n\t));\n\t$wpdb->insert($fingerprints_table, array(\n\t\t'email_hash'       => $hash,\n\t\t'fingerprint_type' => 'payment_method',\n\t\t'fingerprint_hash' => $shared_pay,\n\t\t'first_seen'       => gmdate('Y-m-d H:i:s', time() - 38 * DAY_IN_SECONDS),\n\t\t'last_seen'        => gmdate('Y-m-d H:i:s', time() - 7  * DAY_IN_SECONDS),\n\t\t'times_seen'       => 4,\n\t\t'country_code'     => 'US'\n\t));\n}\n?>"
		},
		{
			"step": "runPHP",
			"code": "<?php\nrequire '/wordpress/wp-load.php';\nglobal $wpdb;\n$events_table = $wpdb->prefix . 'trustlens_events';\n$lookup = function($email) use ($wpdb) {\n\treturn $wpdb->get_var($wpdb->prepare(\"SELECT email_hash FROM {$wpdb->prefix}trustlens_customers WHERE customer_email = %s\", $email));\n};\n$events = array(\n\tarray('fraud.ring.01@demo.test', 'customer_blocked',   array('reason' => 'Auto-blocked after 2 lost chargebacks.'),                            time() - 6 * HOUR_IN_SECONDS),\n\tarray('fraud.ring.01@demo.test', 'dispute_created',    array('source' => 'stripe', 'amount' => 480, 'reason' => 'fraudulent'),                 time() - 1 * DAY_IN_SECONDS),\n\tarray('fraud.ring.02@demo.test', 'linked_account_detected', array('linked_to' => 'fraud.ring.01@demo.test', 'via' => 'shipping_address + payment_method'), time() - 8 * HOUR_IN_SECONDS),\n\tarray('m.alvarado@demo.test',    'dispute_created',    array('source' => 'stripe', 'amount' => 240, 'reason' => 'product_unacceptable'),       time() - 3 * DAY_IN_SECONDS),\n\tarray('t.delacruz@demo.test',    'refund_processed',   array('order_id' => 1042, 'amount' => 180, 'full' => true),                             time() - 4 * HOUR_IN_SECONDS),\n\tarray('k.morales@demo.test',     'refund_processed',   array('order_id' => 1037, 'amount' => 95),                                              time() - 18 * HOUR_IN_SECONDS),\n\tarray('sarah.chen@demo.test',    'order_completed',    array('order_id' => 1055, 'total' => 145),                                              time() - 2 * DAY_IN_SECONDS),\n\tarray('james.rivera@demo.test',  'order_completed',    array('order_id' => 1051, 'total' => 89),                                               time() - 8 * DAY_IN_SECONDS),\n\tarray('rj.albright@demo.test',   'first_order_coupon', array('coupon_code' => 'WELCOME15'),                                                    time() - 9 * DAY_IN_SECONDS)\n);\nforeach ($events as $ev) {\n\t$hash = $lookup($ev[0]);\n\tif ( ! $hash ) { continue; }\n\t$wpdb->insert($events_table, array(\n\t\t'email_hash' => $hash,\n\t\t'event_type' => $ev[1],\n\t\t'event_data' => wp_json_encode($ev[2]),\n\t\t'created_at' => gmdate('Y-m-d H:i:s', $ev[3])\n\t));\n}\n?>"
		},
		{
			"step": "runPHP",
			"code": "<?php\nrequire '/wordpress/wp-load.php';\nglobal $wpdb;\n$velocity_table = $wpdb->prefix . 'trustlens_velocity_events';\nif ( $wpdb->get_var(\"SHOW TABLES LIKE '{$velocity_table}'\") ) {\n\t// Sample card-testing wave: 8 declines from 3 attacker fingerprints over the last 2 hours.\n\t$bot_a = hash('sha256', 'demo_bot_alpha|chrome_headless|GE');\n\t$bot_b = hash('sha256', 'demo_bot_beta|firefox|NL');\n\t$bot_c = hash('sha256', 'demo_bot_gamma|safari|VN');\n\t$declines = array(\n\t\tarray($bot_a, 'card_declined',        90),\n\t\tarray($bot_a, 'card_declined',        80),\n\t\tarray($bot_a, 'card_declined',        70),\n\t\tarray($bot_b, 'insufficient_funds',   65),\n\t\tarray($bot_b, 'card_declined',        50),\n\t\tarray($bot_b, 'card_declined',        45),\n\t\tarray($bot_c, 'expired_card',         30),\n\t\tarray($bot_c, 'card_declined',        15)\n\t);\n\tforeach ($declines as $d) {\n\t\t$wpdb->insert($velocity_table, array(\n\t\t\t'fingerprint_hash' => $d[0],\n\t\t\t'event_type'       => 'decline',\n\t\t\t'decline_code'     => $d[1],\n\t\t\t'created_at'       => gmdate('Y-m-d H:i:s', time() - $d[2] * MINUTE_IN_SECONDS)\n\t\t));\n\t}\n}\n?>"
		},
		{
			"step": "runPHP",
			"code": "<?php\nrequire '/wordpress/wp-load.php';\nif ( ! function_exists('wc_get_product') ) { return; }\n$products = array(\n\tarray('Classic Cotton T-Shirt',  '24.99'),\n\tarray('Slim Fit Denim Jeans',    '79.99'),\n\tarray('Wool Pullover Sweater',   '89.99'),\n\tarray('Wireless Headphones',     '149.99'),\n\tarray('Bluetooth Speaker',       '79.99'),\n\tarray('Leather Bifold Wallet',   '49.99'),\n\tarray('Polarized Sunglasses',    '89.99'),\n\tarray('Ceramic Plant Pot Set',   '34.99')\n);\nforeach ($products as $p) {\n\tif ( wc_get_product_id_by_sku('demo-' . sanitize_title($p[0])) ) { continue; }\n\t$product = new WC_Product_Simple();\n\t$product->set_name($p[0]);\n\t$product->set_regular_price($p[1]);\n\t$product->set_sku('demo-' . sanitize_title($p[0]));\n\t$product->set_stock_status('instock');\n\t$product->set_status('publish');\n\t$product->save();\n}\n?>"
		},
		{
			"step": "runPHP",
			"code": "<?php\nrequire '/wordpress/wp-load.php';\nif ( ! function_exists('wc_create_order') || ! class_exists('TrustLens_Card_Brand_Detector') ) { return; }\n// Seed sample paid orders with card-brand meta so the Chargeback Ratio\n// Speedometer on the dashboard computes a real ratio (otherwise it shows 0/0).\n// Mix of brands matches realistic e-commerce traffic: Visa ~60%, MC ~25%, Amex ~10%, Discover ~5%.\n$brand_mix = array(\n\t'visa' => 28, 'mastercard' => 12, 'amex' => 5, 'discover' => 2\n);\n$dispute_marks = array(15, 22, 31); // 3 of the orders below will be marked as disputed.\n$order_idx = 0;\nforeach ($brand_mix as $brand => $count) {\n\tfor ($i = 0; $i < $count; $i++) {\n\t\t$order = wc_create_order(array('status' => 'completed'));\n\t\t$order->set_total(rand(35, 240));\n\t\t$order->update_meta_data(TrustLens_Card_Brand_Detector::META_BRAND, $brand);\n\t\t$order->update_meta_data(TrustLens_Card_Brand_Detector::META_CARD_TX, '1');\n\t\t$order->set_date_created(time() - rand(1, 28) * DAY_IN_SECONDS);\n\t\tif ( in_array($order_idx, $dispute_marks, true) ) {\n\t\t\t$order->update_meta_data('_trustlens_dispute', true);\n\t\t\t$order->update_meta_data('_trustlens_dispute_data', array('source' => 'stripe', 'amount' => 120, 'reason' => 'fraudulent', 'status' => 'lost'));\n\t\t}\n\t\t$order->save();\n\t\t$order_idx++;\n\t}\n}\n?>"
		},
		{
			"step": "login",
			"username": "admin",
			"password": "password"
		}
	]
}
