' . esc_html__( 'Settings', '4a-loyalty' ) . '' ); } $links[] = '' . esc_html__( 'Upgrade to Pro', '4a-loyalty' ) . ''; return $links; } ); register_activation_hook( __FILE__, static function () { $defaults = array( 'enabled' => 'yes', 'theme' => 'clean', 'level_1_name' => 'Member', 'level_1_orders' => 5, 'level_1_discount' => 5, 'level_2_name' => 'Plus', 'level_2_orders' => 15, 'level_2_discount' => 7.5, 'exclude_sale' => 'yes', 'disable_with_coupons' => 'yes', 'excluded_roles' => "wholesale_customer\nwholesale\nb2b_customer", 'points_enabled' => 'yes', 'points_per_euro' => 1, 'point_value' => 0.01, 'min_redeem_points' => 100, 'voucher_expiry_days' => 365, ); if ( ! get_option( '4a_loyalty_settings' ) ) { add_option( '4a_loyalty_settings', $defaults ); } add_rewrite_endpoint( '4a-loyalty', EP_ROOT | EP_PAGES ); flush_rewrite_rules(); } ); register_deactivation_hook( __FILE__, static function () { flush_rewrite_rules(); } ); final class FourA_Loyalty_Free { private static ?self $instance = null; private array $settings = array(); private bool $coupon_sync_running = false; private const AUTO_COUPON = '4a-loyalty-member'; public static function instance(): self { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } private function __construct() { add_action( 'plugins_loaded', array( $this, 'boot' ) ); } public function boot(): void { if ( foura_loyalty_free_pro_is_active() ) { return; } if ( ! class_exists( 'WooCommerce' ) ) { add_action( 'admin_notices', array( $this, 'woocommerce_required_notice' ) ); return; } $this->settings = wp_parse_args( get_option( '4a_loyalty_settings', array() ), $this->defaults() ); add_action( 'init', array( $this, 'register_endpoint' ) ); add_filter( 'query_vars', array( $this, 'query_vars' ) ); add_filter( 'woocommerce_account_menu_items', array( $this, 'account_menu' ) ); add_action( 'woocommerce_account_4a-loyalty_endpoint', array( $this, 'render_account_page' ) ); add_shortcode( '4a_loyalty', array( $this, 'shortcode' ) ); add_shortcode( 'foura_loyalty', array( $this, 'shortcode' ) ); add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) ); add_action( 'admin_menu', array( $this, 'admin_menu' ), 60 ); add_action( 'admin_notices', array( $this, 'coupon_setting_notice' ) ); add_action( 'admin_post_4a_loyalty_save_settings', array( $this, 'save_settings' ) ); add_action( 'admin_post_4a_loyalty_redeem_points', array( $this, 'redeem_points' ) ); add_filter( 'woocommerce_get_shop_coupon_data', array( $this, 'dynamic_coupon_data' ), 10, 2 ); add_filter( 'woocommerce_coupon_is_valid', array( $this, 'validate_auto_coupon' ), 10, 3 ); add_filter( 'woocommerce_cart_totals_coupon_label', array( $this, 'coupon_label' ), 10, 2 ); add_action( 'woocommerce_before_calculate_totals', array( $this, 'sync_auto_coupon' ), 5 ); add_action( 'woocommerce_cart_loaded_from_session', array( $this, 'sync_auto_coupon' ), 20 ); add_action( 'woocommerce_order_status_processing', array( $this, 'maybe_award_points' ) ); add_action( 'woocommerce_order_status_completed', array( $this, 'maybe_award_points' ) ); add_action( 'woocommerce_order_status_refunded', array( $this, 'maybe_reverse_points' ) ); add_action( 'woocommerce_order_status_cancelled', array( $this, 'maybe_reverse_points' ) ); add_action( 'woocommerce_admin_order_data_after_billing_address', array( $this, 'admin_order_loyalty_info' ) ); } private function defaults(): array { return array( 'enabled' => 'yes', 'theme' => 'clean', 'level_1_name' => 'Member', 'level_1_orders' => 5, 'level_1_discount' => 5, 'level_2_name' => 'Plus', 'level_2_orders' => 15, 'level_2_discount' => 7.5, 'exclude_sale' => 'yes', 'disable_with_coupons' => 'yes', 'excluded_roles' => "wholesale_customer\nwholesale\nb2b_customer", 'points_enabled' => 'yes', 'points_per_euro' => 1, 'point_value' => 0.01, 'min_redeem_points' => 100, 'voucher_expiry_days' => 365, ); } public function woocommerce_required_notice(): void { if ( ! current_user_can( 'activate_plugins' ) ) { return; } $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( ! $screen || 'plugins' !== $screen->id ) { return; } echo '

