PK!alfa_orders/__init__.pyPK!U%D<<alfa_orders/api.pyimport datetime as dt from typing import Iterable, List from alfa_orders.config import AlfaConfig from alfa_orders.loaders.refunds import Refund, RefundLoader from alfa_orders.loaders.transactions import TransactionLoader, Transaction, TransactionStatus from alfa_orders.session import create_alfa_session class AlfaService: """ Usage: >>> username, password = ("**********", "**********") >>> service = AlfaService(username, password) >>> from_date, to_date = dt.datetime(2019, 9, 1), dt.datetime(2019, 10, 1) >>> transactions = list(service.get_transactions(from_date, to_date)) >>> refunds = list(service.get_refunds(from_date, to_date)) """ def __init__(self, username: str, password: str, config: AlfaConfig = None) -> None: self.config = config or AlfaConfig() self.session = create_alfa_session(username, password, self.config) def get_transactions( self, from_date: dt.datetime, to_date: dt.datetime, statuses: List[TransactionStatus] = None ) -> Iterable[Transaction]: return TransactionLoader(self.session, self.config, statuses)(from_date, to_date) def get_refunds(self, from_date: dt.datetime, to_date: dt.datetime) -> Iterable[Refund]: return RefundLoader(self.session, self.config)(from_date, to_date) PK!hA{ { alfa_orders/columns.pyTransactionColumns = { "orderNumber": "Номер заказа", "registerDate": "Дата", "ip": "IP-адрес", "mdOrder": "Уникальный номер заказа в системе", "paymentState": "Состояние", "merchantFullName": "Название продавца", "merchantLogin": "Логин продавца", "orderDescription": "Описание заказа", "amount": "Сумма", "paymentDate": "Дата оплаты", "depositedDate": "Дата списания", "refundedDate": "Дата последнего возврата", "reversedDate": "Дата отмены", "feeAmount": "Сумма комиссии", "currency": "Валюта", "approvedAmount": "Сумма предавторизации", "depositedAmount": "Сумма подтверждения", "refundedAmount": "Сумма возврата", "fraudWeight": "Вес фрода", "paymentWay": "Платежное средство", "cardholderName": "Имя владельца карты", "pan": "Номер карты", "expiry": "Дата окончания действия", "paymentSystem": "Платежная система", "product": "Продукт", "bankName": "Название Банка", "panCountryCode": "Код страны банка", "ipCountryCode": "Код страны IP-адреса", "panCountry": "Страна банка", "ipCountry": "Страна IP-адреса", "actionCode": "Код ответа процессинга", "actionCodeDescription": "Описание кода ответа процессинга", "originalActionCode": "Оригинальный код ответа процессинга", "approvalCode": "Approval Code", "authCode": "Auth code", "referenceNumber": "Reference number", "authReferenceNumber": "Authentication reference number", "terminalId": "Терминал", "processingId": "Процессинг", "transactionDesc": "3DSec/SSL", "eci": "ECI", "clientId": "Идентификатор клиента", "phone": "Телефон клиента", "email": "Email клиента", "orderParamsAsString": "Дополнительные параметры заказа", # extra columns "paymentStateDescription": "Описание состояния", } RefundColumns = { "id": "ID", "refundDate": "Дата возврата", "paymentDate": "Дата платежа", "merchantFullName": "Название продавца", "mdOrder": "ID заказа", "orderNumber": "Номер заказа", "amount": "Сумма возврата", "maskedPan": "Номер карты", "refundState": "Состояние возврата", "currencyName": "Валюта", "cardholder": "Держатель карты", "bankName": "Банк-эмитент", "countryName": "Страна банка-эмитента", "actionCode": "Код результата", "refNum": "Reference number", "processingId": "ID процессинга", "terminalId": "ID терминала", "paymentSystem": "МПС", "merchantMcc": "MCC" } PK!'0=]]alfa_orders/config.pyfrom dataclasses import dataclass @dataclass class AlfaConfig: HOST: str = "https://engine.paymentgate.ru" RETRY_SECONDS: int = 60 PAGE_SIZE: int = 100 MAX_FUTURES: int = 10 PARSE_TIMESTAMP: bool = False PARSE_TIMESTAMP_AS_UTC3: bool = False MAP_RUSSIAN_COLUMNS: bool = False UTC_3_SEARCH: bool = True PK!alfa_orders/loaders/__init__.pyPK!alfa_orders/loaders/base.pyimport datetime as dt from abc import abstractmethod from itertools import count from typing import Generic, Iterable import requests from more_itertools import flatten, chunked from alfa_orders.config import AlfaConfig from alfa_orders.models import AlfaFuture, T class BaseLoader(Generic[T]): def __init__(self, session: requests.Session, config: AlfaConfig): self.session = session self.config = config self.post_processors = [] if config.PARSE_TIMESTAMP: from alfa_orders.processors import TimestampProcessor self.post_processors.append(TimestampProcessor(self, self.config)) if config.MAP_RUSSIAN_COLUMNS: from alfa_orders.processors import RussianColumnProcessor self.post_processors.append(RussianColumnProcessor(self, self.config)) def __call__(self, from_date, to_date): yield from self._load(from_date, to_date) @property @abstractmethod def columns(self): pass def _load(self, from_date, to_date): futures = (self._get_future(from_date, to_date, offset) for offset in count(0, self.config.PAGE_SIZE)) future_batches = chunked(futures, self.config.MAX_FUTURES) order_batches = ( tuple(flatten(map(self._post_process, map(self._get_by_future, batch)))) for batch in future_batches ) for batch in order_batches: yield from batch if len(batch) < self.config.PAGE_SIZE * self.config.MAX_FUTURES: break @abstractmethod def _get_future(self, from_date: dt.datetime, to_date: dt.datetime, offset: int = 0) -> AlfaFuture: pass @abstractmethod def _get_by_future(self, future: AlfaFuture) -> Iterable[T]: pass def _post_process(self, orders: Iterable[T]) -> Iterable[T]: for proc in self.post_processors: orders = proc(orders) return orders PK!w=//alfa_orders/loaders/refunds.pyimport datetime as dt import time from typing import Dict, Iterable from alfa_orders.columns import RefundColumns from alfa_orders.loaders.base import BaseLoader from alfa_orders.models import AlfaFuture, AlfaFutureResult from alfa_orders.utils import timestamp_now Refund = Dict class RefundLoader(BaseLoader[Refund]): DATETIME_FORMAT = "%d.%m.%Y %H:%M" columns = RefundColumns def _get_future(self, from_date: dt.datetime, to_date: dt.datetime, offset: int = 0) -> AlfaFuture: shift = dt.timedelta(hours=3 if self.config.UTC_3_SEARCH else 0) alfa_from_date = (from_date - shift).strftime(self.DATETIME_FORMAT) alfa_to_date = (to_date - shift).strftime(self.DATETIME_FORMAT) data = { 'page': '1', 'start': offset, 'limit': self.config.PAGE_SIZE, 'sort': 'refundDate', 'dir': 'DESC', 'dateFrom': alfa_from_date, 'dateTo': alfa_to_date, } response = self.session.post( f"{self.config.HOST}/mportal/mvc/refunds/search", params={"_dc": timestamp_now()}, data=data ) return response.json() def _get_by_future(self, future: AlfaFuture) -> Iterable[Refund]: url = f"{self.config.HOST}/mportal/mvc/refunds/search" params = {"_dc": timestamp_now()} data = future for _ in range(self.config.RETRY_SECONDS): resp = self.session.post(url, data, params=params) resp_json: AlfaFutureResult = resp.json() if resp_json["done"]: return resp_json["data"] time.sleep(1) else: raise LookupError(f"No orders for future {future['future']} in {self.config.RETRY_SECONDS} seconds") PK!Xo #alfa_orders/loaders/transactions.pyimport datetime as dt import time from enum import Enum from typing import Dict, List import requests from alfa_orders.columns import TransactionColumns from alfa_orders.config import AlfaConfig from alfa_orders.loaders.base import BaseLoader from alfa_orders.models import AlfaFutureResult, AlfaFuture from alfa_orders.utils import timestamp_now # see data/transaction.json and data/transaction_columns.json for order fields Transaction = Dict class TransactionStatus(Enum): DEPOSITED = "DEPOSITED" REFUNDED = "REFUNDED" class TransactionLoader(BaseLoader[Transaction]): DATETIME_FORMAT = "%d.%m.%Y %H:%M:%S" columns = TransactionColumns def __init__(self, session: requests.Session, config: AlfaConfig, statuses: List[TransactionStatus] = None): super().__init__(session, config) self.statuses: List[TransactionStatus] = statuses or [TransactionStatus.DEPOSITED, TransactionStatus.REFUNDED] def _get_future(self, from_date: dt.datetime, to_date: dt.datetime, offset: int = 0) -> AlfaFuture: shift = dt.timedelta(hours=3 if self.config.UTC_3_SEARCH else 0) alfa_from_date = (from_date - shift).strftime(self.DATETIME_FORMAT) alfa_to_date = (to_date - shift).strftime(self.DATETIME_FORMAT) url = f"{self.config.HOST}/mportal/mvc/transaction" params = {"_dc": timestamp_now()} data = { "start": offset, "limit": self.config.PAGE_SIZE, "dateFrom": alfa_from_date, "dateTo": alfa_to_date, "orderStateStr": ",".join(str(status.value) for status in self.statuses), "page": '1', "dateMode": "CREATION_DATE", "merchants": "", } response = self.session.post(url, data, params=params) response.raise_for_status() response_json = response.json() return response_json def _get_by_future(self, future: AlfaFuture): url = f"{self.config.HOST}/mportal/mvc/transaction" params = {"_dc": timestamp_now()} data = future for _ in range(self.config.RETRY_SECONDS): resp = self.session.post(url, data, params=params) resp_json: AlfaFutureResult = resp.json() if resp_json["done"]: return resp_json["data"] time.sleep(1) else: raise LookupError(f"No orders for future {future['future']} in {self.config.RETRY_SECONDS} seconds") PK!.??alfa_orders/models.pyfrom typing import TypeVar, List, Dict from typing_extensions import TypedDict T = TypeVar("T") Offset = int class AlfaFuture(TypedDict): # {'future': 'ea592611-558a-47ff-acd1-0f8a2f3e3ed0'} future: str class AlfaFutureResult(TypedDict): done: bool success: bool data: List[Dict]PK!:alfa_orders/processors.pyfrom abc import abstractmethod from typing import Iterable, Dict from alfa_orders.config import AlfaConfig from alfa_orders.loaders.base import BaseLoader from alfa_orders.utils import parse_timestamp class BaseProcessor: def __init__(self, loader: BaseLoader, config: AlfaConfig): self.loader = loader self.config = config @abstractmethod def __call__(self, orders: Iterable[Dict]) -> Iterable[Dict]: pass class TimestampProcessor(BaseProcessor): def __call__(self, orders: Iterable[Dict]) -> Iterable[Dict]: return self._parse_timestamps(orders) def _parse_timestamps(self, orders: Iterable[Dict]) -> Iterable[Dict]: for order in orders: yield { **order, **{ field: parse_timestamp(value, self.config.PARSE_TIMESTAMP_AS_UTC3) for field, value in order.items() if "Date" in field } } class RussianColumnProcessor(BaseProcessor): def __call__(self, orders: Iterable[Dict]) -> Iterable[Dict]: for order in orders: yield {self.loader.columns.get(field, field): value for field, value in order.items()} PK!nQHalfa_orders/session.pyimport requests def create_alfa_session(username, password, config): session = requests.Session() session.headers = { "Accept-Language": "ru,en;q=0.8", "Cache-Control": "no-cache", "Pragma": "no-cache", "User-Agent": "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.97 " "YaBrowser/15.9.2403.2152 (beta) Safari/537.36", "X-Requested-With": "XMLHttpRequest", "Referer": f"{config.HOST}/mportal/", "Origin": f"{config.HOST}" } response = session.post( f"{config.HOST}/mportal/login", {"username": username, "password": password} ) response.raise_for_status() return sessionPK! tMalfa_orders/utils.pyimport datetime as dt from typing import Optional def timestamp_now() -> int: return int(dt.datetime.now().timestamp()) def parse_timestamp(timestamp: Optional[int], as_utc3: bool = False) -> Optional[dt.datetime]: if not timestamp: return timestamp datetime = dt.datetime.utcfromtimestamp(timestamp / 1000) if as_utc3: datetime += dt.timedelta(hours=3) return datetime PK!HڽTU!alfa_orders-0.2.2.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!Hw$alfa_orders-0.2.2.dist-info/METADATARMo0 W^m@6)֥˲bmcƌ#Ēt$^#FGXX@JOpE~TI0)M c_v}0opanո YGPD% H`[+VU o҄cv_>R}o3=9ï-mx`6N0:y$xx?2PN.ձl%lw@u2kSa>Aqr[u,`fF{<ģ=mjMCPK!Hǘ"alfa_orders-0.2.2.dist-info/RECORD@{9(.zBf>UJߝy=a~ TEf%O87msU.QhW8:8Um/~f 61NJdgs>k8k#vkE*GCN]dJe쫚M87!/ xmh1p\V(ȒyqhWsn໘09`5#ب,P{J}ǔ [sNs"=Qy9eij"WIqק1a[d> !!菛Vi饨~ u w4Yuyy͎ް걊r7 Yy#_%Z^8 @_#W˩v+~Bwt_v~Cڮ{]L0^:^v$Ì:E~t!s-OjGLvb @["';1Ȝ}8I#c ŒIXV00C+AR*,.Q YmDG,M-q