diff --git a/modules/ppcp-agentic-commerce/services.php b/modules/ppcp-agentic-commerce/services.php index bdb7cc5ac..e2d22e259 100644 --- a/modules/ppcp-agentic-commerce/services.php +++ b/modules/ppcp-agentic-commerce/services.php @@ -29,8 +29,13 @@ use WooCommerce\PayPalCommerce\AgenticCommerce\Merchant\MerchantMetadataProvider; use WooCommerce\PayPalCommerce\AgenticCommerce\Registration\RegistrationService; use WooCommerce\PayPalCommerce\AgenticCommerce\Registration\RegistrationEligibility; +use WooCommerce\PayPalCommerce\AgenticCommerce\Inspector\InspectionFormHandler; +use WooCommerce\PayPalCommerce\AgenticCommerce\Inspector\InspectionStatusPage; use WooCommerce\PayPalCommerce\AgenticCommerce\Helper\AgenticCheckoutProcessor; use WooCommerce\PayPalCommerce\AgenticCommerce\Cart\PayPalCartToCartDataAdapter; +use WooCommerce\PayPalCommerce\AgenticCommerce\Inspector\InspectionSessionData; +use WooCommerce\PayPalCommerce\AgenticCommerce\Inspector\Page\RegistrationStatusSection; +use WooCommerce\PayPalCommerce\AgenticCommerce\Inspector\Page\CartSessionSection; return array( // Configuration. @@ -170,4 +175,35 @@ $c->get( 'agentic.registration.eligibility' ) ); }, + + // Inspector. + 'agentic.inspector.form_handler' => static function ( ContainerInterface $c ): InspectionFormHandler { + return new InspectionFormHandler( + $c->get( 'agentic.registration.handler' ), + $c->get( 'woocommerce.logger.woocommerce' ) + ); + }, + 'agentic.inspector.session_info' => static function ( ContainerInterface $c ): InspectionSessionData { + return new InspectionSessionData(); + }, + 'agentic.inspector.page.status' => static function ( ContainerInterface $c ): RegistrationStatusSection { + return new RegistrationStatusSection( + $c->get( 'agentic.registration.handler' ), + $c->get( 'agentic.registration.eligibility' ), + $c->get( 'agentic.auth.provider' ), + $c->get( 'settings.data.general' ) + ); + }, + 'agentic.inspector.page.session' => static function ( ContainerInterface $c ): CartSessionSection { + return new CartSessionSection( + $c->get( 'agentic.inspector.session_info' ), + ); + }, + 'agentic.inspector.page' => static function ( ContainerInterface $c ): InspectionStatusPage { + return new InspectionStatusPage( + $c->get( 'agentic.inspector.form_handler' ), + $c->get( 'agentic.inspector.page.status' ), + $c->get( 'agentic.inspector.page.session' ) + ); + }, ); diff --git a/modules/ppcp-agentic-commerce/src/AgenticCommerceModule.php b/modules/ppcp-agentic-commerce/src/AgenticCommerceModule.php index 272eba41a..1f8b2edd4 100644 --- a/modules/ppcp-agentic-commerce/src/AgenticCommerceModule.php +++ b/modules/ppcp-agentic-commerce/src/AgenticCommerceModule.php @@ -19,6 +19,7 @@ use WooCommerce\PayPalCommerce\AgenticCommerce\Registration\RegistrationService; use WooCommerce\PayPalCommerce\AgenticCommerce\Registration\RegistrationEligibility; use WooCommerce\PayPalCommerce\AgenticCommerce\Setting\AgenticSettingsDataModel; +use WooCommerce\PayPalCommerce\AgenticCommerce\Inspector\InspectionStatusPage; /** * Entry point that integrates agentic commerce logic with the plugin's DI system. @@ -75,12 +76,14 @@ public function run( ContainerInterface $container ): bool { // Sync eligibility cache on init (when WC is available). $this->sync_eligibility_cache( $agentic_settings, $eligibility_check ); + $inspector = $container->get( 'agentic.inspector.page' ); + assert( $inspector instanceof InspectionStatusPage ); + $inspector->init(); + // Early exit if features should not be initialized. if ( ! $agentic_settings->should_initialize_features() ) { $this->ensure_deregistered( $registration_handler ); - // todo: also remove scheduled action? - return true; } diff --git a/modules/ppcp-agentic-commerce/src/Cart/PayPalCartToCartDataAdapter.php b/modules/ppcp-agentic-commerce/src/Cart/PayPalCartToCartDataAdapter.php index aff93e46b..50eed0a8e 100644 --- a/modules/ppcp-agentic-commerce/src/Cart/PayPalCartToCartDataAdapter.php +++ b/modules/ppcp-agentic-commerce/src/Cart/PayPalCartToCartDataAdapter.php @@ -57,6 +57,7 @@ public function translate( PayPalCart $paypal_cart ): CartData { static fn( $issue ): string => $issue->to_array()['message'], $issues ); + // TODO: This is possibly incorrect. Cart should be stored in session and returned to the agent with a validation issue rather than throwing an error. throw new ValidationException( $error_messages, 'Cart validation failed' diff --git a/modules/ppcp-agentic-commerce/src/Config/AgenticWebhookConfiguration.php b/modules/ppcp-agentic-commerce/src/Config/AgenticWebhookConfiguration.php index 723eeb386..026e113bc 100644 --- a/modules/ppcp-agentic-commerce/src/Config/AgenticWebhookConfiguration.php +++ b/modules/ppcp-agentic-commerce/src/Config/AgenticWebhookConfiguration.php @@ -14,7 +14,7 @@ class AgenticWebhookConfiguration { private const LIVE_BASE_URL = 'https://d.joinhoney.com'; - private const SANDBOX_BASE_URL = 'https://d-sandbox.joinhoney.com'; + private const SANDBOX_BASE_URL = 'https://d-staging.joinhoney.com'; private const ENDPOINT_REGISTRATION_INSTALL = '/webhooks/ws/install'; private const ENDPOINT_REGISTRATION_UNINSTALL = '/webhooks/ws/uninstall'; diff --git a/modules/ppcp-agentic-commerce/src/Inspector/InspectionFormHandler.php b/modules/ppcp-agentic-commerce/src/Inspector/InspectionFormHandler.php new file mode 100644 index 000000000..c5812f46c --- /dev/null +++ b/modules/ppcp-agentic-commerce/src/Inspector/InspectionFormHandler.php @@ -0,0 +1,177 @@ +registration_service = $registration_service; + $this->logger = $logger; + } + + public function init(): void { + add_action( 'admin_post_ppcp_agentic_toggle_registration', fn() => $this->handle_toggle() ); + } + + /** + * Handle the registration toggle form submission. + */ + private function handle_toggle(): void { + $this->logger->info( 'Inspector: Registration toggle form submitted' ); + + $toggle_action = $this->verify_request_and_get_action(); + $this->logger->info( "Inspector: Toggle action requested: {$toggle_action}" ); + + $notice = 'error'; + if ( $toggle_action === 'register' ) { + $notice = $this->handle_registration(); + } elseif ( $toggle_action === 'unregister' ) { + $notice = $this->handle_unregistration(); + } else { + $notice = $this->handle_invalid_action( $toggle_action ); + } + + $this->redirect_to_status_page( $notice ); + } + + /** + * Verify nonce and user capability, then return the toggle action. + * + * @return string The toggle action ('register', 'unregister', or empty string). + */ + private function verify_request_and_get_action(): string { + // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + if ( empty( $_POST['ppcp_agentic_nonce'] ) ) { + return ''; + } + + // Verify nonce and get toggle action in same method. + if ( + ! is_string( $_POST['ppcp_agentic_nonce'] ) || + ! wp_verify_nonce( wp_unslash( $_POST['ppcp_agentic_nonce'] ), 'ppcp_agentic_toggle_nonce' ) + ) { + $this->logger->error( 'Inspector: Nonce verification failed' ); + wp_die( esc_html__( 'Security check failed.', 'woocommerce-paypal-payments' ) ); + } + + // Verify toggle action is a string. + if ( empty( $_POST['toggle_action'] ) || ! is_string( $_POST['toggle_action'] ) ) { + return ''; + } + // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + + // Check user capability. + if ( ! current_user_can( 'manage_woocommerce' ) ) { + $this->logger->error( 'Inspector: User lacks manage_woocommerce capability' ); + wp_die( esc_html__( 'You do not have permission to perform this action.', 'woocommerce-paypal-payments' ) ); + } + + return sanitize_text_field( wp_unslash( $_POST['toggle_action'] ) ); + } + + /** + * Handle registration attempt. + * + * @return string Notice type ('registered' or 'error'). + */ + private function handle_registration(): string { + $this->logger->info( 'Inspector: Attempting to register with RegistrationService' ); + $result = $this->registration_service->register(); + + if ( is_wp_error( $result ) ) { + $this->logger->error( + 'Inspector: Registration failed', + array( + 'error_code' => $result->get_error_code(), + 'error_message' => $result->get_error_message(), + ) + ); + + return 'error'; + } + + $this->logger->info( 'Inspector: Registration successful', array( 'result' => $result ) ); + + return 'registered'; + } + + /** + * Handle unregistration attempt. + * + * @return string Notice type ('unregistered' or 'error'). + */ + private function handle_unregistration(): string { + $this->logger->info( 'Inspector: Attempting to unregister with RegistrationService' ); + $result = $this->registration_service->deregister(); + + if ( is_wp_error( $result ) ) { + $this->logger->error( + 'Inspector: Unregistration failed', + array( + 'error_code' => $result->get_error_code(), + 'error_message' => $result->get_error_message(), + ) + ); + + return 'error'; + } + + $this->logger->info( 'Inspector: Unregistration successful', array( 'result' => $result ) ); + + return 'unregistered'; + } + + /** + * Handle invalid toggle action. + * + * @param string $toggle_action The invalid action. + * @return string Notice type ('error'). + */ + private function handle_invalid_action( string $toggle_action ): string { + $this->logger->warning( "Inspector: Invalid toggle action: {$toggle_action}" ); + + return 'error'; + } + + /** + * Redirect back to the status page with a notice. + * + * @param string $notice Notice type to display. + */ + private function redirect_to_status_page( string $notice ): void { + $redirect_url = add_query_arg( + array( + 'page' => 'wc-status', + 'tab' => 'paypal-agentic', + 'ppcp_agentic_notice' => $notice, + ), + admin_url( 'admin.php' ) + ); + + $this->logger->info( "Inspector: Redirecting to status page with notice: {$notice}" ); + wp_safe_redirect( $redirect_url ); + exit; + } +} diff --git a/modules/ppcp-agentic-commerce/src/Inspector/InspectionSessionData.php b/modules/ppcp-agentic-commerce/src/Inspector/InspectionSessionData.php new file mode 100644 index 000000000..3da4f36d4 --- /dev/null +++ b/modules/ppcp-agentic-commerce/src/Inspector/InspectionSessionData.php @@ -0,0 +1,265 @@ +session = new AgenticWcSession(); + $this->session->init(); + } + + /** + * List all agentic cart sessions with summary information. + * + * Returns lightweight session metadata without full cart details. + * Useful for displaying a list of sessions in an admin interface. + * + * @return array Array of session summaries, each containing: + * - session_id: string + * - created: int|null Unix timestamp + * - modified: int|null Unix timestamp + * - item_count: int Number of items in cart + * - ec_token: string PayPal EC token + */ + public function list_all_sessions(): array { + $all_sessions = $this->get_all_sessions(); + $session_summaries = array(); + + foreach ( $all_sessions as $session ) { + // Check if this session contains agentic commerce data. + if ( ! isset( $session['session_value'][ self::SESSION_KEY ] ) ) { + continue; + } + + $session_data = $session['session_value'][ self::SESSION_KEY ]; + + // Validate required data structure. + if ( ! is_array( $session_data ) || ! isset( $session_data['cart'] ) ) { + continue; + } + + // Count items without fully parsing the cart. + $item_count = 0; + if ( isset( $session_data['cart']['items'] ) && is_array( $session_data['cart']['items'] ) ) { + $item_count = count( $session_data['cart']['items'] ); + } + + $session_summaries[] = array( + 'session_id' => $session['session_key'], + 'created' => $session_data['created'] ?? null, + 'modified' => $session_data['modified'] ?? null, + 'item_count' => $item_count, + 'ec_token' => $session_data['ec_token'] ?? '', + ); + } + + // Sort by created date, newest first. + usort( + $session_summaries, + function ( $a, $b ) { + $a_time = $a['created'] ?? 0; + $b_time = $b['created'] ?? 0; + + return $b_time <=> $a_time; + } + ); + + return $session_summaries; + } + + /** + * Inspect a specific cart session by ID. + * + * Returns full cart details including all items, totals, and metadata. + * Use this when you need complete information about a specific session. + * + * @param string $session_id The session ID to inspect. + * @return array|null Array with full session details or null if not found: + * - session_id: string + * - cart: PayPalCart Full cart object + * - ec_token: string PayPal EC token + * - created: int|null Unix timestamp + * - modified: int|null Unix timestamp + * - expires: int|null Unix timestamp when session expires + */ + public function inspect_cart_session( string $session_id ): ?array { + if ( ! $this->session->load_session_by_id( $session_id ) ) { + return null; + } + + $session_data = $this->session->get( self::SESSION_KEY ); + + if ( ! is_array( $session_data ) || ! isset( $session_data['cart'] ) ) { + return null; + } + + try { + $cart = PayPalCart::from_array( $session_data['cart'] ); + + // Get expiry time from database. + $expires = $this->get_session_expiry( $session_id ); + + return array( + 'session_id' => $session_id, + 'cart' => $cart, + 'ec_token' => $session_data['ec_token'] ?? '', + 'created' => $session_data['created'] ?? null, + 'modified' => $session_data['modified'] ?? null, + 'expires' => $expires, + ); + } catch ( \Exception $e ) { + return null; + } + } + + /** + * Get session statistics for dashboard display. + * + * Provides aggregate statistics about agentic commerce sessions. + * + * @return array Statistics including: + * - total_sessions: int + * - total_items: int + * - oldest_session: int|null Unix timestamp + * - newest_session: int|null Unix timestamp + * - average_age_hours: float|null + */ + public function get_session_statistics(): array { + $sessions = $this->list_all_sessions(); + + if ( empty( $sessions ) ) { + return array( + 'total_sessions' => 0, + 'total_items' => 0, + 'oldest_session' => null, + 'newest_session' => null, + 'average_age_hours' => null, + ); + } + + $total_items = 0; + $oldest = null; + $newest = null; + $total_age = 0; + $sessions_with_age = 0; + + foreach ( $sessions as $session ) { + $total_items += $session['item_count']; + + if ( $session['created'] ) { + if ( $oldest === null || $session['created'] < $oldest ) { + $oldest = $session['created']; + } + if ( $newest === null || $session['created'] > $newest ) { + $newest = $session['created']; + } + + $age = time() - $session['created']; + + $total_age += $age; + + ++$sessions_with_age; + } + } + + $average_age_hours = $sessions_with_age > 0 + ? ( $total_age / $sessions_with_age ) / 3600 + : null; + + return array( + 'total_sessions' => count( $sessions ), + 'total_items' => $total_items, + 'oldest_session' => $oldest, + 'newest_session' => $newest, + 'average_age_hours' => $average_age_hours, + ); + } + + /** + * Get the expiry timestamp for a session. + * + * @param string $session_id The session ID. + * @return int|null Unix timestamp or null if not found. + */ + private function get_session_expiry( string $session_id ): ?int { + global $wpdb; + + $expiry = $wpdb->get_var( + $wpdb->prepare( + "SELECT session_expiry FROM {$wpdb->prefix}woocommerce_sessions + WHERE session_key = %s", + $session_id + ) + ); + + return $expiry ? (int) $expiry : null; + } + + /** + * Get all active sessions from the database. + * + * This method directly queries the WooCommerce sessions table. + * Only used for inspection purposes. + * + * @return array Array of sessions with 'session_key' and 'session_value' (unserialized). + */ + private function get_all_sessions(): array { + global $wpdb; + + $results = $wpdb->get_results( + $wpdb->prepare( + "SELECT session_key, session_value FROM {$wpdb->prefix}woocommerce_sessions + WHERE session_expiry > %d", + time() + ) + ); + + if ( ! $results ) { + return array(); + } + + $sessions = array(); + foreach ( $results as $row ) { + $sessions[] = array( + 'session_key' => $row->session_key, + 'session_value' => maybe_unserialize( $row->session_value ), + ); + } + + return $sessions; + } +} diff --git a/modules/ppcp-agentic-commerce/src/Inspector/InspectionStatusPage.php b/modules/ppcp-agentic-commerce/src/Inspector/InspectionStatusPage.php new file mode 100644 index 000000000..ec896ebe0 --- /dev/null +++ b/modules/ppcp-agentic-commerce/src/Inspector/InspectionStatusPage.php @@ -0,0 +1,60 @@ +form_handler = $form_handler; + $this->registration_section = $registration_section; + $this->session_section = $session_section; + } + + /** + * Initialize the status tab and form handler by registering WordPress hooks. + */ + public function init(): void { + $this->form_handler->init(); + + add_filter( 'woocommerce_admin_status_tabs', fn( array $tabs ) => $this->add_tab( $tabs ), 99 ); + add_action( 'woocommerce_admin_status_content_paypal-agentic', fn() => $this->registration_section->render(), 10 ); + add_action( 'woocommerce_admin_status_content_paypal-agentic', fn() => $this->session_section->render(), 11 ); + } + + /** + * Add PayPal Agentic tab to WooCommerce status tabs. + * + * @param array $tabs Existing status tabs. + * @return array Modified tabs array with PayPal Agentic tab added. + */ + private function add_tab( array $tabs ): array { + $tabs['paypal-agentic'] = __( 'PayPal Agentic', 'woocommerce-paypal-payments' ); + + return $tabs; + } +} diff --git a/modules/ppcp-agentic-commerce/src/Inspector/Page/CartSessionSection.php b/modules/ppcp-agentic-commerce/src/Inspector/Page/CartSessionSection.php new file mode 100644 index 000000000..c9b52c5b4 --- /dev/null +++ b/modules/ppcp-agentic-commerce/src/Inspector/Page/CartSessionSection.php @@ -0,0 +1,376 @@ +inspector = $inspector; + } + + /** + * Render the cart session section. + */ + public function render(): void { + $stats = $this->inspector->get_session_statistics(); + $sessions = $this->inspector->list_all_sessions(); + + // Check if we should inspect a specific session. + $inspect_session_id = $this->get_inspected_session_id(); + + ?> +
+

+ + + render_session_details( $inspect_session_id ); ?> + + + render_statistics_table( $stats ); ?> + render_session_list( $sessions, $inspect_session_id ); ?> +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
: + render_help( __( 'Number of active agentic cart sessions', 'woocommerce-paypal-payments' ) ); ?> +
: + render_help( __( 'Total number of items across all carts', 'woocommerce-paypal-payments' ) ); ?> +
: + render_help( __( 'Average age of sessions in hours', 'woocommerce-paypal-payments' ) ); ?> + + hours +
+ +
+

+
+ + + + + + + + + + + + + + 'wc-status', + 'tab' => 'paypal-agentic', + 'inspect_session' => $session_id, + 'inspect_nonce' => wp_create_nonce( 'ppcp_inspect_session_' . $session_id ), + ), + admin_url( 'admin.php' ) + ); + + $is_inspecting = $inspect_session_id === $session_id; + ?> + > + + + + + + + + + +
+ + ... + + h + + + + + + + + + +
+ inspector->inspect_cart_session( $session_id ); + + if ( ! $details ) { + ?> +
+

+
+ +
+
+

+ + + +
+ +
+
+

+ + + + + + + + + + + + + + + + + + + + + + + +
:
:
:
:
:
+
+ +
+

+ + + + + + + + + + + + + + + + + + + + + + + +
:totals->subtotal . ' ' . $cart->currency ); ?>
:totals->shipping . ' ' . $cart->currency ); ?>
:totals->tax . ' ' . $cart->currency ); ?>
:totals->discount . ' ' . $cart->currency ); ?>
:totals->total . ' ' . $cart->currency ); ?>
+
+
+ +

