Skip to content

Commit

Permalink
Merge branch '3.18' of github.com:saleor/saleor into fix-transaction-…
Browse files Browse the repository at this point in the history
…update-metadata-318
  • Loading branch information
tomaszszymanski129 committed Apr 26, 2024
2 parents ef1a056 + cbd1dad commit 662b513
Show file tree
Hide file tree
Showing 14 changed files with 539 additions and 31 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "saleor",
"version": "3.18.35",
"version": "3.18.36",
"engines": {
"node": ">=16 <17",
"npm": ">=7"
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Expand Up @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "saleor"
version = "3.18.35"
version = "3.18.36"
description = "A modular, high performance, headless e-commerce platform built with Python, GraphQL, Django, and React."
authors = [ "Saleor Commerce <hello@saleor.io>" ]
license = "BSD-3-Clause"
Expand Down
2 changes: 1 addition & 1 deletion saleor/__init__.py
Expand Up @@ -3,7 +3,7 @@
from .celeryconf import app as celery_app

__all__ = ["celery_app"]
__version__ = "3.18.35"
__version__ = "3.18.36"


class PatchedSubscriberExecutionContext:
Expand Down
85 changes: 66 additions & 19 deletions saleor/checkout/complete_checkout.py
Expand Up @@ -114,14 +114,53 @@ def _process_voucher_data_for_order(checkout_info: "CheckoutInfo") -> dict:
return {}

customer_email = cast(str, checkout_info.get_customer_email())
increase_voucher_usage(voucher, voucher_code, customer_email)

_increase_checkout_voucher_usage(checkout, voucher_code, voucher, customer_email)
return {
"voucher": voucher,
"voucher_code": voucher_code.code,
}


@traced_atomic_transaction()
def _increase_checkout_voucher_usage(
checkout: "Checkout",
voucher_code: "VoucherCode",
voucher: "Voucher",
customer_email: str,
):
# Prevent race condition when two different threads are processing the same checkout
# with limited usage voucher assigned, both threads increasing the
# voucher usage which causing `NotApplicable` error for voucher.
if checkout.is_voucher_usage_increased:
return

increase_voucher_usage(voucher, voucher_code, customer_email)
checkout.is_voucher_usage_increased = True
checkout.save(update_fields=["is_voucher_usage_increased"])


@traced_atomic_transaction()
def _release_checkout_voucher_usage(
checkout: "Checkout",
voucher_code: Optional["VoucherCode"],
voucher: Optional["Voucher"],
user_email: Optional[str],
):
if not checkout.is_voucher_usage_increased:
return

checkout.is_voucher_usage_increased = False
checkout.save(update_fields=["is_voucher_usage_increased"])

if voucher_code:
release_voucher_code_usage(
voucher_code,
voucher,
user_email,
)


def _process_shipping_data_for_order(
checkout_info: "CheckoutInfo",
base_shipping_price: Money,
Expand Down Expand Up @@ -501,7 +540,8 @@ def _prepare_order_data(
try:
manager.preprocess_order_creation(checkout_info, lines)
except TaxError:
release_voucher_code_usage(
_release_checkout_voucher_usage(
checkout,
checkout_info.voucher_code,
checkout_info.voucher,
order_data.get("user_email"),
Expand Down Expand Up @@ -792,6 +832,7 @@ def _get_order_data(


def _process_payment(
checkout: Checkout,
payment: Payment,
customer_id: Optional[str],
store_source: bool,
Expand Down Expand Up @@ -826,10 +867,8 @@ def _process_payment(
if not txn.is_success:
raise PaymentError(txn.error)
except PaymentError as e:
release_voucher_code_usage(
voucher_code,
voucher,
order_data.get("user_email"),
_release_checkout_voucher_usage(
checkout, voucher_code, voucher, order_data.get("user_email")
)
raise ValidationError(str(e), code=CheckoutErrorCode.PAYMENT_ERROR.value)
return txn
Expand Down Expand Up @@ -905,7 +944,8 @@ def complete_checkout_post_payment_part(
action_required = txn.action_required
if action_required:
action_data = txn.action_required_data
release_voucher_code_usage(
_release_checkout_voucher_usage(
checkout_info.checkout,
checkout_info.voucher_code,
checkout_info.voucher,
order_data.get("user_email"),
Expand All @@ -928,7 +968,8 @@ def complete_checkout_post_payment_part(
# remove checkout after order is successfully created
checkout_info.checkout.delete()
except InsufficientStock as e:
release_voucher_code_usage(
_release_checkout_voucher_usage(
checkout_info.checkout,
checkout_info.voucher_code,
checkout_info.voucher,
order_data.get("user_email"),
Expand All @@ -939,7 +980,8 @@ def complete_checkout_post_payment_part(
error = prepare_insufficient_stock_checkout_validation_error(e)
raise error
except GiftCardNotApplicable as e:
release_voucher_code_usage(
_release_checkout_voucher_usage(
checkout_info.checkout,
checkout_info.voucher_code,
checkout_info.voucher,
order_data.get("user_email"),
Expand Down Expand Up @@ -978,8 +1020,9 @@ def _increase_voucher_code_usage_value(checkout_info: "CheckoutInfo"):
return None

customer_email = cast(str, checkout_info.get_customer_email())
increase_voucher_usage(voucher, code, customer_email)

checkout = checkout_info.checkout
_increase_checkout_voucher_usage(checkout, code, voucher, customer_email)
return code


Expand Down Expand Up @@ -1321,17 +1364,13 @@ def create_order_from_checkout(
checkout_info.checkout.delete()
return order
except InsufficientStock:
release_voucher_code_usage(
code,
voucher,
checkout_info.checkout.get_customer_email(),
_release_checkout_voucher_usage(
checkout, code, voucher, checkout_info.checkout.get_customer_email()
)
raise
except GiftCardNotApplicable:
release_voucher_code_usage(
code,
voucher,
checkout_info.checkout.get_customer_email(),
_release_checkout_voucher_usage(
checkout, code, voucher, checkout_info.checkout.get_customer_email()
)
raise

Expand Down Expand Up @@ -1498,9 +1537,17 @@ def complete_checkout_with_payment(
voucher_code = checkout_info.voucher_code
if payment:
with transaction_with_commit_on_errors():
Checkout.objects.select_for_update().filter(pk=checkout_pk).first()
checkout = (
Checkout.objects.select_for_update().filter(pk=checkout_pk).first()
)

if not checkout:
order = Order.objects.get_by_checkout_token(checkout_pk)
return order, False, {}

payment = Payment.objects.select_for_update().get(id=payment.id)
txn = _process_payment(
checkout=checkout,
payment=payment,
customer_id=customer_id,
store_source=store_source,
Expand Down
@@ -0,0 +1,25 @@
# Generated by Django 3.2.25 on 2024-04-23 06:43

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("checkout", "0063_auto_20240402_1114"),
]

operations = [
migrations.AddField(
model_name="checkout",
name="is_voucher_usage_increased",
field=models.BooleanField(default=False),
),
migrations.RunSQL(
"""
ALTER TABLE checkout_checkout
ALTER COLUMN is_voucher_usage_increased
SET DEFAULT false;
""",
migrations.RunSQL.noop,
),
]
7 changes: 6 additions & 1 deletion saleor/checkout/models.py
Expand Up @@ -171,8 +171,13 @@ class Checkout(models.Model):
discount_name = models.CharField(max_length=255, blank=True, null=True)

translated_discount_name = models.CharField(max_length=255, blank=True, null=True)
voucher_code = models.CharField(max_length=255, blank=True, null=True)
gift_cards = models.ManyToManyField(GiftCard, blank=True, related_name="checkouts")
voucher_code = models.CharField(max_length=255, blank=True, null=True)

# The field is to prevent race condition when two different threads are processing
# the same checkout with limited usage voucher assigned, both threads increasing the
# voucher usage which causing `NotApplicable` error for voucher
is_voucher_usage_increased = models.BooleanField(default=False)

redirect_url = models.URLField(blank=True, null=True)
tracking_code = models.CharField(max_length=255, blank=True, null=True)
Expand Down
53 changes: 53 additions & 0 deletions saleor/checkout/tests/test_checkout.py
Expand Up @@ -48,6 +48,7 @@
get_checkout_metadata,
get_external_shipping_id,
get_voucher_discount_for_checkout,
get_voucher_for_checkout,
get_voucher_for_checkout_info,
is_fully_paid,
recalculate_checkout_discount,
Expand Down Expand Up @@ -1181,6 +1182,58 @@ def test_get_voucher_for_checkout_info_no_voucher_code(checkout):
assert checkout_voucher is None


def test_get_voucher_for_checkout(checkout_with_voucher, voucher):
# given
checkout = checkout_with_voucher
voucher.usage_limit = 1
voucher.save(update_fields=["usage_limit"])

# when
checkout_voucher, _ = get_voucher_for_checkout(checkout, checkout.channel.slug)

# then
assert checkout_voucher == voucher


def test_get_voucher_for_checkout_voucher_used(checkout_with_voucher, voucher):
# given
checkout = checkout_with_voucher
voucher.usage_limit = 1
voucher.save(update_fields=["usage_limit"])

code = voucher.codes.first()
code.used = 1
code.save(update_fields=["used"])

# when
checkout_voucher, _ = get_voucher_for_checkout(checkout, checkout.channel.slug)

# then
assert checkout_voucher is None


def test_get_voucher_for_checkout_voucher_used_voucher_usage_already_increased(
checkout_with_voucher, voucher
):
# given
checkout = checkout_with_voucher
checkout.is_voucher_usage_increased = True
checkout.save(update_fields=["is_voucher_usage_increased"])

voucher.usage_limit = 1
voucher.save(update_fields=["usage_limit"])

code = voucher.codes.first()
code.used = 1
code.save(update_fields=["used"])

# when
checkout_voucher, _ = get_voucher_for_checkout(checkout, checkout.channel.slug)

# then
assert checkout_voucher == voucher


def test_remove_voucher_from_checkout(checkout_with_voucher, voucher_translation_fr):
checkout = checkout_with_voucher
remove_voucher_from_checkout(checkout)
Expand Down

0 comments on commit 662b513

Please sign in to comment.