<?php
// includes/stripe-webhook.php
if (!defined('ABSPATH')) exit;

add_action('rest_api_init', function () {
    register_rest_route('vmo-builder/v1', '/stripe-webhook', [
        'methods'             => 'POST',
        'callback'            => 'vmo_handle_stripe_webhook',
        'permission_callback' => '__return_true' // Public Stripe webhook — validated via \Stripe\Webhook::constructEvent() signature check in callback
    ]);
});

function vmo_stripe_webhook_object_to_array($object) {
    if (is_array($object)) {
        return $object;
    }
    $encoded = wp_json_encode($object);
    $decoded = $encoded ? json_decode($encoded, true) : array();
    return is_array($decoded) ? $decoded : array();
}

function vmo_stripe_webhook_subscription_id(array $object) {
    $candidates = array(
        $object['subscription'] ?? '',
        $object['parent']['subscription_details']['subscription'] ?? '',
        $object['lines']['data'][0]['subscription'] ?? '',
    );
    if (($object['object'] ?? '') === 'subscription') {
        $candidates[] = $object['id'] ?? '';
    }

    foreach ($candidates as $candidate) {
        $candidate = (string) $candidate;
        if ($candidate !== '') {
            return $candidate;
        }
    }

    return '';
}

function vmo_stripe_webhook_metadata(array $object) {
    $metadata = array();
    foreach (array(
        $object['metadata'] ?? array(),
        $object['subscription_details']['metadata'] ?? array(),
        $object['parent']['subscription_details']['metadata'] ?? array(),
    ) as $candidate) {
        if (is_array($candidate)) {
            $metadata = array_merge($metadata, $candidate);
        }
    }
    return $metadata;
}

function vmo_stripe_webhook_find_user_id(array $object) {
    $metadata = vmo_stripe_webhook_metadata($object);
    if (!empty($metadata['user_id'])) {
        return (int) $metadata['user_id'];
    }

    global $wpdb;
    $customer_id = (string) ($object['customer'] ?? '');
    if ($customer_id !== '') {
        $user_id = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = 'vmo_stripe_customer_id' AND meta_value = %s LIMIT 1",
            $customer_id
        ));
        if ($user_id > 0) {
            return $user_id;
        }
    }

    $subscription_id = vmo_stripe_webhook_subscription_id($object);
    if ($subscription_id !== '') {
        $user_id = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = 'vmo_stripe_subscription_id' AND meta_value = %s LIMIT 1",
            $subscription_id
        ));
        if ($user_id > 0) {
            return $user_id;
        }
    }

    return 0;
}

function vmo_stripe_webhook_event_plan($user_id, array $object) {
    $metadata = vmo_stripe_webhook_metadata($object);
    $plan = (string) ($metadata['plan'] ?? '');
    if ($plan === '' && isset($object['items']['data'][0]['price']['metadata']['plan'])) {
        $plan = (string) $object['items']['data'][0]['price']['metadata']['plan'];
    }
    if (function_exists('vmo_get_user_recoverable_plan')) {
        return vmo_get_user_recoverable_plan($user_id, $plan);
    }
    return $plan !== '' ? sanitize_key($plan) : (string) get_user_meta($user_id, 'vmo_subscription_status', true);
}

function vmo_stripe_webhook_sync_free_access($user_id, array $object, $stripe_status = '') {
    $user_id = (int) $user_id;
    if ($user_id <= 0) {
        return;
    }

    $metadata = vmo_stripe_webhook_metadata($object);
    $stripe_status = sanitize_key((string) $stripe_status);
    $expires_at = 0;
    if (!empty($metadata['vmo_free_access_expires_at'])) {
        $expires_at = is_numeric($metadata['vmo_free_access_expires_at']) ? (int) $metadata['vmo_free_access_expires_at'] : strtotime((string) $metadata['vmo_free_access_expires_at']);
    }
    if ($expires_at <= 0 && !empty($object['trial_end'])) {
        $expires_at = (int) $object['trial_end'];
    }

    if ($stripe_status === 'trialing' && $expires_at > 0) {
        update_user_meta($user_id, 'vmo_free_access_status', 'active');
        update_user_meta($user_id, 'vmo_free_access_expires_at', gmdate('Y-m-d H:i:s', $expires_at));
        update_user_meta($user_id, 'vmo_trial_access_expires_at', gmdate('Y-m-d H:i:s', $expires_at));
        if (!empty($metadata['vmo_trial_days'])) {
            update_user_meta($user_id, 'vmo_free_access_trial_days', (int) $metadata['vmo_trial_days']);
        }
        if (!empty($metadata['vmo_discount_code'])) {
            update_user_meta($user_id, 'vmo_free_access_code', sanitize_text_field((string) $metadata['vmo_discount_code']));
        }
    } elseif (in_array($stripe_status, array('active', 'paid'), true)) {
        update_user_meta($user_id, 'vmo_free_access_status', 'converted_to_paid');
    }
}

/**
 * Handle Stripe webhook: plan billing, token crediting, and Connect store orders
 */
