Skip to content

Commit

Permalink
Integrated System - discount API (#177)
Browse files Browse the repository at this point in the history
* Adjusts routing to make sessions and authentication work better; fix logout (#176)

* hold

* Revert "Adjusts routing to make sessions and authentication work better; fix logout (#176)"

This reverts commit 391dd59.

* Auth works

* creates discounts

* Add company serializer

* Reapply "Adjusts routing to make sessions and authentication work better; fix logout (#176)"

This reverts commit 772f0f8.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* ruff

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Add schema

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Revert "Reapply "Adjusts routing to make sessions and authentication work better; fix logout (#176)""

This reverts commit d91c92c.

* generated API spec

* Allow for company added to discount

* Add transaction number

---------

Co-authored-by: James Kachel <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Dec 5, 2024
1 parent 1a15820 commit 6e598d9
Show file tree
Hide file tree
Showing 14 changed files with 342 additions and 7 deletions.
112 changes: 112 additions & 0 deletions openapi/specs/v0.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,19 @@ paths:
schema:
$ref: '#/components/schemas/BasketWithProduct'
description: ''
/api/v0/payments/discounts/:
post:
operationId: payments_discounts_create
description: Create a discount.
tags:
- payments
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Discount'
description: ''
/api/v0/payments/orders/history/:
get:
operationId: payments_orders_history_list
Expand Down Expand Up @@ -541,6 +554,73 @@ components:
- tax
- total_price
- user
Company:
type: object
description: Serializer for companies.
properties:
id:
type: integer
readOnly: true
name:
type: string
maxLength: 255
required:
- id
- name
Discount:
type: object
description: Serializer for discounts.
properties:
id:
type: integer
readOnly: true
discount_code:
type: string
maxLength: 100
amount:
type: string
format: decimal
pattern: ^-?\d{0,18}(?:\.\d{0,2})?$
payment_type:
nullable: true
oneOf:
- $ref: '#/components/schemas/PaymentTypeEnum'
- $ref: '#/components/schemas/NullEnum'
max_redemptions:
type: integer
maximum: 2147483647
minimum: 0
nullable: true
activation_date:
type: string
format: date-time
nullable: true
description: If set, this discount code will not be redeemable before this
date.
expiration_date:
type: string
format: date-time
nullable: true
description: If set, this discount code will not be redeemable after this
date.
integrated_system:
$ref: '#/components/schemas/IntegratedSystem'
product:
$ref: '#/components/schemas/Product'
assigned_users:
type: array
items:
$ref: '#/components/schemas/User'
company:
$ref: '#/components/schemas/Company'
required:
- amount
- assigned_users
- company
- discount_code
- id
- integrated_system
- product
IntegratedSystem:
type: object
description: Serializer for IntegratedSystem model.
Expand Down Expand Up @@ -608,6 +688,9 @@ components:
- quantity
- total_price
- unit_price
NullEnum:
enum:
- null
OrderHistory:
type: object
description: Serializer for order history.
Expand Down Expand Up @@ -780,6 +863,35 @@ components:
format: decimal
pattern: ^-?\d{0,5}(?:\.\d{0,2})?$
description: Price (decimal to two places)
PaymentTypeEnum:
enum:
- marketing
- sales
- financial-assistance
- customer-support
- staff
- legacy
- credit_card
- purchase_order
type: string
description: |-
* `marketing` - marketing
* `sales` - sales
* `financial-assistance` - financial-assistance
* `customer-support` - customer-support
* `staff` - staff
* `legacy` - legacy
* `credit_card` - credit_card
* `purchase_order` - purchase_order
x-enum-descriptions:
- marketing
- sales
- financial-assistance
- customer-support
- staff
- legacy
- credit_card
- purchase_order
Product:
type: object
description: Serializer for Product model.
Expand Down
19 changes: 19 additions & 0 deletions payments/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
Basket,
BlockedCountry,
BulkDiscountCollection,
Company,
Discount,
FulfilledOrder,
Order,
Expand Down Expand Up @@ -513,6 +514,8 @@ def generate_discount_code(**kwargs): # noqa: C901, PLR0912, PLR0915
* expires - date to expire the code
* count - number of codes to create (requires prefix)
* prefix - prefix to append to the codes (max 63 characters)
* company - ID of the company to associate with the discount
* transaction_number - transaction number to associate with the discount
Returns:
* List of generated codes, with the following fields:
Expand Down Expand Up @@ -641,6 +644,20 @@ def generate_discount_code(**kwargs): # noqa: C901, PLR0912, PLR0915
else:
users = None

if "company" in kwargs and kwargs["company"] is not None:
try:
company = Company.objects.get(pk=kwargs["company"])
except Company.DoesNotExist:
error_message = f"Company {kwargs['company']} does not exist."
raise ValueError(error_message) from None
else:
company = None

if "transaction_number" in kwargs and kwargs["transaction_number"] is not None:
transaction_number = kwargs["transaction_number"]
else:
transaction_number = None

generated_codes = []

for code_to_generate in codes_to_generate:
Expand All @@ -657,6 +674,8 @@ def generate_discount_code(**kwargs): # noqa: C901, PLR0912, PLR0915
integrated_system=integrated_system,
product=product,
bulk_discount_collection=bulk_discount_collection,
company=company,
transaction_number=transaction_number,
)
if users:
discount.assigned_users.set(users)
Expand Down
5 changes: 5 additions & 0 deletions payments/management/commands/generate_discount_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ def add_arguments(self, parser) -> None:
help="List of user IDs or emails to associate with the discount.",
)

parser.add_argument(
"--company",
help="Company ID to associate with the discount.",
)

def handle(self, *args, **kwargs): # pylint: disable=unused-argument # noqa: ARG002
"""
Handle the generation of discount codes based on the provided arguments.
Expand Down
11 changes: 11 additions & 0 deletions payments/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from rest_framework_api_key.permissions import BaseHasAPIKey

from system_meta.models import IntegratedSystemAPIKey


class HasIntegratedSystemAPIKey(BaseHasAPIKey):
"""
Permission class to check for Integrated System API Key.
"""

model = IntegratedSystemAPIKey
39 changes: 38 additions & 1 deletion payments/serializers/v0/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
PAYMENT_HOOK_ACTION_POST_SALE,
PAYMENT_HOOK_ACTIONS,
)
from payments.models import Basket, BasketItem, Line, Order
from payments.models import Basket, BasketItem, Company, Discount, Line, Order
from system_meta.models import Product
from system_meta.serializers import IntegratedSystemSerializer, ProductSerializer
from unified_ecommerce.serializers import UserSerializer
Expand Down Expand Up @@ -217,3 +217,40 @@ class Meta:
"updated_on",
]
model = Order


class CompanySerializer(serializers.ModelSerializer):
"""Serializer for companies."""

class Meta:
"""Meta options for CompanySerializer"""

model = Company
fields = ["id", "name"]


class DiscountSerializer(serializers.ModelSerializer):
"""Serializer for discounts."""

assigned_users = UserSerializer(many=True)
integrated_system = IntegratedSystemSerializer()
product = ProductSerializer()
company = CompanySerializer()

class Meta:
"""Meta options for DiscountSerializer"""

fields = [
"id",
"discount_code",
"amount",
"payment_type",
"max_redemptions",
"activation_date",
"expiration_date",
"integrated_system",
"product",
"assigned_users",
"company",
]
model = Discount
46 changes: 45 additions & 1 deletion payments/views/v0/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@
from payments import api
from payments.exceptions import ProductBlockedError
from payments.models import Basket, BasketItem, Discount, Order
from payments.permissions import HasIntegratedSystemAPIKey
from payments.serializers.v0 import (
BasketWithProductSerializer,
DiscountSerializer,
OrderHistorySerializer,
)
from system_meta.models import IntegratedSystem, Product
from system_meta.models import IntegratedSystem, IntegratedSystemAPIKey, Product
from unified_ecommerce import settings
from unified_ecommerce.constants import (
POST_SALE_SOURCE_BACKOFFICE,
Expand Down Expand Up @@ -471,3 +473,45 @@ def add_discount_to_basket(request, system_slug: str):
BasketWithProductSerializer(basket).data,
status=status.HTTP_200_OK,
)


class DiscountAPIViewSet(APIView):
"""
Provides API for creating Discount objects.
Discounts created through this API will be associated
with the integrated system that is linked to the api key.
Responds with a 201 status code if the discount is created successfully.
"""

permission_classes = [HasIntegratedSystemAPIKey]
authentication_classes = [] # disables authentication

@extend_schema(
description="Create a discount.",
methods=["POST"],
request=None,
responses=DiscountSerializer,
)
def post(self, request):
"""
Create discounts.
Args:
request: The request object.
Returns:
Response: The response object.
"""
key = request.META["HTTP_AUTHORIZATION"].split()[1]
api_key = IntegratedSystemAPIKey.objects.get_from_key(key)
discount_dictionary = request.data
discount_dictionary["integrated_system"] = str(api_key.integrated_system.id)
discount_codes = api.generate_discount_code(
**discount_dictionary,
)

return Response(
{"discounts_created": DiscountSerializer(discount_codes, many=True).data},
status=status.HTTP_201_CREATED,
)
6 changes: 6 additions & 0 deletions payments/views/v0/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
BasketViewSet,
CheckoutApiViewSet,
CheckoutCallbackView,
DiscountAPIViewSet,
OrderHistoryViewSet,
add_discount_to_basket,
clear_basket,
Expand Down Expand Up @@ -43,6 +44,11 @@
BackofficeCallbackView.as_view(),
name="checkout-callback",
),
path(
"discounts/",
DiscountAPIViewSet.as_view(),
name="discount-api",
),
re_path(
r"^",
include(
Expand Down
20 changes: 17 additions & 3 deletions poetry.lock

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

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ django-countries = "^7.6.1"
mitol-django-geoip = ">=2024.11.05"
py-moneyed = "^3.0"
django-extensions = "^3.2.3"
djangorestframework-api-key = "^3.0.0"

[tool.poetry.group.dev.dependencies]
bpython = "^0.24"
Expand Down
Loading

0 comments on commit 6e598d9

Please sign in to comment.