payme-pkg 2.5.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of payme-pkg might be problematic. Click here for more details.

payme/__init__.py ADDED
File without changes
payme/admin.py ADDED
@@ -0,0 +1,11 @@
1
+ from django.contrib import admin
2
+
3
+ from payme.models import CUSTOM_ORDER
4
+ from payme.models import Order as DefaultOrderModel
5
+
6
+ from payme.models import MerchatTransactionsModel
7
+
8
+ if not CUSTOM_ORDER:
9
+ admin.site.register(DefaultOrderModel)
10
+
11
+ admin.site.register(MerchatTransactionsModel)
payme/apps.py ADDED
@@ -0,0 +1,10 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class PaymeConfig(AppConfig):
5
+ """
6
+ PaymeConfig AppConfig \
7
+ That is used to configure the payme application with django settings.
8
+ """
9
+ default_auto_field = 'django.db.models.BigAutoField'
10
+ name = 'payme'
@@ -0,0 +1 @@
1
+ from .import subscribe_cards
@@ -0,0 +1,166 @@
1
+ from payme.utils.to_json import to_json
2
+ from payme.decorators.decorators import payme_request
3
+
4
+
5
+ class PaymeSubscribeCards:
6
+ """
7
+ The PaymeSubscribeCards class inclues
8
+ all paycom methods which are belongs to cards.
9
+
10
+ Parameters
11
+ ----------
12
+ base_url: str — The base url of the paycom api
13
+ paycom_id: str — The paycom_id uses to identify
14
+ timeout: int — How many seconds to wait for the server to send data
15
+
16
+ Full method documentation
17
+ -------------------------
18
+ https://developer.help.paycom.uz/metody-subscribe-api/
19
+ """
20
+ def __init__(
21
+ self,
22
+ base_url: str,
23
+ paycom_id: str,
24
+ timeout=5
25
+ ) -> "PaymeSubscribeCards":
26
+ self.base_url: str = base_url
27
+ self.timeout: int = timeout
28
+ self.headers: dict = {
29
+ "X-Auth": paycom_id,
30
+ }
31
+ self.__methods: dict = {
32
+ "cards_check": "cards.check",
33
+ "cards_create": "cards.create",
34
+ "cards_remove": "cards.remove",
35
+ "cards_verify": "cards.verify",
36
+ "cards_get_verify_code": "cards.get_verify_code",
37
+ }
38
+
39
+ @payme_request
40
+ def __request(self, data) -> dict:
41
+ """
42
+ Use this private method to request.
43
+ On success,response will be OK with format JSON.
44
+
45
+ Parameters
46
+ ----------
47
+ data: dict — Includes request data.
48
+
49
+ Returns dictionary Payme Response
50
+ ---------------------------------
51
+ """
52
+ return data
53
+
54
+ def cards_create(self, number: str, expire: str, save: bool = True) -> dict:
55
+ """
56
+ Use this method to create a new card's token.
57
+
58
+ Parameters
59
+ ----------
60
+ number: str — The card number maximum length 18 char
61
+ expire: str — The card expiration string maximum length 5 char
62
+ save: bool \
63
+ Type of token. Optional parameter
64
+ The option is enabled or disabled depending on the application's business logic
65
+ If the flag is true, the token can be used for further payments
66
+ if the flag is false the token can only be used once
67
+ The one-time token is deleted after payment
68
+
69
+ Full method documentation
70
+ -------------------------
71
+ https://developer.help.paycom.uz/metody-subscribe-api/cards.create
72
+ """
73
+ data: dict = {
74
+ "method": self.__methods.get("cards_create"),
75
+ "params": {
76
+ "card": {
77
+ "number": number,
78
+ "expire": expire,
79
+ },
80
+ "save": save,
81
+ }
82
+ }
83
+ return self.__request(to_json(**data))
84
+
85
+ def card_get_verify_code(self, token: str) -> dict:
86
+ """
87
+ Use this method to get the verification code.
88
+
89
+ Parameters
90
+ ----------
91
+ token: str — The card's non-active token
92
+
93
+ Full method documentation
94
+ -------------------------
95
+ https://developer.help.paycom.uz/metody-subscribe-api/cards.get_verify_code
96
+ """
97
+ data: dict = {
98
+ "method": self.__methods.get('cards_get_verify_code'),
99
+ "params": {
100
+ "token": token,
101
+ }
102
+ }
103
+ return self.__request(to_json(**data))
104
+
105
+ def cards_verify(self, verify_code: int, token: str) -> dict:
106
+ """
107
+ Verification of the card using the code sent via SMS.
108
+
109
+ Parameters
110
+ ----------
111
+ verify_code: int — Code for verification
112
+ token: str — The card's non-active token
113
+
114
+ Full method documentation
115
+ -------------------------
116
+ https://developer.help.paycom.uz/metody-subscribe-api/cards.verify
117
+ """
118
+ data: dict = {
119
+ "method": self.__methods.get("cards_verify"),
120
+ "params": {
121
+ "token": token,
122
+ "code": verify_code
123
+ }
124
+ }
125
+ return self.__request(to_json(**data))
126
+
127
+ def cards_check(self, token: str) -> dict:
128
+ """
129
+ Checking the card token active or non-active.
130
+
131
+ Parameters
132
+ ----------
133
+ token: str — The card's non-active token
134
+
135
+ Full method documentation
136
+ -------------------------
137
+ https://developer.help.paycom.uz/metody-subscribe-api/cards.check
138
+ """
139
+ data: dict = {
140
+ "method": self.__methods.get("cards_check"),
141
+ "params": {
142
+ "token": token,
143
+ }
144
+ }
145
+
146
+ return self.__request(to_json(**data))
147
+
148
+ def cards_remove(self, token: str) -> dict:
149
+ """
150
+ Delete card's token on success returns success.
151
+
152
+ Parameters
153
+ ----------
154
+ token: str — The card's non-active token
155
+
156
+ Full method documentation
157
+ -------------------------
158
+ https://developer.help.paycom.uz/metody-subscribe-api/cards.remove
159
+ """
160
+ data: dict = {
161
+ "method": self.__methods.get("cards_remove"),
162
+ "params": {
163
+ "token": token,
164
+ }
165
+ }
166
+ return self.__request(to_json(**data))
File without changes
@@ -0,0 +1,34 @@
1
+ import functools
2
+
3
+ from requests import request
4
+ from requests.exceptions import Timeout
5
+ from requests.exceptions import RequestException
6
+
7
+ from payme.utils.logging import logger
8
+
9
+ from ..errors.exceptions import PaymeTimeoutException
10
+
11
+
12
+ def payme_request(func):
13
+ """
14
+ Payme request decorator.
15
+ """
16
+ @functools.wraps(func)
17
+ def wrapper(self, data):
18
+ response = None
19
+ req_data = {
20
+ "method": "POST",
21
+ "url": self.base_url,
22
+ "data": data,
23
+ "headers": self.headers,
24
+ "timeout": self.timeout,
25
+ }
26
+ try:
27
+ response = request(**req_data)
28
+ response.raise_for_status()
29
+ except (Timeout, RequestException) as error:
30
+ logger.info("Payme request has been failed as error: %s", error)
31
+ raise PaymeTimeoutException() from error
32
+ return response.json()
33
+
34
+ return wrapper
File without changes
@@ -0,0 +1,89 @@
1
+ from rest_framework.exceptions import APIException
2
+
3
+
4
+ class BasePaymeException(APIException):
5
+ """
6
+ BasePaymeException it's APIException.
7
+ """
8
+ status_code = 200
9
+ error_code = None
10
+ message = None
11
+
12
+ # pylint: disable=super-init-not-called
13
+ def __init__(self, error_message: str = None):
14
+ detail: dict = {
15
+ "error": {
16
+ "code": self.error_code,
17
+ "message": self.message,
18
+ "data": error_message
19
+ }
20
+ }
21
+ self.detail = detail
22
+
23
+
24
+ class PermissionDenied(BasePaymeException):
25
+ """
26
+ PermissionDenied APIException \
27
+ That is raised when the client is not allowed to server.
28
+ """
29
+ status_code = 200
30
+ error_code = -32504
31
+ message = "Permission denied"
32
+
33
+
34
+ class MethodNotFound(BasePaymeException):
35
+ """
36
+ MethodNotFound APIException \
37
+ That is raised when the method does not exist.
38
+ """
39
+ status_code = 405
40
+ error_code = -32601
41
+ message = 'Method not found'
42
+
43
+
44
+ class TooManyRequests(BasePaymeException):
45
+ """
46
+ TooManyRequests APIException \
47
+ That is raised when the request exceeds the limit.
48
+ """
49
+ status_code = 200
50
+ error_code = -31099
51
+ message = {
52
+ "uz": "Buyurtma tolovni amalga oshirish jarayonida",
53
+ "ru": "Транзакция в очереди",
54
+ "en": "Order payment status is queued"
55
+ }
56
+
57
+
58
+ class IncorrectAmount(BasePaymeException):
59
+ """
60
+ IncorrectAmount APIException \
61
+ That is raised when the amount is not incorrect.
62
+ """
63
+ status_code = 200
64
+ error_code = -31001
65
+ message = {
66
+ 'ru': 'Неверная сумма',
67
+ 'uz': 'Incorrect amount',
68
+ 'en': 'Incorrect amount',
69
+ }
70
+
71
+
72
+ class PerformTransactionDoesNotExist(BasePaymeException):
73
+ """
74
+ PerformTransactionDoesNotExist APIException \
75
+ That is raised when a transaction does not exist or deleted.
76
+ """
77
+ status_code = 200
78
+ error_code = -31050
79
+ message = {
80
+ "uz": "Buyurtma topilmadi",
81
+ "ru": "Заказ не существует",
82
+ "en": "Order does not exists"
83
+ }
84
+
85
+
86
+ class PaymeTimeoutException(Exception):
87
+ """
88
+ Payme timeout exception that means that payme is working slowly.
89
+ """
File without changes
@@ -0,0 +1,54 @@
1
+ import time
2
+
3
+ from django.db import transaction
4
+
5
+ from payme.utils.logging import logger
6
+ from payme.models import MerchatTransactionsModel
7
+ from payme.errors.exceptions import PerformTransactionDoesNotExist
8
+ from payme.serializers import MerchatTransactionsModelSerializer as MTMS
9
+
10
+
11
+ class CancelTransaction:
12
+ """
13
+ CancelTransaction class
14
+ That is used to cancel a transaction.
15
+
16
+ Full method documentation
17
+ -------------------------
18
+ https://developer.help.paycom.uz/metody-merchant-api/canceltransaction
19
+ """
20
+
21
+ @transaction.atomic
22
+ def __call__(self, params: dict):
23
+ clean_data: dict = MTMS.get_validated_data(
24
+ params=params
25
+ )
26
+ try:
27
+ with transaction.atomic():
28
+ transactions: MerchatTransactionsModel = \
29
+ MerchatTransactionsModel.objects.filter(
30
+ _id=clean_data.get('_id'),
31
+ ).first()
32
+ if transactions.cancel_time == 0:
33
+ transactions.cancel_time = int(time.time() * 1000)
34
+ if transactions.perform_time == 0:
35
+ transactions.state = -1
36
+ if transactions.perform_time != 0:
37
+ transactions.state = -2
38
+ transactions.reason = clean_data.get("reason")
39
+ transactions.save()
40
+
41
+ except PerformTransactionDoesNotExist as error:
42
+ logger.error("Paycom transaction does not exist: %s", error)
43
+ raise PerformTransactionDoesNotExist() from error
44
+
45
+ response: dict = {
46
+ "result": {
47
+ "state": transactions.state,
48
+ "cancel_time": transactions.cancel_time,
49
+ "transaction": transactions.transaction_id,
50
+ "reason": int(transactions.reason),
51
+ }
52
+ }
53
+
54
+ return transactions.order_id, response
@@ -0,0 +1,26 @@
1
+ from payme.utils.get_params import get_params
2
+ from payme.serializers import MerchatTransactionsModelSerializer
3
+
4
+
5
+ class CheckPerformTransaction:
6
+ """
7
+ CheckPerformTransaction class
8
+ That's used to check perform transaction.
9
+
10
+ Full method documentation
11
+ -------------------------
12
+ https://developer.help.paycom.uz/metody-merchant-api/checktransaction
13
+ """
14
+ def __call__(self, params: dict) -> dict:
15
+ serializer = MerchatTransactionsModelSerializer(
16
+ data=get_params(params)
17
+ )
18
+ serializer.is_valid(raise_exception=True)
19
+
20
+ response = {
21
+ "result": {
22
+ "allow": True,
23
+ }
24
+ }
25
+
26
+ return None, response
@@ -0,0 +1,43 @@
1
+ from django.db import DatabaseError
2
+
3
+ from payme.utils.logging import logger
4
+ from payme.models import MerchatTransactionsModel
5
+ from payme.serializers import MerchatTransactionsModelSerializer as MTMS
6
+
7
+
8
+ class CheckTransaction:
9
+ """
10
+ CheckTransaction class
11
+ That's used to check transaction
12
+
13
+ Full method documentation
14
+ -------------------------
15
+ https://developer.help.paycom.uz/metody-merchant-api/checkperformtransaction
16
+ """
17
+ def __call__(self, params: dict) -> None:
18
+ clean_data: dict = MTMS.get_validated_data(
19
+ params=params
20
+ )
21
+
22
+ try:
23
+ transaction = \
24
+ MerchatTransactionsModel.objects.get(
25
+ _id=clean_data.get("_id"),
26
+ )
27
+ response = {
28
+ "result": {
29
+ "create_time": int(transaction.created_at_ms),
30
+ "perform_time": transaction.perform_time,
31
+ "cancel_time": transaction.cancel_time,
32
+ "transaction": transaction.transaction_id,
33
+ "state": transaction.state,
34
+ "reason": None,
35
+ }
36
+ }
37
+ if transaction.reason is not None:
38
+ response["result"]["reason"] = int(transaction.reason)
39
+
40
+ except DatabaseError as error:
41
+ logger.error("Error getting transaction in database: %s", error)
42
+
43
+ return None, response
@@ -0,0 +1,68 @@
1
+ import uuid
2
+ import time
3
+ import datetime
4
+
5
+ from payme.utils.logging import logger
6
+ from payme.utils.get_params import get_params
7
+ from payme.models import MerchatTransactionsModel
8
+ from payme.errors.exceptions import TooManyRequests
9
+ from payme.serializers import MerchatTransactionsModelSerializer
10
+
11
+
12
+ class CreateTransaction:
13
+ """
14
+ CreateTransaction class
15
+ That's used to create transaction
16
+
17
+ Full method documentation
18
+ -------------------------
19
+ https://developer.help.paycom.uz/metody-merchant-api/createtransaction
20
+ """
21
+ def __call__(self, params: dict) -> dict:
22
+ serializer = MerchatTransactionsModelSerializer(
23
+ data=get_params(params)
24
+ )
25
+ serializer.is_valid(raise_exception=True)
26
+ order_id = serializer.validated_data.get("order_id")
27
+
28
+ try:
29
+ transaction = MerchatTransactionsModel.objects.filter(
30
+ order_id=order_id
31
+ ).last()
32
+
33
+ if transaction is not None:
34
+ if transaction._id != serializer.validated_data.get("_id"):
35
+ raise TooManyRequests()
36
+
37
+ except TooManyRequests as error:
38
+ logger.error("Too many requests for transaction %s", error)
39
+ raise TooManyRequests() from error
40
+
41
+ if transaction is None:
42
+ transaction, _ = \
43
+ MerchatTransactionsModel.objects.get_or_create(
44
+ _id=serializer.validated_data.get('_id'),
45
+ order_id=serializer.validated_data.get('order_id'),
46
+ transaction_id=uuid.uuid4(),
47
+ amount=serializer.validated_data.get('amount'),
48
+ created_at_ms=int(time.time() * 1000),
49
+ )
50
+
51
+ if transaction:
52
+ response: dict = {
53
+ "result": {
54
+ "create_time": int(transaction.created_at_ms),
55
+ "transaction": transaction.transaction_id,
56
+ "state": int(transaction.state),
57
+ }
58
+ }
59
+
60
+ return order_id, response
61
+
62
+ @staticmethod
63
+ def _convert_ms_to_datetime(time_ms: str) -> int:
64
+ """Use this format to convert from time ms to datetime format.
65
+ """
66
+ readable_datetime = datetime.datetime.fromtimestamp(time_ms / 1000)
67
+
68
+ return readable_datetime
@@ -0,0 +1,74 @@
1
+ import base64
2
+ from decimal import Decimal
3
+ from dataclasses import dataclass
4
+
5
+ from django.conf import settings
6
+
7
+ PAYME_ID = settings.PAYME.get('PAYME_ID')
8
+ PAYME_ACCOUNT = settings.PAYME.get('PAYME_ACCOUNT')
9
+ PAYME_CALL_BACK_URL = settings.PAYME.get('PAYME_CALL_BACK_URL')
10
+ PAYME_URL = settings.PAYME.get("PAYME_URL")
11
+
12
+
13
+ @dataclass
14
+ class GeneratePayLink:
15
+ """
16
+ GeneratePayLink dataclass
17
+ That's used to generate pay lint for each order.
18
+
19
+ Parameters
20
+ ----------
21
+ order_id: int — The order_id for paying
22
+ amount: int — The amount belong to the order
23
+
24
+ Returns str — pay link
25
+ ----------------------
26
+
27
+ Full method documentation
28
+ -------------------------
29
+ https://developer.help.paycom.uz/initsializatsiya-platezhey/
30
+ """
31
+ order_id: str
32
+ amount: Decimal
33
+
34
+ def generate_link(self) -> str:
35
+ """
36
+ GeneratePayLink for each order.
37
+ """
38
+ generated_pay_link: str = "{payme_url}/{encode_params}"
39
+ params: str = 'm={payme_id};ac.{payme_account}={order_id};a={amount};c={call_back_url}'
40
+
41
+ params = params.format(
42
+ payme_id=PAYME_ID,
43
+ payme_account=PAYME_ACCOUNT,
44
+ order_id=self.order_id,
45
+ amount=self.amount,
46
+ call_back_url=PAYME_CALL_BACK_URL
47
+ )
48
+ encode_params = base64.b64encode(params.encode("utf-8"))
49
+ return generated_pay_link.format(
50
+ payme_url=PAYME_URL,
51
+ encode_params=str(encode_params, 'utf-8')
52
+ )
53
+
54
+ @staticmethod
55
+ def to_tiyin(amount: Decimal) -> Decimal:
56
+ """
57
+ Convert from soum to tiyin.
58
+
59
+ Parameters
60
+ ----------
61
+ amount: Decimal -> order amount
62
+ """
63
+ return amount * 100
64
+
65
+ @staticmethod
66
+ def to_soum(amount: Decimal) -> Decimal:
67
+ """
68
+ Convert from tiyin to soum.
69
+
70
+ Parameters
71
+ ----------
72
+ amount: Decimal -> order amount
73
+ """
74
+ return amount / 100
@@ -0,0 +1,65 @@
1
+ from django.db import DatabaseError
2
+
3
+ from payme.utils.logging import logger
4
+ from payme.models import MerchatTransactionsModel
5
+ from payme.serializers import MerchatTransactionsModelSerializer as MTMS
6
+ from payme.utils.make_aware_datetime import make_aware_datetime as mad
7
+
8
+
9
+ class GetStatement:
10
+ """
11
+ GetStatement class
12
+ Transaction information is used for reconciliation
13
+ of merchant and Payme Business transactions.
14
+
15
+ Full method documentation
16
+ -------------------------
17
+ https://developer.help.paycom.uz/metody-merchant-api/getstatement
18
+ """
19
+
20
+ def __call__(self, params: dict):
21
+ clean_data: dict = MTMS.get_validated_data(
22
+ params=params
23
+ )
24
+
25
+ start_date, end_date = mad(
26
+ int(clean_data.get("start_date")),
27
+ int(clean_data.get("end_date"))
28
+ )
29
+
30
+ try:
31
+ transactions = \
32
+ MerchatTransactionsModel.objects.filter(
33
+ created_at__gte=start_date,
34
+ created_at__lte=end_date
35
+ )
36
+
37
+ if not transactions: # no transactions found for the period
38
+ return {"result": {"transactions": []}}
39
+
40
+ statements = [
41
+ {
42
+ 'id': t._id,
43
+ 'time': int(t.created_at.timestamp()),
44
+ 'amount': t.amount,
45
+ 'account': {'order_id': t.order_id},
46
+ 'create_time': t.state,
47
+ 'perform_time': t.perform_time,
48
+ 'cancel_time': t.cancel_time,
49
+ 'transaction': t.order_id,
50
+ 'state': t.state,
51
+ 'reason': t.reason,
52
+ 'receivers': [] # not implemented
53
+ } for t in transactions
54
+ ]
55
+
56
+ response: dict = {
57
+ "result": {
58
+ "transactions": statements
59
+ }
60
+ }
61
+ except DatabaseError as error:
62
+ logger.error("Error getting transaction in database: %s", error)
63
+ response = {"result": {"transactions": []}}
64
+
65
+ return None, response