function vmo_handle_stripe_webhook(WP_REST_Request $request) {
    $payload    = $request->get_body();
    $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';
    $secret     = get_option('vmo_stripe_webhook_secret');

    if (!$secret) {
        return new WP_REST_Response(['error' => 'Webhook secret is not set.'], 403);
    }

    require_once VMO_AI_BUILDER_DIR . 'stripe-sdk/init.php';

    try {
        $event = \Stripe\Webhook::constructEvent($payload, $sig_header, $secret);
    } catch (\Stripe\Exception\SignatureVerificationException $e) {
        return new WP_REST_Response(['error' => 'Invalid Stripe signature.'], 400);
    } catch (Exception $e) {
        return new WP_REST_Response(['error' => $e->getMessage()], 400);
    }

    // Basic idempotency: skip duplicate delivery of the same event id
    $dup_key = null;
    $event_id = isset($event->id) ? (string)$event->id : '';
    if ($event_id) {
        $dup_key = 'vmo_evt_' . md5($event_id);
        if (get_transient($dup_key)) {
            return new WP_REST_Response(['status' => 'duplicate'], 200);
        }
    }

    $quicklaunch_event_types = [
        'checkout.session.completed',
        'checkout.session.async_payment_succeeded',
        'invoice.payment_succeeded',
        'invoice.payment_failed',
        'customer.subscription.updated',
        'customer.subscription.deleted',
    ];

    if (in_array($event->type, $quicklaunch_event_types, true) && class_exists('VMO_QuickLaunch')) {
        $event_array = json_decode($payload, true);
        $stripe_object = is_array($event_array) ? ($event_array['data']['object'] ?? []) : [];
        $metadata = is_array($stripe_object) && isset($stripe_object['metadata']) && is_array($stripe_object['metadata']) ? $stripe_object['metadata'] : [];
        $quicklaunch_checkout = (($metadata['vmo_checkout_mode'] ?? '') === 'quicklaunch_hosted') || !empty($metadata['vmo_session_id']);
        $quicklaunch_customer_event = false;

        if (!$quicklaunch_checkout && in_array($event->type, ['invoice.payment_succeeded', 'invoice.payment_failed', 'customer.subscription.updated', 'customer.subscription.deleted'], true)) {
            $customer_id = is_array($stripe_object) ? (string) ($stripe_object['customer'] ?? '') : '';
            if ($customer_id !== '') {
                global $wpdb;
                $quicklaunch_user_id = $wpdb->get_var($wpdb->prepare(
                    "SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = 'vmo_stripe_customer_id' AND meta_value = %s LIMIT 1",
                    $customer_id
                ));
                $quicklaunch_customer_event = $quicklaunch_user_id && (string) get_user_meta((int) $quicklaunch_user_id, 'vmo_quicklaunch_session', true) !== '';
            }
        }

        if ($quicklaunch_checkout || $quicklaunch_customer_event) {
            $quicklaunch_response = VMO_QuickLaunch::instance()->api_stripe_webhook($request);
            if (!empty($dup_key) && $quicklaunch_response instanceof WP_REST_Response && $quicklaunch_response->get_status() < 400) {
                set_transient($dup_key, 1, 2 * DAY_IN_SECONDS);
            }
            return $quicklaunch_response;
        }
    }

    $lifecycle_events = array(
        'invoice.payment_succeeded',
        'invoice.payment_failed',
        'customer.subscription.updated',
        'customer.subscription.deleted',
    );

    if (in_array($event->type, $lifecycle_events, true)) {
        $object = vmo_stripe_webhook_object_to_array($event->data->object);
        $user_id = vmo_stripe_webhook_find_user_id($object);

        if ($user_id > 0) {
            $customer_id = (string) ($object['customer'] ?? '');
            $subscription_id = vmo_stripe_webhook_subscription_id($object);
            $recoverable_plan = vmo_stripe_webhook_event_plan($user_id, $object);

            if ($customer_id !== '') {
                update_user_meta($user_id, 'vmo_stripe_customer_id', $customer_id);
            }
            if ($subscription_id !== '') {
                update_user_meta($user_id, 'vmo_stripe_subscription_id', $subscription_id);
            }

            if ($event->type === 'invoice.payment_succeeded') {
                update_user_meta($user_id, 'vmo_payment_failure_count', 0);
                update_user_meta($user_id, 'vmo_last_successful_payment', current_time('mysql'));
                update_user_meta($user_id, 'vmo_subscription_payment_status', 'paid');
                vmo_stripe_webhook_sync_free_access($user_id, $object, 'paid');
                if (function_exists('vmo_unlock_user_services')) {
                    vmo_unlock_user_services($user_id, array(
                        'reason' => 'stripe_invoice_payment_succeeded',
                        'payment_status' => 'paid',
                        'plan' => $recoverable_plan,
                        'restore_pre_lock_tokens' => true,
                    ));
                }
            } elseif ($event->type === 'invoice.payment_failed') {
                $fail_count = (int) get_user_meta($user_id, 'vmo_payment_failure_count', true) + 1;
                update_user_meta($user_id, 'vmo_payment_failure_count', $fail_count);
                update_user_meta($user_id, 'vmo_last_payment_failure', current_time('mysql'));
                update_user_meta($user_id, 'vmo_subscription_status', 'past_due');
                update_user_meta($user_id, 'vmo_subscription_payment_status', 'payment_failed');
                if (function_exists('vmo_lock_user_services')) {
                    vmo_lock_user_services($user_id, 'payment_failed', array(
                        'plan' => $recoverable_plan,
                        'failure_count' => $fail_count,
                        'stripe_customer_id' => $customer_id,
                        'stripe_subscription_id' => $subscription_id,
                        'stripe_invoice_id' => (string) ($object['id'] ?? ''),
                        'preserve_site_assets' => true,
                    ));
                }
            } elseif ($event->type === 'customer.subscription.updated') {
                $stripe_status = sanitize_key((string) ($object['status'] ?? ''));
                if (in_array($stripe_status, array('active', 'trialing'), true)) {
                    update_user_meta($user_id, 'vmo_subscription_payment_status', $stripe_status === 'trialing' ? 'trialing' : 'paid');
                    vmo_stripe_webhook_sync_free_access($user_id, $object, $stripe_status);
                    if ($recoverable_plan !== '') {
                        update_user_meta($user_id, 'vmo_subscription_status', $recoverable_plan);
                    }
                    if (function_exists('vmo_unlock_user_services')) {
                        vmo_unlock_user_services($user_id, array(
                            'reason' => 'stripe_subscription_' . $stripe_status,
                            'payment_status' => $stripe_status === 'trialing' ? 'trialing' : 'paid',
                            'plan' => $recoverable_plan,
                            'restore_pre_lock_tokens' => true,
                        ));
                    }
                } elseif (in_array($stripe_status, array('past_due', 'unpaid', 'paused', 'incomplete', 'incomplete_expired', 'canceled'), true)) {
                    update_user_meta($user_id, 'vmo_subscription_status', $stripe_status);
                    update_user_meta($user_id, 'vmo_subscription_payment_status', $stripe_status);
                    if (function_exists('vmo_lock_user_services')) {
                        vmo_lock_user_services($user_id, $stripe_status, array(
                            'plan' => $recoverable_plan,
                            'stripe_customer_id' => $customer_id,
                            'stripe_subscription_id' => $subscription_id,
                            'preserve_site_assets' => true,
                        ));
                    }
                }
            } elseif ($event->type === 'customer.subscription.deleted') {
                update_user_meta($user_id, 'vmo_subscription_status', 'canceled');
                update_user_meta($user_id, 'vmo_subscription_payment_status', 'canceled');
                update_user_meta($user_id, 'vmo_subscription_canceled_at', current_time('mysql'));
                if (function_exists('vmo_lock_user_services')) {
                    vmo_lock_user_services($user_id, 'subscription_canceled', array(
                        'plan' => $recoverable_plan,
                        'stripe_customer_id' => $customer_id,
                        'stripe_subscription_id' => $subscription_id,
                        'preserve_site_assets' => true,
                    ));
                }
            }

            if (!empty($dup_key)) {
                set_transient($dup_key, 1, 2 * DAY_IN_SECONDS);
            }

            return new WP_REST_Response(array('received' => true, 'user_id' => $user_id, 'action' => $event->type), 200);
        }
    }

    if ($event->type === 'checkout.session.completed') {
        $session = $event->data->object; // \Stripe\Checkout\Session

        // Case 1: Platform plan purchases (VMO plans / top-ups)
        $user_id = intval($session->metadata->user_id ?? 0);
        $tokens  = intval($session->metadata->tokens ?? 0);
        $plan    = sanitize_text_field($session->metadata->plan ?? '');

        // Booster top-up fallback: if metadata lacks 'tokens' but carries a
        // topup_tier, resolve the grant from the centralized booster catalog.
        // This keeps webhook credit logic in lockstep with the catalog so that
        // changing a token grant in one file propagates here automatically.
        $topup_tier = sanitize_text_field($session->metadata->topup_tier ?? '');
        if ($tokens <= 0 && $topup_tier !== '' && class_exists('VMO_Booster_Catalog')) {
            $tokens = (int) VMO_Booster_Catalog::get_token_grant($topup_tier);
        }

        if ($user_id && ($tokens > 0 || !empty($plan))) {
            if (!empty($session->customer)) {
                update_user_meta($user_id, 'vmo_stripe_customer_id', (string) $session->customer);
            }
            if (!empty($session->subscription)) {
                update_user_meta($user_id, 'vmo_stripe_subscription_id', (string) $session->subscription);
            }
            vmo_stripe_webhook_sync_free_access($user_id, vmo_stripe_webhook_object_to_array($session), 'trialing');

            if ($tokens <= 0 && !empty($plan) && function_exists('vmo_get_plan_checkout_token_grant')) {
                $tokens = vmo_get_plan_checkout_token_grant($plan, 0);
            }

            if ($tokens > 0) {
                if (function_exists('vmo_update_token_balance')) {
                    vmo_update_token_balance($user_id, $tokens, 'add');
                } else {
                    update_user_meta($user_id, 'vmo_token_balance', (int) get_user_meta($user_id, 'vmo_token_balance', true) + $tokens);
                }
            }
            if ($plan) {
                // Normalize pack names to a simpler status label
                $status = $plan;
                if (in_array($plan, ['starter_pack','pro_pack','business_pack','enterprise_pack'], true)) {
                    $status = $plan; // keep as-is for clarity
                }
                update_user_meta($user_id, 'vmo_subscription_status', $status);

                // Generate license key for master/client authentication (main plans only, not packs)
                $main_plans = ['tester', 'starter', 'pro', 'elite', 'enterprise'];
                if ( in_array( $plan, $main_plans, true ) && function_exists( 'vmo_generate_license_key' ) ) {
                    $license_result = vmo_generate_license_key( $user_id, $plan );
                    if ( is_wp_error( $license_result ) ) {
                        error_log( 'VMO Stripe Webhook: License key generation failed — ' . $license_result->get_error_message() );
                    }
                }

                if (function_exists('vmo_queue_plan_signup_bonus')) {
                    vmo_queue_plan_signup_bonus($user_id, $plan);
                }

                if (function_exists('vmo_user_has_selected_subscription_plan') && vmo_user_has_selected_subscription_plan($plan) && function_exists('vmo_unlock_user_services')) {
                    vmo_unlock_user_services($user_id, array(
                        'reason' => 'stripe_checkout_completed',
                        'payment_status' => 'paid',
                        'plan' => $plan,
                        'token_balance' => (int) get_user_meta($user_id, 'vmo_token_balance', true),
                    ));
                }
            }
            // Enterprise enables priority compute flag if metadata present
            $priority = intval($session->metadata->priority_compute ?? 0) === 1;
            if (!$priority && class_exists('VMO_Plan_Policy')) {
                $priority = VMO_Plan_Policy::plan_has_priority_compute($plan);
            }
            if ($priority) {
                update_user_meta($user_id, 'vmo_priority_compute', 1);
            }
            if (function_exists('vmo_master_record_checkout_session')) {
                vmo_master_record_checkout_session($user_id, $session, [
                    'source' => 'platform_checkout_webhook',
                    'affiliate_ref' => (string) ($session->metadata->vmo_affiliate_ref ?? ''),
                    'plan' => $plan,
                    'billing_cycle' => (string) ($session->metadata->billing_cycle ?? ''),
                    'email' => (string) ($session->customer_email ?? ''),
                ]);
            }
            // Mark as processed for idempotency
            if (!empty($dup_key)) {
                set_transient($dup_key, 1, 2 * DAY_IN_SECONDS);
            }
    } else {
            // Case 2: Store orders via Stripe Connect (one-time or subscription initial payment)
            global $wpdb;
            $orders = $wpdb->prefix . 'vmo_store_orders';
            $session_id = sanitize_text_field($session->id);
            $account_id = isset($event->account) ? sanitize_text_field($event->account) : '';

            // Transition order to paid only if not already paid (guards against double-processing)
            $now = current_time('mysql');
            $pi_id = isset($session->payment_intent) ? (string)$session->payment_intent : null;
            $sess_amt = isset($session->amount_total) ? intval($session->amount_total) : null;
            // Use explicit SQL to include the status predicate atomically; include amount_total if present
            if ($sess_amt !== null) {
                $sql = $wpdb->prepare(
                    "UPDATE $orders SET status='paid', stripe_payment_intent_id=%s, amount_total=%d, updated_at=%s WHERE stripe_checkout_session_id=%s AND status != 'paid'",
                    $pi_id, $sess_amt, $now, $session_id
                );
            } else {
                $sql = $wpdb->prepare(
                    "UPDATE $orders SET status='paid', stripe_payment_intent_id=%s, updated_at=%s WHERE stripe_checkout_session_id=%s AND status != 'paid'",
                    $pi_id, $now, $session_id
                );
            }
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $updated_rows = $wpdb->query($sql);

            // Try to enrich with receipt URL and charge id
            try {
                require_once VMO_AI_BUILDER_DIR . 'stripe-sdk/init.php';
                \Stripe\Stripe::setApiKey(get_option('vmo_stripe_secret_key'));
                if (!empty($session->payment_intent) && $updated_rows > 0) {
                    $pi = \Stripe\PaymentIntent::retrieve($session->payment_intent, $account_id ? [ 'stripe_account' => $account_id ] : []);
                    $charge    = isset($pi->charges->data[0]) ? $pi->charges->data[0] : null;
                    $charge_id = $charge ? $charge->id : null;
                    $receipt   = $charge ? $charge->receipt_url : null;
                    // Attempt to derive Stripe fee and net from the balance transaction
                    $stripe_fee_cents = 0; $net_to_merchant_cents = null;
                    $balance_tx_id = $charge ? ($charge->balance_transaction ?? null) : null;
                    if ($balance_tx_id) {
                        try {
                            $bt = \Stripe\BalanceTransaction::retrieve($balance_tx_id, $account_id ? [ 'stripe_account' => $account_id ] : []);
                            // Amount signs: Stripe returns positive 'amount' for charge, 'fee' positive in cents
                            $stripe_fee_cents = isset($bt->fee) ? (int)$bt->fee : 0;
                            $net_to_merchant_cents = isset($bt->net) ? (int)$bt->net : null; // net excludes application fee
                        } catch (Exception $e) {
                            error_log('VMO Stripe webhook: balance transaction fetch failed: ' . $e->getMessage());
                        }
                    }
                    // Compute tax: prefer Stripe's amount_tax; otherwise compute advisory based on saved rules
                    $tax_cents = 0; $advisory_tax_cents = null;
                    if (isset($session->total_details) && isset($session->total_details->amount_tax)) {
                        $tax_cents = (int)$session->total_details->amount_tax;
                    }
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                    $wpdb->update($orders, [
                        'stripe_charge_id' => $charge_id,
                        'receipt_url' => $receipt
                    ], [ 'stripe_checkout_session_id' => $session_id ]);

                    // Optional: CRM recording if tables exist
                    $buyer_email = is_string($session->customer_details->email ?? '') ? $session->customer_details->email : '';
                    $buyer_name  = is_string($session->customer_details->name ?? '') ? $session->customer_details->name : '';
                    $amount_paid = isset($session->amount_total) ? (int)$session->amount_total : 0;
                    $meta = (array) ($session->metadata ?? []);
                    $product_name = isset($meta['product_name']) ? (string)$meta['product_name'] : 'Product';

                    $contacts_table = $wpdb->prefix . 'vmo_contacts';
                    $purchases_table = $wpdb->prefix . 'vmo_contact_purchases';
                    $order_row = $wpdb->get_row($wpdb->prepare("SELECT * FROM $orders WHERE stripe_checkout_session_id = %s", $session_id), ARRAY_A);
                    $merchant_user_id = (int) ($order_row['user_id'] ?? 0);
                    $platform_fee_cents = isset($order_row['platform_fee_amount']) ? (int)$order_row['platform_fee_amount'] : 0;
                    $owner_notified = false;
                    // Persist fee/tax breakdown into order metadata for reporting
                    if ($order_row) {
                        $order_meta = json_decode($order_row['metadata'] ?: '{}', true);
                        if (!is_array($order_meta)) $order_meta = [];
                        $order_meta['stripe_fee_cents'] = $stripe_fee_cents;
                        $order_meta['platform_fee_cents'] = $platform_fee_cents;
                        // If net wasn't provided by Stripe's BT, compute it: gross - stripe fee - platform fee
                        $order_meta['net_to_merchant_cents'] = ($net_to_merchant_cents !== null)
                            ? $net_to_merchant_cents
                            : max(0, (int)$amount_paid - (int)$stripe_fee_cents - (int)$platform_fee_cents);
                        // If tax wasn't charged by Stripe, compute advisory using merchant rules
                        if ($tax_cents > 0) {
                            $order_meta['tax_cents'] = $tax_cents;
                        } else {
                            // Fetch merchant advisory tax rules
                            $cfg = get_user_meta($merchant_user_id, 'vmo_payments_config', true);
                            if (!is_array($cfg)) $cfg = [];
                            $rules = $cfg['tax_rules'] ?? [];
                            $addr = [
                                'country' => is_string($session->customer_details->address->country ?? '') ? strtoupper($session->customer_details->address->country) : '',
                                'state'   => is_string($session->customer_details->address->state ?? '') ? strtoupper($session->customer_details->address->state) : '',
                                'postal_code' => is_string($session->customer_details->address->postal_code ?? '') ? $session->customer_details->address->postal_code : ''
                            ];
                            // Minimal inline compute (mirror of class method)
                            $rate_bps_total = 0;
                            if (is_array($rules)) {
                                foreach ($rules as $r) {
                                    $rc = strtoupper($r['country'] ?? ''); if ($rc && $rc !== $addr['country']) continue;
                                    $rs = strtoupper($r['state'] ?? ''); if ($rs && $rs !== $addr['state']) continue;
                                    $bps = (int)($r['rate_bps'] ?? 0); if ($bps > 0) $rate_bps_total += $bps;
                                }
                            }
                            $advisory_tax_cents = (int) round($amount_paid * ($rate_bps_total / 10000));
                            $order_meta['advisory_tax_cents'] = $advisory_tax_cents;
                        }
                        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                        $wpdb->update($orders, [ 'metadata' => wp_json_encode($order_meta) ], [ 'id' => (int)$order_row['id'] ]);
                    }

                    // Decrement inventory if tracked (idempotent via status guard above)
                    if ($order_row) {
                        $order_meta = json_decode($order_row['metadata'] ?: '{}', true);
                        $product_id = isset($order_meta['product_id']) ? intval($order_meta['product_id']) : 0;
                        $qty = isset($order_meta['quantity']) ? max(1, intval($order_meta['quantity'])) : 1;
                        if ($product_id > 0 && $merchant_user_id > 0) {
                            $products_table = $wpdb->prefix . 'vmo_store_products';
                            // Only decrement when stock tracking is enabled
                            $wpdb->query($wpdb->prepare(
                                "UPDATE $products_table SET stock_qty = GREATEST(stock_qty - %d, 0), updated_at=%s WHERE id = %d AND user_id = %d AND stock_tracking = 1",
                                $qty, $now, $product_id, $merchant_user_id
                            ));
                        }
                    }

                    if (!empty($order_row) && function_exists('vmo_business_flow_sync_paid_order')) {
                        $shipping_address = [];
                        if (isset($session->customer_details->address)) {
                            $shipping_address = [
                                'line1' => is_string($session->customer_details->address->line1 ?? '') ? (string) $session->customer_details->address->line1 : '',
                                'line2' => is_string($session->customer_details->address->line2 ?? '') ? (string) $session->customer_details->address->line2 : '',
                                'city' => is_string($session->customer_details->address->city ?? '') ? (string) $session->customer_details->address->city : '',
                                'state' => is_string($session->customer_details->address->state ?? '') ? (string) $session->customer_details->address->state : '',
                                'postal_code' => is_string($session->customer_details->address->postal_code ?? '') ? (string) $session->customer_details->address->postal_code : '',
                                'country' => is_string($session->customer_details->address->country ?? '') ? (string) $session->customer_details->address->country : '',
                            ];
                        }
                        vmo_business_flow_sync_paid_order((int) $order_row['id'], [
                            'session_id' => $session_id,
                            'buyer_email' => $buyer_email,
                            'buyer_name' => $buyer_name,
                            'customer_email' => $buyer_email,
                            'customer_name' => $buyer_name,
                            'amount_total' => $amount_paid,
                            'currency' => (string) ($session->currency ?? 'usd'),
                            'stripe_payment_intent_id' => $pi_id,
                            'shipping_address' => $shipping_address,
                        ]);
                    }

                    // Create/fetch contact and record purchase
                    if ($merchant_user_id && $buyer_email && function_exists('vmo_crm_record_purchase')) {
                        vmo_crm_record_purchase((int) $merchant_user_id, [
                            'full_name' => $buyer_name,
                            'email' => $buyer_email,
                            'source' => 'stripe',
                        ], [
                            'transaction_id' => (string) $charge_id,
                            'product_service' => $product_name,
                            'purchase_type' => 'one_time',
                            'amount' => $amount_paid / 100,
                            'currency' => (string) ($session->currency ?? 'usd'),
                            'status' => 'completed',
                            'purchase_date' => current_time('mysql'),
                            'payment_method' => 'card',
                            'source' => 'vmo_pay',
                            'notes' => 'VMO Pay checkout order',
                            'metadata' => [
                                'charge_id' => $charge_id,
                                'amount_cents' => $amount_paid,
                                'currency' => (string) ($session->currency ?? 'usd'),
                                'receipt_url' => $receipt,
                            ],
                        ]);
                        if (function_exists('vmo_record_attribution_event')) {
                            vmo_record_attribution_event((int) $merchant_user_id, 'purchase', array_merge(is_array($order_meta ?? null) ? $order_meta : [], [
                                'source' => $order_meta['utm_source'] ?? $order_meta['source'] ?? 'vmo_pay',
                                'medium' => $order_meta['utm_medium'] ?? 'checkout',
                                'campaign' => $order_meta['utm_campaign'] ?? '',
                                'contact_id' => '',
                                'related_order_id' => (int) ($order_row['id'] ?? 0),
                                'conversion_value' => $amount_paid / 100,
                                'landing_page' => $order_meta['landing_page'] ?? $order_meta['page_url'] ?? '',
                                'referrer' => $order_meta['referrer'] ?? '',
                                'event_subtype' => 'stripe_checkout_completed',
                            ]));
                        }

                        // Email merchant a simple receipt link
                        $merchant_email = get_userdata($merchant_user_id)->user_email ?? '';
                        if ($merchant_email && $receipt) {
                            $owner_body = 'A new order was placed.' . "\n\n";
                            $owner_body .= 'Product: ' . $product_name . "\n";
                            if ($buyer_name || $buyer_email) {
                                $owner_body .= 'Customer: ' . trim($buyer_name . ' ' . '<' . $buyer_email . '>') . "\n";
                            }
                            $owner_body .= 'Amount: ' . strtoupper((string) ($session->currency ?? 'usd')) . ' ' . number_format($amount_paid / 100, 2) . "\n";
                            if (function_exists('vmo_business_flow_guidance_for_order_meta') && !empty($order_row)) {
                                $guidance_meta = json_decode($order_row['metadata'] ?: '{}', true);
                                if (is_array($guidance_meta)) {
                                    $owner_body .= 'Next action: ' . vmo_business_flow_guidance_for_order_meta($guidance_meta) . "\n";
                                }
                            }
                            if ($receipt) {
                                $owner_body .= 'Receipt: ' . esc_url($receipt) . "\n";
                            }
                            wp_mail($merchant_email, 'New Order: ' . $product_name, $owner_body);
                            $owner_notified = true;
                        }
                    }

                    if (!$owner_notified && $merchant_user_id) {
                        $merchant = get_userdata($merchant_user_id);
                        $merchant_email = $merchant && !empty($merchant->user_email) ? $merchant->user_email : '';
                        if ($merchant_email) {
                            $owner_body = 'A new order was placed.' . "\n\n";
                            $owner_body .= 'Product: ' . $product_name . "\n";
                            if ($buyer_name || $buyer_email) {
                                $owner_body .= 'Customer: ' . trim($buyer_name . ' ' . '<' . $buyer_email . '>') . "\n";
                            }
                            $owner_body .= 'Amount: ' . strtoupper((string) ($session->currency ?? 'usd')) . ' ' . number_format($amount_paid / 100, 2) . "\n";
                            if (function_exists('vmo_business_flow_guidance_for_order_meta') && !empty($order_row)) {
                                $guidance_meta = json_decode($order_row['metadata'] ?: '{}', true);
                                if (is_array($guidance_meta)) {
                                    $owner_body .= 'Next action: ' . vmo_business_flow_guidance_for_order_meta($guidance_meta) . "\n";
                                }
                            }
                            if ($receipt) {
                                $owner_body .= 'Receipt: ' . esc_url($receipt) . "\n";
                            }
                            wp_mail($merchant_email, 'New Order: ' . $product_name, $owner_body);
                        }
                    }

                    // If this checkout created a subscription, upsert subscription record
                    if (!empty($session->subscription)) {
                        $subs_table = $wpdb->prefix . 'vmo_store_subscriptions';
                        $sub_id = (string)$session->subscription;
                        $meta = (array) ($session->metadata ?? []);
                        $product_id = isset($meta['product_id']) ? intval($meta['product_id']) : 0;
                        $status = 'active';
                        $period_end = null;
                        try {
                            \Stripe\Stripe::setApiKey(get_option('vmo_stripe_secret_key'));
                            $sub = \Stripe\Subscription::retrieve($sub_id, $account_id ? [ 'stripe_account' => $account_id ] : []);
                            $status = (string)($sub->status ?? 'active');
                            if (!empty($sub->current_period_end)) {
                                $period_end = date('Y-m-d H:i:s', (int)$sub->current_period_end);
                            }
                        } catch (Exception $e) { /* ignore */ }
                        if (!empty($order_row)) {
                            $buyer_user_id = (int)($order_row['buyer_user_id'] ?? 0);
                            $exists = $wpdb->get_var($wpdb->prepare("SELECT id FROM $subs_table WHERE stripe_subscription_id=%s", $sub_id));
                            if ($exists) {
                                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                                $wpdb->update($subs_table, [
                                    'status' => $status,
                                    'current_period_end' => $period_end,
                                    'updated_at' => current_time('mysql')
                                ], [ 'id' => (int)$exists ]);
                            } else {
                                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                                $wpdb->insert($subs_table, [
                                    'user_id' => $merchant_user_id,
                                    'buyer_user_id' => $buyer_user_id ?: null,
                                    'product_id' => $product_id ?: 0,
                                    'stripe_subscription_id' => $sub_id,
                                    'status' => $status,
                                    'current_period_end' => $period_end,
                                    'metadata' => wp_json_encode([ 'session_id' => $session_id ]),
                                ]);
                            }
                        }
                    }

                    // Email buyer a receipt copy (friendly notice with Stripe receipt link)
                    if ($buyer_email) {
                        $body = 'Thank you for your purchase' . ($product_name ? ' of ' . $product_name : '') . ".\n";
                        if ($receipt) $body .= 'Your receipt: ' . esc_url($receipt) . "\n";
                        // Include downloadable fulfillment link when available
                        if (!empty($order_row)) {
                            $order_meta = json_decode($order_row['metadata'] ?: '{}', true);
                            $product_id = isset($order_meta['product_id']) ? intval($order_meta['product_id']) : 0;
                            if ($product_id > 0) {
                                $products_table = $wpdb->prefix . 'vmo_store_products';
                                $prod = $wpdb->get_row($wpdb->prepare("SELECT download_url FROM $products_table WHERE id=%d", $product_id), ARRAY_A);
                                if (!empty($prod['download_url'])) {
                                    $body .= "\nYour download: " . esc_url($prod['download_url']) . "\n";
                                }
                            }
                        }
                        $body .= "\nIf you have any questions, just reply to this email.";
                        wp_mail($buyer_email, 'Your receipt' . ($product_name ? ': ' . $product_name : ''), $body);
                    }
                }
            } catch (Exception $e) {
                // Non-fatal; log and continue
                error_log('VMO Stripe webhook enrichment failed: ' . $e->getMessage());
                $buyer_email = is_string($session->customer_details->email ?? '') ? sanitize_email($session->customer_details->email) : '';
                $buyer_name = is_string($session->customer_details->name ?? '') ? sanitize_text_field($session->customer_details->name) : '';
                $amount_paid = isset($session->amount_total) ? (int) $session->amount_total : 0;
                $currency = strtoupper(sanitize_text_field((string) ($session->currency ?? 'usd')));
                $meta = (array) ($session->metadata ?? array());
                $product_name = sanitize_text_field((string) ($meta['product_name'] ?? 'Product'));
                $order_row = $wpdb->get_row($wpdb->prepare("SELECT * FROM $orders WHERE stripe_checkout_session_id = %s", $session_id), ARRAY_A);
                if ($order_row && $product_name === 'Product') {
                    $order_meta = json_decode($order_row['metadata'] ?: '{}', true);
                    if (is_array($order_meta) && !empty($order_meta['product_name'])) {
                        $product_name = sanitize_text_field((string) $order_meta['product_name']);
                    }
                }

                if ($buyer_email) {
                    $body = 'Thank you for your purchase' . ($product_name ? ' of ' . $product_name : '') . ".\n";
                    $body .= 'Amount: ' . $currency . ' ' . number_format($amount_paid / 100, 2) . "\n";
                    $body .= "Your payment was successful. If you have any questions, just reply to this email.";
                    wp_mail($buyer_email, 'Your order confirmation' . ($product_name ? ': ' . $product_name : ''), $body);
                }

                $merchant_user_id = (int) ($order_row['user_id'] ?? 0);
                if ($merchant_user_id) {
                    $merchant = get_userdata($merchant_user_id);
                    $merchant_email = $merchant && !empty($merchant->user_email) ? $merchant->user_email : '';
                    if ($merchant_email) {
                        $owner_body = 'A new order was placed.' . "\n\n";
                        $owner_body .= 'Product: ' . $product_name . "\n";
                        if ($buyer_name || $buyer_email) {
                            if (function_exists('vmo_business_flow_guidance_for_order_meta') && !empty($order_row)) {
                                $order_meta = json_decode($order_row['metadata'] ?: '{}', true);
                                if (is_array($order_meta)) {
                                    $owner_body .= 'Next action: ' . vmo_business_flow_guidance_for_order_meta($order_meta) . "\n";
                                }
                            }
                            $owner_body .= 'Customer: ' . trim($buyer_name . ' ' . '<' . $buyer_email . '>') . "\n";
                        }
                        $owner_body .= 'Amount: ' . $currency . ' ' . number_format($amount_paid / 100, 2) . "\n";
                        wp_mail($merchant_email, 'New Order: ' . $product_name, $owner_body);
                    }
                }

                if (!empty($order_row) && function_exists('vmo_business_flow_sync_paid_order')) {
                    vmo_business_flow_sync_paid_order((int) $order_row['id'], [
                        'session_id' => $session_id,
                        'buyer_email' => $buyer_email,
                        'buyer_name' => $buyer_name,
                        'customer_email' => $buyer_email,
                        'customer_name' => $buyer_name,
                        'amount_total' => $amount_paid,
                        'currency' => strtolower($currency),
                        'stripe_payment_intent_id' => isset($session->payment_intent) ? (string) $session->payment_intent : '',
                    ]);
                }
            }

            // Mark event as processed for idempotency (after successful handling)
            if (!empty($dup_key)) {
                set_transient($dup_key, 1, 2 * DAY_IN_SECONDS);
            }
        }
    } elseif ($event->type === 'invoice.payment_succeeded') {
        // Recurring subscription invoice paid; record as order for analytics and update subscription period end
        global $wpdb;
        $invoice = $event->data->object; // \Stripe\Invoice
        $account_id = isset($event->account) ? sanitize_text_field($event->account) : '';
        if (empty($account_id) && function_exists('vmo_master_record_invoice_payment')) {
            $customer_id = (string) ($invoice->customer ?? '');
            $billing_reason = (string) ($invoice->billing_reason ?? '');
            if ($customer_id !== '') {
                $platform_user_id = (int) $wpdb->get_var($wpdb->prepare(
                    "SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = 'vmo_stripe_customer_id' AND meta_value = %s LIMIT 1",
                    $customer_id
                ));
                if ($platform_user_id > 0) {
                    if ($billing_reason === 'subscription_create') {
                        if (!empty($dup_key)) {
                            set_transient($dup_key, 1, 2 * DAY_IN_SECONDS);
                        }
                        return new WP_REST_Response(['status' => 'acknowledged', 'type' => 'platform_initial_invoice'], 200);
                    }
                    vmo_master_record_invoice_payment($platform_user_id, $invoice, [
                        'source' => 'platform_invoice_webhook',
                        'billing_reason' => $billing_reason,
                    ]);
                    if (!empty($dup_key)) {
                        set_transient($dup_key, 1, 2 * DAY_IN_SECONDS);
                    }
                    return new WP_REST_Response(['status' => 'processed', 'type' => 'platform_invoice'], 200);
                }
            }
        }
        $subs_table = $wpdb->prefix . 'vmo_store_subscriptions';
        $orders = $wpdb->prefix . 'vmo_store_orders';
        $sub_id = (string)($invoice->subscription ?? '');
        if ($sub_id) {
            // Lookup merchant by subscription row
            $sub_row = $wpdb->get_row($wpdb->prepare("SELECT * FROM $subs_table WHERE stripe_subscription_id=%s", $sub_id), ARRAY_A);
            $merchant_user_id = (int)($sub_row['user_id'] ?? 0);
            if ($merchant_user_id) {
                // Insert an order row if not already present for this invoice
                $invoice_id = (string)($invoice->id ?? '');
                $exists = $wpdb->get_var($wpdb->prepare("SELECT id FROM $orders WHERE order_reference=%s", $invoice_id));
                if (!$exists) {
                    $amount_paid = (int)($invoice->amount_paid ?? 0);
                    $currency = (string)($invoice->currency ?? 'usd');
                    $platform_fee = 0; $receipt_url = ''; $stripe_fee_cents = 0; $net_to_merchant_cents = null; $tax_cents = 0;
                    try {
                        \Stripe\Stripe::setApiKey(get_option('vmo_stripe_secret_key'));
                        $inv = \Stripe\Invoice::retrieve($invoice_id, $account_id ? [ 'stripe_account' => $account_id ] : []);
                        $receipt_url = (string)($inv->hosted_invoice_url ?? '');
                        // Application fee percent: fall back to platform setting if not present
                        $app_fee_percent = 0;
                        if (!empty($inv->subscription_details) && !empty($inv->subscription_details->metadata)) {
                            $app_fee_percent = (float)($inv->subscription_details->metadata->application_fee_percent ?? 0);
                        }
                        if ($app_fee_percent <= 0) {
                            $platform_fee_bps = (int) get_option('vmo_stripe_platform_fee_bps', 50);
                            $app_fee_percent = max(0, min(100, $platform_fee_bps / 100));
                        }
                        $platform_fee = (int) round($amount_paid * ($app_fee_percent / 100));
                        // Stripe fee and net via Balance Transaction on charge associated with invoice
                        if (!empty($inv->charge)) {
                            try {
                                $charge = \Stripe\Charge::retrieve($inv->charge, $account_id ? [ 'stripe_account' => $account_id ] : []);
                                $bt_id = $charge->balance_transaction ?? null;
                                if ($bt_id) {
                                    $bt = \Stripe\BalanceTransaction::retrieve($bt_id, $account_id ? [ 'stripe_account' => $account_id ] : []);
                                    $stripe_fee_cents = isset($bt->fee) ? (int)$bt->fee : 0;
                                    $net_to_merchant_cents = isset($bt->net) ? (int)$bt->net : null;
                                }
                            } catch (Exception $e) { /* ignore */ }
                        }
                        // Tax amount on invoice if present
                        if (!empty($inv->total_tax_amounts) && isset($inv->total_tax_amounts[0])) {
                            $tax_cents = (int)($inv->total_tax_amounts[0]->amount ?? 0);
                        }
                    } catch (Exception $e) { /* ignore */ }

                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                    $wpdb->insert($orders, [
                        'user_id' => $merchant_user_id,
                        'order_reference' => $invoice_id,
                        'stripe_checkout_session_id' => $invoice_id,
                        'amount_total' => $amount_paid,
                        'currency' => $currency,
                        'platform_fee_amount' => $platform_fee,
                        'status' => 'paid',
                        'receipt_url' => $receipt_url,
                        'metadata' => wp_json_encode([
                            'type' => 'subscription_invoice',
                            'subscription_id' => $sub_id,
                            'stripe_fee_cents' => $stripe_fee_cents,
                            'platform_fee_cents' => $platform_fee,
                            'net_to_merchant_cents' => ($net_to_merchant_cents !== null) ? $net_to_merchant_cents : max(0, $amount_paid - $stripe_fee_cents - $platform_fee),
                            'tax_cents' => $tax_cents
                        ])
                    ]);
                    $subscription_order_id = (int) $wpdb->insert_id;
                    if ($subscription_order_id > 0 && function_exists('vmo_business_flow_sync_paid_order')) {
                        vmo_business_flow_sync_paid_order($subscription_order_id, [
                            'session_id' => $invoice_id,
                            'amount_total' => $amount_paid,
                            'currency' => $currency,
                            'customer_email' => (string) ($invoice->customer_email ?? ''),
                        ]);
                    }
                }
                // Update subscription period end and status
                $status = (string)($invoice->status ?? 'paid');
                $period_end = null;
                if (!empty($invoice->lines) && !empty($invoice->lines->data[0])) {
                    $period = $invoice->lines->data[0]->period ?? null;
                    if ($period && !empty($period->end)) $period_end = date('Y-m-d H:i:s', (int)$period->end);
                }
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $wpdb->update($subs_table, [
                    'status' => ($status === 'paid' ? 'active' : $status),
                    'current_period_end' => $period_end,
                    'updated_at' => current_time('mysql')
                ], [ 'id' => (int)($sub_row['id'] ?? 0) ]);
            }
        }
        if (!empty($dup_key)) set_transient($dup_key, 1, 2 * DAY_IN_SECONDS);
    } elseif ($event->type === 'charge.refunded') {
        global $wpdb;
        $charge = $event->data->object;
        $charge_id = sanitize_text_field((string) ($charge->id ?? ''));
        $payment_intent_id = sanitize_text_field((string) ($charge->payment_intent ?? ''));
        $amount_refunded = (int) ($charge->amount_refunded ?? 0);
        $currency = strtoupper(sanitize_text_field((string) ($charge->currency ?? 'usd')));
        $buyer_email = sanitize_email((string) ($charge->billing_details->email ?? ''));
        $reason = sanitize_text_field((string) ($charge->refunds->data[0]->reason ?? 'requested'));
        $orders = $wpdb->prefix . 'vmo_store_orders';
        $purchases = $wpdb->prefix . 'vmo_contact_purchases';
        $now = current_time('mysql');

        $order_row = null;
        if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $wpdb->esc_like($orders)))) {
            $order_row = $wpdb->get_row($wpdb->prepare(
                "SELECT * FROM {$orders} WHERE stripe_charge_id = %s OR stripe_payment_intent_id = %s ORDER BY id DESC LIMIT 1",
                $charge_id,
                $payment_intent_id
            ), ARRAY_A);
            if ($order_row) {
                $order_meta = json_decode($order_row['metadata'] ?: '{}', true);
                if (!is_array($order_meta)) {
                    $order_meta = array();
                }
                $order_meta['refund_amount_cents'] = $amount_refunded;
                $order_meta['refund_reason'] = $reason;
                $order_meta['refunded_at'] = $now;
                $wpdb->update($orders, array(
                    'status' => 'refunded',
                    'metadata' => wp_json_encode($order_meta),
                    'updated_at' => $now,
                ), array('id' => (int) $order_row['id']));
            }
        }

        if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $wpdb->esc_like($purchases)))) {
            $wpdb->query($wpdb->prepare(
                "UPDATE {$purchases} SET status = 'refunded', updated_at = %s WHERE transaction_id IN (%s, %s)",
                $now,
                $charge_id,
                $payment_intent_id
            ));
        }

        $amount_formatted = number_format($amount_refunded / 100, 2);
        if ($buyer_email) {
            $body = "Your refund has been processed.\n\n";
            $body .= 'Refund amount: ' . $currency . ' ' . $amount_formatted . "\n";
            $body .= 'Transaction: ' . ($payment_intent_id ?: $charge_id) . "\n";
            $body .= 'Reason: ' . $reason . "\n\n";
            $body .= 'The refund will appear in your account according to your bank or card issuer timing.';
            wp_mail($buyer_email, 'Refund processed', $body);
        }

        $merchant_user_id = (int) ($order_row['user_id'] ?? 0);
        if ($merchant_user_id) {
            $merchant = get_userdata($merchant_user_id);
            $merchant_email = $merchant && !empty($merchant->user_email) ? $merchant->user_email : '';
            if ($merchant_email) {
                $body = "A refund was processed for one of your VMO Pay orders.\n\n";
                $body .= 'Refund amount: ' . $currency . ' ' . $amount_formatted . "\n";
                $body .= 'Transaction: ' . ($payment_intent_id ?: $charge_id) . "\n";
                $body .= 'Reason: ' . $reason . "\n";
                wp_mail($merchant_email, 'VMO Pay refund processed', $body);
            }
        }

        if (!empty($dup_key)) {
            set_transient($dup_key, 1, 2 * DAY_IN_SECONDS);
        }
    } elseif ($event->type === 'customer.subscription.updated' || $event->type === 'customer.subscription.deleted') {
        global $wpdb;
        $sub = $event->data->object; // \Stripe\Subscription
        $subs_table = $wpdb->prefix . 'vmo_store_subscriptions';
        $status = (string)($sub->status ?? 'active');
        $period_end = !empty($sub->current_period_end) ? date('Y-m-d H:i:s', (int)$sub->current_period_end) : null;
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->update($subs_table, [
            'status' => $status,
            'current_period_end' => $period_end,
            'updated_at' => current_time('mysql')
        ], [ 'stripe_subscription_id' => (string)$sub->id ]);
        if (!empty($dup_key)) set_transient($dup_key, 1, 2 * DAY_IN_SECONDS);
    } elseif ($event->type === 'account.updated') {
        // Sync connected account status
        $acct = $event->data->object; // \Stripe\Account
        if (!isset($acct->id)) {
            return new WP_REST_Response(['status' => 'ignored'], 200);
        }
        if (class_exists('VMO_Master_Admin')) {
            VMO_Master_Admin::instance()->sync_affiliate_payout_account($acct);
        }
        global $wpdb; $table = $wpdb->prefix . 'vmo_connected_accounts';
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->update($table, [
            'details_submitted' => isset($acct->details_submitted) ? intval($acct->details_submitted) : 0,
            'payouts_enabled'   => isset($acct->payouts_enabled) ? intval($acct->payouts_enabled) : 0,
            'charges_enabled'   => isset($acct->charges_enabled) ? intval($acct->charges_enabled) : 0,
            'capabilities'      => isset($acct->capabilities) ? wp_json_encode($acct->capabilities) : null,
            'updated_at'        => current_time('mysql')
        ], [ 'stripe_account_id' => (string) $acct->id ]);
        if (!empty($dup_key)) {
            set_transient($dup_key, 1, 2 * DAY_IN_SECONDS);
        }
    }

    return new WP_REST_Response(['status' => 'ok'], 200);
}
