Skip to content

Commit

Permalink
Add transaction items limit to TransactionSessionBase (#15793)
Browse files Browse the repository at this point in the history
* Add transaction items limit to TransactionSessionBase

* Styling
  • Loading branch information
tomaszszymanski129 committed Apr 23, 2024
1 parent 91967c5 commit 7b5bd05
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 8 deletions.
17 changes: 17 additions & 0 deletions saleor/graphql/payment/mutations/base.py
@@ -1,13 +1,15 @@
from decimal import Decimal
from typing import TYPE_CHECKING, Optional, Union

from django.conf import settings
from django.core.exceptions import ValidationError
from graphql import GraphQLError

from ....checkout import models as checkout_models
from ....checkout.calculations import fetch_checkout_data
from ....checkout.fetch import fetch_checkout_info, fetch_checkout_lines
from ....order import models as order_models
from ...core.enums import TransactionInitializeErrorCode
from ...core.mutations import BaseMutation
from ...core.utils import from_global_id_or_error

Expand Down Expand Up @@ -74,6 +76,21 @@ def clean_source_object(
)
}
)

if (
source_object.payment_transactions.count()
>= settings.TRANSACTION_ITEMS_LIMIT
):
raise ValidationError(
{
"id": ValidationError(
f"{source_object_type} transactions limit of "
f"{settings.TRANSACTION_ITEMS_LIMIT} reached.",
code=TransactionInitializeErrorCode.INVALID.value,
)
}
)

return source_object

@classmethod
Expand Down
@@ -1,4 +1,5 @@
import graphene
from django.conf import settings

from .....payment.interface import PaymentGatewayData
from ....core.descriptions import ADDED_IN_313, PREVIEW_FEATURE
Expand Down Expand Up @@ -65,7 +66,9 @@ class Meta:
"Initializes a payment gateway session. It triggers the webhook "
"`PAYMENT_GATEWAY_INITIALIZE_SESSION`, to the requested `paymentGateways`. "
"If `paymentGateways` is not provided, the webhook will be send to all "
"subscribed payment gateways." + ADDED_IN_313 + PREVIEW_FEATURE
"subscribed payment gateways. "
f"There is a limit of {settings.TRANSACTION_ITEMS_LIMIT} transaction items "
"per checkout / order." + ADDED_IN_313 + PREVIEW_FEATURE
)
error_type_class = common_types.PaymentGatewayInitializeError

Expand Down
Expand Up @@ -2,6 +2,7 @@
from typing import Optional

import graphene
from django.conf import settings
from django.core.exceptions import ValidationError

from .....app.models import App
Expand Down Expand Up @@ -93,8 +94,8 @@ class Meta:
description = (
"Initializes a transaction session. It triggers the webhook "
"`TRANSACTION_INITIALIZE_SESSION`, to the requested `paymentGateways`. "
+ ADDED_IN_313
+ PREVIEW_FEATURE
f"There is a limit of {settings.TRANSACTION_ITEMS_LIMIT} transaction "
"items per checkout / order." + ADDED_IN_313 + PREVIEW_FEATURE
)
error_type_class = common_types.TransactionInitializeError

Expand Down
@@ -1,10 +1,14 @@
from decimal import Decimal
from unittest import mock

from django.conf import settings
from django.test import override_settings

from .....checkout.calculations import fetch_checkout_data
from .....checkout.fetch import fetch_checkout_info, fetch_checkout_lines
from .....payment.interface import PaymentGatewayData
from ....core.enums import PaymentGatewayConfigErrorCode
from .....payment.models import TransactionItem
from ....core.enums import PaymentGatewayConfigErrorCode, TransactionInitializeErrorCode
from ....core.utils import to_global_id_or_none
from ....tests.utils import get_graphql_content

