<?php
// phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange
/**
 * Yatoon Growth Storefront - customer-facing self-service purchase page for
 * packages and memberships. Renders the [yatoon_growth_storefront] shortcode
 * and exposes two AJAX endpoints:
 *
 *   yatoon_growth_create_purchase  - server-side validation + price calc;
 *                                    creates a pending row and (if Stripe is
 *                                    configured) a PaymentIntent.
 *   yatoon_growth_confirm_purchase - called by the browser after Stripe.js
 *                                    confirms the card; verifies the intent
 *                                    server-side and activates the row.
 *
 * If Stripe is NOT configured, the storefront falls back to "request mode":
 * the row is saved as status='pending' and an admin notification is sent so
 * the salon can collect payment in person and grant the package/membership
 * from the Growth Modules admin page.
 *
 * Activated rows are also activated by the Stripe webhook (via
 * activate_purchase_by_intent) for redundancy if the browser drops the
 * confirm AJAX request.
 */

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

class YATOON_Growth_Storefront {

    public static function init() {
        add_action( 'init', array( __CLASS__, 'maybe_create_columns' ), 6 );
        add_shortcode( 'yatoon_growth_storefront', array( __CLASS__, 'render_shortcode' ) );
        add_action( 'wp_ajax_yatoon_growth_create_purchase',  array( __CLASS__, 'ajax_create_purchase' ) );
        add_action( 'wp_ajax_nopriv_yatoon_growth_create_purchase',  array( __CLASS__, 'ajax_create_purchase' ) );
        add_action( 'wp_ajax_yatoon_growth_confirm_purchase', array( __CLASS__, 'ajax_confirm_purchase' ) );
        add_action( 'wp_ajax_nopriv_yatoon_growth_confirm_purchase', array( __CLASS__, 'ajax_confirm_purchase' ) );
    }

