Coverage for packages/kapitalbank/src/integrify/kapitalbank/handlers.py: 88%
98 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-10-19 02:39 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-10-19 02:39 +0000
1import base64
2import json
3from functools import cached_property
4from typing import ClassVar
6import httpx
7from integrify.api import APIPayloadHandler, APIResponse, _ResponseT
8from integrify.kapitalbank.env import KAPITAL_PASSWORD, KAPITAL_USERNAME
9from integrify.kapitalbank.schemas.request import (
10 ClearingOrderRequestSchema,
11 CreateOrderRequestSchema,
12 FullReverseOrderRequestSchema,
13 LinkCardTokenRequestSchema,
14 OrderInformationRequestSchema,
15 OrderWithSavedCardRequestSchema,
16 PartialReverseOrderRequestSchema,
17 PayAndSaveCardRequestSchema,
18 ProcessPaymentWithSavedCardRequestSchema,
19 RefundOrderRequestSchema,
20 SaveCardRequestSchema,
21)
22from integrify.kapitalbank.schemas.response import (
23 BaseResponseSchema,
24 ClearingOrderResponseSchema,
25 CreateOrderResponseSchema,
26 DetailedOrderInformationResponseSchema,
27 ErrorResponseBodySchema,
28 FullReverseOrderResponseSchema,
29 LinkCardTokenResponseSchema,
30 OrderInformationResponseSchema,
31 PartialReverseOrderResponseSchema,
32 ProcessPaymentWithSavedCardResponseSchema,
33 RefundOrderResponseSchema,
34)
35from pydantic import BaseModel
38def _safe_json(resp: httpx.Response) -> dict:
39 """Cavabı JSON kimi parse etmək; JSON deyilsə (gateway xətası, boş body)
40 exception atmaq əvəzinə boş dict qaytarır."""
41 try:
42 data = resp.json()
43 except (json.JSONDecodeError, ValueError):
44 return {}
46 return data if isinstance(data, dict) else {}
49class BasePayloadHandler(APIPayloadHandler):
50 """Kapitalbank üçün baza handler. `req_model`/`resp_model`/`data_key`
51 alt class-larda ClassVar kimi təyin olunur."""
53 data_key: ClassVar[str | None] = None
55 @cached_property
56 def headers(self):
57 credentials = f'{KAPITAL_USERNAME}:{KAPITAL_PASSWORD}'
58 encoded_credentials = base64.b64encode(credentials.encode()).decode()
59 return {
60 'Authorization': f'Basic {encoded_credentials}',
61 'Content-Type': 'application/json',
62 }
64 def post_handle_payload(self, data):
65 if self.data_key: 65 ↛ 68line 65 didn't jump to line 68 because the condition on line 65 was always true
66 return {self.data_key: data}
68 return data
70 def handle_response(self, resp: httpx.Response) -> APIResponse[_ResponseT]:
71 """
72 Bu funksiya API-dən gələn cavabı status koduna görə tənzimləyir.
73 Əgər status kodu 200-dürsə, gələn cavabı modelə uyğunlaşdırır və APIResponse obyektini qaytarır.
74 200-dən fərqli status kodu gələrsə, gələn cavabı modelə uyğunlaşdırır və error obyektini APIResponse obyektinə əlavə edir.
75 """ # noqa: E501
77 api_resp = APIResponse[BaseResponseSchema].model_validate(resp, from_attributes=True)
78 body = _safe_json(resp)
80 if resp.status_code == 200: 80 ↛ 91line 80 didn't jump to line 91 because the condition on line 80 was always true
81 if not self.resp_model: 81 ↛ 82line 81 didn't jump to line 82 because the condition on line 81 was never true
82 raise ValueError('Response model is not set for this handler.')
84 data = self.get_response_data(body)
86 assert issubclass(self.resp_model, BaseModel)
87 api_resp.body.data = self.resp_model.model_validate(data, from_attributes=True)
88 else:
89 # Error body gözlənilən formatda olmaya bilər (məs., HTML xəta səhifəsi);
90 # bu halda status kodu və `ok` field-i xətanı bildirir.
91 try:
92 api_resp.body.error = ErrorResponseBodySchema.model_validate(
93 body,
94 from_attributes=True,
95 )
96 except (ValueError, TypeError):
97 api_resp.body.error = None
99 return api_resp
101 def get_response_data(self, response_json: dict) -> dict:
102 """`self.data_key` varsa, o key-dəki datanı götürmək"""
103 if not self.data_key:
104 raise NotImplementedError("Subclasses must define 'data_key'")
106 return response_json.get(self.data_key, {})
109class CreateOrderPayloadHandler(BasePayloadHandler):
110 req_model = CreateOrderRequestSchema
111 resp_model = CreateOrderResponseSchema
112 data_key = 'order'
115class OrderInformationPayloadHandler(BasePayloadHandler):
116 req_model = OrderInformationRequestSchema
117 resp_model = OrderInformationResponseSchema
118 data_key = 'order'
120 def post_handle_payload(self, data):
121 return data
124class DetailedOrderInformationPayloadHandler(BasePayloadHandler):
125 req_model = OrderInformationRequestSchema
126 resp_model = DetailedOrderInformationResponseSchema
127 data_key = 'order'
129 def post_handle_payload(self, data):
130 return data
133class RefundOrderPayloadHandler(BasePayloadHandler):
134 req_model = RefundOrderRequestSchema
135 resp_model = RefundOrderResponseSchema
136 data_key = 'tran'
139class SaveCardPayloadHandler(BasePayloadHandler):
140 req_model = SaveCardRequestSchema
141 resp_model = CreateOrderResponseSchema
142 data_key = 'order'
145class PayAndSaveCardPayloadHandler(BasePayloadHandler):
146 req_model = PayAndSaveCardRequestSchema
147 resp_model = CreateOrderResponseSchema
148 data_key = 'order'
151class FullReverseOrderPayloadHandler(BasePayloadHandler):
152 req_model = FullReverseOrderRequestSchema
153 resp_model = FullReverseOrderResponseSchema
154 data_key = 'tran'
157class ClearingOrderPayloadHandler(BasePayloadHandler):
158 req_model = ClearingOrderRequestSchema
159 resp_model = ClearingOrderResponseSchema
160 data_key = 'tran'
163class PartialReverseOrderPayloadHandler(BasePayloadHandler):
164 req_model = PartialReverseOrderRequestSchema
165 resp_model = PartialReverseOrderResponseSchema
166 data_key = 'tran'
169class OrderWithSavedCardPayloadHandler(BasePayloadHandler):
170 req_model = OrderWithSavedCardRequestSchema
171 resp_model = CreateOrderResponseSchema
172 data_key = 'order'
175class LinkCardTokenPayloadHandler(BasePayloadHandler):
176 req_model = LinkCardTokenRequestSchema
177 resp_model = LinkCardTokenResponseSchema
178 data_key = 'order'
180 def post_handle_payload(self, data):
181 # `dict` qaytarırıq (əvvəlki `json.dumps(...)` string qaytarırdı, bu da httpx
182 # tərəfindən ikiqat JSON-encode olunub, body-ni korlayırdı).
183 return {
184 'order': {'initiationEnvKind': 'Server'},
185 'token': {'storedId': data['token']},
186 }
189class ProcessPaymentWithSavedCardPayloadHandler(BasePayloadHandler):
190 req_model = ProcessPaymentWithSavedCardRequestSchema
191 resp_model = ProcessPaymentWithSavedCardResponseSchema
192 data_key = 'tran'