| @@ -266,8 +266,15 @@ | ||
| 266 | 266 | if ((int) $webhook->get_id() !== (int) $keeper->get_id()) { |
| 267 | 267 | $this->_remove_webhook($webhook->get_id()); |
| 268 | 268 | } |
| 269 | 269 | } |
| 270 | + // Paused is a manual opt-out: quotes stay on, order posting stays off. | |
| 271 | + // If any copy is paused, the survivor stays paused so an active | |
| 272 | + // duplicate cannot keep delivering. Disabled is WooCommerce's | |
| 273 | + // automatic status after delivery failures, and we still heal that. | |
| 274 | + if (self::group_has_paused_webhook($existing)) { | |
| 275 | + $values['status'] = 'paused'; | |
| 276 | + } | |
| 270 | 277 | if (!$this->sync_webhook($keeper, $values, $secret, $user_id)) { |
| 271 | 278 | $missing[] = $name; |
| 272 | 279 | } |
| 273 | 280 | } |
| @@ -372,8 +379,59 @@ | ||
| 372 | 379 | return 0; |
| 373 | 380 | } |
| 374 | 381 | |
| 375 | 382 | /** |
| 383 | + * Whether the merchant paused any copy of this webhook. | |
| 384 | + * | |
| 385 | + * @param array $webhooks Webhook-like objects for one name. | |
| 386 | + * @return bool | |
| 387 | + */ | |
| 388 | + private static function group_has_paused_webhook($webhooks) { | |
| 389 | + foreach ($webhooks as $webhook) { | |
| 390 | + if (self::is_webhook_like($webhook) && 'paused' === $webhook->get_status()) { | |
| 391 | + return true; | |
| 392 | + } | |
| 393 | + } | |
| 394 | + return false; | |
| 395 | + } | |
| 396 | + | |
| 397 | + /** | |
| 398 | + * Merchant opted out of plugin order posting. | |
| 399 | + * | |
| 400 | + * True only when every RouteApp Order Upsert webhook is paused. Missing | |
| 401 | + * webhooks, and Active or Disabled ones, still reconcile: Disabled is | |
| 402 | + * WooCommerce's automatic status after delivery failures. | |
| 403 | + * | |
| 404 | + * @return bool | |
| 405 | + */ | |
| 406 | + public static function is_order_upsert_paused() { | |
| 407 | + if (!class_exists('WC_Data_Store')) { | |
| 408 | + return false; | |
| 409 | + } | |
| 410 | + $data_store = \WC_Data_Store::load('webhook'); | |
| 411 | + $ids = $data_store->search_webhooks(array('limit' => -1)); | |
| 412 | + if (!is_array($ids) || empty($ids)) { | |
| 413 | + return false; | |
| 414 | + } | |
| 415 | + | |
| 416 | + $found = false; | |
| 417 | + foreach ($ids as $id) { | |
| 418 | + $webhook = function_exists('wc_get_webhook') ? wc_get_webhook($id) : false; | |
| 419 | + if (!self::is_webhook_like($webhook, true)) { | |
| 420 | + continue; | |
| 421 | + } | |
| 422 | + if ('RouteApp Order Upsert' !== $webhook->get_name()) { | |
| 423 | + continue; | |
| 424 | + } | |
| 425 | + $found = true; | |
| 426 | + if ('paused' !== $webhook->get_status()) { | |
| 427 | + return false; | |
| 428 | + } | |
| 429 | + } | |
| 430 | + return $found; | |
| 431 | + } | |
| 432 | + | |
| 433 | + /** | |
| 376 | 434 | * Update in place. Do not delete+recreate (that is what duplicated hooks). |
| 377 | 435 | * |
| 378 | 436 | * @param object $webhook WC_Webhook. |
| 379 | 437 | * @param array $values Expected webhook config. |
| @@ -390,12 +448,13 @@ | ||
| 390 | 448 | $webhook->set_user_id($effective_user); |
| 391 | 449 | $changed = true; |
| 392 | 450 | } |
| 393 | 451 | |
| 394 | - if ($effective_user && $webhook->get_status() !== $values['status']) { | |
| 452 | + $current_status = $webhook->get_status(); | |
| 453 | + if ($effective_user && 'paused' !== $current_status && $current_status !== $values['status']) { | |
| 395 | 454 | $webhook->set_status($values['status']); |
| 396 | 455 | $changed = true; |
| 397 | - if (method_exists($webhook, 'set_failure_count')) { | |
| 456 | + if ('active' === $values['status'] && method_exists($webhook, 'set_failure_count')) { | |
| 398 | 457 | $webhook->set_failure_count(0); |
| 399 | 458 | } |
| 400 | 459 | } |
| 401 | 460 | |