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!^{ #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(map(str, 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.1.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!Hw$alfa_orders-0.2.1.dist-info/METADATARMo0 W^m@6)֥˲bmcƌ#Ē| (ڻX[ ga&89?}{Q-ԺU6YfbJRkKd,;я:i(ab) '?SU`$Zä6/4pJ}AUqV6gBl4/d joXVUP. ! JJyV}X\J!w +8w-ʴB:qVDzՖ`ewvz:# O!`oN/סl%Պ^cpM~4yc;~PK!H"alfa_orders-0.2.1.dist-info/RECORDr@}Yȏ AD nEn@Y S{9QE$% [^둡 xQӷEZ)EQ`iMk)l K{fM'vT\ e/X-$R&g5.n g*EWVtY!&|`)?dFƏf4nho82y4 65I|x e?4DN OXzh:HC2וfQBRs l/blh4dZo+}nYcBܰθ)+iv`-K2@~ߒQill8(;gz Z9yFMenHc >%Nb] #>wYv*bJ{ݙOXMpR⩅VIٺHM@,|9K }b==N8ntc]GIKa@Fz` C{ƌM)+ƺB* {gت=^tttqjUP ρ^+xMrڲy`_p; B6/чDOV8Fyhpe$Wҽ6_XB *q 3o' /+b+{qݘvuv5y PK!alfa_orders/__init__.pyPK!U%D<<5alfa_orders/api.pyPK!hA{ { alfa_orders/columns.pyPK!'0=]]Palfa_orders/config.pyPK!alfa_orders/loaders/__init__.pyPK!alfa_orders/loaders/base.pyPK!w=//*alfa_orders/loaders/refunds.pyPK!^{ ##alfa_orders/loaders/transactions.pyPK!.??-alfa_orders/models.pyPK!:/alfa_orders/processors.pyPK!nQH>4alfa_orders/session.pyPK! tMm7alfa_orders/utils.pyPK!HڽTU!N9alfa_orders-0.2.1.dist-info/WHEELPK!Hw$9alfa_orders-0.2.1.dist-info/METADATAPK!H" <alfa_orders-0.2.1.dist-info/RECORDPK@>