We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Increase timeout of sync shipping method filtering webhooks
The following sync webhooks currently use 2s timeout:
- `ORDER_FILTER_SHIPPING_METHODS` 
- `CHECKOUT_FILTER_SHIPPING_METHODS`

There are no reasons to make the timout shorter than other webhooks. Other sync webhooks use `settings.WEBHOOK_SYNC_TIMEOUT` which should be used for these two for consistency.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `saleor/plugins/webhook/const.py`
Content:
```
1 CACHE_EXCLUDED_SHIPPING_KEY = "webhook_exclude_shipping_id_"
2 CACHE_EXCLUDED_SHIPPING_TIME = 60 * 3
3 EXCLUDED_SHIPPING_REQUEST_TIMEOUT = 2
4 WEBHOOK_CACHE_DEFAULT_TIMEOUT: int = 5 * 60  # 5 minutes
5
```

Path: `saleor/plugins/webhook/shipping.py`
Content:
```
1 import base64
2 import json
3 import logging
4 from collections import defaultdict
5 from typing import Any, Callable, Dict, List, Optional, Union
6 
7 from django.core.cache import cache
8 from django.db.models import QuerySet
9 from graphql import GraphQLError
10 from prices import Money
11 
12 from ...app.models import App
13 from ...checkout.models import Checkout
14 from ...graphql.core.utils import from_global_id_or_error
15 from ...graphql.shipping.types import ShippingMethod
16 from ...order.models import Order
17 from ...shipping.interface import ShippingMethodData
18 from ...webhook.utils import get_webhooks_for_event
19 from ..base_plugin import ExcludedShippingMethod
20 from ..const import APP_ID_PREFIX
21 from .const import CACHE_EXCLUDED_SHIPPING_TIME, EXCLUDED_SHIPPING_REQUEST_TIMEOUT
22 from .tasks import trigger_webhook_sync
23 
24 logger = logging.getLogger(__name__)
25 
26 
27 def to_shipping_app_id(app: "App", shipping_method_id: str) -> "str":
28     app_identifier = app.identifier or app.id
29     return base64.b64encode(
30         str.encode(f"{APP_ID_PREFIX}:{app_identifier}:{shipping_method_id}")
31     ).decode("utf-8")
32 
33 
34 def convert_to_app_id_with_identifier(shipping_app_id: str):
35     """Prepare the shipping_app_id in format `app:<app-identifier>/method_id>`.
36 
37     The format of shipping_app_id has been changes so we need to support both of them.
38     This method is preparing the new shipping_app_id format based on assumptions
39     that right now the old one is used which is `app:<app-pk>:method_id>`
40     """
41     decoded_id = base64.b64decode(shipping_app_id).decode()
42     splitted_id = decoded_id.split(":")
43     if len(splitted_id) != 3:
44         return
45     try:
46         app_id = int(splitted_id[1])
47     except (TypeError, ValueError):
48         return None
49     app = App.objects.filter(id=app_id).first()
50     if app is None:
51         return None
52     return to_shipping_app_id(app, splitted_id[2])
53 
54 
55 def parse_list_shipping_methods_response(
56     response_data: Any, app: "App"
57 ) -> List["ShippingMethodData"]:
58     shipping_methods = []
59     for shipping_method_data in response_data:
60         method_id = shipping_method_data.get("id")
61         method_name = shipping_method_data.get("name")
62         method_amount = shipping_method_data.get("amount")
63         method_currency = shipping_method_data.get("currency")
64         method_maximum_delivery_days = shipping_method_data.get("maximum_delivery_days")
65 
66         shipping_methods.append(
67             ShippingMethodData(
68                 id=to_shipping_app_id(app, method_id),
69                 name=method_name,
70                 price=Money(method_amount, method_currency),
71                 maximum_delivery_days=method_maximum_delivery_days,
72             )
73         )
74     return shipping_methods
75 
76 
77 def _compare_order_payloads(payload: str, cached_payload: str) -> bool:
78     """Compare two strings of order payloads ignoring meta."""
79     EXCLUDED_KEY = "meta"
80     try:
81         order_payload = json.loads(payload)["order"]
82         cached_order_payload = json.loads(cached_payload)["order"]
83     except:  # noqa
84         return False
85     return {k: v for k, v in order_payload.items() if k != EXCLUDED_KEY} == {
86         k: v for k, v in cached_order_payload.items() if k != EXCLUDED_KEY
87     }
88 
89 
90 def get_excluded_shipping_methods_or_fetch(
91     webhooks: QuerySet,
92     event_type: str,
93     payload: str,
94     cache_key: str,
95     subscribable_object: Optional[Union["Order", "Checkout"]],
96 ) -> Dict[str, List[ExcludedShippingMethod]]:
97     """Return data of all excluded shipping methods.
98 
99     The data will be fetched from the cache. If missing it will fetch it from all
100     defined webhooks by calling a request to each of them one by one.
101     """
102     cached_data = cache.get(cache_key)
103     if cached_data:
104         cached_payload, excluded_shipping_methods = cached_data
105         if (payload == cached_payload) or _compare_order_payloads(
106             payload, cached_payload
107         ):
108             return parse_excluded_shipping_methods(excluded_shipping_methods)
109 
110     excluded_methods = []
111     # Gather responses from webhooks
112     for webhook in webhooks:
113         if not webhook:
114             continue
115         response_data = trigger_webhook_sync(
116             event_type,
117             payload,
118             webhook,
119             subscribable_object=subscribable_object,
120             timeout=EXCLUDED_SHIPPING_REQUEST_TIMEOUT,
121         )
122         if response_data:
123             excluded_methods.extend(
124                 get_excluded_shipping_methods_from_response(response_data)
125             )
126     cache.set(cache_key, (payload, excluded_methods), CACHE_EXCLUDED_SHIPPING_TIME)
127     return parse_excluded_shipping_methods(excluded_methods)
128 
129 
130 def get_excluded_shipping_data(
131     event_type: str,
132     previous_value: List[ExcludedShippingMethod],
133     payload_fun: Callable[[], str],
134     cache_key: str,
135     subscribable_object: Optional[Union["Order", "Checkout"]],
136 ) -> List[ExcludedShippingMethod]:
137     """Exclude not allowed shipping methods by sync webhook.
138 
139     Fetch excluded shipping methods from sync webhooks and return them as a list of
140     excluded shipping methods.
141     The function uses a cache_key to reduce the number of
142     requests which we call to the external APIs. In case when we have the same payload
143     in a cache as we're going to send now, we will skip an additional request and use
144     the response fetched from cache.
145     The function will fetch the payload only in the case that we have any defined
146     webhook.
147     """
148 
149     excluded_methods_map: Dict[str, List[ExcludedShippingMethod]] = defaultdict(list)
150     webhooks = get_webhooks_for_event(event_type)
151     if webhooks:
152         payload = payload_fun()
153 
154         excluded_methods_map = get_excluded_shipping_methods_or_fetch(
155             webhooks, event_type, payload, cache_key, subscribable_object
156         )
157 
158     # Gather responses for previous plugins
159     for method in previous_value:
160         excluded_methods_map[method.id].append(method)
161 
162     # Return a list of excluded methods, unique by id
163     excluded_methods = []
164     for method_id, methods in excluded_methods_map.items():
165         reason = None
166         if reasons := [m.reason for m in methods if m.reason]:
167             reason = " ".join(reasons)
168         excluded_methods.append(ExcludedShippingMethod(id=method_id, reason=reason))
169     return excluded_methods
170 
171 
172 def get_excluded_shipping_methods_from_response(
173     response_data: dict,
174 ) -> List[dict]:
175     excluded_methods = []
176     for method_data in response_data.get("excluded_methods", []):
177         try:
178             type_name, method_id = from_global_id_or_error(method_data["id"])
179             if type_name not in (APP_ID_PREFIX, str(ShippingMethod)):
180                 logger.warning(
181                     "Invalid type received. Expected ShippingMethod, got %s", type_name
182                 )
183                 continue
184 
185         except (KeyError, ValueError, TypeError, GraphQLError) as e:
186             logger.warning("Malformed ShippingMethod id was provided: %s", e)
187             continue
188         excluded_methods.append(
189             {"id": method_id, "reason": method_data.get("reason", "")}
190         )
191     return excluded_methods
192 
193 
194 def parse_excluded_shipping_methods(
195     excluded_methods: List[dict],
196 ) -> Dict[str, List[ExcludedShippingMethod]]:
197     excluded_methods_map = defaultdict(list)
198     for excluded_method in excluded_methods:
199         method_id = excluded_method["id"]
200         excluded_methods_map[method_id].append(
201             ExcludedShippingMethod(
202                 id=method_id, reason=excluded_method.get("reason", "")
203             )
204         )
205     return excluded_methods_map
206 
207 
208 def get_cache_data_for_shipping_list_methods_for_checkout(payload: str) -> dict:
209     key_data = json.loads(payload)
210 
211     # drop fields that change between requests but are not relevant for cache key
212     key_data[0].pop("last_change")
213     key_data[0]["meta"].pop("issued_at")
214     return key_data
215
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/saleor/plugins/webhook/const.py b/saleor/plugins/webhook/const.py
--- a/saleor/plugins/webhook/const.py
+++ b/saleor/plugins/webhook/const.py
@@ -1,4 +1,3 @@
 CACHE_EXCLUDED_SHIPPING_KEY = "webhook_exclude_shipping_id_"
 CACHE_EXCLUDED_SHIPPING_TIME = 60 * 3
-EXCLUDED_SHIPPING_REQUEST_TIMEOUT = 2
 WEBHOOK_CACHE_DEFAULT_TIMEOUT: int = 5 * 60  # 5 minutes
diff --git a/saleor/plugins/webhook/shipping.py b/saleor/plugins/webhook/shipping.py
--- a/saleor/plugins/webhook/shipping.py
+++ b/saleor/plugins/webhook/shipping.py
@@ -4,6 +4,7 @@
 from collections import defaultdict
 from typing import Any, Callable, Dict, List, Optional, Union
 
+from django.conf import settings
 from django.core.cache import cache
 from django.db.models import QuerySet
 from graphql import GraphQLError
@@ -18,7 +19,7 @@
 from ...webhook.utils import get_webhooks_for_event
 from ..base_plugin import ExcludedShippingMethod
 from ..const import APP_ID_PREFIX
-from .const import CACHE_EXCLUDED_SHIPPING_TIME, EXCLUDED_SHIPPING_REQUEST_TIMEOUT
+from .const import CACHE_EXCLUDED_SHIPPING_TIME
 from .tasks import trigger_webhook_sync
 
 logger = logging.getLogger(__name__)
@@ -117,7 +118,7 @@
             payload,
             webhook,
             subscribable_object=subscribable_object,
-            timeout=EXCLUDED_SHIPPING_REQUEST_TIMEOUT,
+            timeout=settings.WEBHOOK_SYNC_TIMEOUT,
         )
         if response_data:
             excluded_methods.extend(

{"golden_diff": "diff --git a/saleor/plugins/webhook/const.py b/saleor/plugins/webhook/const.py\n--- a/saleor/plugins/webhook/const.py\n+++ b/saleor/plugins/webhook/const.py\n@@ -1,4 +1,3 @@\n CACHE_EXCLUDED_SHIPPING_KEY = \"webhook_exclude_shipping_id_\"\n CACHE_EXCLUDED_SHIPPING_TIME = 60 * 3\n-EXCLUDED_SHIPPING_REQUEST_TIMEOUT = 2\n WEBHOOK_CACHE_DEFAULT_TIMEOUT: int = 5 * 60  # 5 minutes\ndiff --git a/saleor/plugins/webhook/shipping.py b/saleor/plugins/webhook/shipping.py\n--- a/saleor/plugins/webhook/shipping.py\n+++ b/saleor/plugins/webhook/shipping.py\n@@ -4,6 +4,7 @@\n from collections import defaultdict\n from typing import Any, Callable, Dict, List, Optional, Union\n \n+from django.conf import settings\n from django.core.cache import cache\n from django.db.models import QuerySet\n from graphql import GraphQLError\n@@ -18,7 +19,7 @@\n from ...webhook.utils import get_webhooks_for_event\n from ..base_plugin import ExcludedShippingMethod\n from ..const import APP_ID_PREFIX\n-from .const import CACHE_EXCLUDED_SHIPPING_TIME, EXCLUDED_SHIPPING_REQUEST_TIMEOUT\n+from .const import CACHE_EXCLUDED_SHIPPING_TIME\n from .tasks import trigger_webhook_sync\n \n logger = logging.getLogger(__name__)\n@@ -117,7 +118,7 @@\n             payload,\n             webhook,\n             subscribable_object=subscribable_object,\n-            timeout=EXCLUDED_SHIPPING_REQUEST_TIMEOUT,\n+            timeout=settings.WEBHOOK_SYNC_TIMEOUT,\n         )\n         if response_data:\n             excluded_methods.extend(\n", "issue": "Increase timeout of sync shipping method filtering webhooks\nThe following sync webhooks currently use 2s timeout:\r\n- `ORDER_FILTER_SHIPPING_METHODS` \r\n- `CHECKOUT_FILTER_SHIPPING_METHODS`\r\n\r\nThere are no reasons to make the timout shorter than other webhooks. Other sync webhooks use `settings.WEBHOOK_SYNC_TIMEOUT` which should be used for these two for consistency.\n", "before_files": [{"content": "CACHE_EXCLUDED_SHIPPING_KEY = \"webhook_exclude_shipping_id_\"\nCACHE_EXCLUDED_SHIPPING_TIME = 60 * 3\nEXCLUDED_SHIPPING_REQUEST_TIMEOUT = 2\nWEBHOOK_CACHE_DEFAULT_TIMEOUT: int = 5 * 60  # 5 minutes\n", "path": "saleor/plugins/webhook/const.py"}, {"content": "import base64\nimport json\nimport logging\nfrom collections import defaultdict\nfrom typing import Any, Callable, Dict, List, Optional, Union\n\nfrom django.core.cache import cache\nfrom django.db.models import QuerySet\nfrom graphql import GraphQLError\nfrom prices import Money\n\nfrom ...app.models import App\nfrom ...checkout.models import Checkout\nfrom ...graphql.core.utils import from_global_id_or_error\nfrom ...graphql.shipping.types import ShippingMethod\nfrom ...order.models import Order\nfrom ...shipping.interface import ShippingMethodData\nfrom ...webhook.utils import get_webhooks_for_event\nfrom ..base_plugin import ExcludedShippingMethod\nfrom ..const import APP_ID_PREFIX\nfrom .const import CACHE_EXCLUDED_SHIPPING_TIME, EXCLUDED_SHIPPING_REQUEST_TIMEOUT\nfrom .tasks import trigger_webhook_sync\n\nlogger = logging.getLogger(__name__)\n\n\ndef to_shipping_app_id(app: \"App\", shipping_method_id: str) -> \"str\":\n    app_identifier = app.identifier or app.id\n    return base64.b64encode(\n        str.encode(f\"{APP_ID_PREFIX}:{app_identifier}:{shipping_method_id}\")\n    ).decode(\"utf-8\")\n\n\ndef convert_to_app_id_with_identifier(shipping_app_id: str):\n    \"\"\"Prepare the shipping_app_id in format `app:<app-identifier>/method_id>`.\n\n    The format of shipping_app_id has been changes so we need to support both of them.\n    This method is preparing the new shipping_app_id format based on assumptions\n    that right now the old one is used which is `app:<app-pk>:method_id>`\n    \"\"\"\n    decoded_id = base64.b64decode(shipping_app_id).decode()\n    splitted_id = decoded_id.split(\":\")\n    if len(splitted_id) != 3:\n        return\n    try:\n        app_id = int(splitted_id[1])\n    except (TypeError, ValueError):\n        return None\n    app = App.objects.filter(id=app_id).first()\n    if app is None:\n        return None\n    return to_shipping_app_id(app, splitted_id[2])\n\n\ndef parse_list_shipping_methods_response(\n    response_data: Any, app: \"App\"\n) -> List[\"ShippingMethodData\"]:\n    shipping_methods = []\n    for shipping_method_data in response_data:\n        method_id = shipping_method_data.get(\"id\")\n        method_name = shipping_method_data.get(\"name\")\n        method_amount = shipping_method_data.get(\"amount\")\n        method_currency = shipping_method_data.get(\"currency\")\n        method_maximum_delivery_days = shipping_method_data.get(\"maximum_delivery_days\")\n\n        shipping_methods.append(\n            ShippingMethodData(\n                id=to_shipping_app_id(app, method_id),\n                name=method_name,\n                price=Money(method_amount, method_currency),\n                maximum_delivery_days=method_maximum_delivery_days,\n            )\n        )\n    return shipping_methods\n\n\ndef _compare_order_payloads(payload: str, cached_payload: str) -> bool:\n    \"\"\"Compare two strings of order payloads ignoring meta.\"\"\"\n    EXCLUDED_KEY = \"meta\"\n    try:\n        order_payload = json.loads(payload)[\"order\"]\n        cached_order_payload = json.loads(cached_payload)[\"order\"]\n    except:  # noqa\n        return False\n    return {k: v for k, v in order_payload.items() if k != EXCLUDED_KEY} == {\n        k: v for k, v in cached_order_payload.items() if k != EXCLUDED_KEY\n    }\n\n\ndef get_excluded_shipping_methods_or_fetch(\n    webhooks: QuerySet,\n    event_type: str,\n    payload: str,\n    cache_key: str,\n    subscribable_object: Optional[Union[\"Order\", \"Checkout\"]],\n) -> Dict[str, List[ExcludedShippingMethod]]:\n    \"\"\"Return data of all excluded shipping methods.\n\n    The data will be fetched from the cache. If missing it will fetch it from all\n    defined webhooks by calling a request to each of them one by one.\n    \"\"\"\n    cached_data = cache.get(cache_key)\n    if cached_data:\n        cached_payload, excluded_shipping_methods = cached_data\n        if (payload == cached_payload) or _compare_order_payloads(\n            payload, cached_payload\n        ):\n            return parse_excluded_shipping_methods(excluded_shipping_methods)\n\n    excluded_methods = []\n    # Gather responses from webhooks\n    for webhook in webhooks:\n        if not webhook:\n            continue\n        response_data = trigger_webhook_sync(\n            event_type,\n            payload,\n            webhook,\n            subscribable_object=subscribable_object,\n            timeout=EXCLUDED_SHIPPING_REQUEST_TIMEOUT,\n        )\n        if response_data:\n            excluded_methods.extend(\n                get_excluded_shipping_methods_from_response(response_data)\n            )\n    cache.set(cache_key, (payload, excluded_methods), CACHE_EXCLUDED_SHIPPING_TIME)\n    return parse_excluded_shipping_methods(excluded_methods)\n\n\ndef get_excluded_shipping_data(\n    event_type: str,\n    previous_value: List[ExcludedShippingMethod],\n    payload_fun: Callable[[], str],\n    cache_key: str,\n    subscribable_object: Optional[Union[\"Order\", \"Checkout\"]],\n) -> List[ExcludedShippingMethod]:\n    \"\"\"Exclude not allowed shipping methods by sync webhook.\n\n    Fetch excluded shipping methods from sync webhooks and return them as a list of\n    excluded shipping methods.\n    The function uses a cache_key to reduce the number of\n    requests which we call to the external APIs. In case when we have the same payload\n    in a cache as we're going to send now, we will skip an additional request and use\n    the response fetched from cache.\n    The function will fetch the payload only in the case that we have any defined\n    webhook.\n    \"\"\"\n\n    excluded_methods_map: Dict[str, List[ExcludedShippingMethod]] = defaultdict(list)\n    webhooks = get_webhooks_for_event(event_type)\n    if webhooks:\n        payload = payload_fun()\n\n        excluded_methods_map = get_excluded_shipping_methods_or_fetch(\n            webhooks, event_type, payload, cache_key, subscribable_object\n        )\n\n    # Gather responses for previous plugins\n    for method in previous_value:\n        excluded_methods_map[method.id].append(method)\n\n    # Return a list of excluded methods, unique by id\n    excluded_methods = []\n    for method_id, methods in excluded_methods_map.items():\n        reason = None\n        if reasons := [m.reason for m in methods if m.reason]:\n            reason = \" \".join(reasons)\n        excluded_methods.append(ExcludedShippingMethod(id=method_id, reason=reason))\n    return excluded_methods\n\n\ndef get_excluded_shipping_methods_from_response(\n    response_data: dict,\n) -> List[dict]:\n    excluded_methods = []\n    for method_data in response_data.get(\"excluded_methods\", []):\n        try:\n            type_name, method_id = from_global_id_or_error(method_data[\"id\"])\n            if type_name not in (APP_ID_PREFIX, str(ShippingMethod)):\n                logger.warning(\n                    \"Invalid type received. Expected ShippingMethod, got %s\", type_name\n                )\n                continue\n\n        except (KeyError, ValueError, TypeError, GraphQLError) as e:\n            logger.warning(\"Malformed ShippingMethod id was provided: %s\", e)\n            continue\n        excluded_methods.append(\n            {\"id\": method_id, \"reason\": method_data.get(\"reason\", \"\")}\n        )\n    return excluded_methods\n\n\ndef parse_excluded_shipping_methods(\n    excluded_methods: List[dict],\n) -> Dict[str, List[ExcludedShippingMethod]]:\n    excluded_methods_map = defaultdict(list)\n    for excluded_method in excluded_methods:\n        method_id = excluded_method[\"id\"]\n        excluded_methods_map[method_id].append(\n            ExcludedShippingMethod(\n                id=method_id, reason=excluded_method.get(\"reason\", \"\")\n            )\n        )\n    return excluded_methods_map\n\n\ndef get_cache_data_for_shipping_list_methods_for_checkout(payload: str) -> dict:\n    key_data = json.loads(payload)\n\n    # drop fields that change between requests but are not relevant for cache key\n    key_data[0].pop(\"last_change\")\n    key_data[0][\"meta\"].pop(\"issued_at\")\n    return key_data\n", "path": "saleor/plugins/webhook/shipping.py"}], "after_files": [{"content": "CACHE_EXCLUDED_SHIPPING_KEY = \"webhook_exclude_shipping_id_\"\nCACHE_EXCLUDED_SHIPPING_TIME = 60 * 3\nWEBHOOK_CACHE_DEFAULT_TIMEOUT: int = 5 * 60  # 5 minutes\n", "path": "saleor/plugins/webhook/const.py"}, {"content": "import base64\nimport json\nimport logging\nfrom collections import defaultdict\nfrom typing import Any, Callable, Dict, List, Optional, Union\n\nfrom django.conf import settings\nfrom django.core.cache import cache\nfrom django.db.models import QuerySet\nfrom graphql import GraphQLError\nfrom prices import Money\n\nfrom ...app.models import App\nfrom ...checkout.models import Checkout\nfrom ...graphql.core.utils import from_global_id_or_error\nfrom ...graphql.shipping.types import ShippingMethod\nfrom ...order.models import Order\nfrom ...shipping.interface import ShippingMethodData\nfrom ...webhook.utils import get_webhooks_for_event\nfrom ..base_plugin import ExcludedShippingMethod\nfrom ..const import APP_ID_PREFIX\nfrom .const import CACHE_EXCLUDED_SHIPPING_TIME\nfrom .tasks import trigger_webhook_sync\n\nlogger = logging.getLogger(__name__)\n\n\ndef to_shipping_app_id(app: \"App\", shipping_method_id: str) -> \"str\":\n    app_identifier = app.identifier or app.id\n    return base64.b64encode(\n        str.encode(f\"{APP_ID_PREFIX}:{app_identifier}:{shipping_method_id}\")\n    ).decode(\"utf-8\")\n\n\ndef convert_to_app_id_with_identifier(shipping_app_id: str):\n    \"\"\"Prepare the shipping_app_id in format `app:<app-identifier>/method_id>`.\n\n    The format of shipping_app_id has been changes so we need to support both of them.\n    This method is preparing the new shipping_app_id format based on assumptions\n    that right now the old one is used which is `app:<app-pk>:method_id>`\n    \"\"\"\n    decoded_id = base64.b64decode(shipping_app_id).decode()\n    splitted_id = decoded_id.split(\":\")\n    if len(splitted_id) != 3:\n        return\n    try:\n        app_id = int(splitted_id[1])\n    except (TypeError, ValueError):\n        return None\n    app = App.objects.filter(id=app_id).first()\n    if app is None:\n        return None\n    return to_shipping_app_id(app, splitted_id[2])\n\n\ndef parse_list_shipping_methods_response(\n    response_data: Any, app: \"App\"\n) -> List[\"ShippingMethodData\"]:\n    shipping_methods = []\n    for shipping_method_data in response_data:\n        method_id = shipping_method_data.get(\"id\")\n        method_name = shipping_method_data.get(\"name\")\n        method_amount = shipping_method_data.get(\"amount\")\n        method_currency = shipping_method_data.get(\"currency\")\n        method_maximum_delivery_days = shipping_method_data.get(\"maximum_delivery_days\")\n\n        shipping_methods.append(\n            ShippingMethodData(\n                id=to_shipping_app_id(app, method_id),\n                name=method_name,\n                price=Money(method_amount, method_currency),\n                maximum_delivery_days=method_maximum_delivery_days,\n            )\n        )\n    return shipping_methods\n\n\ndef _compare_order_payloads(payload: str, cached_payload: str) -> bool:\n    \"\"\"Compare two strings of order payloads ignoring meta.\"\"\"\n    EXCLUDED_KEY = \"meta\"\n    try:\n        order_payload = json.loads(payload)[\"order\"]\n        cached_order_payload = json.loads(cached_payload)[\"order\"]\n    except:  # noqa\n        return False\n    return {k: v for k, v in order_payload.items() if k != EXCLUDED_KEY} == {\n        k: v for k, v in cached_order_payload.items() if k != EXCLUDED_KEY\n    }\n\n\ndef get_excluded_shipping_methods_or_fetch(\n    webhooks: QuerySet,\n    event_type: str,\n    payload: str,\n    cache_key: str,\n    subscribable_object: Optional[Union[\"Order\", \"Checkout\"]],\n) -> Dict[str, List[ExcludedShippingMethod]]:\n    \"\"\"Return data of all excluded shipping methods.\n\n    The data will be fetched from the cache. If missing it will fetch it from all\n    defined webhooks by calling a request to each of them one by one.\n    \"\"\"\n    cached_data = cache.get(cache_key)\n    if cached_data:\n        cached_payload, excluded_shipping_methods = cached_data\n        if (payload == cached_payload) or _compare_order_payloads(\n            payload, cached_payload\n        ):\n            return parse_excluded_shipping_methods(excluded_shipping_methods)\n\n    excluded_methods = []\n    # Gather responses from webhooks\n    for webhook in webhooks:\n        if not webhook:\n            continue\n        response_data = trigger_webhook_sync(\n            event_type,\n            payload,\n            webhook,\n            subscribable_object=subscribable_object,\n            timeout=settings.WEBHOOK_SYNC_TIMEOUT,\n        )\n        if response_data:\n            excluded_methods.extend(\n                get_excluded_shipping_methods_from_response(response_data)\n            )\n    cache.set(cache_key, (payload, excluded_methods), CACHE_EXCLUDED_SHIPPING_TIME)\n    return parse_excluded_shipping_methods(excluded_methods)\n\n\ndef get_excluded_shipping_data(\n    event_type: str,\n    previous_value: List[ExcludedShippingMethod],\n    payload_fun: Callable[[], str],\n    cache_key: str,\n    subscribable_object: Optional[Union[\"Order\", \"Checkout\"]],\n) -> List[ExcludedShippingMethod]:\n    \"\"\"Exclude not allowed shipping methods by sync webhook.\n\n    Fetch excluded shipping methods from sync webhooks and return them as a list of\n    excluded shipping methods.\n    The function uses a cache_key to reduce the number of\n    requests which we call to the external APIs. In case when we have the same payload\n    in a cache as we're going to send now, we will skip an additional request and use\n    the response fetched from cache.\n    The function will fetch the payload only in the case that we have any defined\n    webhook.\n    \"\"\"\n\n    excluded_methods_map: Dict[str, List[ExcludedShippingMethod]] = defaultdict(list)\n    webhooks = get_webhooks_for_event(event_type)\n    if webhooks:\n        payload = payload_fun()\n\n        excluded_methods_map = get_excluded_shipping_methods_or_fetch(\n            webhooks, event_type, payload, cache_key, subscribable_object\n        )\n\n    # Gather responses for previous plugins\n    for method in previous_value:\n        excluded_methods_map[method.id].append(method)\n\n    # Return a list of excluded methods, unique by id\n    excluded_methods = []\n    for method_id, methods in excluded_methods_map.items():\n        reason = None\n        if reasons := [m.reason for m in methods if m.reason]:\n            reason = \" \".join(reasons)\n        excluded_methods.append(ExcludedShippingMethod(id=method_id, reason=reason))\n    return excluded_methods\n\n\ndef get_excluded_shipping_methods_from_response(\n    response_data: dict,\n) -> List[dict]:\n    excluded_methods = []\n    for method_data in response_data.get(\"excluded_methods\", []):\n        try:\n            type_name, method_id = from_global_id_or_error(method_data[\"id\"])\n            if type_name not in (APP_ID_PREFIX, str(ShippingMethod)):\n                logger.warning(\n                    \"Invalid type received. Expected ShippingMethod, got %s\", type_name\n                )\n                continue\n\n        except (KeyError, ValueError, TypeError, GraphQLError) as e:\n            logger.warning(\"Malformed ShippingMethod id was provided: %s\", e)\n            continue\n        excluded_methods.append(\n            {\"id\": method_id, \"reason\": method_data.get(\"reason\", \"\")}\n        )\n    return excluded_methods\n\n\ndef parse_excluded_shipping_methods(\n    excluded_methods: List[dict],\n) -> Dict[str, List[ExcludedShippingMethod]]:\n    excluded_methods_map = defaultdict(list)\n    for excluded_method in excluded_methods:\n        method_id = excluded_method[\"id\"]\n        excluded_methods_map[method_id].append(\n            ExcludedShippingMethod(\n                id=method_id, reason=excluded_method.get(\"reason\", \"\")\n            )\n        )\n    return excluded_methods_map\n\n\ndef get_cache_data_for_shipping_list_methods_for_checkout(payload: str) -> dict:\n    key_data = json.loads(payload)\n\n    # drop fields that change between requests but are not relevant for cache key\n    key_data[0].pop(\"last_change\")\n    key_data[0][\"meta\"].pop(\"issued_at\")\n    return key_data\n", "path": "saleor/plugins/webhook/shipping.py"}]}