' . esc_html__( '4A Loyalty', '4a-loyalty' ) . ' ' . esc_html__( 'requires WooCommerce to be active.', '4a-loyalty' ) . '

'; } public function coupon_setting_notice(): void { if ( ! current_user_can( 'manage_woocommerce' ) || ! function_exists( 'wc_coupons_enabled' ) ) { return; } if ( 'yes' !== $this->settings['enabled'] || wc_coupons_enabled() ) { return; } $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( ! $screen || 'woocommerce_page_4a-loyalty' !== $screen->id ) { return; } echo '

' . esc_html__( '4A Loyalty:', '4a-loyalty' ) . ' ' . esc_html__( 'WooCommerce coupons are disabled. Enable coupons in WooCommerce settings so automatic member discounts and loyalty vouchers can work.', '4a-loyalty' ) . '

'; } public function register_endpoint(): void { add_rewrite_endpoint( '4a-loyalty', EP_ROOT | EP_PAGES ); } public function query_vars( array $vars ): array { $vars[] = '4a-loyalty'; return $vars; } public function account_menu( array $items ): array { if ( ! is_user_logged_in() || $this->is_excluded_user( get_current_user_id() ) ) { return $items; } $new = array(); foreach ( $items as $key => $label ) { $new[ $key ] = $label; if ( 'orders' === $key ) { $new['4a-loyalty'] = __( 'Loyalty', '4a-loyalty' ); } } if ( ! isset( $new['4a-loyalty'] ) ) { $new['4a-loyalty'] = __( 'Loyalty', '4a-loyalty' ); } return $new; } public function enqueue_assets(): void { if ( is_account_page() || has_shortcode( (string) get_post_field( 'post_content', get_queried_object_id() ), '4a_loyalty' ) || has_shortcode( (string) get_post_field( 'post_content', get_queried_object_id() ), 'foura_loyalty' ) ) { wp_enqueue_style( '4a-loyalty', FOURA_LOYALTY_URL . 'assets/loyalty.css', array(), FOURA_LOYALTY_VERSION ); } } public function shortcode(): string { if ( ! is_user_logged_in() ) { return '
' . esc_html__( 'Sign in to view your loyalty status.', '4a-loyalty' ) . '
'; } ob_start(); $this->render_loyalty_card( get_current_user_id() ); return (string) ob_get_clean(); } public function render_account_page(): void { if ( ! is_user_logged_in() ) { return; } $this->render_loyalty_card( get_current_user_id() ); } private function render_loyalty_card( int $user_id ): void { if ( $this->is_excluded_user( $user_id ) ) { echo '

' . esc_html__( 'Loyalty is not available for this account.', '4a-loyalty' ) . '

'; return; } $orders = $this->get_qualifying_order_count( $user_id ); $tier = $this->get_tier_for_user( $user_id ); $points = $this->get_points( $user_id ); $theme = sanitize_html_class( $this->settings['theme'] ); $next = $this->get_next_tier( $orders ); $percentage = (float) $tier['discount']; echo '
'; echo '
'; echo '
' . esc_html__( 'YOUR LOYALTY', '4a-loyalty' ) . '
'; echo '

' . esc_html( $tier['name'] ) . '

'; if ( $percentage > 0 ) { echo '

' . esc_html( $this->format_percent( $percentage ) ) . '% ' . esc_html__( 'member discount', '4a-loyalty' ) . '

'; } else { echo '

' . esc_html__( 'Your loyalty journey has started.', '4a-loyalty' ) . '

