Skip to content

Commit

Permalink
Mutation for creating an order from checkout (#9343)
Browse files Browse the repository at this point in the history
* Add new mutation for creating order from checkout

* Add implementaion of mutation CheckoutFromOrderCreate

* Add preview label

* Remove field for depreceated error field

* Add missing error code to Saleor error codes

* Move validation to mutation part

* Fix typo in field describtion

* Clean in doc string in main method of the PR. Remove fixme that will be covered by separate PR

* Add missing space in description of the field.

* Apply changes after review

* Fix incorrect field name for input id field

* Rename orderFromCheckoutCreate to orderCreateFromCheckout

* Add label ADDED_IN_32 to mutation description

* Use HANDLE_CHECKOUTS permission, instead of MANAGE_CHECKOUTS

* Update changelog

* Fix tests

* Fix typing after changing order's pk from int to uuid
  • Loading branch information
Maciej Korycinski committed Mar 30, 2022
1 parent 49bafec commit ef22676
Show file tree
Hide file tree
Showing 20 changed files with 2,648 additions and 54 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ All notable, unreleased changes to this project will be documented in this file.

### Other changes
- Fix failing `checkoutCustomerAttach` mutation - #9401 by @IKarbowiak

- Add new mutation `orderCreateFromCheckout` - #9343 by @korycins

# 3.1.7

Expand Down
111 changes: 107 additions & 4 deletions saleor/checkout/checkout_cleaner.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
from typing import TYPE_CHECKING, Iterable, Optional, Type, Union
from datetime import date
from typing import TYPE_CHECKING, Iterable, List, Optional, Type, Union

import graphene
from django.core.exceptions import ValidationError

from ..core.exceptions import GiftCardNotApplicable
from ..core.taxes import TaxError
from ..discount import DiscountInfo
from ..giftcard.models import GiftCard
from ..payment import gateway
from ..payment import models as payment_models
from ..payment.error_codes import PaymentErrorCode
from ..plugins.manager import PluginsManager
from .error_codes import CheckoutErrorCode
from . import models
from .error_codes import CheckoutErrorCode, OrderCreateFromCheckoutErrorCode
from .models import Checkout
from .utils import clear_delivery_method, is_fully_paid, is_shipping_required

if TYPE_CHECKING:
Expand All @@ -17,7 +24,11 @@
def clean_checkout_shipping(
checkout_info: "CheckoutInfo",
lines: Iterable["CheckoutLineInfo"],
error_code: Union[Type[CheckoutErrorCode], Type[PaymentErrorCode]],
error_code: Union[
Type[CheckoutErrorCode],
Type[PaymentErrorCode],
Type[OrderCreateFromCheckoutErrorCode],
],
):
delivery_method_info = checkout_info.delivery_method_info

Expand Down Expand Up @@ -54,7 +65,11 @@ def clean_checkout_shipping(

def clean_billing_address(
checkout_info: "CheckoutInfo",
error_code: Union[Type[CheckoutErrorCode], Type[PaymentErrorCode]],
error_code: Union[
Type[CheckoutErrorCode],
Type[PaymentErrorCode],
Type[OrderCreateFromCheckoutErrorCode],
],
):
if not checkout_info.billing_address:
raise ValidationError(
Expand Down Expand Up @@ -84,3 +99,91 @@ def clean_checkout_payment(
"Provided payment methods can not cover the checkout's total amount",
code=error_code.CHECKOUT_NOT_FULLY_PAID.value,
)


def validate_checkout_email(checkout: models.Checkout):
if not checkout.email:
raise ValidationError(
"Checkout email must be set.",
code=CheckoutErrorCode.EMAIL_NOT_SET.value,
)


def _validate_gift_cards(checkout: Checkout):
"""Check if all gift cards assigned to checkout are available."""
today = date.today()
all_gift_cards = GiftCard.objects.filter(checkouts=checkout.token).count()
active_gift_cards = (
GiftCard.objects.active(date=today).filter(checkouts=checkout.token).count()
)
if not all_gift_cards == active_gift_cards:
msg = "Gift card has expired. Order placement cancelled."
raise GiftCardNotApplicable(msg)


def validate_checkout(
checkout_info: "CheckoutInfo",
lines: Iterable["CheckoutLineInfo"],
unavailable_variant_pks: Iterable[int],
discounts: List["DiscountInfo"],
manager: "PluginsManager",
):
"""Validate all required data for converting checkout into order."""
if not checkout_info.channel.is_active:
raise ValidationError(
{
"channel": ValidationError(
"Cannot complete checkout with inactive channel.",
code=OrderCreateFromCheckoutErrorCode.CHANNEL_INACTIVE.value,
)
}
)
if unavailable_variant_pks:
not_available_variants_ids = {
graphene.Node.to_global_id("ProductVariant", pk)
for pk in unavailable_variant_pks
}
code = OrderCreateFromCheckoutErrorCode.UNAVAILABLE_VARIANT_IN_CHANNEL.value
raise ValidationError(
{
"lines": ValidationError(
"Some of the checkout lines variants are unavailable.",
code=code,
params={"variants": not_available_variants_ids},
)
}
)
if not lines:
raise ValidationError(
{
"lines": ValidationError(
"Cannot complete checkout without lines",
code=OrderCreateFromCheckoutErrorCode.NO_LINES.value,
)
}
)

if checkout_info.checkout.voucher_code and not checkout_info.voucher:
raise ValidationError(
{
"voucher_code": ValidationError(
"Voucher not applicable",
code=OrderCreateFromCheckoutErrorCode.VOUCHER_NOT_APPLICABLE.value,
)
}
)
validate_checkout_email(checkout_info.checkout)

clean_billing_address(checkout_info, OrderCreateFromCheckoutErrorCode)
clean_checkout_shipping(checkout_info, lines, OrderCreateFromCheckoutErrorCode)
_validate_gift_cards(checkout_info.checkout)

# call plugin's hooks to validate if we are able to create an order
# can raise TaxError
try:
manager.preprocess_order_creation(checkout_info, discounts, lines)
except TaxError as tax_error:
raise ValidationError(
"Unable to calculate taxes - %s" % str(tax_error),
code=OrderCreateFromCheckoutErrorCode.TAX_ERROR.value,
)

0 comments on commit ef22676

Please sign in to comment.