shops-payment-processing 1.2.0.dev33607__tar.gz

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.
Files changed (39) hide show
  1. shops_payment_processing-1.2.0.dev33607/PKG-INFO +12 -0
  2. shops_payment_processing-1.2.0.dev33607/README.md +0 -0
  3. shops_payment_processing-1.2.0.dev33607/pyproject.toml +32 -0
  4. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/__init__.py +0 -0
  5. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/__init__.pyi +0 -0
  6. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/handlers/__init__.py +0 -0
  7. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/handlers/__init__.pyi +0 -0
  8. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/handlers/invoice_creation.py +295 -0
  9. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/handlers/invoice_creation.pyi +23 -0
  10. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/logging_config.py +3 -0
  11. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/logging_config.pyi +3 -0
  12. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/models/__init__.py +0 -0
  13. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/models/__init__.pyi +0 -0
  14. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/models/invoice.py +16 -0
  15. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/models/invoice.pyi +14 -0
  16. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/models/order.py +89 -0
  17. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/models/order.pyi +71 -0
  18. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/models/payment.py +22 -0
  19. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/models/payment.pyi +17 -0
  20. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/models/shop.py +10 -0
  21. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/models/shop.pyi +9 -0
  22. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/models/user.py +9 -0
  23. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/models/user.pyi +8 -0
  24. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/services/__init__.py +0 -0
  25. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/services/__init__.pyi +0 -0
  26. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/services/base.py +186 -0
  27. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/services/base.pyi +65 -0
  28. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/services/cloudpayments.py +180 -0
  29. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/services/cloudpayments.pyi +13 -0
  30. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/services/life_pay.py +61 -0
  31. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/services/life_pay.pyi +13 -0
  32. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/services/tkassa.py +114 -0
  33. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/services/tkassa.pyi +10 -0
  34. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/services/yookassa.py +115 -0
  35. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/services/yookassa.pyi +10 -0
  36. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/utils/__init__.py +0 -0
  37. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/utils/__init__.pyi +0 -0
  38. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/utils/link_generation.py +9 -0
  39. shops_payment_processing-1.2.0.dev33607/shops_payment_processing/utils/link_generation.pyi +1 -0
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: shops-payment-processing
3
+ Version: 1.2.0.dev33607
4
+ Summary: Payment processing library for tg-shops.
5
+ Author-email: Pavel Mulin <pavel@shopsbuilder.app>
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: annotated-types>=0.7.0
8
+ Requires-Dist: fastapi>=0.112.2
9
+ Requires-Dist: httpx>=0.25.0
10
+ Requires-Dist: pydantic-core>=2.27.1
11
+ Requires-Dist: pydantic>=2.10.3
12
+ Requires-Dist: typing-extensions>=4.12.2
File without changes
@@ -0,0 +1,32 @@
1
+ [project]
2
+ name = "shops-payment-processing"
3
+ version = "v1.2.0.dev33607"
4
+ description = "Payment processing library for tg-shops."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ authors = [{ name = "Pavel Mulin", email = "pavel@shopsbuilder.app" }]
8
+ dependencies = [
9
+ "fastapi>=0.112.2",
10
+ "httpx>=0.25.0",
11
+ "pydantic>=2.10.3",
12
+ "pydantic-core>=2.27.1",
13
+ "annotated-types>=0.7.0",
14
+ "typing_extensions>=4.12.2",
15
+ ]
16
+
17
+ [build-system]
18
+ requires = ["hatchling"]
19
+ build-backend = "hatchling.build"
20
+
21
+ # If your package directory is named `shops_payment_processing/`
22
+ # this ensures hatch builds the right wheel contents.
23
+ [tool.hatch.build.targets.wheel]
24
+ packages = ["shops_payment_processing"]
25
+
26
+ # Optional: include package sources in sdist (nice for PyPI)
27
+ [tool.hatch.build.targets.sdist]
28
+ include = [
29
+ "shops_payment_processing/**",
30
+ "README.md",
31
+ "pyproject.toml",
32
+ ]
@@ -0,0 +1,295 @@
1
+ from fastapi import HTTPException
2
+
3
+ from shops_payment_processing.logging_config import logger
4
+ from shops_payment_processing.models.invoice import InvoiceWithPaymentLinkMessageModel
5
+ from shops_payment_processing.models.order import OrderResponseModel, PaymentTypes
6
+ from shops_payment_processing.models.payment import PaymentMethodDBResponseModel
7
+ from shops_payment_processing.models.shop import ShopDetailsModel
8
+ from shops_payment_processing.models.user import UserModel
9
+ from shops_payment_processing.services.base import CreateInvoiceRequest
10
+ from shops_payment_processing.services.cloudpayments import CloudPaymentsAPI
11
+ from shops_payment_processing.services.life_pay import LifePayAPI
12
+ from shops_payment_processing.services.tkassa import TKassaAPI
13
+ from shops_payment_processing.services.yookassa import YooKassaAPI
14
+ from shops_payment_processing.utils.link_generation import generate_web_app_order_link
15
+
16
+
17
+ class InvoiceCreation:
18
+ def __init__(self, base_redirect_url: str, base_callback_url: str):
19
+ self.BASE_REDIRECT_URL = base_redirect_url
20
+ self.BASE_CALLBACK_URL = base_callback_url
21
+
22
+ async def get_life_pay_sbp_payment_data(
23
+ self,
24
+ user: UserModel,
25
+ order: OrderResponseModel,
26
+ payment: PaymentMethodDBResponseModel,
27
+ shop_details: ShopDetailsModel,
28
+ ) -> InvoiceWithPaymentLinkMessageModel:
29
+ fiat_currency = order.basket.products[0].currency
30
+ if fiat_currency != "RUB":
31
+ raise HTTPException(
32
+ status_code=404, detail="LifePay payment method is only available for RUB currency"
33
+ )
34
+
35
+ login = ""
36
+ if payment.meta:
37
+ for meta in payment.meta:
38
+ if meta.key == "LIFE_PAY_LOGIN" and meta.value:
39
+ login = meta.value[0]
40
+ break
41
+ if not login or not payment.payment_data:
42
+ raise HTTPException(
43
+ status_code=404, detail="LifePay login was not configured for the shop"
44
+ )
45
+ life_pay_connector = LifePayAPI(callback_base_url=self.BASE_CALLBACK_URL)
46
+ invoice_response = await life_pay_connector.create_sbp_invoice(
47
+ shop_name=shop_details.shop_name, api_key=payment.payment_data, login=login, order=order
48
+ )
49
+ if invoice_response.status_code != 200:
50
+ raise HTTPException(
51
+ status_code=500, detail="Error creating LifePay invoice: " + invoice_response.text
52
+ )
53
+ invoice_json = invoice_response.json()
54
+ invoice_data = invoice_json.get("data")
55
+ if not invoice_data:
56
+ invoice_error_message = invoice_json.get("message")
57
+ if invoice_error_message:
58
+ raise HTTPException(status_code=500, detail=invoice_error_message)
59
+ raise HTTPException(
60
+ status_code=500, detail="Error creating LifePay invoice: " + invoice_response.text
61
+ )
62
+
63
+ logger.debug(f"LifePay SBP invoice data: {invoice_data}")
64
+ payment_url = invoice_data.get("paymentUrlWeb")
65
+ number = invoice_data.get("number")
66
+ qr_url = invoice_data.get("paymentUrl")
67
+ if not payment_url or not number or not qr_url:
68
+ raise HTTPException(
69
+ status_code=500, detail="Missing data from LifePay: " + invoice_response.text
70
+ )
71
+ return InvoiceWithPaymentLinkMessageModel(
72
+ chat_id=user.tg_id,
73
+ order_id=order.id,
74
+ order_number=order.order_number,
75
+ payload=number,
76
+ currency=fiat_currency,
77
+ payment_address=qr_url,
78
+ payment_link=payment_url,
79
+ amount=order.basket.amount,
80
+ )
81
+
82
+ async def get_yookassa_payment_data(
83
+ self,
84
+ user: UserModel,
85
+ order: OrderResponseModel,
86
+ payment: PaymentMethodDBResponseModel,
87
+ shop_details: ShopDetailsModel,
88
+ ) -> InvoiceWithPaymentLinkMessageModel:
89
+ fiat_currency = order.basket.products[0].currency
90
+ if fiat_currency != "RUB":
91
+ raise HTTPException(
92
+ status_code=404, detail="Yookassa payment method is only available for RUB currency"
93
+ )
94
+
95
+ account_id = ""
96
+ if payment.meta:
97
+ for meta in payment.meta:
98
+ if meta.key == "YOOKASSA_ACCOUNT_ID" and meta.value:
99
+ account_id = meta.value[0]
100
+ break
101
+ if not account_id or not payment.payment_data:
102
+ raise HTTPException(
103
+ status_code=404, detail="YooKassa login was not configured for the shop"
104
+ )
105
+ yookassa_connector = YooKassaAPI(account_id, payment.payment_data)
106
+ invoice_json = await yookassa_connector.create_sbp_invoice(
107
+ shop_name=shop_details.shop_name,
108
+ order=order,
109
+ receipt_email=shop_details.contact_email,
110
+ redirect_url=generate_web_app_order_link(
111
+ shop_name=shop_details.shop_name,
112
+ order_id=order.id,
113
+ tg_web_app_url=self.BASE_REDIRECT_URL,
114
+ ),
115
+ )
116
+ invoice_data = invoice_json.get("confirmation", {})
117
+ if not invoice_data:
118
+ invoice_error_message = invoice_json.get("description")
119
+ if invoice_error_message:
120
+ raise HTTPException(status_code=500, detail=invoice_error_message)
121
+ raise HTTPException(status_code=500, detail=f"YooKassa: {invoice_data}")
122
+
123
+ logger.debug(f"YooKassa SBP invoice data: {invoice_data}")
124
+ payment_url = invoice_data.get("confirmation_url")
125
+ number = invoice_json.get("id")
126
+ qr_url = invoice_data.get("confirmation_url")
127
+ if not payment_url or not number or not qr_url:
128
+ raise HTTPException(status_code=500, detail=f"YooKassa: {invoice_data}")
129
+ return InvoiceWithPaymentLinkMessageModel(
130
+ chat_id=user.tg_id,
131
+ order_id=order.id,
132
+ order_number=order.order_number,
133
+ payload=number,
134
+ currency=fiat_currency,
135
+ payment_address=qr_url,
136
+ payment_link=payment_url,
137
+ amount=order.basket.amount,
138
+ )
139
+
140
+ async def get_tkassa_payment_data(
141
+ self,
142
+ user: UserModel,
143
+ order: OrderResponseModel,
144
+ payment: PaymentMethodDBResponseModel,
145
+ shop_details: ShopDetailsModel,
146
+ ) -> InvoiceWithPaymentLinkMessageModel:
147
+ fiat_currency = order.basket.products[0].currency
148
+ if fiat_currency != "RUB":
149
+ raise HTTPException(
150
+ status_code=404, detail="Tkassa payment method is only available for RUB currency"
151
+ )
152
+
153
+ terminal_id = ""
154
+ if payment.meta:
155
+ for meta in payment.meta:
156
+ if meta.key == "TKASSA_TERMINAL_ID" and meta.value:
157
+ terminal_id = meta.value[0]
158
+ break
159
+ if not terminal_id or not payment.payment_data:
160
+ raise HTTPException(
161
+ status_code=404, detail="Tkassa credentials was not configured for the shop"
162
+ )
163
+ tkassa_connector = TKassaAPI(
164
+ terminal_id=terminal_id, terminal_password=payment.payment_data
165
+ )
166
+ invoice_json = await tkassa_connector.create_sbp_invoice(
167
+ shop_name=shop_details.shop_name,
168
+ receipt_email=shop_details.contact_email,
169
+ order=order,
170
+ redirect_url=generate_web_app_order_link(
171
+ shop_name=shop_details.shop_name,
172
+ order_id=order.id,
173
+ tg_web_app_url=self.BASE_REDIRECT_URL,
174
+ ),
175
+ )
176
+ if not invoice_json.get("Success"):
177
+ invoice_error_message = invoice_json.get("Message")
178
+ invoice_error_details = invoice_json.get("Details")
179
+ if invoice_error_message:
180
+ invoice_error_response_message: str = invoice_error_message
181
+ if invoice_error_details:
182
+ invoice_error_response_message = (
183
+ invoice_error_response_message + " : " + invoice_error_details
184
+ )
185
+ raise HTTPException(status_code=500, detail=invoice_error_response_message)
186
+ raise HTTPException(status_code=500, detail=f"Tkassa: {invoice_json}")
187
+
188
+ logger.debug(f"Tkassa SBP invoice data: {invoice_json}")
189
+ payment_url = invoice_json.get("PaymentURL")
190
+ number = invoice_json.get("PaymentId")
191
+ qr_url = invoice_json.get("PaymentURL")
192
+ if not payment_url or not number or not qr_url:
193
+ raise HTTPException(status_code=500, detail=f"Tkassa: {invoice_json}")
194
+ return InvoiceWithPaymentLinkMessageModel(
195
+ chat_id=user.tg_id,
196
+ order_id=order.id,
197
+ order_number=order.order_number,
198
+ payload=number,
199
+ currency=fiat_currency,
200
+ payment_address=qr_url,
201
+ payment_link=payment_url,
202
+ amount=order.basket.amount,
203
+ )
204
+
205
+ async def get_cloudpayments_data(
206
+ self,
207
+ user: UserModel,
208
+ order: OrderResponseModel,
209
+ payment: PaymentMethodDBResponseModel,
210
+ shop_details: ShopDetailsModel,
211
+ ) -> InvoiceWithPaymentLinkMessageModel:
212
+ fiat_currency = order.basket.products[0].currency
213
+ if fiat_currency != "RUB":
214
+ raise HTTPException(
215
+ status_code=404, detail="CloudPayments method is only available for RUB currency"
216
+ )
217
+
218
+ account_id = ""
219
+ if payment.meta:
220
+ for meta in payment.meta:
221
+ if meta.key == "CLOUDPAYMENTS_ACCOUNT_ID" and meta.value:
222
+ account_id = meta.value[0]
223
+ break
224
+ if not account_id or not payment.payment_data:
225
+ raise HTTPException(
226
+ status_code=404, detail="CloudPayments credentials were not configured for the shop"
227
+ )
228
+
229
+ # Create request DTO
230
+ invoice_request = CreateInvoiceRequest(
231
+ shop_name=shop_details.shop_name,
232
+ order_id=order.id,
233
+ order_number=order.order_number,
234
+ amount=order.basket.amount,
235
+ currency=fiat_currency,
236
+ description=f"Заказ {order.order_number}",
237
+ customer_phone=order.user_contact_number,
238
+ customer_email=shop_details.contact_email,
239
+ metadata={
240
+ "user_id": user.tg_id,
241
+ "orderId": order.id,
242
+ "orderNumber": order.order_number,
243
+ },
244
+ )
245
+
246
+ # Create API client and call create_invoice
247
+ cloudpayments_connector = CloudPaymentsAPI(
248
+ account_id=account_id, password=payment.payment_data
249
+ )
250
+ invoice_response = await cloudpayments_connector.create_invoice(invoice_request)
251
+ if not invoice_response.payment_id or not invoice_response.payment_url:
252
+ raise HTTPException(status_code=500, detail="Error creating CloudPayments invoice")
253
+
254
+ logger.debug(f"CloudPayments invoice data: {invoice_response.raw_response}")
255
+
256
+ return InvoiceWithPaymentLinkMessageModel(
257
+ chat_id=user.tg_id,
258
+ order_id=order.id,
259
+ order_number=order.order_number,
260
+ payload=invoice_response.payment_id,
261
+ currency=fiat_currency,
262
+ payment_address=invoice_response.payment_url,
263
+ payment_link=invoice_response.payment_url,
264
+ amount=order.basket.amount,
265
+ )
266
+
267
+ async def get_invoice_data(
268
+ self,
269
+ payment_type: PaymentMethodDBResponseModel,
270
+ user: UserModel,
271
+ order: OrderResponseModel,
272
+ shop_details: ShopDetailsModel,
273
+ ) -> InvoiceWithPaymentLinkMessageModel | None:
274
+ invoice_data = None
275
+ if payment_type.type == PaymentTypes.life_pay.value:
276
+ invoice_data = await self.get_life_pay_sbp_payment_data(
277
+ user=user, order=order, payment=payment_type, shop_details=shop_details
278
+ )
279
+
280
+ elif payment_type.type == PaymentTypes.yookassa.value:
281
+ invoice_data = await self.get_yookassa_payment_data(
282
+ user=user, order=order, payment=payment_type, shop_details=shop_details
283
+ )
284
+
285
+ elif payment_type.type == PaymentTypes.tkassa.value:
286
+ invoice_data = await self.get_tkassa_payment_data(
287
+ user=user, order=order, payment=payment_type, shop_details=shop_details
288
+ )
289
+
290
+ elif payment_type.type == PaymentTypes.cloud_payments.value:
291
+ invoice_data = await self.get_cloudpayments_data(
292
+ user=user, order=order, payment=payment_type, shop_details=shop_details
293
+ )
294
+
295
+ return invoice_data
@@ -0,0 +1,23 @@
1
+ from _typeshed import Incomplete
2
+ from shops_payment_processing.logging_config import logger as logger
3
+ from shops_payment_processing.models.invoice import InvoiceWithPaymentLinkMessageModel as InvoiceWithPaymentLinkMessageModel
4
+ from shops_payment_processing.models.order import OrderResponseModel as OrderResponseModel, PaymentTypes as PaymentTypes
5
+ from shops_payment_processing.models.payment import PaymentMethodDBResponseModel as PaymentMethodDBResponseModel
6
+ from shops_payment_processing.models.shop import ShopDetailsModel as ShopDetailsModel
7
+ from shops_payment_processing.models.user import UserModel as UserModel
8
+ from shops_payment_processing.services.base import CreateInvoiceRequest as CreateInvoiceRequest
9
+ from shops_payment_processing.services.cloudpayments import CloudPaymentsAPI as CloudPaymentsAPI
10
+ from shops_payment_processing.services.life_pay import LifePayAPI as LifePayAPI
11
+ from shops_payment_processing.services.tkassa import TKassaAPI as TKassaAPI
12
+ from shops_payment_processing.services.yookassa import YooKassaAPI as YooKassaAPI
13
+ from shops_payment_processing.utils.link_generation import generate_web_app_order_link as generate_web_app_order_link
14
+
15
+ class InvoiceCreation:
16
+ BASE_REDIRECT_URL: Incomplete
17
+ BASE_CALLBACK_URL: Incomplete
18
+ def __init__(self, base_redirect_url: str, base_callback_url: str) -> None: ...
19
+ async def get_life_pay_sbp_payment_data(self, user: UserModel, order: OrderResponseModel, payment: PaymentMethodDBResponseModel, shop_details: ShopDetailsModel) -> InvoiceWithPaymentLinkMessageModel: ...
20
+ async def get_yookassa_payment_data(self, user: UserModel, order: OrderResponseModel, payment: PaymentMethodDBResponseModel, shop_details: ShopDetailsModel) -> InvoiceWithPaymentLinkMessageModel: ...
21
+ async def get_tkassa_payment_data(self, user: UserModel, order: OrderResponseModel, payment: PaymentMethodDBResponseModel, shop_details: ShopDetailsModel) -> InvoiceWithPaymentLinkMessageModel: ...
22
+ async def get_cloudpayments_data(self, user: UserModel, order: OrderResponseModel, payment: PaymentMethodDBResponseModel, shop_details: ShopDetailsModel) -> InvoiceWithPaymentLinkMessageModel: ...
23
+ async def get_invoice_data(self, payment_type: PaymentMethodDBResponseModel, user: UserModel, order: OrderResponseModel, shop_details: ShopDetailsModel) -> InvoiceWithPaymentLinkMessageModel | None: ...
@@ -0,0 +1,3 @@
1
+ import logging
2
+
3
+ logger = logging.getLogger(__name__)
@@ -0,0 +1,3 @@
1
+ from _typeshed import Incomplete
2
+
3
+ logger: Incomplete
@@ -0,0 +1,16 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class InvoiceBaseModel(BaseModel):
5
+ chat_id: int
6
+ order_id: str
7
+ order_number: str # order_number
8
+ payload: str # can be used for subscription for update
9
+ amount: float
10
+ currency: str
11
+ payment_address: str
12
+ payment_timeout: int | None = None
13
+
14
+
15
+ class InvoiceWithPaymentLinkMessageModel(InvoiceBaseModel):
16
+ payment_link: str
@@ -0,0 +1,14 @@
1
+ from pydantic import BaseModel
2
+
3
+ class InvoiceBaseModel(BaseModel):
4
+ chat_id: int
5
+ order_id: str
6
+ order_number: str
7
+ payload: str
8
+ amount: float
9
+ currency: str
10
+ payment_address: str
11
+ payment_timeout: int | None
12
+
13
+ class InvoiceWithPaymentLinkMessageModel(InvoiceBaseModel):
14
+ payment_link: str
@@ -0,0 +1,89 @@
1
+ from enum import Enum
2
+ from typing import Annotated
3
+
4
+ from annotated_types import Ge
5
+ from pydantic import BaseModel, Field, field_validator
6
+ from pydantic_core.core_schema import ValidationInfo
7
+ from typing_extensions import Doc
8
+
9
+
10
+ class PaymentTypes(str, Enum):
11
+ manual_payment_request = "ManualPaymentRequest"
12
+ external_card_payment_provider = "ExternalCardPaymentProvider"
13
+ crypto_ton = "CryptoTON"
14
+ xtr = "XTR"
15
+ life_pay = "LifePay"
16
+ yookassa = "yookassa"
17
+ tkassa = "tkassa"
18
+ cloud_payments = "CloudPayments"
19
+
20
+
21
+ class DBProductInBasketV2(BaseModel):
22
+ id: str # product ID
23
+ unique_id: str # unique product ID in the basket, used it for product deleting
24
+ extra_option_ids: list[str] = []
25
+
26
+
27
+ class MetaBaseModel(BaseModel):
28
+ metadata: dict = Field(default_factory=dict, exclude=True)
29
+
30
+
31
+ class BaseProductResponseModel(MetaBaseModel):
32
+ id: str
33
+ name: str
34
+ description: str = ""
35
+ price: float
36
+ final_price: float = 0
37
+ currency: str
38
+ preview_url: list[str] = []
39
+ stock_qty: int
40
+ orders_qty: int = 0
41
+
42
+ @field_validator("final_price", mode="before")
43
+ def set_final_price(cls, value, values: ValidationInfo):
44
+ if not value:
45
+ return values.data.get("price")
46
+ return value
47
+
48
+
49
+ class ProductsInBasket(BaseProductResponseModel):
50
+ count_in_basket: int = 1
51
+
52
+
53
+ class UserBasketResponseModel(BaseModel):
54
+ id: str | None = None
55
+ user_id: str | None = None
56
+ order_id: str | None = None
57
+ products_id: list[DBProductInBasketV2] = []
58
+ coupon: str | None = None
59
+ coupon_discount: Annotated[
60
+ float, Doc("The amount of discount from attached coupon, if any.")
61
+ ] = 0
62
+ amount: Annotated[float, Ge(0)] = 0 # this amount already includes discount
63
+ preview_url: str = ""
64
+ products: list[ProductsInBasket] = []
65
+
66
+
67
+ class ExtraFieldPayload(BaseModel):
68
+ name: str
69
+ value: str
70
+
71
+
72
+ class OrderDeliveryTypeModel(BaseModel):
73
+ name: str
74
+ address: str | None = ""
75
+ amount: float | None = 0.0
76
+ extra_fields_payload: list[ExtraFieldPayload] | None = []
77
+
78
+
79
+ class OrderResponseModel(UserBasketResponseModel):
80
+ id: str
81
+ delivery: OrderDeliveryTypeModel
82
+ basket: UserBasketResponseModel
83
+ user_id: str | None = None
84
+ basket_id: str | None = None
85
+ status: str | None = None
86
+ order_number: str = "#0001"
87
+ process_key: int | None = None
88
+ coupon: str | None = None
89
+ user_contact_number: str | None = None
@@ -0,0 +1,71 @@
1
+ from annotated_types import Ge as Ge
2
+ from enum import Enum
3
+ from pydantic import BaseModel
4
+ from pydantic_core.core_schema import ValidationInfo as ValidationInfo
5
+ from typing import Annotated
6
+
7
+ class PaymentTypes(str, Enum):
8
+ manual_payment_request = 'ManualPaymentRequest'
9
+ external_card_payment_provider = 'ExternalCardPaymentProvider'
10
+ crypto_ton = 'CryptoTON'
11
+ xtr = 'XTR'
12
+ life_pay = 'LifePay'
13
+ yookassa = 'yookassa'
14
+ tkassa = 'tkassa'
15
+ cloud_payments = 'CloudPayments'
16
+
17
+ class DBProductInBasketV2(BaseModel):
18
+ id: str
19
+ unique_id: str
20
+ extra_option_ids: list[str]
21
+
22
+ class MetaBaseModel(BaseModel):
23
+ metadata: dict
24
+
25
+ class BaseProductResponseModel(MetaBaseModel):
26
+ id: str
27
+ name: str
28
+ description: str
29
+ price: float
30
+ final_price: float
31
+ currency: str
32
+ preview_url: list[str]
33
+ stock_qty: int
34
+ orders_qty: int
35
+ def set_final_price(cls, value, values: ValidationInfo): ...
36
+
37
+ class ProductsInBasket(BaseProductResponseModel):
38
+ count_in_basket: int
39
+
40
+ class UserBasketResponseModel(BaseModel):
41
+ id: str | None
42
+ user_id: str | None
43
+ order_id: str | None
44
+ products_id: list[DBProductInBasketV2]
45
+ coupon: str | None
46
+ coupon_discount: Annotated[float, None]
47
+ amount: Annotated[float, None]
48
+ preview_url: str
49
+ products: list[ProductsInBasket]
50
+
51
+ class ExtraFieldPayload(BaseModel):
52
+ name: str
53
+ value: str
54
+
55
+ class OrderDeliveryTypeModel(BaseModel):
56
+ name: str
57
+ address: str | None
58
+ amount: float | None
59
+ extra_fields_payload: list[ExtraFieldPayload] | None
60
+
61
+ class OrderResponseModel(UserBasketResponseModel):
62
+ id: str
63
+ delivery: OrderDeliveryTypeModel
64
+ basket: UserBasketResponseModel
65
+ user_id: str | None
66
+ basket_id: str | None
67
+ status: str | None
68
+ order_number: str
69
+ process_key: int | None
70
+ coupon: str | None
71
+ user_contact_number: str | None
@@ -0,0 +1,22 @@
1
+ from pydantic import BaseModel
2
+
3
+ from shops_payment_processing.models.order import PaymentTypes
4
+
5
+
6
+ class Metadata(BaseModel):
7
+ key: str
8
+ value: list[str]
9
+
10
+
11
+ class PaymentMethodModel(BaseModel):
12
+ name: str
13
+ type: PaymentTypes
14
+ payment_data: str | None = None # payment_token, TON address etc....
15
+ meta: list[Metadata] | None = []
16
+
17
+ class Config:
18
+ use_enum_values = True
19
+
20
+
21
+ class PaymentMethodDBResponseModel(PaymentMethodModel):
22
+ id: str
@@ -0,0 +1,17 @@
1
+ from pydantic import BaseModel
2
+ from shops_payment_processing.models.order import PaymentTypes as PaymentTypes
3
+
4
+ class Metadata(BaseModel):
5
+ key: str
6
+ value: list[str]
7
+
8
+ class PaymentMethodModel(BaseModel):
9
+ name: str
10
+ type: PaymentTypes
11
+ payment_data: str | None
12
+ meta: list[Metadata] | None
13
+ class Config:
14
+ use_enum_values: bool
15
+
16
+ class PaymentMethodDBResponseModel(PaymentMethodModel):
17
+ id: str
@@ -0,0 +1,10 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class ShopDetailsModel(BaseModel):
5
+ shop_id: str
6
+ shop_name: str
7
+ friendly_name: str
8
+ shop_language: str = "RU"
9
+ contact_phone: str | None = None
10
+ contact_email: str
@@ -0,0 +1,9 @@
1
+ from pydantic import BaseModel
2
+
3
+ class ShopDetailsModel(BaseModel):
4
+ shop_id: str
5
+ shop_name: str
6
+ friendly_name: str
7
+ shop_language: str
8
+ contact_phone: str | None
9
+ contact_email: str
@@ -0,0 +1,9 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class UserModel(BaseModel):
5
+ tg_id: int
6
+ first_name: str | None = ""
7
+ last_name: str | None = ""
8
+ tg_language: str | None = ""
9
+ username: str | None = None
@@ -0,0 +1,8 @@
1
+ from pydantic import BaseModel
2
+
3
+ class UserModel(BaseModel):
4
+ tg_id: int
5
+ first_name: str | None
6
+ last_name: str | None
7
+ tg_language: str | None
8
+ username: str | None