'; } echo '
'; echo '
' . esc_html( (string) $orders ) . '' . esc_html__( 'qualifying orders', '4a-loyalty' ) . '
'; if ( 'yes' === $this->settings['points_enabled'] ) { echo '
' . esc_html( (string) $points ) . '' . esc_html__( 'points', '4a-loyalty' ) . '
'; } echo '
'; if ( $next ) { $remaining = max( 0, (int) $next['orders'] - $orders ); $progress = min( 100, max( 0, ( $orders / max( 1, (int) $next['orders'] ) ) * 100 ) ); echo '
' . sprintf( /* translators: 1: Number of remaining qualifying orders, 2: Name of the next loyalty level. */ esc_html( _n( '%1$d more order to unlock %2$s.', '%1$d more orders to unlock %2$s.', $remaining, '4a-loyalty' ) ), esc_html( (string) $remaining ), esc_html( $next['name'] ) ) . '
'; echo ''; } else { echo '
' . esc_html__( 'You have reached the highest level.', '4a-loyalty' ) . '
'; } echo '
'; if ( 'yes' === $this->settings['points_enabled'] ) { $this->render_points_section( $user_id, $points ); } $this->render_vouchers( $user_id ); echo '
'; } private function render_points_section( int $user_id, int $points ): void { $min_points = max( 1, (int) $this->settings['min_redeem_points'] ); $value = (float) $this->settings['point_value']; echo '
'; echo '

' . esc_html__( 'Use your points', '4a-loyalty' ) . '

'; echo '

' . sprintf( /* translators: 1: Minimum number of loyalty points, 2: Formatted monetary value of those points. */ esc_html__( '%1$d points are worth %2$s.', '4a-loyalty' ), esc_html( $min_points ), wp_kses_post( wc_price( $min_points * $value ) ) ) . '

'; if ( isset( $_GET['4a-redeemed'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended echo ''; } if ( $points >= $min_points ) { $max_amount = $points * $value; echo '

' . esc_html__( 'Available value:', '4a-loyalty' ) . ' ' . wp_kses_post( wc_price( $max_amount ) ) . '

'; echo '
'; echo ''; wp_nonce_field( '4a_loyalty_redeem_points', '4a_loyalty_nonce' ); echo ''; echo ''; echo ''; echo '
'; } else { $needed = $min_points - $points; echo '

' . sprintf( /* translators: %d: Number of additional loyalty points required to create a voucher. */ esc_html( _n( 'Earn %d more point to create a voucher.', 'Earn %d more points to create a voucher.', $needed, '4a-loyalty' ) ), esc_html( (string) $needed ) ) . '

'; } echo '
'; } private function render_vouchers( int $user_id ): void { $ids = get_user_meta( $user_id, '_4a_loyalty_vouchers', true ); if ( ! is_array( $ids ) || empty( $ids ) ) { return; } $active = array(); foreach ( array_reverse( $ids ) as $coupon_id ) { $coupon = new WC_Coupon( (int) $coupon_id ); if ( ! $coupon->get_id() || $coupon->get_usage_count() >= 1 ) { continue; } $expires = $coupon->get_date_expires(); if ( $expires && $expires->getTimestamp() < time() ) { continue; } $active[] = $coupon; } if ( empty( $active ) ) { return; } echo '
'; echo '

' . esc_html__( 'Your vouchers', '4a-loyalty' ) . '

