From 73b7d553a1585c700fc2d354f86ef4006f34eafc Mon Sep 17 00:00:00 2001 From: hoosnick Date: Tue, 22 Aug 2023 22:25:55 +0500 Subject: [PATCH 01/32] fixed unspecified-encoding #pylint --- lib/payme/methods/generate_link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/payme/methods/generate_link.py b/lib/payme/methods/generate_link.py index 454fa04..5590adf 100644 --- a/lib/payme/methods/generate_link.py +++ b/lib/payme/methods/generate_link.py @@ -125,7 +125,7 @@ def to_qrcode(self, path: str = 'qr-codes', filename: str = None, **kwargs): image_name = uuid.uuid4().hex if not filename else filename image_output_path = f'{path}/{image_name}.svg' - with open(image_output_path, 'w') as svg: + with open(image_output_path, 'w', encoding='utf-8') as svg: svg.write(message.split(',')[-1]) return image_output_path From 4b12d7227d316dc2e00615030d5bacc3f41c916c Mon Sep 17 00:00:00 2001 From: hoosnick Date: Tue, 22 Aug 2023 22:34:18 +0500 Subject: [PATCH 02/32] fixed on push (docs.yml) --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6011155..3946ad7 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,7 +3,7 @@ name: Documentation on: push: branches: - - payme-pkg-3.0b + - master jobs: deploy: runs-on: ubuntu-latest From 79a8c39b831c74daa49fd67f29195e4d01a67770 Mon Sep 17 00:00:00 2001 From: hoosnick Date: Thu, 24 Aug 2023 09:37:16 +0500 Subject: [PATCH 03/32] README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a0685d4..f315bae 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Payme Uzbekistan Integration Uzcard and Humo +