+ + + + + + + + + + + + items as $item ) : ?> + + + + + + + + + +
+ name ); ?> + image_url ) ) : ?> +
+ +
sku ?: '-' ); ?>quantity ); ?>unit_amount . ' ' . $cart->currency ); ?> + unit_amount * $item->quantity ) . ' ' . $cart->currency ); ?> +
+ + shipping_address ) ) : ?> +

+
+ shipping_address; + echo esc_html( $address->name ?? '' ); + if ( ! empty( $address->address_line_1 ) ) { + echo '
' . esc_html( $address->address_line_1 ); + } + if ( ! empty( $address->address_line_2 ) ) { + echo '
' . esc_html( $address->address_line_2 ); + } + if ( ! empty( $address->city ) || ! empty( $address->state ) || ! empty( $address->postal_code ) ) { + echo '
' . esc_html( + implode( + ', ', + array_filter( + array( + $address->city ?? '', + $address->state ?? '', + $address->postal_code ?? '', + ) + ) + ) + ); + } + if ( ! empty( $address->country_code ) ) { + echo '
' . esc_html( $address->country_code ); + } + ?> +
+ + +
+ + +
+
+ registration_service = $registration_service; + $this->eligibility_check = $eligibility_check; + $this->auth_provider = $auth_provider; + $this->general_settings = $general_settings; + } + + /** + * Render the registration status section. + */ + public function render(): void { + $is_eligible = $this->eligibility_check->is_eligible(); + $is_registered = $this->registration_service->is_registered(); + $merchant_id = $this->general_settings->get_merchant_id(); + $auth_service = $this->auth_provider->auth_service(); + + ?> +
+

+ + render_notices(); ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ : + + render_help( __( 'Whether this store can use agentic commerce features', 'woocommerce-paypal-payments' ) ); ?> + + render_boolean_badge( + $is_eligible, + esc_html__( 'Eligible', 'woocommerce-paypal-payments' ), + esc_html__( 'Not eligible', 'woocommerce-paypal-payments' ) + ); + ?> +
+ : + + render_help( __( 'Which implementation verifies the JWK token?', 'woocommerce-paypal-payments' ) ); ?> + + %s', get_class( $auth_service ) ) ); ?> +
+ : + + render_help( __( 'Is the store registered with the joinhoney service?', 'woocommerce-paypal-payments' ) ); ?> + +
+ render_boolean_badge( + $is_registered, + esc_html__( 'Registered', 'woocommerce-paypal-payments' ), + esc_html__( 'Not registered', 'woocommerce-paypal-payments' ) + ); + ?> + render_toggle_form( $is_registered ); ?> +
+
+ : +
+
+ +
+ + + + +
+ __( 'Successfully registered with PayPal Agentic Commerce.', 'woocommerce-paypal-payments' ), + 'unregistered' => __( 'Successfully unregistered from PayPal Agentic Commerce.', 'woocommerce-paypal-payments' ), + 'error' => __( 'Failed to update registration status. Please try again.', 'woocommerce-paypal-payments' ), + ); + + if ( ! isset( $messages[ $notice_type ] ) ) { + return; + } + + $class = $notice_type === 'error' ? 'error' : 'updated'; + ?> +
+