    /**
     * Add columns we need for purchase tracking. Idempotent - safe to run on
     * every request. We avoid dbDelta here because it cannot rename existing
     * columns and we only need to add new ones.
     */
    public static function maybe_create_columns() {
        global $wpdb;
        $installed_schema_version = (string) get_option( 'yatoon_growth_storefront_schema_version', '0' );
        if ( !yatoon_should_run_schema_migration( $installed_schema_version, YATOON_DB_VERSION ) ) {
            return;
        }

        $tables = array( 'yatoon_customer_packages', 'yatoon_customer_memberships' );
        $schema_complete = true;
        foreach ( $tables as $tbl ) {
            $full = $wpdb->prefix . sanitize_key( $tbl );
            $safe_full = esc_sql( $full );
            $exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $full ) );
            if ( ! $exists ) { $schema_complete = false; continue;
            }
            $cols = $wpdb->get_col( "SHOW COLUMNS FROM `{$safe_full}`", 0 ); // phpcs:ignore
            if ( ! in_array( 'stripe_intent_id', (array) $cols, true ) ) {
                $wpdb->query( "ALTER TABLE `{$safe_full}` ADD COLUMN stripe_intent_id varchar(100) DEFAULT NULL AFTER status" );
                $wpdb->query( "ALTER TABLE `{$safe_full}` ADD INDEX stripe_intent_id (stripe_intent_id)" );
            }
            if ( ! in_array( 'source', (array) $cols, true ) ) {
                $wpdb->query( "ALTER TABLE `{$safe_full}` ADD COLUMN source varchar(30) DEFAULT 'admin' AFTER stripe_intent_id" );
            }
            $cols = $wpdb->get_col( "SHOW COLUMNS FROM `{$safe_full}`", 0 ); // phpcs:ignore
            $has_intent_index = (bool) $wpdb->get_var( "SHOW INDEX FROM `{$safe_full}` WHERE Key_name = 'stripe_intent_id'" ); // phpcs:ignore
            if ( ! in_array( 'stripe_intent_id', (array) $cols, true ) || ! in_array( 'source', (array) $cols, true ) || ! $has_intent_index ) {
                $schema_complete = false;
            }
        }
        if ( $schema_complete ) {
            update_option( 'yatoon_growth_storefront_schema_version', YATOON_DB_VERSION, false );
        } else {
            yatoon_safe_log( __( 'Yatoon: growth storefront schema remains incomplete and will be retried.', 'yatoon-booking-system' ) );
        }
    }

    /* ---------------------------------------------------------------------
       Shortcode
       --------------------------------------------------------------------- */

    public static function render_shortcode( $atts = array() ) {
        if ( ! class_exists( 'YATOON_License' ) || ! YATOON_License::is_pro() ) {
            return '<div class="yatoon-storefront-empty"><p>' . esc_html__( 'Package and membership storefronts require an active Yatoon Pro license.', 'yatoon-booking-system' ) . '</p></div>';
        }

        $atts = shortcode_atts( array(
            'show'          => 'both', // both | packages | memberships
            'package_id'    => 0,
            'membership_id' => 0,
            'title'         => '',
        ), $atts, 'yatoon_growth_storefront' );
        $atts['package_id']    = absint( $atts['package_id'] );
        $atts['membership_id'] = absint( $atts['membership_id'] );
        $atts['title']         = $atts['title'] !== '' ? $atts['title'] : __( 'Buy a Package or Membership', 'yatoon-booking-system' );

        global $wpdb;
        $packages    = ( $atts['show'] !== 'memberships' && YATOON_Growth_Modules::is_enabled( 'packages' ) )
            ? $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}yatoon_packages WHERE is_active = 1 ORDER BY price ASC, id ASC" )
            : array();
        $memberships = ( $atts['show'] !== 'packages' && YATOON_Growth_Modules::is_enabled( 'memberships' ) )
            ? $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}yatoon_memberships WHERE is_active = 1 ORDER BY price ASC, id ASC" )
            : array();
        if ( $atts['package_id'] ) {
            $packages = array_values( array_filter( $packages, static function ( $package ) use ( $atts ) {
 return (int) $package->id === $atts['package_id'];
} ) );
        }
        if ( $atts['membership_id'] ) {
            $memberships = array_values( array_filter( $memberships, static function ( $membership ) use ( $atts ) {
 return (int) $membership->id === $atts['membership_id'];
} ) );
        }

        if ( empty( $packages ) && empty( $memberships ) ) {
            return '<div class="yatoon-storefront-empty"><p>' . esc_html__( 'No packages or memberships are available right now. Please check back later.', 'yatoon-booking-system' ) . '</p></div>';
        }

        $stripe_ready = class_exists( 'Yatoon_Stripe' ) && Yatoon_Stripe::is_configured();
        // Existing settings UI stores the publishable key as
        // yatoon_deposit_stripe_key (see admin/views/settings.php).
        $publishable  = trim( get_option( 'yatoon_deposit_stripe_key', '' ) );
        if ( $stripe_ready && empty( $publishable ) ) {
            // Stripe secret is set but publishable key is missing - fall back
            // to request mode rather than rendering a broken card form.
            $stripe_ready = false;
        }

        ob_start();
        // The storefront brings its own stylesheet and script so it works on
        // any page without depending on the booking form being loaded too.
        // Both are real files rather than inline blocks: this shortcode can
        // appear on a page a customer visits repeatedly, and inline assets are
        // re-sent in full every single time.
        $storefront_ver = defined( 'SBS_VERSION' ) ? SBS_VERSION : false;
        $storefront_rtl = ( function_exists( 'is_rtl' ) && is_rtl() ) ? '-rtl' : '';
        wp_enqueue_style(
            'yatoon-storefront',
            SBS_PLUGIN_URL . 'assets/css/yatoon-storefront' . YATOON_ASSET_SUFFIX . $storefront_rtl . '.css',
            array(),
            $storefront_ver
        );
        if ( $stripe_ready ) {
            wp_enqueue_script( 'yatoon-stripe-js', 'https://js.stripe.com/v3/', array(), '3', true );
        }
        // In the footer, and after Stripe: the script binds against markup this
        // shortcode has already printed.
        wp_enqueue_script(
            'yatoon-storefront',
            SBS_PLUGIN_URL . 'assets/js/yatoon-storefront' . YATOON_ASSET_SUFFIX . '.js',
            $stripe_ready ? array( 'yatoon-stripe-js' ) : array(),
            $storefront_ver,
            true
        );

        $nonce = wp_create_nonce( 'yatoon_storefront_nonce' );
        ?>
        <div class="yatoon-storefront" data-stripe="<?php echo esc_attr( $stripe_ready ? '1' : '0' ); ?>" data-pk="<?php echo esc_attr( $publishable ); ?>" data-nonce="<?php echo esc_attr( $nonce ); ?>" data-ajax="<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>" data-currency="<?php echo esc_attr( strtolower( (string) get_option( 'yatoon_currency', 'USD' ) ) ); ?>">
            <h2 class="yatoon-storefront-title"><?php echo esc_html( $atts['title'] ); ?></h2>
            <?php if ( ! empty( $packages ) ) : ?>
                <h3><?php esc_html_e( 'Packages', 'yatoon-booking-system' ); ?></h3>
                <div class="yatoon-storefront-grid">
                    <?php foreach ( $packages as $p ) : ?>
                        <div class="yatoon-storefront-card" data-type="package" data-id="<?php echo esc_attr( intval( $p->id ) ); ?>" data-amount="<?php echo esc_attr( self::stripe_minor_units( $p->price ) ); ?>">
                            <h4><?php echo esc_html( $p->name ); ?></h4>
                            <?php if ( $p->description ) : ?><p class="ys-desc"><?php echo esc_html( $p->description ); ?></p><?php endif; ?>
                            <p class="ys-meta">
                                <strong><?php echo esc_html( number_format( floatval( $p->price ), 2 ) ); ?></strong>
                                &mdash; <?php
                                /* translators: %d: number of included package visits. */
                                echo esc_html( sprintf( _n( '%d visit', '%d visits', intval( $p->included_visits ), 'yatoon-booking-system' ), intval( $p->included_visits ) ) );
                                ?>,
                                <?php
                                /* translators: %d: number of days the package remains valid. */
                                echo esc_html( sprintf( _n( 'valid %d day', 'valid %d days', intval( $p->validity_days ), 'yatoon-booking-system' ), intval( $p->validity_days ) ) );
                                ?>
                            </p>
                            <button type="button" class="ys-buy-btn"><?php esc_html_e( 'Buy Now', 'yatoon-booking-system' ); ?></button>
                        </div>
                    <?php endforeach; ?>
                </div>
            <?php endif; ?>

            <?php if ( ! empty( $memberships ) ) : ?>
                <h3><?php esc_html_e( 'Memberships', 'yatoon-booking-system' ); ?></h3>
                <div class="yatoon-storefront-grid">
                    <?php foreach ( $memberships as $m ) : ?>
                        <div class="yatoon-storefront-card" data-type="membership" data-id="<?php echo esc_attr( intval( $m->id ) ); ?>" data-amount="<?php echo esc_attr( self::stripe_minor_units( $m->price ) ); ?>">
                            <h4><?php echo esc_html( $m->name ); ?></h4>
                            <?php if ( $m->description ) : ?><p class="ys-desc"><?php echo esc_html( $m->description ); ?></p><?php endif; ?>
                            <p class="ys-meta">
                                <strong>$<?php echo esc_html( number_format( floatval( $m->price ), 2 ) ); ?></strong>
                                / <?php echo esc_html( $m->billing_period === 'yearly' ? __( 'year', 'yatoon-booking-system' ) : __( 'month', 'yatoon-booking-system' ) ); ?>
                                <?php if ( floatval( $m->booking_discount_percent ) > 0 ) : ?>
                                    &mdash; <?php
                                    /* translators: %s: membership booking discount percentage. */
                                    echo esc_html( sprintf( __( '%s%% off every booking', 'yatoon-booking-system' ), rtrim( rtrim( number_format( floatval( $m->booking_discount_percent ), 2 ), '0' ), '.' ) ) );
                                    ?>
                                <?php endif; ?>
                            </p>
                            <button type="button" class="ys-buy-btn"><?php esc_html_e( 'Join Now', 'yatoon-booking-system' ); ?></button>
                        </div>
                    <?php endforeach; ?>
                </div>
            <?php endif; ?>

            <div class="yatoon-storefront-modal" hidden>
                <div class="ys-modal-inner">
                    <button type="button" class="ys-modal-close" aria-label="Close">&times;</button>
                    <h3 class="ys-modal-title"></h3>
                    <p class="ys-modal-summary"></p>
                    <form class="ys-purchase-form">
                        <p><label><?php esc_html_e( 'Full name', 'yatoon-booking-system' ); ?> *<br><input type="text" name="customer_name" required></label></p>
                        <p><label><?php esc_html_e( 'Email', 'yatoon-booking-system' ); ?> *<br><input type="email" name="customer_email" required></label></p>
                        <p><label><?php esc_html_e( 'Phone', 'yatoon-booking-system' ); ?> *<br><input type="tel" name="customer_phone" required></label></p>
                        <?php if ( $stripe_ready ) : ?>
                            <div class="ys-card-wrap">
                                <label><?php esc_html_e( 'Card details', 'yatoon-booking-system' ); ?> *</label>
                                <div class="ys-payment-element"></div>
                                <div class="ys-card-error" role="alert"></div>
                            </div>
                        <?php else : ?>
                            <p class="ys-notice"><?php esc_html_e( 'Online payment is not configured yet. Submit your request and we will contact you to confirm and arrange payment in person.', 'yatoon-booking-system' ); ?></p>
                        <?php endif; ?>
                        <p class="ys-status" aria-live="polite"></p>
                        <p><button type="submit" class="ys-submit"><?php echo esc_html( $stripe_ready ? __( 'Pay & Activate', 'yatoon-booking-system' ) : __( 'Submit Request', 'yatoon-booking-system' ) ); ?></button></p>
                    </form>
                </div>
            </div>
        </div>


        <?php
        return ob_get_clean();
    }

    /* ---------------------------------------------------------------------
       AJAX
       --------------------------------------------------------------------- */

    public static function ajax_create_purchase() {
        check_ajax_referer( 'yatoon_storefront_nonce', 'nonce' );

        if ( ! class_exists( 'YATOON_License' ) || ! YATOON_License::is_pro() ) {
            wp_send_json_error( array( 'message' => __( 'Storefront purchases require an active Yatoon Pro license.', 'yatoon-booking-system' ) ) );
        }

        $item_type = isset( $_POST['item_type'] ) ? sanitize_key( wp_unslash( $_POST['item_type'] ) ) : '';
        $item_id   = isset( $_POST['item_id'] )   ? absint( $_POST['item_id'] )                       : 0;
        $name      = isset( $_POST['customer_name'] )  ? sanitize_text_field( wp_unslash( $_POST['customer_name'] ) )  : '';
        $email     = isset( $_POST['customer_email'] ) ? sanitize_email( wp_unslash( $_POST['customer_email'] ) )      : '';
        $phone     = isset( $_POST['customer_phone'] ) ? sanitize_text_field( wp_unslash( $_POST['customer_phone'] ) ) : '';

        if ( ! in_array( $item_type, array( 'package', 'membership' ), true ) ) {
            wp_send_json_error( array( 'message' => __( 'Invalid item.', 'yatoon-booking-system' ) ) );
        }
        if ( ! $item_id || $name === '' || ! is_email( $email ) || $phone === '' ) {
            wp_send_json_error( array( 'message' => __( 'Please fill in your name, a valid email and phone number.', 'yatoon-booking-system' ) ) );
        }

        // Light rate-limit: max 5 requests per IP per 10 min.
        $ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
        if ( $ip ) {
            $rk    = 'yatoon_sf_rate_' . md5( $ip );
            $count = (int) get_transient( $rk );
            if ( $count >= 5 ) {
                wp_send_json_error( array( 'message' => __( 'Too many attempts. Please wait a few minutes.', 'yatoon-booking-system' ) ) );
            }
            set_transient( $rk, $count + 1, 10 * MINUTE_IN_SECONDS );
        }

        global $wpdb;
        $module_enabled = ( $item_type === 'package' )
            ? YATOON_Growth_Modules::is_enabled( 'packages' )
            : YATOON_Growth_Modules::is_enabled( 'memberships' );
        if ( ! $module_enabled ) {
            wp_send_json_error( array( 'message' => __( 'This module is not currently available.', 'yatoon-booking-system' ) ) );
        }

        if ( $item_type === 'package' ) {
            $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}yatoon_packages WHERE id = %d AND is_active = 1", $item_id ) );
            if ( ! $row ) { wp_send_json_error( array( 'message' => __( 'Package is no longer available.', 'yatoon-booking-system' ) ) );
            }
            $price_cents = (int) round( floatval( $row->price ) * 100 );
            $description = sprintf( 'Package: %s', $row->name );
        } else {
            $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}yatoon_memberships WHERE id = %d AND is_active = 1", $item_id ) );
            if ( ! $row ) { wp_send_json_error( array( 'message' => __( 'Membership is no longer available.', 'yatoon-booking-system' ) ) );
            }
            $price_cents = (int) round( floatval( $row->price ) * 100 );
            $description = sprintf( 'Membership: %s', $row->name );
        }
        if ( $price_cents <= 0 ) {
            wp_send_json_error( array( 'message' => __( 'This item has no price set. Please contact us.', 'yatoon-booking-system' ) ) );
        }

        // Insert pending row first so we have a stable id for Stripe metadata.
        $purchase_id = self::insert_pending_row( $item_type, $row, $name, $email, $phone );
        if ( ! $purchase_id ) {
            wp_send_json_error( array( 'message' => __( 'Could not create purchase record. Please try again.', 'yatoon-booking-system' ) ) );
        }

        if ( class_exists( 'Yatoon_Stripe' ) && Yatoon_Stripe::is_configured() ) {
            $intent = Yatoon_Stripe::create_purchase_intent(
                $price_cents,
                $email,
                $description,
                array(
                    'yatoon_source' => 'growth_storefront',
                    'item_type'     => $item_type,
                    'item_id'       => $item_id,
                    'purchase_id'   => $purchase_id,
                )
            );
            if ( is_wp_error( $intent ) || empty( $intent['client_secret'] ) ) {
                self::delete_pending_row( $item_type, $purchase_id );
                wp_send_json_error( array( 'message' => is_wp_error( $intent ) ? $intent->get_error_message() : __( 'Payment provider error.', 'yatoon-booking-system' ) ) );
            }
            $table = ( $item_type === 'package' )
                ? $wpdb->prefix . 'yatoon_customer_packages'
                : $wpdb->prefix . 'yatoon_customer_memberships';
            $wpdb->update( $table, array( 'stripe_intent_id' => $intent['id'] ), array( 'id' => $purchase_id ), array( '%s' ), array( '%d' ) );
            wp_send_json_success( array(
                'purchase_id'   => $purchase_id,
                'client_secret' => $intent['client_secret'],
                'intent_id'     => $intent['id'],
                'mode'          => 'stripe',
            ) );
        }

        // Manual / request mode
        self::notify_admin_manual_request( $item_type, $row, $purchase_id, $name, $email, $phone );
        wp_send_json_success( array(
            'purchase_id' => $purchase_id,
            'mode'        => 'manual',
            'message'     => __( 'Request received. We will contact you shortly to confirm and arrange payment.', 'yatoon-booking-system' ),
        ) );
    }

    public static function ajax_confirm_purchase() {
        check_ajax_referer( 'yatoon_storefront_nonce', 'nonce' );
        if ( function_exists( 'yatoon_public_rate_limit' ) ) {
            yatoon_public_rate_limit( 'storefront_confirm', 12, 10 );
        }

        if ( ! class_exists( 'YATOON_License' ) || ! YATOON_License::is_pro() ) {
            wp_send_json_error( array( 'message' => __( 'Storefront purchases require an active Yatoon Pro license.', 'yatoon-booking-system' ) ) );
        }

        $intent_id   = isset( $_POST['intent_id'] )   ? sanitize_text_field( wp_unslash( $_POST['intent_id'] ) ) : '';
        $purchase_id = isset( $_POST['purchase_id'] ) ? absint( $_POST['purchase_id'] )                          : 0;
        if ( ! $intent_id || ! $purchase_id ) {
            wp_send_json_error( array( 'message' => __( 'Missing data.', 'yatoon-booking-system' ) ) );
        }

        $verify = class_exists( 'Yatoon_Stripe' ) ? Yatoon_Stripe::verify_payment( $intent_id ) : new WP_Error( 'no_stripe', __( 'Stripe unavailable.', 'yatoon-booking-system' ) );
        if ( is_wp_error( $verify ) ) {
            wp_send_json_error( array( 'message' => $verify->get_error_message() ) );
        }

        $activated = self::activate_purchase_by_intent( $intent_id );
        if ( ! $activated ) {
            wp_send_json_error( array( 'message' => __( 'Payment confirmed but activation failed. Please contact us.', 'yatoon-booking-system' ) ) );
        }
        wp_send_json_success( array( 'message' => __( 'Payment received. Your package/membership is now active!', 'yatoon-booking-system' ) ) );
    }

    /* ---------------------------------------------------------------------
       Helpers
       --------------------------------------------------------------------- */

    private static function insert_pending_row( $item_type, $row, $name, $email, $phone ) {
        global $wpdb;
        $today = current_time( 'Y-m-d' );

        if ( $item_type === 'package' ) {
            $expires = gmdate( 'Y-m-d', strtotime( $today . ' +' . max( 1, intval( $row->validity_days ) ) . ' days' ) );
            $ok = $wpdb->insert( $wpdb->prefix . 'yatoon_customer_packages', array(
                'package_id'       => intval( $row->id ),
                'customer_name'    => $name,
                'customer_email'   => $email,
                'customer_phone'   => $phone,
                'purchased_visits' => intval( $row->included_visits ),
                'remaining_visits' => intval( $row->included_visits ),
                'paid_amount'      => floatval( $row->price ),
                'starts_at'        => $today,
                'expires_at'       => $expires,
                'status'           => 'pending',
                'source'           => 'storefront',
                'notes'            => 'Purchased via storefront',
            ) );
        } else {
            $days    = ( $row->billing_period === 'yearly' ) ? 365 : 30;
            $expires = gmdate( 'Y-m-d', strtotime( $today . ' +' . $days . ' days' ) );
            $ok = $wpdb->insert( $wpdb->prefix . 'yatoon_customer_memberships', array(
                'membership_id'  => intval( $row->id ),
                'customer_name'  => $name,
                'customer_email' => $email,
                'customer_phone' => $phone,
                'paid_amount'    => floatval( $row->price ),
                'starts_at'      => $today,
                'expires_at'     => $expires,
                'status'         => 'pending',
                'source'         => 'storefront',
                'notes'          => 'Purchased via storefront',
            ) );
        }
        return $ok ? intval( $wpdb->insert_id ) : 0;
    }

    private static function delete_pending_row( $item_type, $purchase_id ) {
        global $wpdb;
        $tbl = ( $item_type === 'package' )
            ? $wpdb->prefix . 'yatoon_customer_packages'
            : $wpdb->prefix . 'yatoon_customer_memberships';
        $wpdb->delete( $tbl, array( 'id' => intval( $purchase_id ), 'status' => 'pending' ), array( '%d', '%s' ) );
    }

    /**
     * Activate a pending purchase row matched by Stripe intent id. Called both
     * by the customer-facing confirm AJAX and by the Stripe webhook so the
     * customer is provisioned even if the browser drops the AJAX after pay.
     *
     * Idempotent - calling twice on an already-active row is a no-op.
     */
    public static function activate_purchase_by_intent( $intent_id ) {
        if ( empty( $intent_id ) ) { return false;
        }
        global $wpdb;
        $tables = array(
            'package'    => $wpdb->prefix . 'yatoon_customer_packages',
            'membership' => $wpdb->prefix . 'yatoon_customer_memberships',
        );
        foreach ( $tables as $type => $tbl ) {
            $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$tbl} WHERE stripe_intent_id = %s LIMIT 1", $intent_id ) );
            if ( ! $row ) { continue;
            }
            if ( $row->status === 'active' ) { return true; // already activated
            }
            $wpdb->update( $tbl,
                array( 'status' => 'active', 'updated_at' => current_time( 'mysql' ) ),
                array( 'id' => intval( $row->id ) ),
                array( '%s', '%s' ),
                array( '%d' )
            );
            self::send_buyer_receipt( $type, $row );
            self::notify_admin_purchase( $type, $row );
            return true;
        }
        return false;
    }

    public static function mark_purchase_refunded_by_intent( $intent_id ) {
        if ( empty( $intent_id ) ) { return;
        }
        global $wpdb;
        foreach ( array( 'yatoon_customer_packages', 'yatoon_customer_memberships' ) as $tbl ) {
            $wpdb->update(
                $wpdb->prefix . $tbl,
                array( 'status' => 'refunded', 'updated_at' => current_time( 'mysql' ) ),
                array( 'stripe_intent_id' => $intent_id ),
                array( '%s', '%s' ),
                array( '%s' )
            );
        }
    }

    private static function send_buyer_receipt( $type, $row ) {
        if ( empty( $row->customer_email ) || ! is_email( $row->customer_email ) ) { return;
        }
        $business = get_option( 'yatoon_business_name', get_bloginfo( 'name' ) );
        /* translators: %s: business name. */
        $subject  = sprintf( __( '[%s] Purchase confirmation', 'yatoon-booking-system' ), $business );
        $label    = ( $type === 'package' ) ? __( 'package', 'yatoon-booking-system' ) : __( 'membership', 'yatoon-booking-system' );
        $body     = sprintf(
            /* translators: 1: customer name, 2: purchase type, 3: expiration date, 4: business name. */
            __( "Hi %1\$s,\n\nThank you for your %2\$s purchase. It is now active and will be applied to your bookings automatically when you book using this email or phone number.\n\nValid until: %3\$s\n\n- %4\$s", 'yatoon-booking-system' ),
            $row->customer_name ?: __( 'there', 'yatoon-booking-system' ),
            $label,
            $row->expires_at ?: __( 'open-ended', 'yatoon-booking-system' ),
            $business
        );
        wp_mail( $row->customer_email, $subject, $body );
    }

    private static function notify_admin_purchase( $type, $row ) {
        $admin_email = get_option( 'yatoon_notify_email' ) ?: get_option( 'admin_email' );
        if ( ! $admin_email || ! is_email( $admin_email ) ) { return;
        }
        /* translators: 1: purchase type, 2: customer name. */
        $subject = sprintf( __( '[Yatoon] New %1$s purchase: %2$s', 'yatoon-booking-system' ), $type, $row->customer_name );
        $body    = sprintf(
            "%s purchase activated.\n\nName : %s\nEmail: %s\nPhone: %s\nPaid : $%s\nValid: %s -> %s",
            ucfirst( $type ),
            $row->customer_name,
            $row->customer_email,
            $row->customer_phone,
            number_format( floatval( $row->paid_amount ), 2 ),
            $row->starts_at,
            $row->expires_at
        );
        wp_mail( $admin_email, $subject, $body );
    }

    private static function notify_admin_manual_request( $type, $item, $purchase_id, $name, $email, $phone ) {
        $admin_email = get_option( 'yatoon_notify_email' ) ?: get_option( 'admin_email' );
        if ( ! $admin_email || ! is_email( $admin_email ) ) { return;
        }
        /* translators: 1: purchase type, 2: customer name. */
        $subject = sprintf( __( '[Yatoon] %1$s purchase request from %2$s', 'yatoon-booking-system' ), ucfirst( $type ), $name );
        $url     = admin_url( 'admin.php?page=yatoon-growth-modules' );
        $body    = sprintf(
            "A customer requested to buy a %s online but Stripe is not configured.\nPlease contact them, collect payment, then activate the row in Growth Modules.\n\nItem : %s ($%s)\nName : %s\nEmail: %s\nPhone: %s\nID   : %d\n\nReview: %s",
            $type,
            $item->name,
            number_format( floatval( $item->price ), 2 ),
            $name,
            $email,
            $phone,
            $purchase_id,
            $url
        );
        wp_mail( $admin_email, $subject, $body );
    }

    /**
     * A price in Stripe's smallest currency unit.
     *
     * Payment Element needs the amount before the PaymentIntent exists
     * (deferred-intent mode), so the value has to be rendered into the markup.
     * Delegates to Yatoon_Stripe when available so zero-decimal currencies
     * (JPY, KRW) are not multiplied by 100.
     *
     * @since 4.4.3
     * @param float $price
     * @return int
     */
    private static function stripe_minor_units( $price ) {
        if ( class_exists( 'Yatoon_Stripe' ) && method_exists( 'Yatoon_Stripe', 'to_stripe_amount' ) ) {
            return (int) Yatoon_Stripe::to_stripe_amount( (float) $price );
        }
        return (int) round( (float) $price * 100 );
    }
}

YATOON_Growth_Storefront::init();