@@ -73,6 +74,7 @@ ORDER_MODEL = 'your_app.models.Your_Order_Model' ``` Create a new View that about handling call backs + ```python from payme.views import MerchantAPIView From acb5e7f422d9772ad35696681c71e8bb10ef0ece Mon Sep 17 00:00:00 2001 From: hoosnick Date: Thu, 24 Aug 2023 09:40:17 +0500 Subject: [PATCH 04/32] added payme-pkg-3.0b to docs.yml --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3946ad7..6011155 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,7 +3,7 @@ name: Documentation on: push: branches: - - master + - payme-pkg-3.0b jobs: deploy: runs-on: ubuntu-latest From b1a8d5e4c1a477a3319a1345c338ef100705423f Mon Sep 17 00:00:00 2001 From: hoosnick Date: Thu, 24 Aug 2023 09:42:56 +0500 Subject: [PATCH 05/32] LICENSE --- LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.txt b/LICENSE.txt index aeb6397..94a79ff 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2021 Giorgos Myrianthous +Copyright (c) 2023 Muhammadali Akbarov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the From 138567bb744344478ec48ab1d6b54d0501a81809 Mon Sep 17 00:00:00 2001 From: hoosnick Date: Thu, 24 Aug 2023 10:40:00 +0500 Subject: [PATCH 06/32] .gitignore --- .gitignore | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 998cacb..56dbef2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,19 @@ +# virtual env +env/ +venv/ + +# dev test core/ +my_app/ db.sqlite3 +manage.py dist/ -venv/ + +# ide configs .vscode/ .idea/ + +# caches __pycache__/ paymentsuz.egg-info/ payme_pkg.egg-info/ -manage.py \ No newline at end of file From e52a9ded064c4b446b2b9902813ec5cd2503658a Mon Sep 17 00:00:00 2001 From: hoosnick Date: Thu, 24 Aug 2023 10:40:49 +0500 Subject: [PATCH 07/32] .pylintrc --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 70226f1..1df69b9 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,5 +1,5 @@ [MASTER] -disable=no-member, +disable=no-member,invalid-name, unnecessary-pass, useless-option-value, too-few-public-methods, From 33032cc96c1fd7a097e91729319987e2b078e55c Mon Sep 17 00:00:00 2001 From: hoosnick Date: Thu, 24 Aug 2023 10:42:34 +0500 Subject: [PATCH 08/32] guide config (Website and Docs #21) --- mkdocs.yml | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 mkdocs.yml diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..4413364 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,112 @@ +# Project information +site_name: "Payme-pkg uchun yo'riqnoma" +site_description: 🟒 Merchant API and Subscribe API Integration 2023β€”08 +site_author: Hoosnick +site_url: https://paytechuz.github.io/payme-pkg +docs_dir: docs/src +site_dir: payme + +# Repository +repo_name: PayTechUz/payme-pkg +repo_url: https://github.com/PayTechUz/payme-pkg +edit_uri: "" + +# Copyright +copyright: Copyright © 2023 PayTechUz + +# Configuration +theme: + name: material + custom_dir: docs/src/overrides + language: uz + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: teal + toggle: + icon: material/brightness-4 + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: teal + toggle: + icon: material/brightness-7 + name: Switch to light mode + font: + text: Ubuntu + code: Roboto Mono + # icon: + # logo: material/cash-multiple + logo: assets/logo.png + +# Extras +extra: + social: + - icon: fontawesome/brands/telegram + link: https://t.me/+7Gn-JZ99TfgwZDNi + name: "Biz telegramda" + - icon: fontawesome/brands/github-alt + link: https://github.com/PayTechUz/payme-pkg + name: GitHub + +# Extensions +markdown_extensions: + - admonition + - pymdownx.highlight + - pymdownx.superfences + - pymdownx.details + - meta + - attr_list + - toc: + permalink: true + +nav: + - "home": index.md + - "interaction_setup": setup.md + - "merchant_api": merchant-api.md + - "subscribe_api": subscribe-api.md + - "sandbox": sandbox.md + - "support": support.md + +plugins: + - search + - i18n: + default_language: uz + default_language_only: false + docs_structure: folder + + languages: + en: + name: "en English" + build: true + site_name: "Documentation for payme-pkg" + uz: + name: "uz O'zbekcha" + build: true + ru: + name: "ru Русский" + build: true + site_name: "ДокумСнтация для payme-pkg" + + nav_translations: + en: + "home": "Introduction" + "interaction_setup": "Interaction setup" + "merchant_api": "Merchant API" + "subscribe_api": "Subscribe API" + "sandbox": "Sandbox (Testing)" + "support": "Technical support" + ru: + "home": "Π’Π²Π΅Π΄Π΅Π½ΠΈΠ΅" + "interaction_setup": "Настройка взаимодСйствия" + "merchant_api": "Merchant API" + "subscribe_api": "Subscribe API" + "sandbox": "ΠŸΠ΅ΡΠΎΡ‡Π½ΠΈΡ†Π° (ВСстированиС)" + "support": "ВСхничСская ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ°" + uz: + "home": "Kirish" + "interaction_setup": "Sozlash (Setup)" + "merchant_api": "Merchant API" + "subscribe_api": "Subscribe API" + "sandbox": "Qumdon (Sandbox)" + "support": "Texnik yordam" From a46646e6e0a3ece7e86504589a231a2cbac7f3dc Mon Sep 17 00:00:00 2001 From: hoosnick Date: Thu, 24 Aug 2023 10:43:52 +0500 Subject: [PATCH 09/32] mkdocs-reqs.txt (Website and Docs #21) --- requirements/docs-requirements.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 requirements/docs-requirements.txt diff --git a/requirements/docs-requirements.txt b/requirements/docs-requirements.txt new file mode 100644 index 0000000..55f78da --- /dev/null +++ b/requirements/docs-requirements.txt @@ -0,0 +1,2 @@ +mkdocs-material==9.1.21 +mkdocs-static-i18n==0.56 \ No newline at end of file From e9c3c22b4d4c3f4b310f38e510544ee417314b4c Mon Sep 17 00:00:00 2001 From: hoosnick Date: Thu, 24 Aug 2023 10:45:23 +0500 Subject: [PATCH 10/32] add: websocket-client library (#12) --- requirements/requirements.txt | 1 + setup.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 0739c50..c9370e5 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,3 +1,4 @@ requests==2.* dataclasses==0.* djangorestframework==3.14.0 +websocket-client==1.6.1 \ No newline at end of file diff --git a/setup.py b/setup.py index dce6ed4..caa7156 100644 --- a/setup.py +++ b/setup.py @@ -14,6 +14,7 @@ install_requires=[ 'requests==2.*', 'dataclasses==0.*', - 'djangorestframework==3.14.0' - ], + 'djangorestframework==3.14.0', + 'websocket-client==1.6.1' + ], ) From 9ecbf942dd9f6e48698f1b826e017b2ec6314fbb Mon Sep 17 00:00:00 2001 From: hoosnick Date: Thu, 24 Aug 2023 10:48:17 +0500 Subject: [PATCH 11/32] feat: to qrcode (#14) --- lib/payme/errors/exceptions.py | 7 +++ lib/payme/methods/generate_link.py | 93 +++++++++++++++++++++++++++--- 2 files changed, 93 insertions(+), 7 deletions(-) diff --git a/lib/payme/errors/exceptions.py b/lib/payme/errors/exceptions.py index feb2e78..faadbb9 100644 --- a/lib/payme/errors/exceptions.py +++ b/lib/payme/errors/exceptions.py @@ -87,3 +87,10 @@ class PaymeTimeoutException(Exception): """ Payme timeout exception that means that payme is working slowly. """ + + +class QRCodeError(Exception): + """ + QRCodeError Exception \ + Raises when an error occurs while generating a QR code. + """ diff --git a/lib/payme/methods/generate_link.py b/lib/payme/methods/generate_link.py index 0738315..5590adf 100644 --- a/lib/payme/methods/generate_link.py +++ b/lib/payme/methods/generate_link.py @@ -1,9 +1,17 @@ import base64 -from decimal import Decimal +import json +import os +import typing +import uuid from dataclasses import dataclass +import websocket +from websocket import WebSocketApp + from django.conf import settings +from payme.errors.exceptions import QRCodeError + PAYME_ID = settings.PAYME.get('PAYME_ID') PAYME_ACCOUNT = settings.PAYME.get('PAYME_ACCOUNT') PAYME_CALL_BACK_URL = settings.PAYME.get('PAYME_CALL_BACK_URL') @@ -19,7 +27,7 @@ class GeneratePayLink: Parameters ---------- order_id: int β€” The order_id for paying - amount: int β€” The amount belong to the order + amount: float β€” The amount belong to the order Returns str β€” pay link ---------------------- @@ -29,11 +37,16 @@ class GeneratePayLink: https://developer.help.paycom.uz/initsializatsiya-platezhey/ """ order_id: str - amount: Decimal + amount: float def generate_link(self) -> str: """ GeneratePayLink for each order. + + Full method documentation + ---------- + https://developer.help.paycom.uz/initsializatsiya-platezhey/otpravka-cheka-po-metodu-get + """ generated_pay_link: str = "{payme_url}/{encode_params}" params: str = 'm={payme_id};ac.{payme_account}={order_id};a={amount};c={call_back_url}' @@ -51,24 +64,90 @@ def generate_link(self) -> str: encode_params=str(encode_params, 'utf-8') ) + def __send_and_receive_data(self, data) -> typing.Union[str, None]: + # pylint: disable=missing-function-docstring + + message = None + + def on_message(ws: WebSocketApp, _message): + nonlocal message + message = _message + + ws.close() + + websocket.enableTrace(False) + + ws = WebSocketApp( + url="wss://checkout.paycom.uz/", + keep_running=False, + on_message=on_message + ) + + ws.on_open = lambda ws: ws.send(json.dumps(data)) + ws.run_forever(ping_interval=0.1) + + return message + + def to_qrcode(self, path: str = 'qr-codes', filename: str = None, **kwargs): + """ + Generate qr-code for order. + + Full method documentation + ---------- + https://developer.help.paycom.uz/initsializatsiya-platezhey/generatsiya-knopki-oplaty-i-qr-koda + + Parameters + ---------- + path: str -> output path (folder) name + filename: str -> output image name without suffix + lang: str -> user language. available values: ru, uz, en. + callback: str -> return url after payment or payment cancellation. + + Returns + ---------- + str -> path of qr code svg + """ + data = { + "lang": kwargs.get("lang", "ru"), + "merchant": PAYME_ID, + "amount": self.amount, + "account": {PAYME_ACCOUNT: self.order_id}, + "callback": kwargs.get("callback", PAYME_CALL_BACK_URL) + } + message = self.__send_and_receive_data(data) + + if message is None: + raise QRCodeError + + if not os.path.exists(path): + os.makedirs(path) + + image_name = uuid.uuid4().hex if not filename else filename + image_output_path = f'{path}/{image_name}.svg' + + with open(image_output_path, 'w', encoding='utf-8') as svg: + svg.write(message.split(',')[-1]) + + return image_output_path + @staticmethod - def to_tiyin(amount: Decimal) -> Decimal: + def to_tiyin(amount: float) -> float: """ Convert from soum to tiyin. Parameters ---------- - amount: Decimal -> order amount + amount: float -> order amount """ return amount * 100 @staticmethod - def to_soum(amount: Decimal) -> Decimal: + def to_soum(amount: float) -> float: """ Convert from tiyin to soum. Parameters ---------- - amount: Decimal -> order amount + amount: float -> order amount """ return amount / 100 From cc3eaf3f6b5961edffb51d1c673c433a05d352d5 Mon Sep 17 00:00:00 2001 From: hoosnick Date: Thu, 24 Aug 2023 11:00:05 +0500 Subject: [PATCH 12/32] feat: Dynamic ICPS (#12) --- lib/payme/admin.py | 25 ++-- .../methods/check_perform_transaction.py | 19 ++- lib/payme/migrations/0001_initial.py | 54 ++++++--- lib/payme/models.py | 111 +++++++++++++----- lib/payme/serializers.py | 52 ++++++-- lib/payme/utils/order_finder.py | 41 +++++++ lib/payme/views.py | 8 +- 7 files changed, 240 insertions(+), 70 deletions(-) create mode 100644 lib/payme/utils/order_finder.py diff --git a/lib/payme/admin.py b/lib/payme/admin.py index c5cf4b1..c07bd87 100644 --- a/lib/payme/admin.py +++ b/lib/payme/admin.py @@ -1,11 +1,20 @@ from django.contrib import admin -from payme.models import CUSTOM_ORDER -from payme.models import Order as DefaultOrderModel +from payme.models import ( + Item, MerchatTransactionsModel, + OrderDetail, ShippingDetail +) +# pylint: disable=fixme +# TODO: order Payme models in admin panel +# 1. OrderDetail +# 2. Item +# 3. ShippingDetail +# 4. MerchatTransactionsModel -from payme.models import MerchatTransactionsModel - -if not CUSTOM_ORDER: - admin.site.register(DefaultOrderModel) - -admin.site.register(MerchatTransactionsModel) +admin.site.register( + [ + OrderDetail, Item, + ShippingDetail, + MerchatTransactionsModel, + ] +) diff --git a/lib/payme/methods/check_perform_transaction.py b/lib/payme/methods/check_perform_transaction.py index 78a7411..a7d17ec 100644 --- a/lib/payme/methods/check_perform_transaction.py +++ b/lib/payme/methods/check_perform_transaction.py @@ -1,5 +1,9 @@ +from payme.serializers import ( + MerchatTransactionsModelSerializer, + OrderModelSerializer +) from payme.utils.get_params import get_params -from payme.serializers import MerchatTransactionsModelSerializer +from payme.utils.order_finder import Order class CheckPerformTransaction: @@ -11,16 +15,27 @@ class CheckPerformTransaction: ------------------------- https://developer.help.paycom.uz/metody-merchant-api/checktransaction """ + def __call__(self, params: dict) -> dict: serializer = MerchatTransactionsModelSerializer( data=get_params(params) ) serializer.is_valid(raise_exception=True) + order = OrderModelSerializer( + instance=Order.objects.get( + id=serializer.validated_data.get('order_id') + ) + ) + response = { "result": { "allow": True, - } + "detail": order.data.get("detail") } + } + + if not order.data.get("detail"): + del response["result"]["detail"] return None, response diff --git a/lib/payme/migrations/0001_initial.py b/lib/payme/migrations/0001_initial.py index eec730a..bb3bc2a 100644 --- a/lib/payme/migrations/0001_initial.py +++ b/lib/payme/migrations/0001_initial.py @@ -1,22 +1,35 @@ -# pylint: disable=invalid-name +# Generated by Django 4.2.3 on 2023-08-24 05:54 + from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): - # pylint: disable=missing-class-docstring + initial = True - dependencies = [] + + dependencies = [ + ] operations = [ + migrations.CreateModel( + name='Item', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('discount', models.FloatField(blank=True, null=True)), + ('title', models.CharField(max_length=255)), + ('price', models.FloatField(blank=True, null=True)), + ('count', models.IntegerField(default=1)), + ('code', models.CharField(max_length=17)), + ('units', models.IntegerField(blank=True, null=True)), + ('package_code', models.CharField(max_length=255)), + ('vat_percent', models.IntegerField(blank=True, default=0, null=True)), + ], + ), migrations.CreateModel( name='MerchatTransactionsModel', fields=[ - ('id', models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name='ID') - ), + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('_id', models.CharField(max_length=255, null=True)), ('transaction_id', models.CharField(max_length=255, null=True)), ('order_id', models.BigIntegerField(blank=True, null=True)), @@ -32,17 +45,20 @@ class Migration(migrations.Migration): ], ), migrations.CreateModel( - name='Order', + name='ShippingDetail', fields=[ - ('id', models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name='ID') - ), - ('amount', models.IntegerField(blank=True, null=True)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255)), + ('price', models.FloatField(default=0)), + ], + ), + migrations.CreateModel( + name='OrderDetail', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('receipt_type', models.IntegerField(default=0)), + ('items', models.ManyToManyField(to='payme.item')), + ('shipping', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='payme.shippingdetail')), ], ), ] diff --git a/lib/payme/models.py b/lib/payme/models.py index 52d46cf..75c2c99 100644 --- a/lib/payme/models.py +++ b/lib/payme/models.py @@ -1,9 +1,4 @@ from django.db import models -from django.conf import settings -from django.utils.module_loading import import_string -from django.core.exceptions import FieldError - -from payme.utils.logging import logger class MerchatTransactionsModel(models.Model): @@ -28,34 +23,92 @@ def __str__(self): return str(self._id) -try: - CUSTOM_ORDER = import_string(settings.ORDER_MODEL) +class ShippingDetail(models.Model): + """ + ShippingDetail class \ + That's used for managing shipping + """ + title = models.CharField(max_length=255) + price = models.FloatField(default=0) + + def __str__(self) -> str: + return f"[{self.pk}] {self.title} {self.price}" + + +class Item(models.Model): + """ + Item class \ + That's used for managing order items + """ + discount = models.FloatField(null=True, blank=True) + title = models.CharField(max_length=255) + price = models.FloatField(null=True, blank=True) + count = models.IntegerField(default=1) + code = models.CharField(max_length=17) + units = models.IntegerField(null=True, blank=True) + package_code = models.CharField(max_length=255) + vat_percent = models.IntegerField(default=0, null=True, blank=True) + + def __str__(self) -> str: + return f"[{self.id}] {self.title} #{self.code}" + + +class OrderDetail(models.Model): + """ + OrderDetail class \ + That's used for managing order details + """ + receipt_type = models.IntegerField(default=0) + shipping = models.ForeignKey( + to=ShippingDetail, + null=True, blank=True, + on_delete=models.CASCADE + ) + items = models.ManyToManyField(Item) + + def get_items_display(self): + # pylint: disable=missing-function-docstring + return ', '.join([items.title for items in self.items.all()]) + + def __str__(self) -> str: + return f"[{self.pk}] {self.get_items_display()}" + + +class DisallowOverrideMetaclass(models.base.ModelBase): + # pylint: disable=missing-class-docstring + def __new__(mcs, name, bases, attrs: dict, **kwargs): + disallowed_fields = ['amount', 'detail'] + + if name != 'BaseOrder': + for field_name in disallowed_fields: + if not attrs.get(field_name): + continue - if not isinstance(CUSTOM_ORDER, models.base.ModelBase): - raise TypeError("The input must be an instance of models.Model class") + raise TypeError( + f"Field '{field_name}' in '{name}' cannot be overridden." + ) - # pylint: disable=protected-access - if 'amount' not in [f.name for f in CUSTOM_ORDER._meta.fields]: - raise FieldError("Missing 'amount' field in your custom order model") + return super().__new__(mcs, name, bases, attrs, **kwargs) - Order = CUSTOM_ORDER -except (ImportError, AttributeError): - logger.warning("You have no payme custom order model") - CUSTOM_ORDER = None +class BaseOrder(models.Model, metaclass=DisallowOverrideMetaclass): + """ + Order class \ + That's used for managing order process + """ + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) - class Order(models.Model): - """ - Order class \ - That's used for managing order process - """ - amount = models.IntegerField(null=True, blank=True) - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) + amount = models.FloatField(null=True, blank=True) + detail = models.ForeignKey( + OrderDetail, + null=True, blank=True, + on_delete=models.CASCADE + ) - def __str__(self): - return f"ORDER ID: {self.id} - AMOUNT: {self.amount}" + def __str__(self): + return f"ORDER ID: {self.id} - AMOUNT: {self.amount}" - class Meta: - # pylint: disable=missing-class-docstring - managed = False + class Meta: + # pylint: disable=missing-class-docstring + abstract = True diff --git a/lib/payme/serializers.py b/lib/payme/serializers.py index 8f9b3f3..3d36ed4 100644 --- a/lib/payme/serializers.py +++ b/lib/payme/serializers.py @@ -2,12 +2,15 @@ from rest_framework import serializers -from payme.models import Order -from payme.utils.logging import logger -from payme.utils.get_params import get_params from payme.models import MerchatTransactionsModel -from payme.errors.exceptions import IncorrectAmount -from payme.errors.exceptions import PerformTransactionDoesNotExist +from payme.utils.get_params import get_params +from payme.utils.logging import logger +from payme.utils.order_finder import Order + +from payme.errors.exceptions import ( + PerformTransactionDoesNotExist, + IncorrectAmount +) class MerchatTransactionsModelSerializer(serializers.ModelSerializer): @@ -33,7 +36,7 @@ def validate(self, attrs) -> dict: order = Order.objects.get( id=attrs['order_id'] ) - if order.amount != int(attrs['amount']): + if order.amount != float(attrs['amount']): raise IncorrectAmount() except IncorrectAmount as error: @@ -42,19 +45,19 @@ def validate(self, attrs) -> dict: return attrs - def validate_amount(self, amount) -> int: + def validate_amount(self, amount: float) -> float: """ Validator for Transactions Amount. """ if amount is None: raise IncorrectAmount() - if int(amount) <= int(settings.PAYME.get("PAYME_MIN_AMOUNT", 0)): + if amount <= float(settings.PAYME.get("PAYME_MIN_AMOUNT", 0)): raise IncorrectAmount("Payment amount is less than allowed.") return amount - def validate_order_id(self, order_id) -> int: + def validate_order_id(self, order_id: int) -> int: """ Use this method to check if a transaction is allowed to be executed. @@ -86,3 +89,34 @@ def get_validated_data(params: dict) -> dict: clean_data: dict = serializer.validated_data return clean_data + + +class OrderModelSerializer(serializers.ModelSerializer): + """ + OrderModelSerializer class \ + That's used to serialize orders detail data. + """ + class Meta: + # pylint: disable=missing-class-docstring + model = Order + depth = 2 + exclude = ["id"] + read_only_fields = ["__all__"] + + def clean_empty(self, data): + # pylint: disable=missing-function-docstring + if isinstance(data, dict): + return { + k: v + for k, v in ((k, self.clean_empty(v)) for k, v in data.items()) + if v is not None and k != 'id' + } + if isinstance(data, list): + return [v for v in map(self.clean_empty, data) if v] + + return data + + def to_representation(self, instance): + ret = super().to_representation(instance) + + return self.clean_empty(ret) diff --git a/lib/payme/utils/order_finder.py b/lib/payme/utils/order_finder.py new file mode 100644 index 0000000..53e9aac --- /dev/null +++ b/lib/payme/utils/order_finder.py @@ -0,0 +1,41 @@ +from django.conf import settings +from django.db import models +from django.utils.module_loading import import_string + + +def _custom_order_model(): + """ + Get custom order model class from settings. + + Returns the custom order model class + defined in 'PAYME' or main settings. + + raise ImportError if both are undefined. + """ + order_model_paths = [] + + if hasattr(settings, 'ORDER_MODEL'): + order_model_paths.append(settings.ORDER_MODEL) + + if hasattr(settings, 'PAYME'): + order_model_paths.append(settings.PAYME.get('ORDER_MODEL')) + + for model_path in order_model_paths: + try: + return import_string(model_path) + except (ImportError, AttributeError): + pass + + raise ImportError + + +try: + CUSTOM_ORDER = _custom_order_model() +except (ImportError, AttributeError): + # pylint: disable=raise-missing-from + raise NotImplementedError("Order model not implemented!") + +if not isinstance(CUSTOM_ORDER, models.base.ModelBase): + raise TypeError("The input must be an instance of models.Model class") + +Order = CUSTOM_ORDER diff --git a/lib/payme/views.py b/lib/payme/views.py index 25db03c..bdd3382 100644 --- a/lib/payme/views.py +++ b/lib/payme/views.py @@ -31,7 +31,7 @@ class MerchantAPIView(APIView): def post(self, request) -> Response: """ Payme sends post request to our call back url. - That methods are includes 5 methods + That methods are includes 6 methods - CheckPerformTransaction - CreateTransaction - PerformTransaction @@ -55,7 +55,9 @@ def post(self, request) -> Response: raise MethodNotFound() from error except PerformTransactionDoesNotExist as error: - logger.error("PerformTransactionDoesNotExist Error occurred: %s", error) + logger.error( + "PerformTransactionDoesNotExist Error occurred: %s", error + ) raise PerformTransactionDoesNotExist() from error order_id, action = paycom_method(incoming_data.get("params")) @@ -156,7 +158,7 @@ def perform_transaction(self, order_id, action) -> None: """ pass - def cancel_transaction(self,order_id, action) -> None: + def cancel_transaction(self, order_id, action) -> None: """ need implement in your view class """ From e58b6fdf163be62805ecbde4567352ec5c7f81a2 Mon Sep 17 00:00:00 2001 From: hoosnick Date: Thu, 24 Aug 2023 11:04:08 +0500 Subject: [PATCH 13/32] add: guide sections, assets, logo, yml (#21) --- .github/workflows/docs.yml | 16 +++++++ docs/src/assets/logo.png | Bin 0 -> 15689 bytes docs/src/en/index.md | 65 +++++++++++++++++++++++++++ docs/src/en/merchant-api.md | 6 +++ docs/src/en/sandbox.md | 6 +++ docs/src/en/setup.md | 6 +++ docs/src/en/subscribe-api.md | 6 +++ docs/src/en/support.md | 6 +++ docs/src/overrides/partials/nav.html | 23 ++++++++++ docs/src/ru/index.md | 5 +++ docs/src/ru/merchant-api.md | 6 +++ docs/src/ru/sandbox.md | 6 +++ docs/src/ru/setup.md | 6 +++ docs/src/ru/subscribe-api.md | 6 +++ docs/src/ru/support.md | 6 +++ docs/src/uz/index.md | 5 +++ docs/src/uz/merchant-api.md | 6 +++ docs/src/uz/sandbox.md | 6 +++ docs/src/uz/setup.md | 6 +++ docs/src/uz/subscribe-api.md | 6 +++ docs/src/uz/support.md | 6 +++ 21 files changed, 204 insertions(+) create mode 100644 .github/workflows/docs.yml create mode 100644 docs/src/assets/logo.png create mode 100644 docs/src/en/index.md create mode 100644 docs/src/en/merchant-api.md create mode 100644 docs/src/en/sandbox.md create mode 100644 docs/src/en/setup.md create mode 100644 docs/src/en/subscribe-api.md create mode 100644 docs/src/en/support.md create mode 100644 docs/src/overrides/partials/nav.html create mode 100644 docs/src/ru/index.md create mode 100644 docs/src/ru/merchant-api.md create mode 100644 docs/src/ru/sandbox.md create mode 100644 docs/src/ru/setup.md create mode 100644 docs/src/ru/subscribe-api.md create mode 100644 docs/src/ru/support.md create mode 100644 docs/src/uz/index.md create mode 100644 docs/src/uz/merchant-api.md create mode 100644 docs/src/uz/sandbox.md create mode 100644 docs/src/uz/setup.md create mode 100644 docs/src/uz/subscribe-api.md create mode 100644 docs/src/uz/support.md diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..6011155 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,16 @@ +name: Documentation + +on: + push: + branches: + - payme-pkg-3.0b +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: 3.x + - run: pip install -r requirements/docs-requirements.txt + - run: mkdocs gh-deploy --force diff --git a/docs/src/assets/logo.png b/docs/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..35d3c11b7dbffcf9e48ab23075a39fd21d46e691 GIT binary patch literal 15689 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](https://pay-tech.uz) + +Welcome to the PayTechUz organization! This project aims to provide developers with seamless integration and implementation of Payment Providers APIs in Uzbekistan. Whether you're a Python enthusiast or a Go guru, we've got you covered with our versatile tech stack. + +## Table of Contents + +- [Introduction](#introduction) +- [Website](#website) +- [Telegram Group](#telegram-group) +- [Tech Stack](#tech-stack) +- [Usage](#usage) +- [Contributing](#contributing) +- [Support](#support) +- [License](#license) + +## Introduction + +Integrating payment providers into your applications is crucial in today's digital landscape. This project simplifies the process of integrating Payment Providers APIs in Uzbekistan, allowing you to focus on building amazing products while leaving the payment processing worries behind. + +## Website + +Visit our website [pay-tech.uz](https://pay-tech.uz) to explore detailed documentation, guides, and API references. We regularly update the documentation to ensure you have the most up-to-date information at your disposal. + +## Telegram Group + +Join our vibrant developer community on Telegram to connect with fellow developers, ask questions, share experiences, and stay updated with the latest news and announcements. + +[Telegram Group](https://t.me/+krHlGUizJrI1Zjli) + +## Tech Stack + +We have carefully chosen the following tech stack to ensure flexibility and ease of use: + +- [![Python](https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white)](https://python.org/) +- [![Go](https://img.shields.io/badge/Go-00ADD8?style=for-the-badge&logo=go&logoColor=white)](https://go.dev/) + +The combination of Python and Go offers a wide range of possibilities to cater to the preferences of different developers. + +## Usage + +Our packages are designed to streamline the integration process. Follow the detailed instructions in the documentation for each package to integrate the desired Payment Providers API seamlessly. + +## Contributing + +We welcome contributions from the community to make this project even better. If you find any issues or have suggestions for improvements, feel free to open an issue or submit a pull request. Please ensure you follow our [contribution guidelines](https://github.com/PayTechUz/.github/blob/main/profile/CONTRIBUTING.md). + +## Support + +If you encounter any problems, have questions, or need assistance with the integration, our team is here to help. Reach out to us on our [Telegram Group](https://t.me/+ydVV_9B3Xh02NGEy) or email us at paytechuz@gmail.com. + +## License + +This project is licensed under the [MIT License](https://github.com/PayTechUz/.github/blob/main/profile/LICENSE.txt), making it open and free for everyone to use, modify, and distribute. + +--- + +We hope our PayTechUz packages make your payment integration process a delightful experience. Happy coding! diff --git a/docs/src/en/merchant-api.md b/docs/src/en/merchant-api.md new file mode 100644 index 0000000..83c1c74 --- /dev/null +++ b/docs/src/en/merchant-api.md @@ -0,0 +1,6 @@ +--- +title: Merchant API methods +description: Merchant API methods +--- + +# Merchant API methods diff --git a/docs/src/en/sandbox.md b/docs/src/en/sandbox.md new file mode 100644 index 0000000..da2d56d --- /dev/null +++ b/docs/src/en/sandbox.md @@ -0,0 +1,6 @@ +--- +title: Sandbox (Testing) +description: Sandbox (Testing) +--- + +# Sandbox (Testing) diff --git a/docs/src/en/setup.md b/docs/src/en/setup.md new file mode 100644 index 0000000..12f0f53 --- /dev/null +++ b/docs/src/en/setup.md @@ -0,0 +1,6 @@ +--- +title: Interaction setup +description: Interaction setup +--- + +# Interaction setup diff --git a/docs/src/en/subscribe-api.md b/docs/src/en/subscribe-api.md new file mode 100644 index 0000000..95b8128 --- /dev/null +++ b/docs/src/en/subscribe-api.md @@ -0,0 +1,6 @@ +--- +title: Subscribe API methods +description: Subscribe API methods +--- + +# Subscribe API methods diff --git a/docs/src/en/support.md b/docs/src/en/support.md new file mode 100644 index 0000000..386243f --- /dev/null +++ b/docs/src/en/support.md @@ -0,0 +1,6 @@ +--- +title: Technical support +description: Technical support +--- + +# Technical support diff --git a/docs/src/overrides/partials/nav.html b/docs/src/overrides/partials/nav.html new file mode 100644 index 0000000..f4025e5 --- /dev/null +++ b/docs/src/overrides/partials/nav.html @@ -0,0 +1,23 @@ +

