Source code for amazonorders.transactions

__copyright__ = "Copyright (c) 2024-2025 Alex Laird"
__license__ = "MIT"

import datetime
import logging
from typing import Dict, List, Optional, Tuple, Any

from bs4 import Tag
from dateutil import parser

from amazonorders import util
from amazonorders.conf import AmazonOrdersConfig
from amazonorders.entity.transaction import Transaction
from amazonorders.exception import AmazonOrdersError
from amazonorders.session import AmazonSession

logger = logging.getLogger(__name__)


def _parse_transaction_form_tag(form_tag: Tag,
                                config: AmazonOrdersConfig) \
        -> Tuple[List[Transaction], Optional[Dict[str, str]]]:
    transactions = []
    date_container_tags = util.select(form_tag, config.selectors.TRANSACTION_DATE_CONTAINERS_SELECTOR)
    for date_container_tag in date_container_tags:
        date_tag = util.select_one(date_container_tag, config.selectors.FIELD_TRANSACTION_COMPLETED_DATE_SELECTOR)
        if not date_tag:
            logger.warning("Could not find date tag in Transaction form.")
            continue

        date_str = date_tag.text
        date = parser.parse(date_str).date()

        transactions_container_tag = date_container_tag.find_next_sibling(
            config.selectors.TRANSACTIONS_CONTAINER_SELECTOR)
        if not isinstance(transactions_container_tag, Tag):
            logger.warning("Could not find Transactions container tag in Transaction form.")
            continue

        transaction_tags = util.select(transactions_container_tag, config.selectors.TRANSACTIONS_SELECTOR)
        for transaction_tag in transaction_tags:
            transaction = Transaction(transaction_tag, config, date)
            transactions.append(transaction)

    form_state_input = util.select_one(form_tag, config.selectors.TRANSACTIONS_NEXT_PAGE_INPUT_STATE_SELECTOR)
    form_ie_input = util.select_one(form_tag, config.selectors.TRANSACTIONS_NEXT_PAGE_INPUT_IE_SELECTOR)
    next_page_input = util.select_one(form_tag, config.selectors.TRANSACTIONS_NEXT_PAGE_INPUT_SELECTOR)
    if not next_page_input or not form_state_input or not form_ie_input:
        return transactions, None

    next_page_data = {
        "ppw-widgetState": str(form_state_input["value"]),
        "ie": str(form_ie_input["value"]),
        str(next_page_input["name"]): "",
    }

    return transactions, next_page_data


[docs] class AmazonTransactions: """ Using an authenticated :class:`~amazonorders.session.AmazonSession`, can be used to query Amazon for Transaction details and history. """ def __init__(self, amazon_session: AmazonSession, debug: Optional[bool] = None, config: Optional[AmazonOrdersConfig] = None) -> None: if not debug: debug = amazon_session.debug if not config: config = amazon_session.config #: The session to use for requests. self.amazon_session: AmazonSession = amazon_session #: The config to use. self.config: AmazonOrdersConfig = config #: Setting logger to ``DEBUG`` will send output to ``stderr``. self.debug: bool = debug if self.debug: logger.setLevel(logging.DEBUG)
[docs] def get_transactions(self, days: int = 365, next_page_data: Optional[Dict[str, Any]] = None, keep_paging: bool = True, order_id: Optional[str] = None) -> List[Transaction]: """ Get Amazon Transaction history for a given number of days, or for a single Order. :param days: The number of days worth of Transactions to get. Ignored when ``order_id`` is given. :param next_page_data: If a call to this method previously errored out, passing the exception's :attr:`~amazonorders.exception.AmazonOrdersError.meta` will continue paging where it left off. :param keep_paging: ``False`` if only one page should be fetched. :param order_id: If given, only Transactions for this Amazon Order ID are returned, scoped server-side via Amazon's ``transactionTag`` filter (the ``days`` window does not apply). :return: A list of the requested Transactions. """ if not self.amazon_session.is_authenticated: raise AmazonOrdersError("Call AmazonSession.login() to authenticate first.") url = self.config.constants.TRANSACTION_HISTORY_URL if order_id: url = f"{url}?transactionTag={order_id}" else: min_date = datetime.date.today() - datetime.timedelta(days=days) transactions: List[Transaction] = [] first_page = True while first_page or keep_paging: first_page = False page_response = self.amazon_session.post(url, data=next_page_data) self.amazon_session.check_response(page_response, meta=next_page_data) form_tag = util.select_one(page_response.parsed, self.config.selectors.TRANSACTION_HISTORY_FORM_SELECTOR) if not form_tag: transaction_container = util.select_one(page_response.parsed, self.config.selectors.TRANSACTION_HISTORY_CONTAINER_SELECTOR) if transaction_container and "don't have any transactions" in transaction_container.text: break else: raise AmazonOrdersError("Could not parse Transaction history. Check if Amazon changed the HTML.") loaded_transactions, next_page_data = ( _parse_transaction_form_tag(form_tag, self.config) ) for transaction in loaded_transactions: if order_id or transaction.completed_date >= min_date: transactions.append(transaction) else: next_page_data = None break if not next_page_data: keep_paging = False return transactions