We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Allow to remove shipping method
Current state:
Once the Shipping Method is assigned to the checkout, it cannot be removed by the user.

DoD:
make `checkoutShippingMethodUpdate.shippingMethodId` argument nullable
if null is passed as a value, remove the shipping method from the checkout

--- 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/graphql/checkout/mutations/checkout_shipping_method_update.py`
Content:
```
1 from typing import Optional
2 
3 import graphene
4 from django.core.exceptions import ValidationError
5 
6 from ....checkout.error_codes import CheckoutErrorCode
7 from ....checkout.fetch import fetch_checkout_info, fetch_checkout_lines
8 from ....checkout.utils import (
9     delete_external_shipping_id,
10     get_checkout_metadata,
11     invalidate_checkout_prices,
12     is_shipping_required,
13     set_external_shipping_id,
14 )
15 from ....plugins.webhook.utils import APP_ID_PREFIX
16 from ....shipping import interface as shipping_interface
17 from ....shipping import models as shipping_models
18 from ....shipping.utils import convert_to_shipping_method_data
19 from ....webhook.event_types import WebhookEventAsyncType, WebhookEventSyncType
20 from ...core import ResolveInfo
21 from ...core.descriptions import ADDED_IN_34, DEPRECATED_IN_3X_INPUT
22 from ...core.doc_category import DOC_CATEGORY_CHECKOUT
23 from ...core.mutations import BaseMutation
24 from ...core.scalars import UUID
25 from ...core.types import CheckoutError
26 from ...core.utils import WebhookEventInfo, from_global_id_or_error
27 from ...plugins.dataloaders import get_plugin_manager_promise
28 from ...shipping.types import ShippingMethod
29 from ..types import Checkout
30 from .utils import ERROR_DOES_NOT_SHIP, clean_delivery_method, get_checkout
31 
32 
33 class CheckoutShippingMethodUpdate(BaseMutation):
34     checkout = graphene.Field(Checkout, description="An updated checkout.")
35 
36     class Arguments:
37         id = graphene.ID(
38             description="The checkout's ID." + ADDED_IN_34,
39             required=False,
40         )
41         token = UUID(
42             description=f"Checkout token.{DEPRECATED_IN_3X_INPUT} Use `id` instead.",
43             required=False,
44         )
45         checkout_id = graphene.ID(
46             required=False,
47             description=(
48                 f"The ID of the checkout. {DEPRECATED_IN_3X_INPUT} Use `id` instead."
49             ),
50         )
51         shipping_method_id = graphene.ID(required=True, description="Shipping method.")
52 
53     class Meta:
54         description = "Updates the shipping method of the checkout."
55         doc_category = DOC_CATEGORY_CHECKOUT
56         error_type_class = CheckoutError
57         error_type_field = "checkout_errors"
58         webhook_events_info = [
59             WebhookEventInfo(
60                 type=WebhookEventSyncType.SHIPPING_LIST_METHODS_FOR_CHECKOUT,
61                 description=(
62                     "Triggered when updating the checkout shipping method with "
63                     "the external one."
64                 ),
65             ),
66             WebhookEventInfo(
67                 type=WebhookEventAsyncType.CHECKOUT_UPDATED,
68                 description="A checkout was updated.",
69             ),
70         ]
71 
72     @staticmethod
73     def _resolve_delivery_method_type(id_) -> Optional[str]:
74         if id_ is None:
75             return None
76 
77         possible_types = ("ShippingMethod", APP_ID_PREFIX)
78         type_, id_ = from_global_id_or_error(id_)
79         str_type = str(type_)
80 
81         if str_type not in possible_types:
82             raise ValidationError(
83                 {
84                     "shipping_method_id": ValidationError(
85                         "ID does not belong to known shipping methods",
86                         code=CheckoutErrorCode.INVALID.value,
87                     )
88                 }
89             )
90 
91         return str_type
92 
93     @classmethod
94     def perform_mutation(  # type: ignore[override]
95         cls,
96         _root,
97         info: ResolveInfo,
98         /,
99         *,
100         checkout_id=None,
101         id=None,
102         shipping_method_id,
103         token=None,
104     ):
105         checkout = get_checkout(cls, info, checkout_id=checkout_id, token=token, id=id)
106 
107         manager = get_plugin_manager_promise(info.context).get()
108 
109         lines, unavailable_variant_pks = fetch_checkout_lines(checkout)
110         if unavailable_variant_pks:
111             not_available_variants_ids = {
112                 graphene.Node.to_global_id("ProductVariant", pk)
113                 for pk in unavailable_variant_pks
114             }
115             raise ValidationError(
116                 {
117                     "lines": ValidationError(
118                         "Some of the checkout lines variants are unavailable.",
119                         code=CheckoutErrorCode.UNAVAILABLE_VARIANT_IN_CHANNEL.value,
120                         params={"variants": not_available_variants_ids},
121                     )
122                 }
123             )
124         checkout_info = fetch_checkout_info(checkout, lines, manager)
125         if not is_shipping_required(lines):
126             raise ValidationError(
127                 {
128                     "shipping_method": ValidationError(
129                         ERROR_DOES_NOT_SHIP,
130                         code=CheckoutErrorCode.SHIPPING_NOT_REQUIRED.value,
131                     )
132                 }
133             )
134 
135         type_name = cls._resolve_delivery_method_type(shipping_method_id)
136 
137         if type_name == "ShippingMethod":
138             return cls.perform_on_shipping_method(
139                 info, shipping_method_id, checkout_info, lines, checkout, manager
140             )
141         return cls.perform_on_external_shipping_method(
142             info, shipping_method_id, checkout_info, lines, checkout, manager
143         )
144 
145     @staticmethod
146     def _check_delivery_method(
147         checkout_info,
148         lines,
149         *,
150         delivery_method: Optional[shipping_interface.ShippingMethodData],
151     ) -> None:
152         delivery_method_is_valid = clean_delivery_method(
153             checkout_info=checkout_info,
154             lines=lines,
155             method=delivery_method,
156         )
157         if not delivery_method_is_valid or not delivery_method:
158             raise ValidationError(
159                 {
160                     "shipping_method": ValidationError(
161                         "This shipping method is not applicable.",
162                         code=CheckoutErrorCode.SHIPPING_METHOD_NOT_APPLICABLE.value,
163                     )
164                 }
165             )
166 
167     @classmethod
168     def perform_on_shipping_method(
169         cls,
170         info: ResolveInfo,
171         shipping_method_id,
172         checkout_info,
173         lines,
174         checkout,
175         manager,
176     ):
177         shipping_method = cls.get_node_or_error(
178             info,
179             shipping_method_id,
180             only_type=ShippingMethod,
181             field="shipping_method_id",
182             qs=shipping_models.ShippingMethod.objects.prefetch_related(
183                 "postal_code_rules"
184             ),
185         )
186         listing = shipping_models.ShippingMethodChannelListing.objects.filter(
187             shipping_method=shipping_method,
188             channel=checkout_info.channel,
189         ).first()
190         if not listing:
191             raise ValidationError(
192                 {
193                     "shipping_method": ValidationError(
194                         "Shipping method not found for this channel.",
195                         code=CheckoutErrorCode.NOT_FOUND.value,
196                     )
197                 }
198             )
199         delivery_method = convert_to_shipping_method_data(shipping_method, listing)
200 
201         cls._check_delivery_method(
202             checkout_info, lines, delivery_method=delivery_method
203         )
204 
205         delete_external_shipping_id(checkout=checkout)
206         checkout.shipping_method = shipping_method
207         invalidate_prices_updated_fields = invalidate_checkout_prices(
208             checkout_info, lines, manager, save=False
209         )
210         checkout.save(
211             update_fields=[
212                 "shipping_method",
213             ]
214             + invalidate_prices_updated_fields
215         )
216         get_checkout_metadata(checkout).save()
217 
218         cls.call_event(manager.checkout_updated, checkout)
219         return CheckoutShippingMethodUpdate(checkout=checkout)
220 
221     @classmethod
222     def perform_on_external_shipping_method(
223         cls,
224         info: ResolveInfo,
225         shipping_method_id,
226         checkout_info,
227         lines,
228         checkout,
229         manager,
230     ):
231         delivery_method = manager.get_shipping_method(
232             checkout=checkout,
233             channel_slug=checkout.channel.slug,
234             shipping_method_id=shipping_method_id,
235         )
236 
237         cls._check_delivery_method(
238             checkout_info, lines, delivery_method=delivery_method
239         )
240 
241         set_external_shipping_id(checkout=checkout, app_shipping_id=delivery_method.id)
242         checkout.shipping_method = None
243         invalidate_prices_updated_fields = invalidate_checkout_prices(
244             checkout_info, lines, manager, save=False
245         )
246         checkout.save(
247             update_fields=[
248                 "shipping_method",
249             ]
250             + invalidate_prices_updated_fields
251         )
252         get_checkout_metadata(checkout).save()
253         cls.call_event(manager.checkout_updated, checkout)
254 
255         return CheckoutShippingMethodUpdate(checkout=checkout)
256
```
--- 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/graphql/checkout/mutations/checkout_shipping_method_update.py b/saleor/graphql/checkout/mutations/checkout_shipping_method_update.py
--- a/saleor/graphql/checkout/mutations/checkout_shipping_method_update.py
+++ b/saleor/graphql/checkout/mutations/checkout_shipping_method_update.py
@@ -48,7 +48,9 @@
                 f"The ID of the checkout. {DEPRECATED_IN_3X_INPUT} Use `id` instead."
             ),
         )
-        shipping_method_id = graphene.ID(required=True, description="Shipping method.")
+        shipping_method_id = graphene.ID(
+            required=False, default_value=None, description="Shipping method."
+        )
 
     class Meta:
         description = "Updates the shipping method of the checkout."
@@ -91,7 +93,7 @@
         return str_type
 
     @classmethod
-    def perform_mutation(  # type: ignore[override]
+    def perform_mutation(
         cls,
         _root,
         info: ResolveInfo,
@@ -99,7 +101,7 @@
         *,
         checkout_id=None,
         id=None,
-        shipping_method_id,
+        shipping_method_id=None,
         token=None,
     ):
         checkout = get_checkout(cls, info, checkout_id=checkout_id, token=token, id=id)
@@ -131,6 +133,8 @@
                     )
                 }
             )
+        if shipping_method_id is None:
+            return cls.remove_shipping_method(checkout, checkout_info, lines, manager)
 
         type_name = cls._resolve_delivery_method_type(shipping_method_id)
 
@@ -253,3 +257,21 @@
         cls.call_event(manager.checkout_updated, checkout)
 
         return CheckoutShippingMethodUpdate(checkout=checkout)
+
+    @classmethod
+    def remove_shipping_method(cls, checkout, checkout_info, lines, manager):
+        checkout.shipping_method = None
+        delete_external_shipping_id(checkout=checkout)
+        invalidate_prices_updated_fields = invalidate_checkout_prices(
+            checkout_info, lines, manager, save=False
+        )
+        checkout.save(
+            update_fields=[
+                "shipping_method",
+            ]
+            + invalidate_prices_updated_fields
+        )
+        get_checkout_metadata(checkout).save()
+
+        cls.call_event(manager.checkout_updated, checkout)
+        return CheckoutShippingMethodUpdate(checkout=checkout)

{"golden_diff": "diff --git a/saleor/graphql/checkout/mutations/checkout_shipping_method_update.py b/saleor/graphql/checkout/mutations/checkout_shipping_method_update.py\n--- a/saleor/graphql/checkout/mutations/checkout_shipping_method_update.py\n+++ b/saleor/graphql/checkout/mutations/checkout_shipping_method_update.py\n@@ -48,7 +48,9 @@\n                 f\"The ID of the checkout. {DEPRECATED_IN_3X_INPUT} Use `id` instead.\"\n             ),\n         )\n-        shipping_method_id = graphene.ID(required=True, description=\"Shipping method.\")\n+        shipping_method_id = graphene.ID(\n+            required=False, default_value=None, description=\"Shipping method.\"\n+        )\n \n     class Meta:\n         description = \"Updates the shipping method of the checkout.\"\n@@ -91,7 +93,7 @@\n         return str_type\n \n     @classmethod\n-    def perform_mutation(  # type: ignore[override]\n+    def perform_mutation(\n         cls,\n         _root,\n         info: ResolveInfo,\n@@ -99,7 +101,7 @@\n         *,\n         checkout_id=None,\n         id=None,\n-        shipping_method_id,\n+        shipping_method_id=None,\n         token=None,\n     ):\n         checkout = get_checkout(cls, info, checkout_id=checkout_id, token=token, id=id)\n@@ -131,6 +133,8 @@\n                     )\n                 }\n             )\n+        if shipping_method_id is None:\n+            return cls.remove_shipping_method(checkout, checkout_info, lines, manager)\n \n         type_name = cls._resolve_delivery_method_type(shipping_method_id)\n \n@@ -253,3 +257,21 @@\n         cls.call_event(manager.checkout_updated, checkout)\n \n         return CheckoutShippingMethodUpdate(checkout=checkout)\n+\n+    @classmethod\n+    def remove_shipping_method(cls, checkout, checkout_info, lines, manager):\n+        checkout.shipping_method = None\n+        delete_external_shipping_id(checkout=checkout)\n+        invalidate_prices_updated_fields = invalidate_checkout_prices(\n+            checkout_info, lines, manager, save=False\n+        )\n+        checkout.save(\n+            update_fields=[\n+                \"shipping_method\",\n+            ]\n+            + invalidate_prices_updated_fields\n+        )\n+        get_checkout_metadata(checkout).save()\n+\n+        cls.call_event(manager.checkout_updated, checkout)\n+        return CheckoutShippingMethodUpdate(checkout=checkout)\n", "issue": "Allow to remove shipping method\nCurrent state:\r\nOnce the Shipping Method is assigned to the checkout, it cannot be removed by the user.\r\n\r\nDoD:\r\nmake `checkoutShippingMethodUpdate.shippingMethodId` argument nullable\r\nif null is passed as a value, remove the shipping method from the checkout\n", "before_files": [{"content": "from typing import Optional\n\nimport graphene\nfrom django.core.exceptions import ValidationError\n\nfrom ....checkout.error_codes import CheckoutErrorCode\nfrom ....checkout.fetch import fetch_checkout_info, fetch_checkout_lines\nfrom ....checkout.utils import (\n    delete_external_shipping_id,\n    get_checkout_metadata,\n    invalidate_checkout_prices,\n    is_shipping_required,\n    set_external_shipping_id,\n)\nfrom ....plugins.webhook.utils import APP_ID_PREFIX\nfrom ....shipping import interface as shipping_interface\nfrom ....shipping import models as shipping_models\nfrom ....shipping.utils import convert_to_shipping_method_data\nfrom ....webhook.event_types import WebhookEventAsyncType, WebhookEventSyncType\nfrom ...core import ResolveInfo\nfrom ...core.descriptions import ADDED_IN_34, DEPRECATED_IN_3X_INPUT\nfrom ...core.doc_category import DOC_CATEGORY_CHECKOUT\nfrom ...core.mutations import BaseMutation\nfrom ...core.scalars import UUID\nfrom ...core.types import CheckoutError\nfrom ...core.utils import WebhookEventInfo, from_global_id_or_error\nfrom ...plugins.dataloaders import get_plugin_manager_promise\nfrom ...shipping.types import ShippingMethod\nfrom ..types import Checkout\nfrom .utils import ERROR_DOES_NOT_SHIP, clean_delivery_method, get_checkout\n\n\nclass CheckoutShippingMethodUpdate(BaseMutation):\n    checkout = graphene.Field(Checkout, description=\"An updated checkout.\")\n\n    class Arguments:\n        id = graphene.ID(\n            description=\"The checkout's ID.\" + ADDED_IN_34,\n            required=False,\n        )\n        token = UUID(\n            description=f\"Checkout token.{DEPRECATED_IN_3X_INPUT} Use `id` instead.\",\n            required=False,\n        )\n        checkout_id = graphene.ID(\n            required=False,\n            description=(\n                f\"The ID of the checkout. {DEPRECATED_IN_3X_INPUT} Use `id` instead.\"\n            ),\n        )\n        shipping_method_id = graphene.ID(required=True, description=\"Shipping method.\")\n\n    class Meta:\n        description = \"Updates the shipping method of the checkout.\"\n        doc_category = DOC_CATEGORY_CHECKOUT\n        error_type_class = CheckoutError\n        error_type_field = \"checkout_errors\"\n        webhook_events_info = [\n            WebhookEventInfo(\n                type=WebhookEventSyncType.SHIPPING_LIST_METHODS_FOR_CHECKOUT,\n                description=(\n                    \"Triggered when updating the checkout shipping method with \"\n                    \"the external one.\"\n                ),\n            ),\n            WebhookEventInfo(\n                type=WebhookEventAsyncType.CHECKOUT_UPDATED,\n                description=\"A checkout was updated.\",\n            ),\n        ]\n\n    @staticmethod\n    def _resolve_delivery_method_type(id_) -> Optional[str]:\n        if id_ is None:\n            return None\n\n        possible_types = (\"ShippingMethod\", APP_ID_PREFIX)\n        type_, id_ = from_global_id_or_error(id_)\n        str_type = str(type_)\n\n        if str_type not in possible_types:\n            raise ValidationError(\n                {\n                    \"shipping_method_id\": ValidationError(\n                        \"ID does not belong to known shipping methods\",\n                        code=CheckoutErrorCode.INVALID.value,\n                    )\n                }\n            )\n\n        return str_type\n\n    @classmethod\n    def perform_mutation(  # type: ignore[override]\n        cls,\n        _root,\n        info: ResolveInfo,\n        /,\n        *,\n        checkout_id=None,\n        id=None,\n        shipping_method_id,\n        token=None,\n    ):\n        checkout = get_checkout(cls, info, checkout_id=checkout_id, token=token, id=id)\n\n        manager = get_plugin_manager_promise(info.context).get()\n\n        lines, unavailable_variant_pks = fetch_checkout_lines(checkout)\n        if unavailable_variant_pks:\n            not_available_variants_ids = {\n                graphene.Node.to_global_id(\"ProductVariant\", pk)\n                for pk in unavailable_variant_pks\n            }\n            raise ValidationError(\n                {\n                    \"lines\": ValidationError(\n                        \"Some of the checkout lines variants are unavailable.\",\n                        code=CheckoutErrorCode.UNAVAILABLE_VARIANT_IN_CHANNEL.value,\n                        params={\"variants\": not_available_variants_ids},\n                    )\n                }\n            )\n        checkout_info = fetch_checkout_info(checkout, lines, manager)\n        if not is_shipping_required(lines):\n            raise ValidationError(\n                {\n                    \"shipping_method\": ValidationError(\n                        ERROR_DOES_NOT_SHIP,\n                        code=CheckoutErrorCode.SHIPPING_NOT_REQUIRED.value,\n                    )\n                }\n            )\n\n        type_name = cls._resolve_delivery_method_type(shipping_method_id)\n\n        if type_name == \"ShippingMethod\":\n            return cls.perform_on_shipping_method(\n                info, shipping_method_id, checkout_info, lines, checkout, manager\n            )\n        return cls.perform_on_external_shipping_method(\n            info, shipping_method_id, checkout_info, lines, checkout, manager\n        )\n\n    @staticmethod\n    def _check_delivery_method(\n        checkout_info,\n        lines,\n        *,\n        delivery_method: Optional[shipping_interface.ShippingMethodData],\n    ) -> None:\n        delivery_method_is_valid = clean_delivery_method(\n            checkout_info=checkout_info,\n            lines=lines,\n            method=delivery_method,\n        )\n        if not delivery_method_is_valid or not delivery_method:\n            raise ValidationError(\n                {\n                    \"shipping_method\": ValidationError(\n                        \"This shipping method is not applicable.\",\n                        code=CheckoutErrorCode.SHIPPING_METHOD_NOT_APPLICABLE.value,\n                    )\n                }\n            )\n\n    @classmethod\n    def perform_on_shipping_method(\n        cls,\n        info: ResolveInfo,\n        shipping_method_id,\n        checkout_info,\n        lines,\n        checkout,\n        manager,\n    ):\n        shipping_method = cls.get_node_or_error(\n            info,\n            shipping_method_id,\n            only_type=ShippingMethod,\n            field=\"shipping_method_id\",\n            qs=shipping_models.ShippingMethod.objects.prefetch_related(\n                \"postal_code_rules\"\n            ),\n        )\n        listing = shipping_models.ShippingMethodChannelListing.objects.filter(\n            shipping_method=shipping_method,\n            channel=checkout_info.channel,\n        ).first()\n        if not listing:\n            raise ValidationError(\n                {\n                    \"shipping_method\": ValidationError(\n                        \"Shipping method not found for this channel.\",\n                        code=CheckoutErrorCode.NOT_FOUND.value,\n                    )\n                }\n            )\n        delivery_method = convert_to_shipping_method_data(shipping_method, listing)\n\n        cls._check_delivery_method(\n            checkout_info, lines, delivery_method=delivery_method\n        )\n\n        delete_external_shipping_id(checkout=checkout)\n        checkout.shipping_method = shipping_method\n        invalidate_prices_updated_fields = invalidate_checkout_prices(\n            checkout_info, lines, manager, save=False\n        )\n        checkout.save(\n            update_fields=[\n                \"shipping_method\",\n            ]\n            + invalidate_prices_updated_fields\n        )\n        get_checkout_metadata(checkout).save()\n\n        cls.call_event(manager.checkout_updated, checkout)\n        return CheckoutShippingMethodUpdate(checkout=checkout)\n\n    @classmethod\n    def perform_on_external_shipping_method(\n        cls,\n        info: ResolveInfo,\n        shipping_method_id,\n        checkout_info,\n        lines,\n        checkout,\n        manager,\n    ):\n        delivery_method = manager.get_shipping_method(\n            checkout=checkout,\n            channel_slug=checkout.channel.slug,\n            shipping_method_id=shipping_method_id,\n        )\n\n        cls._check_delivery_method(\n            checkout_info, lines, delivery_method=delivery_method\n        )\n\n        set_external_shipping_id(checkout=checkout, app_shipping_id=delivery_method.id)\n        checkout.shipping_method = None\n        invalidate_prices_updated_fields = invalidate_checkout_prices(\n            checkout_info, lines, manager, save=False\n        )\n        checkout.save(\n            update_fields=[\n                \"shipping_method\",\n            ]\n            + invalidate_prices_updated_fields\n        )\n        get_checkout_metadata(checkout).save()\n        cls.call_event(manager.checkout_updated, checkout)\n\n        return CheckoutShippingMethodUpdate(checkout=checkout)\n", "path": "saleor/graphql/checkout/mutations/checkout_shipping_method_update.py"}], "after_files": [{"content": "from typing import Optional\n\nimport graphene\nfrom django.core.exceptions import ValidationError\n\nfrom ....checkout.error_codes import CheckoutErrorCode\nfrom ....checkout.fetch import fetch_checkout_info, fetch_checkout_lines\nfrom ....checkout.utils import (\n    delete_external_shipping_id,\n    get_checkout_metadata,\n    invalidate_checkout_prices,\n    is_shipping_required,\n    set_external_shipping_id,\n)\nfrom ....plugins.webhook.utils import APP_ID_PREFIX\nfrom ....shipping import interface as shipping_interface\nfrom ....shipping import models as shipping_models\nfrom ....shipping.utils import convert_to_shipping_method_data\nfrom ....webhook.event_types import WebhookEventAsyncType, WebhookEventSyncType\nfrom ...core import ResolveInfo\nfrom ...core.descriptions import ADDED_IN_34, DEPRECATED_IN_3X_INPUT\nfrom ...core.doc_category import DOC_CATEGORY_CHECKOUT\nfrom ...core.mutations import BaseMutation\nfrom ...core.scalars import UUID\nfrom ...core.types import CheckoutError\nfrom ...core.utils import WebhookEventInfo, from_global_id_or_error\nfrom ...plugins.dataloaders import get_plugin_manager_promise\nfrom ...shipping.types import ShippingMethod\nfrom ..types import Checkout\nfrom .utils import ERROR_DOES_NOT_SHIP, clean_delivery_method, get_checkout\n\n\nclass CheckoutShippingMethodUpdate(BaseMutation):\n    checkout = graphene.Field(Checkout, description=\"An updated checkout.\")\n\n    class Arguments:\n        id = graphene.ID(\n            description=\"The checkout's ID.\" + ADDED_IN_34,\n            required=False,\n        )\n        token = UUID(\n            description=f\"Checkout token.{DEPRECATED_IN_3X_INPUT} Use `id` instead.\",\n            required=False,\n        )\n        checkout_id = graphene.ID(\n            required=False,\n            description=(\n                f\"The ID of the checkout. {DEPRECATED_IN_3X_INPUT} Use `id` instead.\"\n            ),\n        )\n        shipping_method_id = graphene.ID(\n            required=False, default_value=None, description=\"Shipping method.\"\n        )\n\n    class Meta:\n        description = \"Updates the shipping method of the checkout.\"\n        doc_category = DOC_CATEGORY_CHECKOUT\n        error_type_class = CheckoutError\n        error_type_field = \"checkout_errors\"\n        webhook_events_info = [\n            WebhookEventInfo(\n                type=WebhookEventSyncType.SHIPPING_LIST_METHODS_FOR_CHECKOUT,\n                description=(\n                    \"Triggered when updating the checkout shipping method with \"\n                    \"the external one.\"\n                ),\n            ),\n            WebhookEventInfo(\n                type=WebhookEventAsyncType.CHECKOUT_UPDATED,\n                description=\"A checkout was updated.\",\n            ),\n        ]\n\n    @staticmethod\n    def _resolve_delivery_method_type(id_) -> Optional[str]:\n        if id_ is None:\n            return None\n\n        possible_types = (\"ShippingMethod\", APP_ID_PREFIX)\n        type_, id_ = from_global_id_or_error(id_)\n        str_type = str(type_)\n\n        if str_type not in possible_types:\n            raise ValidationError(\n                {\n                    \"shipping_method_id\": ValidationError(\n                        \"ID does not belong to known shipping methods\",\n                        code=CheckoutErrorCode.INVALID.value,\n                    )\n                }\n            )\n\n        return str_type\n\n    @classmethod\n    def perform_mutation(\n        cls,\n        _root,\n        info: ResolveInfo,\n        /,\n        *,\n        checkout_id=None,\n        id=None,\n        shipping_method_id=None,\n        token=None,\n    ):\n        checkout = get_checkout(cls, info, checkout_id=checkout_id, token=token, id=id)\n\n        manager = get_plugin_manager_promise(info.context).get()\n\n        lines, unavailable_variant_pks = fetch_checkout_lines(checkout)\n        if unavailable_variant_pks:\n            not_available_variants_ids = {\n                graphene.Node.to_global_id(\"ProductVariant\", pk)\n                for pk in unavailable_variant_pks\n            }\n            raise ValidationError(\n                {\n                    \"lines\": ValidationError(\n                        \"Some of the checkout lines variants are unavailable.\",\n                        code=CheckoutErrorCode.UNAVAILABLE_VARIANT_IN_CHANNEL.value,\n                        params={\"variants\": not_available_variants_ids},\n                    )\n                }\n            )\n        checkout_info = fetch_checkout_info(checkout, lines, manager)\n        if not is_shipping_required(lines):\n            raise ValidationError(\n                {\n                    \"shipping_method\": ValidationError(\n                        ERROR_DOES_NOT_SHIP,\n                        code=CheckoutErrorCode.SHIPPING_NOT_REQUIRED.value,\n                    )\n                }\n            )\n        if shipping_method_id is None:\n            return cls.remove_shipping_method(checkout, checkout_info, lines, manager)\n\n        type_name = cls._resolve_delivery_method_type(shipping_method_id)\n\n        if type_name == \"ShippingMethod\":\n            return cls.perform_on_shipping_method(\n                info, shipping_method_id, checkout_info, lines, checkout, manager\n            )\n        return cls.perform_on_external_shipping_method(\n            info, shipping_method_id, checkout_info, lines, checkout, manager\n        )\n\n    @staticmethod\n    def _check_delivery_method(\n        checkout_info,\n        lines,\n        *,\n        delivery_method: Optional[shipping_interface.ShippingMethodData],\n    ) -> None:\n        delivery_method_is_valid = clean_delivery_method(\n            checkout_info=checkout_info,\n            lines=lines,\n            method=delivery_method,\n        )\n        if not delivery_method_is_valid or not delivery_method:\n            raise ValidationError(\n                {\n                    \"shipping_method\": ValidationError(\n                        \"This shipping method is not applicable.\",\n                        code=CheckoutErrorCode.SHIPPING_METHOD_NOT_APPLICABLE.value,\n                    )\n                }\n            )\n\n    @classmethod\n    def perform_on_shipping_method(\n        cls,\n        info: ResolveInfo,\n        shipping_method_id,\n        checkout_info,\n        lines,\n        checkout,\n        manager,\n    ):\n        shipping_method = cls.get_node_or_error(\n            info,\n            shipping_method_id,\n            only_type=ShippingMethod,\n            field=\"shipping_method_id\",\n            qs=shipping_models.ShippingMethod.objects.prefetch_related(\n                \"postal_code_rules\"\n            ),\n        )\n        listing = shipping_models.ShippingMethodChannelListing.objects.filter(\n            shipping_method=shipping_method,\n            channel=checkout_info.channel,\n        ).first()\n        if not listing:\n            raise ValidationError(\n                {\n                    \"shipping_method\": ValidationError(\n                        \"Shipping method not found for this channel.\",\n                        code=CheckoutErrorCode.NOT_FOUND.value,\n                    )\n                }\n            )\n        delivery_method = convert_to_shipping_method_data(shipping_method, listing)\n\n        cls._check_delivery_method(\n            checkout_info, lines, delivery_method=delivery_method\n        )\n\n        delete_external_shipping_id(checkout=checkout)\n        checkout.shipping_method = shipping_method\n        invalidate_prices_updated_fields = invalidate_checkout_prices(\n            checkout_info, lines, manager, save=False\n        )\n        checkout.save(\n            update_fields=[\n                \"shipping_method\",\n            ]\n            + invalidate_prices_updated_fields\n        )\n        get_checkout_metadata(checkout).save()\n\n        cls.call_event(manager.checkout_updated, checkout)\n        return CheckoutShippingMethodUpdate(checkout=checkout)\n\n    @classmethod\n    def perform_on_external_shipping_method(\n        cls,\n        info: ResolveInfo,\n        shipping_method_id,\n        checkout_info,\n        lines,\n        checkout,\n        manager,\n    ):\n        delivery_method = manager.get_shipping_method(\n            checkout=checkout,\n            channel_slug=checkout.channel.slug,\n            shipping_method_id=shipping_method_id,\n        )\n\n        cls._check_delivery_method(\n            checkout_info, lines, delivery_method=delivery_method\n        )\n\n        set_external_shipping_id(checkout=checkout, app_shipping_id=delivery_method.id)\n        checkout.shipping_method = None\n        invalidate_prices_updated_fields = invalidate_checkout_prices(\n            checkout_info, lines, manager, save=False\n        )\n        checkout.save(\n            update_fields=[\n                \"shipping_method\",\n            ]\n            + invalidate_prices_updated_fields\n        )\n        get_checkout_metadata(checkout).save()\n        cls.call_event(manager.checkout_updated, checkout)\n\n        return CheckoutShippingMethodUpdate(checkout=checkout)\n\n    @classmethod\n    def remove_shipping_method(cls, checkout, checkout_info, lines, manager):\n        checkout.shipping_method = None\n        delete_external_shipping_id(checkout=checkout)\n        invalidate_prices_updated_fields = invalidate_checkout_prices(\n            checkout_info, lines, manager, save=False\n        )\n        checkout.save(\n            update_fields=[\n                \"shipping_method\",\n            ]\n            + invalidate_prices_updated_fields\n        )\n        get_checkout_metadata(checkout).save()\n\n        cls.call_event(manager.checkout_updated, checkout)\n        return CheckoutShippingMethodUpdate(checkout=checkout)\n", "path": "saleor/graphql/checkout/mutations/checkout_shipping_method_update.py"}]}