+
+ ', + esc_attr( $label ) + ) + ); + } + + /** + * Render a boolean status badge (yes/no with icon). + * + * @param bool $is_true Whether the status is true. + * @param string $label_true Label to display when true. + * @param string $label_false Label to display when false. + */ + protected function render_boolean_badge( bool $is_true, string $label_true, string $label_false ): void { + if ( $is_true ) { + echo wp_kses_post( + sprintf( + ' %s', + $label_true + ) + ); + + return; + } + + echo wp_kses_post( + sprintf( + ' %s', + $label_false + ) + ); + } +} diff --git a/modules/ppcp-agentic-commerce/src/Registration/RegistrationService.php b/modules/ppcp-agentic-commerce/src/Registration/RegistrationService.php index 795e27974..0d097282f 100644 --- a/modules/ppcp-agentic-commerce/src/Registration/RegistrationService.php +++ b/modules/ppcp-agentic-commerce/src/Registration/RegistrationService.php @@ -24,6 +24,7 @@ public function __construct( AgenticWebhookConfiguration $webhook_urls, MerchantMetadataProvider $metadata_provider ) { + $this->webhook_urls = $webhook_urls; $this->metadata_provider = $metadata_provider; } diff --git a/tests/PHPUnit/AgenticCommerce/Config/AgenticWebhookConfigurationTest.php b/tests/PHPUnit/AgenticCommerce/Config/AgenticWebhookConfigurationTest.php index 9ed332e2d..b11d70f11 100644 --- a/tests/PHPUnit/AgenticCommerce/Config/AgenticWebhookConfigurationTest.php +++ b/tests/PHPUnit/AgenticCommerce/Config/AgenticWebhookConfigurationTest.php @@ -51,9 +51,9 @@ public function environment_specific_url_provider(): array { return array( 'sandbox environment uses sandbox base URL' => array( false, - 'https://d-sandbox.joinhoney.com/webhooks/ws/install', - 'https://d-sandbox.joinhoney.com/webhooks/ws/uninstall', - 'https://d-sandbox.joinhoney.com/webhooks/products', + 'https://d-staging.joinhoney.com/webhooks/ws/install', + 'https://d-staging.joinhoney.com/webhooks/ws/uninstall', + 'https://d-staging.joinhoney.com/webhooks/products', ), 'production environment uses live base URL' => array( true,