diff --git a/docs/src/ru/index.md b/docs/src/ru/index.md new file mode 100644 index 0000000..5554065 --- /dev/null +++ b/docs/src/ru/index.md @@ -0,0 +1,5 @@ +--- +title: Π’Π²Π΅Π΄Π΅Π½ΠΈΠ΅ +--- + +# **Π”ΠΎΠ±Ρ€ΠΎ ΠΏΠΎΠΆΠ°Π»ΠΎΠ²Π°Ρ‚ΡŒ!** diff --git a/docs/src/ru/merchant-api.md b/docs/src/ru/merchant-api.md new file mode 100644 index 0000000..1398766 --- /dev/null +++ b/docs/src/ru/merchant-api.md @@ -0,0 +1,6 @@ +--- +title: ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ Merchant API +description: ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ Merchant API +--- + +# ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ Merchant API diff --git a/docs/src/ru/sandbox.md b/docs/src/ru/sandbox.md new file mode 100644 index 0000000..4631217 --- /dev/null +++ b/docs/src/ru/sandbox.md @@ -0,0 +1,6 @@ +--- +title: ΠŸΠ΅ΡΠΎΡ‡Π½ΠΈΡ†Π° (ВСстированиС) +description: ΠŸΠ΅ΡΠΎΡ‡Π½ΠΈΡ†Π° (ВСстированиС) +--- + +# ΠŸΠ΅ΡΠΎΡ‡Π½ΠΈΡ†Π° (ВСстированиС) diff --git a/docs/src/ru/setup.md b/docs/src/ru/setup.md new file mode 100644 index 0000000..ac946b7 --- /dev/null +++ b/docs/src/ru/setup.md @@ -0,0 +1,6 @@ +--- +title: Настройка взаимодСйствия +description: Настройка взаимодСйствия +--- + +# Настройка взаимодСйствия diff --git a/docs/src/ru/subscribe-api.md b/docs/src/ru/subscribe-api.md new file mode 100644 index 0000000..b1fe530 --- /dev/null +++ b/docs/src/ru/subscribe-api.md @@ -0,0 +1,6 @@ +--- +title: ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ Subscribe API +description: ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ Subscribe API +--- + +# ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ Subscribe API diff --git a/docs/src/ru/support.md b/docs/src/ru/support.md new file mode 100644 index 0000000..060c771 --- /dev/null +++ b/docs/src/ru/support.md @@ -0,0 +1,6 @@ +--- +title: ВСхничСская ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° +description: ВСхничСская ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° +--- + +# ВСхничСская ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° diff --git a/docs/src/uz/index.md b/docs/src/uz/index.md new file mode 100644 index 0000000..7905467 --- /dev/null +++ b/docs/src/uz/index.md @@ -0,0 +1,5 @@ +--- +title: Kirish +--- + +# **Xush kelibsiz!** diff --git a/docs/src/uz/merchant-api.md b/docs/src/uz/merchant-api.md new file mode 100644 index 0000000..8e5f324 --- /dev/null +++ b/docs/src/uz/merchant-api.md @@ -0,0 +1,6 @@ +--- +title: Merchant API metodlari +description: Merchant API metodlari +--- + +# Merchant API metodlari diff --git a/docs/src/uz/sandbox.md b/docs/src/uz/sandbox.md new file mode 100644 index 0000000..99c75ae --- /dev/null +++ b/docs/src/uz/sandbox.md @@ -0,0 +1,6 @@ +--- +title: Qumdon (Sandbox) +description: Qumdon (Sandbox) +--- + +# Qumdon (Sandbox) diff --git a/docs/src/uz/setup.md b/docs/src/uz/setup.md new file mode 100644 index 0000000..fb18afd --- /dev/null +++ b/docs/src/uz/setup.md @@ -0,0 +1,6 @@ +--- +title: Sozlash (Setup) +description: Sozlash (Setup) +--- + +# Sozlash (Setup) diff --git a/docs/src/uz/subscribe-api.md b/docs/src/uz/subscribe-api.md new file mode 100644 index 0000000..cce1674 --- /dev/null +++ b/docs/src/uz/subscribe-api.md @@ -0,0 +1,6 @@ +--- +title: Subscribe API metodlari +description: Subscribe API metodlari +--- + +# Subscribe API metodlari diff --git a/docs/src/uz/support.md b/docs/src/uz/support.md new file mode 100644 index 0000000..a00db19 --- /dev/null +++ b/docs/src/uz/support.md @@ -0,0 +1,6 @@ +--- +title: Texnik yordam +description: Texnik yordam +--- + +# Texnik yordam From e59fd92180fb80a9c5bedb72a70fd76167db8551 Mon Sep 17 00:00:00 2001 From: hoosnick Date: Thu, 24 Aug 2023 11:06:29 +0500 Subject: [PATCH 14/32] fix: autopep8 (solved pylint problems) --- lib/payme/migrations/0001_initial.py | 37 +++++++++++++++++++--------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/lib/payme/migrations/0001_initial.py b/lib/payme/migrations/0001_initial.py index bb3bc2a..7c7c6f9 100644 --- a/lib/payme/migrations/0001_initial.py +++ b/lib/payme/migrations/0001_initial.py @@ -1,21 +1,21 @@ -# Generated by Django 4.2.3 on 2023-08-24 05:54 - -from django.db import migrations, models +# pylint: disable=invalid-name import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): - + # pylint: disable=missing-class-docstring initial = True - - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( name='Item', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('id', models.BigAutoField( + auto_created=True, primary_key=True, + serialize=False, verbose_name='ID' + )), ('discount', models.FloatField(blank=True, null=True)), ('title', models.CharField(max_length=255)), ('price', models.FloatField(blank=True, null=True)), @@ -29,7 +29,10 @@ class Migration(migrations.Migration): migrations.CreateModel( name='MerchatTransactionsModel', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('id', models.BigAutoField( + auto_created=True, primary_key=True, + serialize=False, verbose_name='ID' + )), ('_id', models.CharField(max_length=255, null=True)), ('transaction_id', models.CharField(max_length=255, null=True)), ('order_id', models.BigIntegerField(blank=True, null=True)), @@ -47,7 +50,10 @@ class Migration(migrations.Migration): migrations.CreateModel( name='ShippingDetail', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('id', models.BigAutoField( + auto_created=True, primary_key=True, + serialize=False, verbose_name='ID' + )), ('title', models.CharField(max_length=255)), ('price', models.FloatField(default=0)), ], @@ -55,10 +61,17 @@ class Migration(migrations.Migration): migrations.CreateModel( name='OrderDetail', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('id', models.BigAutoField( + auto_created=True, primary_key=True, + serialize=False, verbose_name='ID' + )), ('receipt_type', models.IntegerField(default=0)), ('items', models.ManyToManyField(to='payme.item')), - ('shipping', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='payme.shippingdetail')), + ('shipping', models.ForeignKey( + blank=True, null=True, + on_delete=django.db.models.deletion.CASCADE, + to='payme.shippingdetail' + )), ], ), ] From 18440422585f0510481235fe71480aeb6f5d316d Mon Sep 17 00:00:00 2001 From: hoosnick Date: Fri, 25 Aug 2023 18:37:33 +0500 Subject: [PATCH 15/32] feat: Dynamic ICPS optimized (#12) hotfix: HotFix CheckTransaction bug (#43) --- lib/payme/methods/generate_link.py | 12 +++++------ lib/payme/migrations/0001_initial.py | 6 +++--- lib/payme/models.py | 32 +++++++++++++++++----------- lib/payme/serializers.py | 14 ++++++------ 4 files changed, 35 insertions(+), 29 deletions(-) diff --git a/lib/payme/methods/generate_link.py b/lib/payme/methods/generate_link.py index 5590adf..404f985 100644 --- a/lib/payme/methods/generate_link.py +++ b/lib/payme/methods/generate_link.py @@ -27,7 +27,7 @@ class GeneratePayLink: Parameters ---------- order_id: int β€” The order_id for paying - amount: float β€” The amount belong to the order + amount: int β€” The amount belong to the order Returns str β€” pay link ---------------------- @@ -37,7 +37,7 @@ class GeneratePayLink: https://developer.help.paycom.uz/initsializatsiya-platezhey/ """ order_id: str - amount: float + amount: int def generate_link(self) -> str: """ @@ -131,23 +131,23 @@ def to_qrcode(self, path: str = 'qr-codes', filename: str = None, **kwargs): return image_output_path @staticmethod - def to_tiyin(amount: float) -> float: + def to_tiyin(amount: int) -> int: """ Convert from soum to tiyin. Parameters ---------- - amount: float -> order amount + amount: int -> order amount """ return amount * 100 @staticmethod - def to_soum(amount: float) -> float: + def to_soum(amount: int) -> int: """ Convert from tiyin to soum. Parameters ---------- - amount: float -> order amount + amount: int -> order amount """ return amount / 100 diff --git a/lib/payme/migrations/0001_initial.py b/lib/payme/migrations/0001_initial.py index 7c7c6f9..fba08cd 100644 --- a/lib/payme/migrations/0001_initial.py +++ b/lib/payme/migrations/0001_initial.py @@ -18,7 +18,7 @@ class Migration(migrations.Migration): )), ('discount', models.FloatField(blank=True, null=True)), ('title', models.CharField(max_length=255)), - ('price', models.FloatField(blank=True, null=True)), + ('price', models.BigIntegerField(default=0)), ('count', models.IntegerField(default=1)), ('code', models.CharField(max_length=17)), ('units', models.IntegerField(blank=True, null=True)), @@ -36,7 +36,7 @@ class Migration(migrations.Migration): ('_id', models.CharField(max_length=255, null=True)), ('transaction_id', models.CharField(max_length=255, null=True)), ('order_id', models.BigIntegerField(blank=True, null=True)), - ('amount', models.FloatField(blank=True, null=True)), + ('amount', models.BigIntegerField(blank=True, null=True)), ('time', models.BigIntegerField(blank=True, null=True)), ('perform_time', models.BigIntegerField(default=0, null=True)), ('cancel_time', models.BigIntegerField(default=0, null=True)), @@ -55,7 +55,7 @@ class Migration(migrations.Migration): serialize=False, verbose_name='ID' )), ('title', models.CharField(max_length=255)), - ('price', models.FloatField(default=0)), + ('price', models.BigIntegerField(default=0)), ], ), migrations.CreateModel( diff --git a/lib/payme/models.py b/lib/payme/models.py index 75c2c99..2f7d8c9 100644 --- a/lib/payme/models.py +++ b/lib/payme/models.py @@ -9,7 +9,7 @@ class MerchatTransactionsModel(models.Model): _id = models.CharField(max_length=255, null=True, blank=False) transaction_id = models.CharField(max_length=255, null=True, blank=False) order_id = models.BigIntegerField(null=True, blank=True) - amount = models.FloatField(null=True, blank=True) + amount = models.BigIntegerField(null=True, blank=True) time = models.BigIntegerField(null=True, blank=True) perform_time = models.BigIntegerField(null=True, default=0) cancel_time = models.BigIntegerField(null=True, default=0) @@ -29,7 +29,7 @@ class ShippingDetail(models.Model): That's used for managing shipping """ title = models.CharField(max_length=255) - price = models.FloatField(default=0) + price = models.BigIntegerField(default=0) def __str__(self) -> str: return f"[{self.pk}] {self.title} {self.price}" @@ -42,7 +42,7 @@ class Item(models.Model): """ discount = models.FloatField(null=True, blank=True) title = models.CharField(max_length=255) - price = models.FloatField(null=True, blank=True) + price = models.BigIntegerField(default=0) count = models.IntegerField(default=1) code = models.CharField(max_length=17) units = models.IntegerField(null=True, blank=True) @@ -50,7 +50,7 @@ class Item(models.Model): vat_percent = models.IntegerField(default=0, null=True, blank=True) def __str__(self) -> str: - return f"[{self.id}] {self.title} #{self.code}" + return f"[{self.id}] {self.title} ({self.count} pc.) x {self.price}" class OrderDetail(models.Model): @@ -66,12 +66,22 @@ class OrderDetail(models.Model): ) items = models.ManyToManyField(Item) + @property def get_items_display(self): # pylint: disable=missing-function-docstring - return ', '.join([items.title for items in self.items.all()]) + return ', '.join([items.title for items in self.items.all()[:2]]) + + @property + def get_total_items_price(self) -> int: + # pylint: disable=missing-function-docstring + return self.items.all().aggregate( + total_price=models.Sum( + models.F('price') * models.F('count') + ) + )['total_price'] def __str__(self) -> str: - return f"[{self.pk}] {self.get_items_display()}" + return f"{self.get_items_display}" class DisallowOverrideMetaclass(models.base.ModelBase): @@ -96,18 +106,16 @@ class BaseOrder(models.Model, metaclass=DisallowOverrideMetaclass): Order class \ That's used for managing order process """ - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) - - amount = models.FloatField(null=True, blank=True) detail = models.ForeignKey( OrderDetail, null=True, blank=True, on_delete=models.CASCADE ) - def __str__(self): - return f"ORDER ID: {self.id} - AMOUNT: {self.amount}" + @property + def amount(self): + # pylint: disable=missing-function-docstring + return self.detail.get_total_items_price class Meta: # pylint: disable=missing-class-docstring diff --git a/lib/payme/serializers.py b/lib/payme/serializers.py index 3d36ed4..82fb5fa 100644 --- a/lib/payme/serializers.py +++ b/lib/payme/serializers.py @@ -27,7 +27,7 @@ class Meta: fields: str = "__all__" extra_fields = ['start_date', 'end_date'] - def validate(self, attrs) -> dict: + def validate(self, attrs: dict) -> dict: """ Validate the data given to the MerchatTransactionsModel. """ @@ -36,7 +36,7 @@ def validate(self, attrs) -> dict: order = Order.objects.get( id=attrs['order_id'] ) - if order.amount != float(attrs['amount']): + if order.amount != int(attrs['amount']): raise IncorrectAmount() except IncorrectAmount as error: @@ -45,15 +45,13 @@ def validate(self, attrs) -> dict: return attrs - def validate_amount(self, amount: float) -> float: + def validate_amount(self, amount: int) -> int: """ Validator for Transactions Amount. """ - if amount is None: - raise IncorrectAmount() - - if amount <= float(settings.PAYME.get("PAYME_MIN_AMOUNT", 0)): - raise IncorrectAmount("Payment amount is less than allowed.") + if amount is not None: + if amount <= int(settings.PAYME.get("PAYME_MIN_AMOUNT")): + raise IncorrectAmount("Payment amount is less than allowed.") return amount From 010e45444f2cae08ddeefebd6fbc2fe3af88ecfb Mon Sep 17 00:00:00 2001 From: hoosnick Date: Fri, 25 Aug 2023 18:50:22 +0500 Subject: [PATCH 16/32] del: removed /docs/ from repo --- .github/workflows/docs.yml | 16 ---- docs/src/assets/logo.png | Bin 15689 -> 0 bytes docs/src/en/index.md | 65 ---------------- docs/src/en/merchant-api.md | 6 -- docs/src/en/sandbox.md | 6 -- docs/src/en/setup.md | 6 -- docs/src/en/subscribe-api.md | 6 -- docs/src/en/support.md | 6 -- docs/src/overrides/partials/nav.html | 23 ------ docs/src/ru/index.md | 5 -- docs/src/ru/merchant-api.md | 6 -- docs/src/ru/sandbox.md | 6 -- docs/src/ru/setup.md | 6 -- docs/src/ru/subscribe-api.md | 6 -- docs/src/ru/support.md | 6 -- docs/src/uz/index.md | 5 -- docs/src/uz/merchant-api.md | 6 -- docs/src/uz/sandbox.md | 6 -- docs/src/uz/setup.md | 6 -- docs/src/uz/subscribe-api.md | 6 -- docs/src/uz/support.md | 6 -- mkdocs.yml | 112 --------------------------- requirements/docs-requirements.txt | 2 - 23 files changed, 318 deletions(-) delete mode 100644 .github/workflows/docs.yml delete mode 100644 docs/src/assets/logo.png delete mode 100644 docs/src/en/index.md delete mode 100644 docs/src/en/merchant-api.md delete mode 100644 docs/src/en/sandbox.md delete mode 100644 docs/src/en/setup.md delete mode 100644 docs/src/en/subscribe-api.md delete mode 100644 docs/src/en/support.md delete mode 100644 docs/src/overrides/partials/nav.html delete mode 100644 docs/src/ru/index.md delete mode 100644 docs/src/ru/merchant-api.md delete mode 100644 docs/src/ru/sandbox.md delete mode 100644 docs/src/ru/setup.md delete mode 100644 docs/src/ru/subscribe-api.md delete mode 100644 docs/src/ru/support.md delete mode 100644 docs/src/uz/index.md delete mode 100644 docs/src/uz/merchant-api.md delete mode 100644 docs/src/uz/sandbox.md delete mode 100644 docs/src/uz/setup.md delete mode 100644 docs/src/uz/subscribe-api.md delete mode 100644 docs/src/uz/support.md delete mode 100644 mkdocs.yml delete mode 100644 requirements/docs-requirements.txt diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 6011155..0000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Documentation - -on: - push: - branches: - - payme-pkg-3.0b -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: 3.x - - run: pip install -r requirements/docs-requirements.txt - - run: mkdocs gh-deploy --force diff --git a/docs/src/assets/logo.png b/docs/src/assets/logo.png deleted file mode 100644 index 35d3c11b7dbffcf9e48ab23075a39fd21d46e691..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15689 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](https://pay-tech.uz) - -Welcome to the PayTechUz organization! This project aims to provide developers with seamless integration and implementation of Payment Providers APIs in Uzbekistan. Whether you're a Python enthusiast or a Go guru, we've got you covered with our versatile tech stack. - -## Table of Contents - -- [Introduction](#introduction) -- [Website](#website) -- [Telegram Group](#telegram-group) -- [Tech Stack](#tech-stack) -- [Usage](#usage) -- [Contributing](#contributing) -- [Support](#support) -- [License](#license) - -## Introduction - -Integrating payment providers into your applications is crucial in today's digital landscape. This project simplifies the process of integrating Payment Providers APIs in Uzbekistan, allowing you to focus on building amazing products while leaving the payment processing worries behind. - -## Website - -Visit our website [pay-tech.uz](https://pay-tech.uz) to explore detailed documentation, guides, and API references. We regularly update the documentation to ensure you have the most up-to-date information at your disposal. - -## Telegram Group - -Join our vibrant developer community on Telegram to connect with fellow developers, ask questions, share experiences, and stay updated with the latest news and announcements. - -[Telegram Group](https://t.me/+krHlGUizJrI1Zjli) - -## Tech Stack - -We have carefully chosen the following tech stack to ensure flexibility and ease of use: - -- [![Python](https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white)](https://python.org/) -- [![Go](https://img.shields.io/badge/Go-00ADD8?style=for-the-badge&logo=go&logoColor=white)](https://go.dev/) - -The combination of Python and Go offers a wide range of possibilities to cater to the preferences of different developers. - -## Usage - -Our packages are designed to streamline the integration process. Follow the detailed instructions in the documentation for each package to integrate the desired Payment Providers API seamlessly. - -## Contributing - -We welcome contributions from the community to make this project even better. If you find any issues or have suggestions for improvements, feel free to open an issue or submit a pull request. Please ensure you follow our [contribution guidelines](https://github.com/PayTechUz/.github/blob/main/profile/CONTRIBUTING.md). - -## Support - -If you encounter any problems, have questions, or need assistance with the integration, our team is here to help. Reach out to us on our [Telegram Group](https://t.me/+ydVV_9B3Xh02NGEy) or email us at paytechuz@gmail.com. - -## License - -This project is licensed under the [MIT License](https://github.com/PayTechUz/.github/blob/main/profile/LICENSE.txt), making it open and free for everyone to use, modify, and distribute. - ---- - -We hope our PayTechUz packages make your payment integration process a delightful experience. Happy coding! diff --git a/docs/src/en/merchant-api.md b/docs/src/en/merchant-api.md deleted file mode 100644 index 83c1c74..0000000 --- a/docs/src/en/merchant-api.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Merchant API methods -description: Merchant API methods ---- - -# Merchant API methods diff --git a/docs/src/en/sandbox.md b/docs/src/en/sandbox.md deleted file mode 100644 index da2d56d..0000000 --- a/docs/src/en/sandbox.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Sandbox (Testing) -description: Sandbox (Testing) ---- - -# Sandbox (Testing) diff --git a/docs/src/en/setup.md b/docs/src/en/setup.md deleted file mode 100644 index 12f0f53..0000000 --- a/docs/src/en/setup.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Interaction setup -description: Interaction setup ---- - -# Interaction setup diff --git a/docs/src/en/subscribe-api.md b/docs/src/en/subscribe-api.md deleted file mode 100644 index 95b8128..0000000 --- a/docs/src/en/subscribe-api.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Subscribe API methods -description: Subscribe API methods ---- - -# Subscribe API methods diff --git a/docs/src/en/support.md b/docs/src/en/support.md deleted file mode 100644 index 386243f..0000000 --- a/docs/src/en/support.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Technical support -description: Technical support ---- - -# Technical support diff --git a/docs/src/overrides/partials/nav.html b/docs/src/overrides/partials/nav.html deleted file mode 100644 index f4025e5..0000000 --- a/docs/src/overrides/partials/nav.html +++ /dev/null @@ -1,23 +0,0 @@ -