Expand Down Expand Up @@ -77,6 +81,40 @@ def test_for_checkout_without_payment_gateways(
mocked_initialize.assert_called_once_with(checkout.total.gross.amount, [], checkout)


@override_settings(TRANSACTION_ITEMS_LIMIT=3)
def test_for_checkout_transactions_limit_on_gateway_initialize(
user_api_client, checkout_with_prices
):
# given
TransactionItem.objects.bulk_create(
[
TransactionItem(
checkout=checkout_with_prices, currency=checkout_with_prices.currency
)
for _ in range(settings.TRANSACTION_ITEMS_LIMIT)
]
)

variables = {
"id": to_global_id_or_none(checkout_with_prices),
"paymentGateways": None,
}

# when
response = user_api_client.post_graphql(PAYMENT_GATEWAY_INITIALIZE, variables)

# then
content = get_graphql_content(response)
data = content["data"]["paymentGatewayInitialize"]
assert data["errors"]
error = data["errors"][0]
assert error["code"] == TransactionInitializeErrorCode.INVALID.name
assert error["field"] == "id"
assert error["message"] == (
f"Checkout transactions limit of {settings.TRANSACTION_ITEMS_LIMIT} reached."
)


@mock.patch("saleor.plugins.manager.PluginsManager.payment_gateway_initialize_session")
def test_for_order_without_payment_gateways(
mocked_initialize,
Expand Down
Expand Up @@ -3,6 +3,8 @@
from unittest import mock

import pytest
from django.conf import settings
from django.test import override_settings
from freezegun import freeze_time

from .....channel import TransactionFlowStrategy
Expand All @@ -17,6 +19,7 @@
TransactionSessionData,
TransactionSessionResult,
)
from .....payment.models import TransactionItem
from ....channel.enums import TransactionFlowStrategyEnum
from ....core.enums import TransactionInitializeErrorCode
from ....core.utils import to_global_id_or_none
Expand Down Expand Up @@ -233,6 +236,42 @@ def test_for_checkout_without_payment_gateway_data(
assert checkout.authorize_status == CheckoutAuthorizeStatus.PARTIAL


@override_settings(TRANSACTION_ITEMS_LIMIT=3)
def test_for_checkout_transactions_limit_on_transaction_initialize(
user_api_client, checkout_with_prices
):
# given
TransactionItem.objects.bulk_create(
[
TransactionItem(
checkout=checkout_with_prices, currency=checkout_with_prices.currency
)
for _ in range(settings.TRANSACTION_ITEMS_LIMIT)
]
)

variables = {
"action": None,
"amount": 99,
"id": to_global_id_or_none(checkout_with_prices),
"paymentGateway": {"id": "any", "data": None},
}

# when
response = user_api_client.post_graphql(TRANSACTION_INITIALIZE, variables)

# then
content = get_graphql_content(response)
data = content["data"]["transactionInitialize"]
assert data["errors"]
error = data["errors"][0]
assert error["code"] == TransactionInitializeErrorCode.INVALID.name
assert error["field"] == "id"
assert error["message"] == (
f"Checkout transactions limit of {settings.TRANSACTION_ITEMS_LIMIT} reached."
)


@mock.patch("saleor.plugins.manager.PluginsManager.transaction_initialize_session")
def test_for_checkout_with_idempotency_key(
mocked_initialize,
Expand Down
8 changes: 4 additions & 4 deletions saleor/graphql/schema.graphql
Expand Up @@ -16716,7 +16716,7 @@ type Mutation {
): TransactionEventReport @doc(category: "Payments")

"""
Initializes a payment gateway session. It triggers the webhook `PAYMENT_GATEWAY_INITIALIZE_SESSION`, to the requested `paymentGateways`. If `paymentGateways` is not provided, the webhook will be send to all subscribed payment gateways.
Initializes a payment gateway session. It triggers the webhook `PAYMENT_GATEWAY_INITIALIZE_SESSION`, to the requested `paymentGateways`. If `paymentGateways` is not provided, the webhook will be send to all subscribed payment gateways. There is a limit of 100 transaction items per checkout / order.

Added in Saleor 3.13.

Expand All @@ -16736,7 +16736,7 @@ type Mutation {
): PaymentGatewayInitialize @doc(category: "Payments")

"""
Initializes a transaction session. It triggers the webhook `TRANSACTION_INITIALIZE_SESSION`, to the requested `paymentGateways`.
Initializes a transaction session. It triggers the webhook `TRANSACTION_INITIALIZE_SESSION`, to the requested `paymentGateways`. There is a limit of 100 transaction items per checkout / order.

Added in Saleor 3.13.

Expand Down Expand Up @@ -24561,7 +24561,7 @@ enum TransactionEventReportErrorCode @doc(category: "Payments") {
}

"""
Initializes a payment gateway session. It triggers the webhook `PAYMENT_GATEWAY_INITIALIZE_SESSION`, to the requested `paymentGateways`. If `paymentGateways` is not provided, the webhook will be send to all subscribed payment gateways.
Initializes a payment gateway session. It triggers the webhook `PAYMENT_GATEWAY_INITIALIZE_SESSION`, to the requested `paymentGateways`. If `paymentGateways` is not provided, the webhook will be send to all subscribed payment gateways. There is a limit of 100 transaction items per checkout / order.

Added in Saleor 3.13.

Expand Down Expand Up @@ -24631,7 +24631,7 @@ input PaymentGatewayToInitialize @doc(category: "Payments") {
}

"""
Initializes a transaction session. It triggers the webhook `TRANSACTION_INITIALIZE_SESSION`, to the requested `paymentGateways`.
Initializes a transaction session. It triggers the webhook `TRANSACTION_INITIALIZE_SESSION`, to the requested `paymentGateways`. There is a limit of 100 transaction items per checkout / order.

Added in Saleor 3.13.

Expand Down
5 changes: 5 additions & 0 deletions saleor/settings.py
Expand Up @@ -901,3 +901,8 @@ def SENTRY_INIT(dsn: str, sentry_opts: dict):
ENABLE_LIMITING_WEBHOOKS_FOR_IDENTICAL_PAYLOADS = get_bool_from_env(
"ENABLE_LIMITING_WEBHOOKS_FOR_IDENTICAL_PAYLOADS", False
)


# Transaction items limit for PaymentGatewayInitialize / TransactionInitialize.
# That setting limits the allowed number of transaction items for single entity.
TRANSACTION_ITEMS_LIMIT = 100

0 comments on commit 7b5bd05

Please sign in to comment.