'; foreach ( $active as $coupon ) { echo '
'; echo '
' . esc_html__( 'Voucher', '4a-loyalty' ) . '' . esc_html( $coupon->get_code() ) . '
'; echo '
' . wp_kses_post( wc_price( (float) $coupon->get_amount() ) ) . '
'; echo '
'; } echo '
'; } public function redeem_points(): void { if ( ! is_user_logged_in() ) { wp_safe_redirect( wc_get_page_permalink( 'myaccount' ) ); exit; } if ( ! isset( $_POST['4a_loyalty_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['4a_loyalty_nonce'] ) ), '4a_loyalty_redeem_points' ) ) { wp_die( esc_html__( 'Security check failed.', '4a-loyalty' ) ); } $user_id = get_current_user_id(); if ( $this->is_excluded_user( $user_id ) || 'yes' !== $this->settings['points_enabled'] ) { wp_die( esc_html__( 'Points redemption is not available for this account.', '4a-loyalty' ) ); } $requested = isset( $_POST['points'] ) ? absint( $_POST['points'] ) : 0; $balance = $this->get_points( $user_id ); $minimum = max( 1, (int) $this->settings['min_redeem_points'] ); if ( $requested < $minimum || $requested > $balance ) { wc_add_notice( __( 'Please choose a valid number of points.', '4a-loyalty' ), 'error' ); wp_safe_redirect( wc_get_account_endpoint_url( '4a-loyalty' ) ); exit; } $amount = round( $requested * (float) $this->settings['point_value'], wc_get_price_decimals() ); if ( $amount <= 0 ) { wc_add_notice( __( 'Voucher value is too low.', '4a-loyalty' ), 'error' ); wp_safe_redirect( wc_get_account_endpoint_url( '4a-loyalty' ) ); exit; } $user = get_userdata( $user_id ); $coupon = new WC_Coupon(); $code = '4a-' . strtolower( wp_generate_password( 10, false, false ) ); $coupon->set_code( $code ); $coupon->set_discount_type( 'fixed_cart' ); $coupon->set_amount( $amount ); $coupon->set_individual_use( true ); $coupon->set_usage_limit( 1 ); $coupon->set_usage_limit_per_user( 1 ); $coupon->set_description( sprintf( '4A Loyalty voucher – %d points', $requested ) ); if ( $user && $user->user_email ) { $coupon->set_email_restrictions( array( $user->user_email ) ); } $expiry_days = max( 0, (int) $this->settings['voucher_expiry_days'] ); if ( $expiry_days > 0 ) { $coupon->set_date_expires( time() + ( DAY_IN_SECONDS * $expiry_days ) ); } $coupon_id = $coupon->save(); if ( ! $coupon_id ) { wc_add_notice( __( 'The voucher could not be created.', '4a-loyalty' ), 'error' ); wp_safe_redirect( wc_get_account_endpoint_url( '4a-loyalty' ) ); exit; } update_post_meta( $coupon_id, '_4a_loyalty_user_id', $user_id ); update_post_meta( $coupon_id, '_4a_loyalty_points_spent', $requested ); $this->set_points( $user_id, $balance - $requested ); $vouchers = get_user_meta( $user_id, '_4a_loyalty_vouchers', true ); $vouchers = is_array( $vouchers ) ? $vouchers : array(); $vouchers[] = $coupon_id; update_user_meta( $user_id, '_4a_loyalty_vouchers', array_values( array_unique( array_map( 'absint', $vouchers ) ) ) ); wp_safe_redirect( add_query_arg( '4a-redeemed', '1', wc_get_account_endpoint_url( '4a-loyalty' ) ) ); exit; } public function dynamic_coupon_data( $data, string $code ) { if ( self::AUTO_COUPON !== strtolower( $code ) ) { return $data; } if ( ! $this->auto_discount_available() ) { return false; } $tier = $this->get_tier_for_user( get_current_user_id() ); $discount = (float) $tier['discount']; if ( $discount <= 0 ) { return false; } return array( 'id' => 0, 'discount_type' => 'percent', 'amount' => $discount, 'individual_use' => false, 'product_ids' => array(), 'exclude_product_ids' => array(), 'usage_limit' => 0, 'usage_limit_per_user' => 0, 'limit_usage_to_x_items' => null, 'usage_count' => 0, 'date_expires' => null, 'free_shipping' => false, 'product_categories' => array(), 'exclude_product_categories' => array(), 'exclude_sale_items' => ( 'yes' === $this->settings['exclude_sale'] ), 'minimum_amount' => '', 'maximum_amount' => '', 'customer_email' => array(), ); } public function validate_auto_coupon( bool $valid, WC_Coupon $coupon, $discounts ): bool { if ( self::AUTO_COUPON !== strtolower( $coupon->get_code() ) ) { return $valid; } if ( ! $this->auto_discount_available() ) { return false; } if ( 'yes' === $this->settings['disable_with_coupons'] && function_exists( 'WC' ) && WC()->cart ) { $applied = array_map( 'strtolower', WC()->cart->get_applied_coupons() ); $other = array_values( array_diff( $applied, array( self::AUTO_COUPON ) ) ); if ( ! empty( $other ) ) { return false; } } return true; } public function coupon_label( string $label, WC_Coupon $coupon ): string { if ( self::AUTO_COUPON === strtolower( $coupon->get_code() ) ) { $tier = $this->get_tier_for_user( get_current_user_id() ); /* translators: %s: Loyalty discount percentage. */ return sprintf( __( '4A Loyalty – %s%%', '4a-loyalty' ), $this->format_percent( (float) $tier['discount'] ) ); } return $label; } public function sync_auto_coupon(): void { if ( $this->coupon_sync_running || ! function_exists( 'WC' ) || ! WC()->cart ) { return; } if ( function_exists( 'wc_coupons_enabled' ) && ! wc_coupons_enabled() ) { return; } $this->coupon_sync_running = true; $applied = array_map( 'strtolower', WC()->cart->get_applied_coupons() ); $has_own = in_array( self::AUTO_COUPON, $applied, true ); $other = array_values( array_diff( $applied, array( self::AUTO_COUPON ) ) ); $eligible = $this->auto_discount_available(); if ( 'yes' === $this->settings['disable_with_coupons'] && ! empty( $other ) ) { $eligible = false; } if ( $eligible && ! $has_own ) { WC()->cart->apply_coupon( self::AUTO_COUPON ); } elseif ( ! $eligible && $has_own ) { WC()->cart->remove_coupon( self::AUTO_COUPON ); } $this->coupon_sync_running = false; } private function auto_discount_available(): bool { if ( 'yes' !== $this->settings['enabled'] || ! is_user_logged_in() ) { return false; } $user_id = get_current_user_id(); if ( $this->is_excluded_user( $user_id ) ) { return false; } $tier = $this->get_tier_for_user( $user_id ); return (float) $tier['discount'] > 0; } public function maybe_award_points( int $order_id ): void { if ( 'yes' !== $this->settings['points_enabled'] ) { return; } $order = wc_get_order( $order_id ); if ( ! $order || 'yes' === $order->get_meta( '_4a_loyalty_points_awarded', true ) ) { return; } $user_id = (int) $order->get_user_id(); if ( ! $user_id || $this->is_excluded_user( $user_id ) ) { return; } $eligible_total = 0.0; foreach ( $order->get_items( 'line_item' ) as $item ) { $eligible_total += (float) $item->get_total(); } $points = (int) floor( $eligible_total * (float) $this->settings['points_per_euro'] ); if ( $points <= 0 ) { return; } $this->set_points( $user_id, $this->get_points( $user_id ) + $points ); $order->update_meta_data( '_4a_loyalty_points_awarded', 'yes' ); $order->update_meta_data( '_4a_loyalty_points_reversed', 'no' ); $order->update_meta_data( '_4a_loyalty_points_amount', $points ); $order->save(); } public function maybe_reverse_points( int $order_id ): void { $order = wc_get_order( $order_id ); if ( ! $order || 'yes' !== $order->get_meta( '_4a_loyalty_points_awarded', true ) || 'yes' === $order->get_meta( '_4a_loyalty_points_reversed', true ) ) { return; } $user_id = (int) $order->get_user_id(); $points = (int) $order->get_meta( '_4a_loyalty_points_amount', true ); if ( ! $user_id || $points <= 0 ) { return; } $this->set_points( $user_id, max( 0, $this->get_points( $user_id ) - $points ) ); $order->update_meta_data( '_4a_loyalty_points_awarded', 'no' ); $order->update_meta_data( '_4a_loyalty_points_reversed', 'yes' ); $order->save(); } private function get_points( int $user_id ): int { return max( 0, (int) get_user_meta( $user_id, '_4a_loyalty_points', true ) ); } private function set_points( int $user_id, int $points ): void { update_user_meta( $user_id, '_4a_loyalty_points', max( 0, $points ) ); } private function get_qualifying_order_count( int $user_id ): int { if ( ! $user_id ) { return 0; } $results = wc_get_orders( array( 'customer_id' => $user_id, 'status' => array( 'wc-processing', 'wc-completed' ), 'limit' => 1, 'paginate' => true, 'return' => 'ids', ) ); return is_object( $results ) && isset( $results->total ) ? absint( $results->total ) : 0; } private function get_tier_for_user( int $user_id ): array { $orders = $this->get_qualifying_order_count( $user_id ); $l1 = max( 0, (int) $this->settings['level_1_orders'] ); $l2 = max( $l1, (int) $this->settings['level_2_orders'] ); if ( $l2 > 0 && $orders >= $l2 ) { return array( 'name' => (string) $this->settings['level_2_name'], 'discount' => (float) $this->settings['level_2_discount'], 'orders' => $l2, ); } if ( $l1 > 0 && $orders >= $l1 ) { return array( 'name' => (string) $this->settings['level_1_name'], 'discount' => (float) $this->settings['level_1_discount'], 'orders' => $l1, ); } return array( 'name' => __( 'Starter', '4a-loyalty' ), 'discount' => 0, 'orders' => 0, ); } private function get_next_tier( int $orders ): ?array { $l1 = max( 0, (int) $this->settings['level_1_orders'] ); $l2 = max( $l1, (int) $this->settings['level_2_orders'] ); if ( $orders < $l1 ) { return array( 'orders' => $l1, 'name' => (string) $this->settings['level_1_name'] ); } if ( $orders < $l2 ) { return array( 'orders' => $l2, 'name' => (string) $this->settings['level_2_name'] ); } return null; } private function is_excluded_user( int $user_id ): bool { if ( ! $user_id ) { return true; } $user = get_userdata( $user_id ); if ( ! $user ) { return true; } $excluded = preg_split( '/[\r\n,]+/', (string) $this->settings['excluded_roles'] ); $excluded = array_filter( array_map( 'sanitize_key', (array) $excluded ) ); return (bool) array_intersect( $excluded, (array) $user->roles ); } private function format_percent( float $value ): string { return rtrim( rtrim( number_format( $value, 2, '.', '' ), '0' ), '.' ); } public function admin_menu(): void { add_submenu_page( 'woocommerce', __( '4A Loyalty', '4a-loyalty' ), __( '4A Loyalty', '4a-loyalty' ), 'manage_woocommerce', '4a-loyalty', array( $this, 'settings_page' ) ); } public function settings_page(): void { if ( ! current_user_can( 'manage_woocommerce' ) ) { return; } $s = wp_parse_args( get_option( '4a_loyalty_settings', array() ), $this->defaults() ); ?>

General

Levels & automatic discount

Points

isset( $_POST['enabled'] ) ? 'yes' : 'no', 'theme' => $theme, 'level_1_name' => sanitize_text_field( wp_unslash( $_POST['level_1_name'] ?? 'Member' ) ), 'level_1_orders' => max( 1, absint( wp_unslash( $_POST['level_1_orders'] ?? 5 ) ) ), 'level_1_discount' => min( 100, max( 0, (float) sanitize_text_field( wp_unslash( $_POST['level_1_discount'] ?? '5' ) ) ) ), 'level_2_name' => sanitize_text_field( wp_unslash( $_POST['level_2_name'] ?? 'Plus' ) ), 'level_2_orders' => max( 1, absint( wp_unslash( $_POST['level_2_orders'] ?? 15 ) ) ), 'level_2_discount' => min( 100, max( 0, (float) sanitize_text_field( wp_unslash( $_POST['level_2_discount'] ?? '7.5' ) ) ) ), 'exclude_sale' => isset( $_POST['exclude_sale'] ) ? 'yes' : 'no', 'disable_with_coupons' => isset( $_POST['disable_with_coupons'] ) ? 'yes' : 'no', 'excluded_roles' => sanitize_textarea_field( wp_unslash( $_POST['excluded_roles'] ?? '' ) ), 'points_enabled' => isset( $_POST['points_enabled'] ) ? 'yes' : 'no', 'points_per_euro' => max( 0, (float) sanitize_text_field( wp_unslash( $_POST['points_per_euro'] ?? '1' ) ) ), 'point_value' => max( 0, (float) sanitize_text_field( wp_unslash( $_POST['point_value'] ?? '0.01' ) ) ), 'min_redeem_points' => max( 1, absint( wp_unslash( $_POST['min_redeem_points'] ?? 100 ) ) ), 'voucher_expiry_days' => max( 0, absint( wp_unslash( $_POST['voucher_expiry_days'] ?? 365 ) ) ), ); if ( $settings['level_2_orders'] <= $settings['level_1_orders'] ) { $settings['level_2_orders'] = $settings['level_1_orders'] + 1; } update_option( '4a_loyalty_settings', $settings ); $this->settings = wp_parse_args( $settings, $this->defaults() ); wp_safe_redirect( add_query_arg( array( 'page' => '4a-loyalty', 'updated' => '1' ), admin_url( 'admin.php' ) ) ); exit; } public function admin_order_loyalty_info( WC_Order $order ): void { $points = (int) $order->get_meta( '_4a_loyalty_points_amount', true ); if ( $points > 0 ) { echo '

' . esc_html__( '4A Loyalty points:', '4a-loyalty' ) . ' ' . esc_html( (string) $points ) . '

'; } } } FourA_Loyalty_Free::instance();