diff --git a/docs/src/ru/index.md b/docs/src/ru/index.md deleted file mode 100644 index 5554065..0000000 --- a/docs/src/ru/index.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Π’Π²Π΅Π΄Π΅Π½ΠΈΠ΅ ---- - -# **Π”ΠΎΠ±Ρ€ΠΎ ΠΏΠΎΠΆΠ°Π»ΠΎΠ²Π°Ρ‚ΡŒ!** diff --git a/docs/src/ru/merchant-api.md b/docs/src/ru/merchant-api.md deleted file mode 100644 index 1398766..0000000 --- a/docs/src/ru/merchant-api.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ Merchant API -description: ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ Merchant API ---- - -# ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ Merchant API diff --git a/docs/src/ru/sandbox.md b/docs/src/ru/sandbox.md deleted file mode 100644 index 4631217..0000000 --- a/docs/src/ru/sandbox.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: ΠŸΠ΅ΡΠΎΡ‡Π½ΠΈΡ†Π° (ВСстированиС) -description: ΠŸΠ΅ΡΠΎΡ‡Π½ΠΈΡ†Π° (ВСстированиС) ---- - -# ΠŸΠ΅ΡΠΎΡ‡Π½ΠΈΡ†Π° (ВСстированиС) diff --git a/docs/src/ru/setup.md b/docs/src/ru/setup.md deleted file mode 100644 index ac946b7..0000000 --- a/docs/src/ru/setup.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Настройка взаимодСйствия -description: Настройка взаимодСйствия ---- - -# Настройка взаимодСйствия diff --git a/docs/src/ru/subscribe-api.md b/docs/src/ru/subscribe-api.md deleted file mode 100644 index b1fe530..0000000 --- a/docs/src/ru/subscribe-api.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ Subscribe API -description: ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ Subscribe API ---- - -# ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ Subscribe API diff --git a/docs/src/ru/support.md b/docs/src/ru/support.md deleted file mode 100644 index 060c771..0000000 --- a/docs/src/ru/support.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: ВСхничСская ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° -description: ВСхничСская ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° ---- - -# ВСхничСская ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° diff --git a/docs/src/uz/index.md b/docs/src/uz/index.md deleted file mode 100644 index 7905467..0000000 --- a/docs/src/uz/index.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Kirish ---- - -# **Xush kelibsiz!** diff --git a/docs/src/uz/merchant-api.md b/docs/src/uz/merchant-api.md deleted file mode 100644 index 8e5f324..0000000 --- a/docs/src/uz/merchant-api.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Merchant API metodlari -description: Merchant API metodlari ---- - -# Merchant API metodlari diff --git a/docs/src/uz/sandbox.md b/docs/src/uz/sandbox.md deleted file mode 100644 index 99c75ae..0000000 --- a/docs/src/uz/sandbox.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Qumdon (Sandbox) -description: Qumdon (Sandbox) ---- - -# Qumdon (Sandbox) diff --git a/docs/src/uz/setup.md b/docs/src/uz/setup.md deleted file mode 100644 index fb18afd..0000000 --- a/docs/src/uz/setup.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Sozlash (Setup) -description: Sozlash (Setup) ---- - -# Sozlash (Setup) diff --git a/docs/src/uz/subscribe-api.md b/docs/src/uz/subscribe-api.md deleted file mode 100644 index cce1674..0000000 --- a/docs/src/uz/subscribe-api.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Subscribe API metodlari -description: Subscribe API metodlari ---- - -# Subscribe API metodlari diff --git a/docs/src/uz/support.md b/docs/src/uz/support.md deleted file mode 100644 index a00db19..0000000 --- a/docs/src/uz/support.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Texnik yordam -description: Texnik yordam ---- - -# Texnik yordam diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index 4413364..0000000 --- a/mkdocs.yml +++ /dev/null @@ -1,112 +0,0 @@ -# Project information -site_name: "Payme-pkg uchun yo'riqnoma" -site_description: 🟒 Merchant API and Subscribe API Integration 2023β€”08 -site_author: Hoosnick -site_url: https://paytechuz.github.io/payme-pkg -docs_dir: docs/src -site_dir: payme - -# Repository -repo_name: PayTechUz/payme-pkg -repo_url: https://github.com/PayTechUz/payme-pkg -edit_uri: "" - -# Copyright -copyright: Copyright © 2023 PayTechUz - -# Configuration -theme: - name: material - custom_dir: docs/src/overrides - language: uz - palette: - - media: "(prefers-color-scheme: light)" - scheme: default - primary: teal - toggle: - icon: material/brightness-4 - name: Switch to dark mode - - media: "(prefers-color-scheme: dark)" - scheme: slate - primary: teal - toggle: - icon: material/brightness-7 - name: Switch to light mode - font: - text: Ubuntu - code: Roboto Mono - # icon: - # logo: material/cash-multiple - logo: assets/logo.png - -# Extras -extra: - social: - - icon: fontawesome/brands/telegram - link: https://t.me/+7Gn-JZ99TfgwZDNi - name: "Biz telegramda" - - icon: fontawesome/brands/github-alt - link: https://github.com/PayTechUz/payme-pkg - name: GitHub - -# Extensions -markdown_extensions: - - admonition - - pymdownx.highlight - - pymdownx.superfences - - pymdownx.details - - meta - - attr_list - - toc: - permalink: true - -nav: - - "home": index.md - - "interaction_setup": setup.md - - "merchant_api": merchant-api.md - - "subscribe_api": subscribe-api.md - - "sandbox": sandbox.md - - "support": support.md - -plugins: - - search - - i18n: - default_language: uz - default_language_only: false - docs_structure: folder - - languages: - en: - name: "en English" - build: true - site_name: "Documentation for payme-pkg" - uz: - name: "uz O'zbekcha" - build: true - ru: - name: "ru Русский" - build: true - site_name: "ДокумСнтация для payme-pkg" - - nav_translations: - en: - "home": "Introduction" - "interaction_setup": "Interaction setup" - "merchant_api": "Merchant API" - "subscribe_api": "Subscribe API" - "sandbox": "Sandbox (Testing)" - "support": "Technical support" - ru: - "home": "Π’Π²Π΅Π΄Π΅Π½ΠΈΠ΅" - "interaction_setup": "Настройка взаимодСйствия" - "merchant_api": "Merchant API" - "subscribe_api": "Subscribe API" - "sandbox": "ΠŸΠ΅ΡΠΎΡ‡Π½ΠΈΡ†Π° (ВСстированиС)" - "support": "ВСхничСская ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ°" - uz: - "home": "Kirish" - "interaction_setup": "Sozlash (Setup)" - "merchant_api": "Merchant API" - "subscribe_api": "Subscribe API" - "sandbox": "Qumdon (Sandbox)" - "support": "Texnik yordam" diff --git a/requirements/docs-requirements.txt b/requirements/docs-requirements.txt deleted file mode 100644 index 55f78da..0000000 --- a/requirements/docs-requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -mkdocs-material==9.1.21 -mkdocs-static-i18n==0.56 \ No newline at end of file From fc32c0a830be097e834ec141310cf95706018dd4 Mon Sep 17 00:00:00 2001 From: hoosnick Date: Sat, 26 Aug 2023 17:05:35 +0500 Subject: [PATCH 17/32] fix: shipping price to amount (readable fields) --- lib/payme/models.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/payme/models.py b/lib/payme/models.py index 2f7d8c9..50be5f5 100644 --- a/lib/payme/models.py +++ b/lib/payme/models.py @@ -32,7 +32,7 @@ class ShippingDetail(models.Model): price = models.BigIntegerField(default=0) def __str__(self) -> str: - return f"[{self.pk}] {self.title} {self.price}" + return f"[{self.pk}] {self.title} - {self.price:,}" class Item(models.Model): @@ -50,7 +50,7 @@ class Item(models.Model): vat_percent = models.IntegerField(default=0, null=True, blank=True) def __str__(self) -> str: - return f"[{self.id}] {self.title} ({self.count} pc.) x {self.price}" + return f"[{self.id}] {self.title} ({self.count} pc.) x {self.price:,}" class OrderDetail(models.Model): @@ -69,12 +69,13 @@ class OrderDetail(models.Model): @property def get_items_display(self): # pylint: disable=missing-function-docstring - return ', '.join([items.title for items in self.items.all()[:2]]) + return ', '.join([items.title for items in self.items.all()]) @property def get_total_items_price(self) -> int: # pylint: disable=missing-function-docstring - return self.items.all().aggregate( + shipping_price = self.shipping.price if self.shipping else 0 + return shipping_price + self.items.all().aggregate( total_price=models.Sum( models.F('price') * models.F('count') ) @@ -117,6 +118,9 @@ def amount(self): # pylint: disable=missing-function-docstring return self.detail.get_total_items_price + def __str__(self): + return f"ORDER ID: {self.id} - AMOUNT: {self.amount:,}" + class Meta: # pylint: disable=missing-class-docstring abstract = True From 396c7be185edfda3b2a00168c6c179aa20a2a99f Mon Sep 17 00:00:00 2001 From: hoosnick Date: Fri, 8 Sep 2023 11:45:00 +0500 Subject: [PATCH 18/32] add: calculate discount --- lib/payme/migrations/0001_initial.py | 2 +- lib/payme/models.py | 47 ++++++++++++++++++++++------ 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/lib/payme/migrations/0001_initial.py b/lib/payme/migrations/0001_initial.py index fba08cd..883bad3 100644 --- a/lib/payme/migrations/0001_initial.py +++ b/lib/payme/migrations/0001_initial.py @@ -16,7 +16,7 @@ class Migration(migrations.Migration): auto_created=True, primary_key=True, serialize=False, verbose_name='ID' )), - ('discount', models.FloatField(blank=True, null=True)), + ('discount', models.BigIntegerField(blank=True, null=True)), ('title', models.CharField(max_length=255)), ('price', models.BigIntegerField(default=0)), ('count', models.IntegerField(default=1)), diff --git a/lib/payme/models.py b/lib/payme/models.py index 50be5f5..d4c4e19 100644 --- a/lib/payme/models.py +++ b/lib/payme/models.py @@ -40,7 +40,7 @@ class Item(models.Model): Item class \ That's used for managing order items """ - discount = models.FloatField(null=True, blank=True) + discount = models.BigIntegerField(null=True, blank=True) title = models.CharField(max_length=255) price = models.BigIntegerField(default=0) count = models.IntegerField(default=1) @@ -50,7 +50,7 @@ class Item(models.Model): vat_percent = models.IntegerField(default=0, null=True, blank=True) def __str__(self) -> str: - return f"[{self.id}] {self.title} ({self.count} pc.) x {self.price:,}" + return f"[{self.id}] {self.title} ({self.count} pc.) x {self.price:,} UZS" class OrderDetail(models.Model): @@ -69,17 +69,46 @@ class OrderDetail(models.Model): @property def get_items_display(self): # pylint: disable=missing-function-docstring - return ', '.join([items.title for items in self.items.all()]) + item_display = [f'[{self.pk}]'] + + for fld in self._meta.get_fields(): + if not isinstance(fld, models.ManyToOneRel): + continue + + if not issubclass(fld.related_model, BaseOrder): + continue + + related_order = fld.get_accessor_name() + orders = getattr(self, related_order).values() + + order_id = orders[0].get('id') if orders else 'None' + + item_display.append(f'FOR ORDER - {order_id}') + if self.shipping: item_display.append(f'ADDRESS: {self.shipping.title}') + item_display.append(f'AMOUNT: {self.get_total_items_price} UZS') + + return ' '.join(item_display) @property def get_total_items_price(self) -> int: # pylint: disable=missing-function-docstring - shipping_price = self.shipping.price if self.shipping else 0 - return shipping_price + self.items.all().aggregate( + items = self.items.all().aggregate( total_price=models.Sum( - models.F('price') * models.F('count') + models.Case( + models.When( + discount__isnull=True, + then=models.F('price') * models.F('count') + ), + default=( + (models.F('price') * models.F('count')) - + models.F('discount') + ), + output_field=models.BigIntegerField() + ) ) - )['total_price'] + ) + shipping_price = self.shipping.price if self.shipping else 0 + return f"{shipping_price + items['total_price']:,}" def __str__(self) -> str: return f"{self.get_items_display}" @@ -116,10 +145,10 @@ class BaseOrder(models.Model, metaclass=DisallowOverrideMetaclass): @property def amount(self): # pylint: disable=missing-function-docstring - return self.detail.get_total_items_price + return self.detail.get_total_items_price if self.detail else 0 def __str__(self): - return f"ORDER ID: {self.id} - AMOUNT: {self.amount:,}" + return f"ORDER ID: {self.id} - AMOUNT: {self.amount} UZS" class Meta: # pylint: disable=missing-class-docstring From 1030250ae4b2c5a9865cd430fb82f2efa9ca6de3 Mon Sep 17 00:00:00 2001 From: hoosnick Date: Fri, 8 Sep 2023 11:47:08 +0500 Subject: [PATCH 19/32] lint: fix multiple-statements --- lib/payme/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/payme/models.py b/lib/payme/models.py index d4c4e19..c8a19fc 100644 --- a/lib/payme/models.py +++ b/lib/payme/models.py @@ -84,7 +84,8 @@ def get_items_display(self): order_id = orders[0].get('id') if orders else 'None' item_display.append(f'FOR ORDER - {order_id}') - if self.shipping: item_display.append(f'ADDRESS: {self.shipping.title}') + if self.shipping: + item_display.append(f'ADDRESS: {self.shipping.title}') item_display.append(f'AMOUNT: {self.get_total_items_price} UZS') return ' '.join(item_display) From fdd3f3943591fb110f128b4e06e2aade7a3f3c40 Mon Sep 17 00:00:00 2001 From: hoosnick Date: Sat, 9 Sep 2023 13:27:51 +0500 Subject: [PATCH 20/32] feat: display amount (soums) in the admin's ui --- lib/payme/models.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/payme/models.py b/lib/payme/models.py index c8a19fc..869652e 100644 --- a/lib/payme/models.py +++ b/lib/payme/models.py @@ -50,7 +50,8 @@ class Item(models.Model): vat_percent = models.IntegerField(default=0, null=True, blank=True) def __str__(self) -> str: - return f"[{self.id}] {self.title} ({self.count} pc.) x {self.price:,} UZS" + item_price = self.price / 100 # item price in soum + return f"[{self.id}] {self.title} ({self.count} pc.) x {item_price:,} UZS" class OrderDetail(models.Model): @@ -80,13 +81,14 @@ def get_items_display(self): related_order = fld.get_accessor_name() orders = getattr(self, related_order).values() - order_id = orders[0].get('id') if orders else 'None' - + item_display.append(f'FOR ORDER - {order_id}') + if self.shipping: item_display.append(f'ADDRESS: {self.shipping.title}') - item_display.append(f'AMOUNT: {self.get_total_items_price} UZS') + + item_display.append(f'AMOUNT: {self.get_total_items_price:,} UZS') return ' '.join(item_display) @@ -109,10 +111,10 @@ def get_total_items_price(self) -> int: ) ) shipping_price = self.shipping.price if self.shipping else 0 - return f"{shipping_price + items['total_price']:,}" + return int(shipping_price + items['total_price']) / 100 def __str__(self) -> str: - return f"{self.get_items_display}" + return self.get_items_display class DisallowOverrideMetaclass(models.base.ModelBase): @@ -149,7 +151,7 @@ def amount(self): return self.detail.get_total_items_price if self.detail else 0 def __str__(self): - return f"ORDER ID: {self.id} - AMOUNT: {self.amount} UZS" + return f"ORDER ID: {self.id} - AMOUNT: {self.amount:,} UZS" class Meta: # pylint: disable=missing-class-docstring From 7cb66580ab58e09b343d74ecbba6bb694f9d1990 Mon Sep 17 00:00:00 2001 From: hoosnick Date: Sat, 9 Sep 2023 13:34:10 +0500 Subject: [PATCH 21/32] lint: fix trailing whitespace --- lib/payme/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/payme/models.py b/lib/payme/models.py index 869652e..b4acd4b 100644 --- a/lib/payme/models.py +++ b/lib/payme/models.py @@ -82,12 +82,12 @@ def get_items_display(self): related_order = fld.get_accessor_name() orders = getattr(self, related_order).values() order_id = orders[0].get('id') if orders else 'None' - + item_display.append(f'FOR ORDER - {order_id}') - + if self.shipping: item_display.append(f'ADDRESS: {self.shipping.title}') - + item_display.append(f'AMOUNT: {self.get_total_items_price:,} UZS') return ' '.join(item_display) From 0c3dc236f4b09399e8b9d1e2700a73caa5453c09 Mon Sep 17 00:00:00 2001 From: hoosnick Date: Sat, 9 Sep 2023 15:29:44 +0500 Subject: [PATCH 22/32] fix: shipping price to soum --- lib/payme/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/payme/models.py b/lib/payme/models.py index b4acd4b..7dac598 100644 --- a/lib/payme/models.py +++ b/lib/payme/models.py @@ -32,7 +32,8 @@ class ShippingDetail(models.Model): price = models.BigIntegerField(default=0) def __str__(self) -> str: - return f"[{self.pk}] {self.title} - {self.price:,}" + shipping_price = self.price / 100 # shipping price in soum + return f"[{self.pk}] {self.title} - {shipping_price:,}" class Item(models.Model): From 108446fa0033e904d42a6e910906a1efb246e6b5 Mon Sep 17 00:00:00 2001 From: hoosnick Date: Sun, 1 Oct 2023 13:40:02 +0500 Subject: [PATCH 23/32] fix: typo "Merchat" to "Merchant" in methods --- lib/payme/methods/cancel_transaction.py | 6 +++--- lib/payme/methods/check_transaction.py | 4 ++-- lib/payme/methods/create_transaction.py | 6 +++--- lib/payme/methods/get_statement.py | 6 +++--- lib/payme/methods/perform_transaction.py | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/payme/methods/cancel_transaction.py b/lib/payme/methods/cancel_transaction.py index d318480..adf22c4 100644 --- a/lib/payme/methods/cancel_transaction.py +++ b/lib/payme/methods/cancel_transaction.py @@ -3,7 +3,7 @@ from django.db import transaction from payme.utils.logging import logger -from payme.models import MerchatTransactionsModel +from payme.models import MerchantTransactionsModel from payme.errors.exceptions import PerformTransactionDoesNotExist from payme.serializers import MerchatTransactionsModelSerializer as MTMS @@ -25,8 +25,8 @@ def __call__(self, params: dict): ) try: with transaction.atomic(): - transactions: MerchatTransactionsModel = \ - MerchatTransactionsModel.objects.filter( + transactions: MerchantTransactionsModel = \ + MerchantTransactionsModel.objects.filter( _id=clean_data.get('_id'), ).first() if transactions.cancel_time == 0: diff --git a/lib/payme/methods/check_transaction.py b/lib/payme/methods/check_transaction.py index 4768154..72eaded 100644 --- a/lib/payme/methods/check_transaction.py +++ b/lib/payme/methods/check_transaction.py @@ -1,7 +1,7 @@ from django.db import DatabaseError from payme.utils.logging import logger -from payme.models import MerchatTransactionsModel +from payme.models import MerchantTransactionsModel from payme.serializers import MerchatTransactionsModelSerializer as MTMS @@ -21,7 +21,7 @@ def __call__(self, params: dict) -> None: try: transaction = \ - MerchatTransactionsModel.objects.get( + MerchantTransactionsModel.objects.get( _id=clean_data.get("_id"), ) response = { diff --git a/lib/payme/methods/create_transaction.py b/lib/payme/methods/create_transaction.py index a5f2c77..ed99688 100644 --- a/lib/payme/methods/create_transaction.py +++ b/lib/payme/methods/create_transaction.py @@ -4,7 +4,7 @@ from payme.utils.logging import logger from payme.utils.get_params import get_params -from payme.models import MerchatTransactionsModel +from payme.models import MerchantTransactionsModel from payme.errors.exceptions import TooManyRequests from payme.serializers import MerchatTransactionsModelSerializer @@ -26,7 +26,7 @@ def __call__(self, params: dict) -> dict: order_id = serializer.validated_data.get("order_id") try: - transaction = MerchatTransactionsModel.objects.filter( + transaction = MerchantTransactionsModel.objects.filter( order_id=order_id ).last() @@ -40,7 +40,7 @@ def __call__(self, params: dict) -> dict: if transaction is None: transaction, _ = \ - MerchatTransactionsModel.objects.get_or_create( + MerchantTransactionsModel.objects.get_or_create( _id=serializer.validated_data.get('_id'), order_id=serializer.validated_data.get('order_id'), transaction_id=uuid.uuid4(), diff --git a/lib/payme/methods/get_statement.py b/lib/payme/methods/get_statement.py index 56342ec..81e5aaf 100644 --- a/lib/payme/methods/get_statement.py +++ b/lib/payme/methods/get_statement.py @@ -1,7 +1,7 @@ from django.db import DatabaseError from payme.utils.logging import logger -from payme.models import MerchatTransactionsModel +from payme.models import MerchantTransactionsModel from payme.serializers import MerchatTransactionsModelSerializer as MTMS from payme.utils.make_aware_datetime import make_aware_datetime as mad @@ -29,13 +29,13 @@ def __call__(self, params: dict): try: transactions = \ - MerchatTransactionsModel.objects.filter( + MerchantTransactionsModel.objects.filter( created_at__gte=start_date, created_at__lte=end_date ) if not transactions: # no transactions found for the period - return {"result": {"transactions": []}} + return None, {"result": {"transactions": []}} statements = [ { diff --git a/lib/payme/methods/perform_transaction.py b/lib/payme/methods/perform_transaction.py index 5312065..e0eb0fe 100644 --- a/lib/payme/methods/perform_transaction.py +++ b/lib/payme/methods/perform_transaction.py @@ -4,7 +4,7 @@ from payme.utils.logging import logger from payme.utils.get_params import get_params -from payme.models import MerchatTransactionsModel +from payme.models import MerchantTransactionsModel from payme.serializers import MerchatTransactionsModelSerializer @@ -26,7 +26,7 @@ def __call__(self, params: dict) -> dict: response: dict = None try: transaction = \ - MerchatTransactionsModel.objects.get( + MerchantTransactionsModel.objects.get( _id=clean_data.get("_id"), ) transaction.state = 2 From d5df016c52bc3d84ccbed641e337784e331f8e46 Mon Sep 17 00:00:00 2001 From: hoosnick Date: Sun, 1 Oct 2023 13:43:05 +0500 Subject: [PATCH 24/32] feat: ICPS (#12), verbose_name, typo, type hints --- lib/payme/admin.py | 17 ++-- lib/payme/migrations/0001_initial.py | 122 +++++++++++++++---------- lib/payme/models.py | 132 ++++++++++++--------------- lib/payme/serializers.py | 66 +++++++++++--- lib/payme/utils/order_finder.py | 2 +- 5 files changed, 194 insertions(+), 145 deletions(-) diff --git a/lib/payme/admin.py b/lib/payme/admin.py index c07bd87..85d92f9 100644 --- a/lib/payme/admin.py +++ b/lib/payme/admin.py @@ -1,20 +1,15 @@ from django.contrib import admin from payme.models import ( - Item, MerchatTransactionsModel, - OrderDetail, ShippingDetail + FiscalData, Item, ShippingDetail, + MerchantTransactionsModel ) -# pylint: disable=fixme -# TODO: order Payme models in admin panel -# 1. OrderDetail -# 2. Item -# 3. ShippingDetail -# 4. MerchatTransactionsModel admin.site.register( [ - OrderDetail, Item, - ShippingDetail, - MerchatTransactionsModel, + MerchantTransactionsModel, + FiscalData, + Item, + ShippingDetail ] ) diff --git a/lib/payme/migrations/0001_initial.py b/lib/payme/migrations/0001_initial.py index 883bad3..898b988 100644 --- a/lib/payme/migrations/0001_initial.py +++ b/lib/payme/migrations/0001_initial.py @@ -10,68 +10,92 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Item', + name="FiscalData", fields=[ - ('id', models.BigAutoField( - auto_created=True, primary_key=True, - serialize=False, verbose_name='ID' - )), - ('discount', models.BigIntegerField(blank=True, null=True)), - ('title', models.CharField(max_length=255)), - ('price', models.BigIntegerField(default=0)), - ('count', models.IntegerField(default=1)), - ('code', models.CharField(max_length=17)), - ('units', models.IntegerField(blank=True, null=True)), - ('package_code', models.CharField(max_length=255)), - ('vat_percent', models.IntegerField(blank=True, default=0, null=True)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("code", models.CharField(max_length=17)), + ("units", models.IntegerField(blank=True, null=True)), + ("package_code", models.CharField(max_length=255)), + ("vat_percent", models.IntegerField(blank=True, default=0, null=True)), ], ), migrations.CreateModel( - name='MerchatTransactionsModel', + name="MerchantTransactionsModel", fields=[ - ('id', models.BigAutoField( - auto_created=True, primary_key=True, - serialize=False, verbose_name='ID' - )), - ('_id', models.CharField(max_length=255, null=True)), - ('transaction_id', models.CharField(max_length=255, null=True)), - ('order_id', models.BigIntegerField(blank=True, null=True)), - ('amount', models.BigIntegerField(blank=True, null=True)), - ('time', models.BigIntegerField(blank=True, null=True)), - ('perform_time', models.BigIntegerField(default=0, null=True)), - ('cancel_time', models.BigIntegerField(default=0, null=True)), - ('state', models.IntegerField(default=1, null=True)), - ('reason', models.CharField(blank=True, max_length=255, null=True)), - ('created_at_ms', models.CharField(blank=True, max_length=255, null=True)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("_id", models.CharField(max_length=255, null=True)), + ("transaction_id", models.CharField(max_length=255, null=True)), + ("order_id", models.BigIntegerField(blank=True, null=True)), + ("amount", models.BigIntegerField(blank=True, null=True)), + ("time", models.BigIntegerField(blank=True, null=True)), + ("perform_time", models.BigIntegerField(default=0, null=True)), + ("cancel_time", models.BigIntegerField(default=0, null=True)), + ("state", models.IntegerField(default=1, null=True)), + ("reason", models.CharField(blank=True, max_length=255, null=True)), + ( + "created_at_ms", + models.CharField(blank=True, max_length=255, null=True), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), ], ), migrations.CreateModel( - name='ShippingDetail', + name="ShippingDetail", fields=[ - ('id', models.BigAutoField( - auto_created=True, primary_key=True, - serialize=False, verbose_name='ID' - )), - ('title', models.CharField(max_length=255)), - ('price', models.BigIntegerField(default=0)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=255)), + ("price", models.BigIntegerField(default=0)), ], ), migrations.CreateModel( - name='OrderDetail', + name="Item", fields=[ - ('id', models.BigAutoField( - auto_created=True, primary_key=True, - serialize=False, verbose_name='ID' - )), - ('receipt_type', models.IntegerField(default=0)), - ('items', models.ManyToManyField(to='payme.item')), - ('shipping', models.ForeignKey( - blank=True, null=True, - on_delete=django.db.models.deletion.CASCADE, - to='payme.shippingdetail' - )), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("discount", models.BigIntegerField(blank=True, null=True)), + ("title", models.CharField(max_length=255)), + ("price", models.BigIntegerField(default=0)), + ("count", models.IntegerField(default=1)), + ( + "fiscal_data", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="payme.fiscaldata", + ), + ), ], ), ] diff --git a/lib/payme/models.py b/lib/payme/models.py index 7dac598..4e07db4 100644 --- a/lib/payme/models.py +++ b/lib/payme/models.py @@ -1,9 +1,10 @@ from django.db import models +from django.db.models import Case, F, Sum, When -class MerchatTransactionsModel(models.Model): +class MerchantTransactionsModel(models.Model): """ - MerchatTransactionsModel class \ + MerchantTransactionsModel class \ That's used for managing transactions in database. """ _id = models.CharField(max_length=255, null=True, blank=False) @@ -22,6 +23,11 @@ class MerchatTransactionsModel(models.Model): def __str__(self): return str(self._id) + class Meta: + # pylint: disable=missing-class-docstring + verbose_name = "Merchant Transaction" + verbose_name_plural = "Merchant Transactions" + class ShippingDetail(models.Model): """ @@ -35,93 +41,59 @@ def __str__(self) -> str: shipping_price = self.price / 100 # shipping price in soum return f"[{self.pk}] {self.title} - {shipping_price:,}" + class Meta: + # pylint: disable=missing-class-docstring + verbose_name = "Shipping Detail" + verbose_name_plural = "Shipping Details" -class Item(models.Model): + +class FiscalData(models.Model): """ - Item class \ - That's used for managing order items + FiscalData class \ + That's used for managing fiscalization items """ - discount = models.BigIntegerField(null=True, blank=True) - title = models.CharField(max_length=255) - price = models.BigIntegerField(default=0) - count = models.IntegerField(default=1) code = models.CharField(max_length=17) units = models.IntegerField(null=True, blank=True) package_code = models.CharField(max_length=255) vat_percent = models.IntegerField(default=0, null=True, blank=True) def __str__(self) -> str: - item_price = self.price / 100 # item price in soum - return f"[{self.id}] {self.title} ({self.count} pc.) x {item_price:,} UZS" + return f"[{self.pk}] {self.code} - {self.package_code}" + + class Meta: + # pylint: disable=missing-class-docstring + verbose_name = "Fiscal Data" + verbose_name_plural = "Fiscal Data" -class OrderDetail(models.Model): +class Item(models.Model): """ - OrderDetail class \ - That's used for managing order details + Item class \ + That's used for managing order items """ - receipt_type = models.IntegerField(default=0) - shipping = models.ForeignKey( - to=ShippingDetail, - null=True, blank=True, - on_delete=models.CASCADE + discount = models.BigIntegerField(null=True, blank=True) + title = models.CharField(max_length=255) + price = models.BigIntegerField(default=0) + count = models.IntegerField(default=1) + fiscal_data = models.ForeignKey( + FiscalData, null=True, + on_delete=models.SET_NULL ) - items = models.ManyToManyField(Item) - - @property - def get_items_display(self): - # pylint: disable=missing-function-docstring - item_display = [f'[{self.pk}]'] - - for fld in self._meta.get_fields(): - if not isinstance(fld, models.ManyToOneRel): - continue - - if not issubclass(fld.related_model, BaseOrder): - continue - - related_order = fld.get_accessor_name() - orders = getattr(self, related_order).values() - order_id = orders[0].get('id') if orders else 'None' - - item_display.append(f'FOR ORDER - {order_id}') - - if self.shipping: - item_display.append(f'ADDRESS: {self.shipping.title}') - - item_display.append(f'AMOUNT: {self.get_total_items_price:,} UZS') - - return ' '.join(item_display) - - @property - def get_total_items_price(self) -> int: - # pylint: disable=missing-function-docstring - items = self.items.all().aggregate( - total_price=models.Sum( - models.Case( - models.When( - discount__isnull=True, - then=models.F('price') * models.F('count') - ), - default=( - (models.F('price') * models.F('count')) - - models.F('discount') - ), - output_field=models.BigIntegerField() - ) - ) - ) - shipping_price = self.shipping.price if self.shipping else 0 - return int(shipping_price + items['total_price']) / 100 def __str__(self) -> str: - return self.get_items_display + item_price = self.price / 100 # item price in soum + return f"[{self.pk}] {self.title} ({self.count} pc.) x {item_price:,} UZS" + + class Meta: + # pylint: disable=missing-class-docstring + verbose_name = "Order Item" + verbose_name_plural = "Order Items" class DisallowOverrideMetaclass(models.base.ModelBase): # pylint: disable=missing-class-docstring def __new__(mcs, name, bases, attrs: dict, **kwargs): - disallowed_fields = ['amount', 'detail'] + disallowed_fields = ['amount', 'receipt_type', 'shipping'] if name != 'BaseOrder': for field_name in disallowed_fields: @@ -140,20 +112,34 @@ class BaseOrder(models.Model, metaclass=DisallowOverrideMetaclass): Order class \ That's used for managing order process """ - detail = models.ForeignKey( - OrderDetail, + receipt_type = models.IntegerField(default=0) + items = models.ManyToManyField(Item) + shipping = models.ForeignKey( + to=ShippingDetail, null=True, blank=True, on_delete=models.CASCADE ) @property - def amount(self): + def amount(self) -> int: # pylint: disable=missing-function-docstring - return self.detail.get_total_items_price if self.detail else 0 + items = self.items.all().aggregate( + total_price=Sum( + Case( + When(discount__isnull=True, then=F('price') * F('count')), + default=(F('price') * F('count')) - F('discount'), + output_field=models.BigIntegerField() + ) + ) + ) + shipping_price = self.shipping.price if self.shipping else 0 + return int(shipping_price + items['total_price']) / 100 def __str__(self): - return f"ORDER ID: {self.id} - AMOUNT: {self.amount:,} UZS" + return f"ORDER ID: {self.pk} - AMOUNT: {self.amount:,} UZS" class Meta: # pylint: disable=missing-class-docstring abstract = True + verbose_name = "Order" + verbose_name_plural = "Orders" diff --git a/lib/payme/serializers.py b/lib/payme/serializers.py index 82fb5fa..e362187 100644 --- a/lib/payme/serializers.py +++ b/lib/payme/serializers.py @@ -1,16 +1,17 @@ -from django.conf import settings +import typing as t -from rest_framework import serializers +from django.conf import settings -from payme.models import MerchatTransactionsModel +from payme.errors.exceptions import ( + IncorrectAmount, + PerformTransactionDoesNotExist +) +from payme.models import MerchantTransactionsModel from payme.utils.get_params import get_params from payme.utils.logging import logger from payme.utils.order_finder import Order -from payme.errors.exceptions import ( - PerformTransactionDoesNotExist, - IncorrectAmount -) +from rest_framework import serializers class MerchatTransactionsModelSerializer(serializers.ModelSerializer): @@ -23,13 +24,13 @@ class MerchatTransactionsModelSerializer(serializers.ModelSerializer): class Meta: # pylint: disable=missing-class-docstring - model: MerchatTransactionsModel = MerchatTransactionsModel + model: MerchantTransactionsModel = MerchantTransactionsModel fields: str = "__all__" extra_fields = ['start_date', 'end_date'] def validate(self, attrs: dict) -> dict: """ - Validate the data given to the MerchatTransactionsModel. + Validate the data given to the MerchantTransactionsModel. """ if attrs.get("order_id") is not None: try: @@ -114,7 +115,50 @@ def clean_empty(self, data): return data + def restructure_data(self, input_data: dict): + # pylint: disable=missing-function-docstring + result = { + "detail": { + key: value + for key, value in input_data.items() + if key in ["receipt_type", "shipping", "items"] + } + } + + if "shipping" in input_data: + shipping: t.Dict[str, t.Any] = input_data["shipping"] + result["detail"]["shipping"] = { + key: value + for key, value in shipping.items() + if key in ["title", "price"] + } + + if "items" in input_data: + items: t.List[t.Dict[str, t.Any]] = input_data["items"] + result["detail"]["items"] = [] + + for item in items: + new_item = { + key: value + for key, value in item.items() + if key in ["discount", "title", "price", "count"] + } + + if "fiscal_data" in item: + fiscal_data: t.Dict[str, t.Any] = item["fiscal_data"] + new_item.update( + { + key: value + for key, value in fiscal_data.items() + if key in ["code", "units", "vat_percent", "package_code"] + } + ) + + result["detail"]["items"].append(new_item) + + return result + def to_representation(self, instance): ret = super().to_representation(instance) - - return self.clean_empty(ret) + clean_data = self.clean_empty(ret) + return self.restructure_data(clean_data) diff --git a/lib/payme/utils/order_finder.py b/lib/payme/utils/order_finder.py index 53e9aac..ed3f4bc 100644 --- a/lib/payme/utils/order_finder.py +++ b/lib/payme/utils/order_finder.py @@ -38,4 +38,4 @@ def _custom_order_model(): if not isinstance(CUSTOM_ORDER, models.base.ModelBase): raise TypeError("The input must be an instance of models.Model class") -Order = CUSTOM_ORDER +Order: models.Model = CUSTOM_ORDER From b7d13faea324e0fa4088283b114838b69023f019 Mon Sep 17 00:00:00 2001 From: hoosnick Date: Sun, 1 Oct 2023 16:19:54 +0500 Subject: [PATCH 25/32] add: verbose_name (+ plural) --- lib/payme/migrations/0001_initial.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/payme/migrations/0001_initial.py b/lib/payme/migrations/0001_initial.py index 898b988..5ea2cf1 100644 --- a/lib/payme/migrations/0001_initial.py +++ b/lib/payme/migrations/0001_initial.py @@ -26,6 +26,10 @@ class Migration(migrations.Migration): ("package_code", models.CharField(max_length=255)), ("vat_percent", models.IntegerField(blank=True, default=0, null=True)), ], + options={ + "verbose_name": "Fiscal Data", + "verbose_name_plural": "Fiscal Data", + }, ), migrations.CreateModel( name="MerchantTransactionsModel", @@ -55,6 +59,10 @@ class Migration(migrations.Migration): ("created_at", models.DateTimeField(auto_now_add=True)), ("updated_at", models.DateTimeField(auto_now=True)), ], + options={ + "verbose_name": "Merchant Transaction", + "verbose_name_plural": "Merchant Transactions", + }, ), migrations.CreateModel( name="ShippingDetail", @@ -71,6 +79,10 @@ class Migration(migrations.Migration): ("title", models.CharField(max_length=255)), ("price", models.BigIntegerField(default=0)), ], + options={ + "verbose_name": "Shipping Detail", + "verbose_name_plural": "Shipping Details", + }, ), migrations.CreateModel( name="Item", @@ -97,5 +109,9 @@ class Migration(migrations.Migration): ), ), ], + options={ + "verbose_name": "Order Item", + "verbose_name_plural": "Order Items", + }, ), ] From 18bce008d8ae20fb329bda6f8fc9dd07a6ffc46c Mon Sep 17 00:00:00 2001 From: hoosnick Date: Fri, 13 Oct 2023 19:52:22 +0500 Subject: [PATCH 26/32] del: removed `order_finder` util --- lib/payme/utils/order_finder.py | 41 --------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 lib/payme/utils/order_finder.py diff --git a/lib/payme/utils/order_finder.py b/lib/payme/utils/order_finder.py deleted file mode 100644 index ed3f4bc..0000000 --- a/lib/payme/utils/order_finder.py +++ /dev/null @@ -1,41 +0,0 @@ -from django.conf import settings -from django.db import models -from django.utils.module_loading import import_string - - -def _custom_order_model(): - """ - Get custom order model class from settings. - - Returns the custom order model class - defined in 'PAYME' or main settings. - - raise ImportError if both are undefined. - """ - order_model_paths = [] - - if hasattr(settings, 'ORDER_MODEL'): - order_model_paths.append(settings.ORDER_MODEL) - - if hasattr(settings, 'PAYME'): - order_model_paths.append(settings.PAYME.get('ORDER_MODEL')) - - for model_path in order_model_paths: - try: - return import_string(model_path) - except (ImportError, AttributeError): - pass - - raise ImportError - - -try: - CUSTOM_ORDER = _custom_order_model() -except (ImportError, AttributeError): - # pylint: disable=raise-missing-from - raise NotImplementedError("Order model not implemented!") - -if not isinstance(CUSTOM_ORDER, models.base.ModelBase): - raise TypeError("The input must be an instance of models.Model class") - -Order: models.Model = CUSTOM_ORDER From 326e99640e47070d6a35c7c457dc00869fd69b6b Mon Sep 17 00:00:00 2001 From: hoosnick Date: Fri, 13 Oct 2023 19:53:06 +0500 Subject: [PATCH 27/32] add: `missing-function-docstring` --- .pylintrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 69e1724..47fd4d5 100644 --- a/.pylintrc +++ b/.pylintrc @@ -4,4 +4,5 @@ disable=no-member, unnecessary-pass, useless-option-value, too-few-public-methods, - missing-module-docstring + missing-module-docstring, + missing-function-docstring From af8b4257b3de3f4cf1164f7475ea26564ce74291 Mon Sep 17 00:00:00 2001 From: hoosnick Date: Fri, 13 Oct 2023 19:53:53 +0500 Subject: [PATCH 28/32] feat: new util for cleaning `detail` data --- lib/payme/utils/get_params.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/payme/utils/get_params.py b/lib/payme/utils/get_params.py index 8005c86..3649705 100644 --- a/lib/payme/utils/get_params.py +++ b/lib/payme/utils/get_params.py @@ -22,3 +22,19 @@ def get_params(params: dict) -> dict: clean_params["order_id"] = account[account_name] return clean_params + + +def clean_empty(data) -> dict: + """ + Use this function to clean the parameters from the instance. + """ + if isinstance(data, dict): + return { + k: v + for k, v in ((k, clean_empty(v)) for k, v in data.items()) + if v is not None and k != 'id' + } + if isinstance(data, list): + return [v for v in map(clean_empty, data) if v] + + return data From 6575a3a7eb25e6167bbd684df97e757856c088fc Mon Sep 17 00:00:00 2001 From: hoosnick Date: Fri, 13 Oct 2023 19:54:34 +0500 Subject: [PATCH 29/32] upd: new model, changes in fields --- lib/payme/migrations/0001_initial.py | 72 ++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 5 deletions(-) diff --git a/lib/payme/migrations/0001_initial.py b/lib/payme/migrations/0001_initial.py index 5ea2cf1..883a477 100644 --- a/lib/payme/migrations/0001_initial.py +++ b/lib/payme/migrations/0001_initial.py @@ -85,7 +85,7 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name="Item", + name="PaymeOrder", fields=[ ( "id", @@ -96,10 +96,37 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("discount", models.BigIntegerField(blank=True, null=True)), + ("receipt_type", models.IntegerField(default=0)), + ( + "shipping", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="payme.shippingdetail", + ), + ), + ], + options={ + "verbose_name": "Payme Order", + "verbose_name_plural": "Payme Orders", + }, + ), + migrations.CreateModel( + name="PaymeItem", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("discount", models.BigIntegerField(default=0)), ("title", models.CharField(max_length=255)), ("price", models.BigIntegerField(default=0)), - ("count", models.IntegerField(default=1)), ( "fiscal_data", models.ForeignKey( @@ -110,8 +137,43 @@ class Migration(migrations.Migration): ), ], options={ - "verbose_name": "Order Item", - "verbose_name_plural": "Order Items", + "verbose_name": "Payme Item", + "verbose_name_plural": "Payme Items", + }, + ), + migrations.CreateModel( + name="OrderItem", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("count", models.IntegerField(default=1)), + ( + "item", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="payme.paymeitem", + ), + ), + ( + "order", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="payme.paymeorder", + ), + ), + ], + options={ + "verbose_name": "Payme Order Item", + "verbose_name_plural": "Payme Order Items", }, ), ] From 02e154129014d77e52dfe3340366e07b17b91cf7 Mon Sep 17 00:00:00 2001 From: hoosnick Date: Fri, 13 Oct 2023 19:56:18 +0500 Subject: [PATCH 30/32] del: removed unused serializer --- lib/payme/serializers.py | 78 +--------------------------------------- 1 file changed, 1 insertion(+), 77 deletions(-) diff --git a/lib/payme/serializers.py b/lib/payme/serializers.py index e362187..e7de003 100644 --- a/lib/payme/serializers.py +++ b/lib/payme/serializers.py @@ -1,5 +1,3 @@ -import typing as t - from django.conf import settings from payme.errors.exceptions import ( @@ -9,7 +7,7 @@ from payme.models import MerchantTransactionsModel from payme.utils.get_params import get_params from payme.utils.logging import logger -from payme.utils.order_finder import Order +from payme.models import PaymeOrder as Order from rest_framework import serializers @@ -88,77 +86,3 @@ def get_validated_data(params: dict) -> dict: clean_data: dict = serializer.validated_data return clean_data - - -class OrderModelSerializer(serializers.ModelSerializer): - """ - OrderModelSerializer class \ - That's used to serialize orders detail data. - """ - class Meta: - # pylint: disable=missing-class-docstring - model = Order - depth = 2 - exclude = ["id"] - read_only_fields = ["__all__"] - - def clean_empty(self, data): - # pylint: disable=missing-function-docstring - if isinstance(data, dict): - return { - k: v - for k, v in ((k, self.clean_empty(v)) for k, v in data.items()) - if v is not None and k != 'id' - } - if isinstance(data, list): - return [v for v in map(self.clean_empty, data) if v] - - return data - - def restructure_data(self, input_data: dict): - # pylint: disable=missing-function-docstring - result = { - "detail": { - key: value - for key, value in input_data.items() - if key in ["receipt_type", "shipping", "items"] - } - } - - if "shipping" in input_data: - shipping: t.Dict[str, t.Any] = input_data["shipping"] - result["detail"]["shipping"] = { - key: value - for key, value in shipping.items() - if key in ["title", "price"] - } - - if "items" in input_data: - items: t.List[t.Dict[str, t.Any]] = input_data["items"] - result["detail"]["items"] = [] - - for item in items: - new_item = { - key: value - for key, value in item.items() - if key in ["discount", "title", "price", "count"] - } - - if "fiscal_data" in item: - fiscal_data: t.Dict[str, t.Any] = item["fiscal_data"] - new_item.update( - { - key: value - for key, value in fiscal_data.items() - if key in ["code", "units", "vat_percent", "package_code"] - } - ) - - result["detail"]["items"].append(new_item) - - return result - - def to_representation(self, instance): - ret = super().to_representation(instance) - clean_data = self.clean_empty(ret) - return self.restructure_data(clean_data) From 747d7687258e2caa05e1041faac0db030043bc11 Mon Sep 17 00:00:00 2001 From: hoosnick Date: Fri, 13 Oct 2023 19:57:07 +0500 Subject: [PATCH 31/32] upd: new model and changed model name --- lib/payme/admin.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/payme/admin.py b/lib/payme/admin.py index 85d92f9..bcba25c 100644 --- a/lib/payme/admin.py +++ b/lib/payme/admin.py @@ -1,15 +1,16 @@ from django.contrib import admin - from payme.models import ( - FiscalData, Item, ShippingDetail, - MerchantTransactionsModel + FiscalData, MerchantTransactionsModel, PaymeItem, + PaymeOrder, ShippingDetail, OrderItem ) admin.site.register( [ MerchantTransactionsModel, FiscalData, - Item, + PaymeItem, + PaymeOrder, + OrderItem, ShippingDetail ] ) From 84649e4301ba7c54982fa5f29e6b4817d6abc3bd Mon Sep 17 00:00:00 2001 From: hoosnick Date: Fri, 13 Oct 2023 19:58:36 +0500 Subject: [PATCH 32/32] feat: added new Dynamic ICPS feature (#12) --- .../methods/check_perform_transaction.py | 21 ++-- lib/payme/models.py | 114 ++++++++++++------ 2 files changed, 81 insertions(+), 54 deletions(-) diff --git a/lib/payme/methods/check_perform_transaction.py b/lib/payme/methods/check_perform_transaction.py index a7d17ec..134a65d 100644 --- a/lib/payme/methods/check_perform_transaction.py +++ b/lib/payme/methods/check_perform_transaction.py @@ -1,9 +1,6 @@ -from payme.serializers import ( - MerchatTransactionsModelSerializer, - OrderModelSerializer -) -from payme.utils.get_params import get_params -from payme.utils.order_finder import Order +from payme.models import PaymeOrder as Order +from payme.serializers import MerchatTransactionsModelSerializer +from payme.utils.get_params import clean_empty, get_params class CheckPerformTransaction: @@ -22,20 +19,16 @@ def __call__(self, params: dict) -> dict: ) serializer.is_valid(raise_exception=True) - order = OrderModelSerializer( - instance=Order.objects.get( - id=serializer.validated_data.get('order_id') - ) + order = Order.objects.get( + pk=serializer.validated_data.get('order_id') ) + detail = clean_empty(order.to_detail()) response = { "result": { "allow": True, - "detail": order.data.get("detail") + "detail": detail } } - if not order.data.get("detail"): - del response["result"]["detail"] - return None, response diff --git a/lib/payme/models.py b/lib/payme/models.py index 4e07db4..8027bc8 100644 --- a/lib/payme/models.py +++ b/lib/payme/models.py @@ -1,5 +1,4 @@ from django.db import models -from django.db.models import Case, F, Sum, When class MerchantTransactionsModel(models.Model): @@ -66,15 +65,14 @@ class Meta: verbose_name_plural = "Fiscal Data" -class Item(models.Model): +class PaymeItem(models.Model): """ Item class \ That's used for managing order items """ - discount = models.BigIntegerField(null=True, blank=True) + discount = models.BigIntegerField(default=0) title = models.CharField(max_length=255) price = models.BigIntegerField(default=0) - count = models.IntegerField(default=1) fiscal_data = models.ForeignKey( FiscalData, null=True, on_delete=models.SET_NULL @@ -82,38 +80,20 @@ class Item(models.Model): def __str__(self) -> str: item_price = self.price / 100 # item price in soum - return f"[{self.pk}] {self.title} ({self.count} pc.) x {item_price:,} UZS" + return f"[{self.pk}] {self.title} {item_price:,} UZS" class Meta: # pylint: disable=missing-class-docstring - verbose_name = "Order Item" - verbose_name_plural = "Order Items" - - -class DisallowOverrideMetaclass(models.base.ModelBase): - # pylint: disable=missing-class-docstring - def __new__(mcs, name, bases, attrs: dict, **kwargs): - disallowed_fields = ['amount', 'receipt_type', 'shipping'] - - if name != 'BaseOrder': - for field_name in disallowed_fields: - if not attrs.get(field_name): - continue - - raise TypeError( - f"Field '{field_name}' in '{name}' cannot be overridden." - ) - - return super().__new__(mcs, name, bases, attrs, **kwargs) + verbose_name = "Payme Item" + verbose_name_plural = "Payme Items" -class BaseOrder(models.Model, metaclass=DisallowOverrideMetaclass): +class PaymeOrder(models.Model): """ Order class \ That's used for managing order process """ receipt_type = models.IntegerField(default=0) - items = models.ManyToManyField(Item) shipping = models.ForeignKey( to=ShippingDetail, null=True, blank=True, @@ -122,24 +102,78 @@ class BaseOrder(models.Model, metaclass=DisallowOverrideMetaclass): @property def amount(self) -> int: - # pylint: disable=missing-function-docstring - items = self.items.all().aggregate( - total_price=Sum( - Case( - When(discount__isnull=True, then=F('price') * F('count')), - default=(F('price') * F('count')) - F('discount'), - output_field=models.BigIntegerField() - ) - ) - ) shipping_price = self.shipping.price if self.shipping else 0 - return int(shipping_price + items['total_price']) / 100 + return int(shipping_price + self.cart_total) / 100 + + @property + def cart_total(self): + orderitems = self.orderitem_set.all() + total = sum(item.price for item in orderitems) + return total + + @property + def cart_items(self): + orderitems = self.orderitem_set.all() + total_count = sum(item.count for item in orderitems) + return total_count + + def to_detail(self): + orderitems = self.orderitem_set.all() + return { + "receipt_type": self.receipt_type, + "shipping": { + 'title': self.shipping.title, + 'price': self.shipping.price + } if self.shipping else None, + "items": [item.item_as_dict for item in orderitems] + } def __str__(self): return f"ORDER ID: {self.pk} - AMOUNT: {self.amount:,} UZS" class Meta: # pylint: disable=missing-class-docstring - abstract = True - verbose_name = "Order" - verbose_name_plural = "Orders" + verbose_name = "Payme Order" + verbose_name_plural = "Payme Orders" + + +class OrderItem(models.Model): + """ + Order Item class \ + That's used for managing order items process + """ + item = models.ForeignKey(PaymeItem, on_delete=models.SET_NULL, null=True) + order = models.ForeignKey(PaymeOrder, on_delete=models.SET_NULL, null=True) + count = models.IntegerField(default=1) + + @property + def price(self): + return (self.item.price * self.count) - (self.item.discount * self.count) + + @property + def item_as_dict(self): + fiscal_data = { + 'code': self.item.fiscal_data.code, + 'units': self.item.fiscal_data.units, + 'package_code': self.item.fiscal_data.package_code, + 'vat_percent': self.item.fiscal_data.vat_percent + } if self.item.fiscal_data else {} + + return { + 'discount': self.item.discount, + 'title': self.item.title, + 'count': self.count, + 'price': self.item.price, + 'code': fiscal_data.get('code'), + 'units': fiscal_data.get('units'), + 'package_code': fiscal_data.get('package_code'), + 'vat_percent': fiscal_data.get('vat_percent') + } + + def __str__(self): + return f"ORDER ID: {self.order.pk} | ITEM: {self.item.title}" + + class Meta: + # pylint: disable=missing-class-docstring + verbose_name = "Payme Order Item" + verbose_name_plural = "Payme Order Items"