From b24d09b9b416775c2c912293f3b8d71163e777c9 Mon Sep 17 00:00:00 2001 From: poolitzer Date: Fri, 5 Jul 2024 21:37:08 +0200 Subject: [PATCH 1/6] Feat: Api 7.7 --- docs/source/telegram.payments-tree.rst | 1 + docs/source/telegram.refundedpayment.rst | 6 ++ telegram/__init__.py | 2 + telegram/_message.py | 13 +++ telegram/_payment/refundedpayment.py | 91 ++++++++++++++++++ telegram/constants.py | 7 +- telegram/ext/filters.py | 10 ++ tests/_payment/test_refundedpayment.py | 115 +++++++++++++++++++++++ tests/ext/test_filters.py | 5 + tests/test_message.py | 3 + 10 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 docs/source/telegram.refundedpayment.rst create mode 100644 telegram/_payment/refundedpayment.py create mode 100644 tests/_payment/test_refundedpayment.py diff --git a/docs/source/telegram.payments-tree.rst b/docs/source/telegram.payments-tree.rst index 2a09c5415ac..0db0ba21959 100644 --- a/docs/source/telegram.payments-tree.rst +++ b/docs/source/telegram.payments-tree.rst @@ -8,6 +8,7 @@ Payments telegram.labeledprice telegram.orderinfo telegram.precheckoutquery + telegram.refundedpayment telegram.revenuewithdrawalstate telegram.revenuewithdrawalstatefailed telegram.revenuewithdrawalstatepending diff --git a/docs/source/telegram.refundedpayment.rst b/docs/source/telegram.refundedpayment.rst new file mode 100644 index 00000000000..f99349c859c --- /dev/null +++ b/docs/source/telegram.refundedpayment.rst @@ -0,0 +1,6 @@ +RefundedPayment +=============== + +.. autoclass:: telegram.RefundedPayment + :members: + :show-inheritance: diff --git a/telegram/__init__.py b/telegram/__init__.py index af2336a4ac9..5b52bf85c40 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -204,6 +204,7 @@ "ReactionType", "ReactionTypeCustomEmoji", "ReactionTypeEmoji", + "RefundedPayment", "ReplyKeyboardMarkup", "ReplyKeyboardRemove", "ReplyParameters", @@ -446,6 +447,7 @@ from ._payment.labeledprice import LabeledPrice from ._payment.orderinfo import OrderInfo from ._payment.precheckoutquery import PreCheckoutQuery +from ._payment.refundedpayment import RefundedPayment from ._payment.shippingaddress import ShippingAddress from ._payment.shippingoption import ShippingOption from ._payment.shippingquery import ShippingQuery diff --git a/telegram/_message.py b/telegram/_message.py index b52b2bc9b48..5c45b9582a4 100644 --- a/telegram/_message.py +++ b/telegram/_message.py @@ -55,6 +55,7 @@ from telegram._paidmedia import PaidMediaInfo from telegram._passport.passportdata import PassportData from telegram._payment.invoice import Invoice +from telegram._payment.refundedpayment import RefundedPayment from telegram._payment.successfulpayment import SuccessfulPayment from telegram._poll import Poll from telegram._proximityalerttriggered import ProximityAlertTriggered @@ -576,6 +577,10 @@ class Message(MaybeInaccessibleMessage): paid_media (:obj:`telegram.PaidMediaInfo`, optional): Message contains paid media; information about the paid media. + .. versionadded:: NEXT.VERSION + refunded_payment (:obj:`telegram.RefundedPayment`, optional): Message is a service message + about a refunded payment, information about the payment. + .. versionadded:: NEXT.VERSION Attributes: @@ -894,6 +899,10 @@ class Message(MaybeInaccessibleMessage): paid_media (:obj:`telegram.PaidMediaInfo`): Optional. Message contains paid media; information about the paid media. + .. versionadded:: NEXT.VERSION + refunded_payment (:obj:`telegram.RefundedPayment`): Optional. Message is a service message + about a refunded payment, information about the payment. + .. versionadded:: NEXT.VERSION .. |custom_emoji_no_md1_support| replace:: Since custom emoji entities are not supported by @@ -968,6 +977,7 @@ class Message(MaybeInaccessibleMessage): "poll", "proximity_alert_triggered", "quote", + "refunded_payment", "reply_markup", "reply_to_message", "reply_to_story", @@ -1080,6 +1090,7 @@ def __init__( effect_id: Optional[str] = None, show_caption_above_media: Optional[bool] = None, paid_media: Optional[PaidMediaInfo] = None, + refunded_payment: Optional[RefundedPayment] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -1182,6 +1193,7 @@ def __init__( self.effect_id: Optional[str] = effect_id self.show_caption_above_media: Optional[bool] = show_caption_above_media self.paid_media: Optional[PaidMediaInfo] = paid_media + self.refunded_payment: Optional[RefundedPayment] = refunded_payment self._effective_attachment = DEFAULT_NONE @@ -1298,6 +1310,7 @@ def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optio data["chat_shared"] = ChatShared.de_json(data.get("chat_shared"), bot) data["chat_background_set"] = ChatBackground.de_json(data.get("chat_background_set"), bot) data["paid_media"] = PaidMediaInfo.de_json(data.get("paid_media"), bot) + data["refunded_payment"] = RefundedPayment.de_json(data.get("refunded_payment"), bot) # Unfortunately, this needs to be here due to cyclic imports from telegram._giveaway import ( # pylint: disable=import-outside-toplevel diff --git a/telegram/_payment/refundedpayment.py b/telegram/_payment/refundedpayment.py new file mode 100644 index 00000000000..275da332de8 --- /dev/null +++ b/telegram/_payment/refundedpayment.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2024 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +"""This module contains an object that represents a Telegram RefundedPayment.""" + +from typing import Optional + +from telegram._telegramobject import TelegramObject +from telegram._utils.types import JSONDict + + +class RefundedPayment(TelegramObject): + """This object contains basic information about a refunded payment. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`telegram_payment_charge_id` is equal. + + Args: + currency (:obj:`str`): Three-letter ISO 4217 `currency + `_ code, or ``XTR`` for + payments in |tg_stars|. Currently, always ``XTR``. + total_amount (:obj:`int`): Total refunded price in the smallest units of the currency + (integer, not float/double). For example, for a price of ``US$ 1.45``, ``total_amount + = 145``. See the ``exp`` parameter in + `currencies.json `_, + it shows the number of digits past the decimal point for each currency + (2 for the majority of currencies). + invoice_payload (:obj:`str`): Bot-specified invoice payload. + telegram_payment_charge_id (:obj:`str`): Telegram payment identifier. + provider_payment_charge_id (:obj:`str`, optional): Provider payment identifier. + + Attributes: + currency (:obj:`str`): Three-letter ISO 4217 `currency + `_ code, or ``XTR`` for + payments in |tg_stars|. Currently, always ``XTR``. + total_amount (:obj:`int`): Total refunded price in the smallest units of the currency + (integer, not float/double). For example, for a price of ``US$ 1.45``, ``total_amount + = 145``. See the ``exp`` parameter in + `currencies.json `_, + it shows the number of digits past the decimal point for each currency + (2 for the majority of currencies). + invoice_payload (:obj:`str`): Bot-specified invoice payload. + telegram_payment_charge_id (:obj:`str`): Telegram payment identifier. + provider_payment_charge_id (:obj:`str`): Optional. Provider payment identifier. + + """ + + __slots__ = ( + "currency", + "invoice_payload", + "provider_payment_charge_id", + "telegram_payment_charge_id", + "total_amount", + ) + + def __init__( + self, + currency: str, + total_amount: int, + invoice_payload: str, + telegram_payment_charge_id: str, + provider_payment_charge_id: Optional[str] = None, + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.currency: str = currency + self.total_amount: int = total_amount + self.invoice_payload: str = invoice_payload + self.telegram_payment_charge_id: str = telegram_payment_charge_id + # Optional + self.provider_payment_charge_id: Optional[str] = provider_payment_charge_id + + self._id_attrs = (self.telegram_payment_charge_id,) + + self._freeze() diff --git a/telegram/constants.py b/telegram/constants.py index d9b26b417c6..9cd41b5f3a2 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -151,7 +151,7 @@ class _AccentColor(NamedTuple): #: :data:`telegram.__bot_api_version_info__`. #: #: .. versionadded:: 20.0 -BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=6) +BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=7) #: :obj:`str`: Telegram Bot API #: version supported by this version of `python-telegram-bot`. Also available as #: :data:`telegram.__bot_api_version__`. @@ -1920,6 +1920,11 @@ class MessageType(StringEnum): """:obj:`str`: Messages with :attr:`telegram.Message.poll`.""" PROXIMITY_ALERT_TRIGGERED = "proximity_alert_triggered" """:obj:`str`: Messages with :attr:`telegram.Message.proximity_alert_triggered`.""" + REFUNDED_PAYMENT = "refunded_payment" + """:obj:`str`: Messages with :attr:`telegram.Message.refunded_payment`. + + .. versionadded:: NEXT.VERSION + """ REPLY_TO_STORY = "reply_to_story" """:obj:`str`: Messages with :attr:`telegram.Message.reply_to_story`. diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index 5147574e07a..ec882163a35 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -1944,6 +1944,7 @@ def filter(self, update: Update) -> bool: or StatusUpdate.NEW_CHAT_TITLE.check_update(update) or StatusUpdate.PINNED_MESSAGE.check_update(update) or StatusUpdate.PROXIMITY_ALERT_TRIGGERED.check_update(update) + or StatusUpdate.REFUNDED_PAYMENT.check_update(update) or StatusUpdate.USERS_SHARED.check_update(update) or StatusUpdate.USER_SHARED.check_update(update) or StatusUpdate.VIDEO_CHAT_ENDED.check_update(update) @@ -2190,6 +2191,15 @@ def filter(self, message: Message) -> bool: ) """Messages that contain :attr:`telegram.Message.proximity_alert_triggered`.""" + class _RefundedPayment(MessageFilter): + __slots__ = () + + def filter(self, message: Message) -> bool: + return bool(message.refunded_payment) + + REFUNDED_PAYMENT = _RefundedPayment("filters.StatusUpdate.REFUNDED_PAYMENT") + """Messages that contain :attr:`telegram.Message.refunded_payment`.""" + class _UserShared(MessageFilter): __slots__ = () diff --git a/tests/_payment/test_refundedpayment.py b/tests/_payment/test_refundedpayment.py new file mode 100644 index 00000000000..7a6d5c5f271 --- /dev/null +++ b/tests/_payment/test_refundedpayment.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2024 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +import pytest + +from telegram import RefundedPayment +from tests.auxil.slots import mro_slots + + +@pytest.fixture(scope="module") +def refunded_payment(): + return RefundedPayment( + TestRefundedPayment.currency, + TestRefundedPayment.total_amount, + TestRefundedPayment.invoice_payload, + TestRefundedPayment.telegram_payment_charge_id, + TestRefundedPayment.provider_payment_charge_id, + ) + + +class TestRefundedPayment: + invoice_payload = "invoice_payload" + currency = "EUR" + total_amount = 100 + telegram_payment_charge_id = "telegram_payment_charge_id" + provider_payment_charge_id = "provider_payment_charge_id" + + +class TestRefundedPaymentWithoutRequest(TestRefundedPayment): + def test_slot_behaviour(self, refunded_payment): + inst = refunded_payment + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_de_json(self, bot): + json_dict = { + "invoice_payload": self.invoice_payload, + "currency": self.currency, + "total_amount": self.total_amount, + "telegram_payment_charge_id": self.telegram_payment_charge_id, + "provider_payment_charge_id": self.provider_payment_charge_id, + } + refunded_payment = RefundedPayment.de_json(json_dict, bot) + assert refunded_payment.api_kwargs == {} + + assert refunded_payment.invoice_payload == self.invoice_payload + assert refunded_payment.currency == self.currency + assert refunded_payment.total_amount == self.total_amount + assert refunded_payment.telegram_payment_charge_id == self.telegram_payment_charge_id + assert refunded_payment.provider_payment_charge_id == self.provider_payment_charge_id + + def test_to_dict(self, refunded_payment): + refunded_payment_dict = refunded_payment.to_dict() + + assert isinstance(refunded_payment_dict, dict) + assert refunded_payment_dict["invoice_payload"] == refunded_payment.invoice_payload + assert refunded_payment_dict["currency"] == refunded_payment.currency + assert refunded_payment_dict["total_amount"] == refunded_payment.total_amount + assert ( + refunded_payment_dict["telegram_payment_charge_id"] + == refunded_payment.telegram_payment_charge_id + ) + assert ( + refunded_payment_dict["provider_payment_charge_id"] + == refunded_payment.provider_payment_charge_id + ) + + def test_equality(self): + a = RefundedPayment( + self.currency, + self.total_amount, + self.invoice_payload, + self.telegram_payment_charge_id, + self.provider_payment_charge_id, + ) + b = RefundedPayment( + self.currency, + self.total_amount, + self.invoice_payload, + self.telegram_payment_charge_id, + self.provider_payment_charge_id, + ) + c = RefundedPayment("", 0, "", self.telegram_payment_charge_id) + d = RefundedPayment( + self.currency, + self.total_amount, + self.invoice_payload, + "", + ) + + assert a == b + assert hash(a) == hash(b) + assert a is not b + + assert a == c + assert hash(a) == hash(c) + + assert a != d + assert hash(a) != hash(d) diff --git a/tests/ext/test_filters.py b/tests/ext/test_filters.py index 97d17e2ebaf..0ea2b34b0d1 100644 --- a/tests/ext/test_filters.py +++ b/tests/ext/test_filters.py @@ -1095,6 +1095,11 @@ def test_filters_status_update(self, update): assert filters.StatusUpdate.CHAT_BACKGROUND_SET.check_update(update) update.message.chat_background_set = None + update.message.refunded_payment = "refunded_payment" + assert filters.StatusUpdate.ALL.check_update(update) + assert filters.StatusUpdate.REFUNDED_PAYMENT.check_update(update) + update.message.refunded_payment = None + def test_filters_forwarded(self, update, message_origin_user): assert filters.FORWARDED.check_update(update) update.message.forward_origin = MessageOriginHiddenUser(datetime.datetime.utcnow(), 1) diff --git a/tests/test_message.py b/tests/test_message.py index 5596710396d..6352845ffa2 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -53,6 +53,7 @@ Poll, PollOption, ProximityAlertTriggered, + RefundedPayment, ReplyParameters, SharedUser, Sticker, @@ -278,6 +279,7 @@ def message(bot): {"effect_id": "123456789"}, {"show_caption_above_media": True}, {"paid_media": PaidMediaInfo(5, [PaidMediaPreview(10, 10, 10)])}, + {"refunded_payment": RefundedPayment("EUR", 243, "payload", "charge_id", "provider_id")}, ], ids=[ "reply", @@ -350,6 +352,7 @@ def message(bot): "effect_id", "show_caption_above_media", "paid_media", + "refunded_payment", ], ) def message_params(bot, request): From a27755ab8e4b1960aa2a60445b238159096662d0 Mon Sep 17 00:00:00 2001 From: poolitzer Date: Fri, 5 Jul 2024 21:48:40 +0200 Subject: [PATCH 2/6] Fix: Align docs formatting to telegrams --- telegram/_payment/refundedpayment.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/telegram/_payment/refundedpayment.py b/telegram/_payment/refundedpayment.py index 275da332de8..19bdfe84649 100644 --- a/telegram/_payment/refundedpayment.py +++ b/telegram/_payment/refundedpayment.py @@ -34,9 +34,9 @@ class RefundedPayment(TelegramObject): currency (:obj:`str`): Three-letter ISO 4217 `currency `_ code, or ``XTR`` for payments in |tg_stars|. Currently, always ``XTR``. - total_amount (:obj:`int`): Total refunded price in the smallest units of the currency - (integer, not float/double). For example, for a price of ``US$ 1.45``, ``total_amount - = 145``. See the ``exp`` parameter in + total_amount (:obj:`int`): Total refunded price in the *smallest units* of the currency + (integer, **not** float/double). For example, for a price of ``US$ 1.45``, + ``total_amount = 145``. See the *exp* parameter in `currencies.json `_, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). @@ -48,9 +48,9 @@ class RefundedPayment(TelegramObject): currency (:obj:`str`): Three-letter ISO 4217 `currency `_ code, or ``XTR`` for payments in |tg_stars|. Currently, always ``XTR``. - total_amount (:obj:`int`): Total refunded price in the smallest units of the currency - (integer, not float/double). For example, for a price of ``US$ 1.45``, ``total_amount - = 145``. See the ``exp`` parameter in + total_amount (:obj:`int`): Total refunded price in the *smallest units* of the currency + (integer, **not** float/double). For example, for a price of ``US$ 1.45``, + ``total_amount = 145``. See the *exp* parameter in `currencies.json `_, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). From b2191bc2ab601dbd905a09aa63916a07a1c08ee8 Mon Sep 17 00:00:00 2001 From: Poolitzer Date: Sun, 7 Jul 2024 21:10:49 +0200 Subject: [PATCH 3/6] Fix: Properly name TestBase class Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com> --- tests/_payment/test_refundedpayment.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/_payment/test_refundedpayment.py b/tests/_payment/test_refundedpayment.py index 7a6d5c5f271..75e252660da 100644 --- a/tests/_payment/test_refundedpayment.py +++ b/tests/_payment/test_refundedpayment.py @@ -25,15 +25,15 @@ @pytest.fixture(scope="module") def refunded_payment(): return RefundedPayment( - TestRefundedPayment.currency, - TestRefundedPayment.total_amount, - TestRefundedPayment.invoice_payload, - TestRefundedPayment.telegram_payment_charge_id, - TestRefundedPayment.provider_payment_charge_id, + TestRefundedPaymentBase.currency, + TestRefundedPaymentBase.total_amount, + TestRefundedPaymentBase.invoice_payload, + TestRefundedPaymentBase.telegram_payment_charge_id, + TestRefundedPaymentBase.provider_payment_charge_id, ) -class TestRefundedPayment: +class TestRefundedPaymentBase: invoice_payload = "invoice_payload" currency = "EUR" total_amount = 100 @@ -41,7 +41,7 @@ class TestRefundedPayment: provider_payment_charge_id = "provider_payment_charge_id" -class TestRefundedPaymentWithoutRequest(TestRefundedPayment): +class TestRefundedPaymentWithoutRequest(TestRefundedPaymentBase): def test_slot_behaviour(self, refunded_payment): inst = refunded_payment for attr in inst.__slots__: From 003aa79a67ce59876e9a224ca3ebe958f416977a Mon Sep 17 00:00:00 2001 From: poolitzer Date: Sun, 7 Jul 2024 21:13:00 +0200 Subject: [PATCH 4/6] Fix: Forgot README.rst --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index e8aecc8df93..d58e814c391 100644 --- a/README.rst +++ b/README.rst @@ -11,7 +11,7 @@ :target: https://pypi.org/project/python-telegram-bot/ :alt: Supported Python versions -.. image:: https://img.shields.io/badge/Bot%20API-7.6-blue?logo=telegram +.. image:: https://img.shields.io/badge/Bot%20API-7.7-blue?logo=telegram :target: https://core.telegram.org/bots/api-changelog :alt: Supported Bot API version @@ -79,7 +79,7 @@ make the development of bots easy and straightforward. These classes are contain Telegram API support ==================== -All types and methods of the Telegram Bot API **7.6** are supported. +All types and methods of the Telegram Bot API **7.7** are supported. Installing ========== From d0b8c047ab0af170a5a575fdbc9055a673cef345 Mon Sep 17 00:00:00 2001 From: Poolitzer Date: Sun, 7 Jul 2024 21:40:11 +0200 Subject: [PATCH 5/6] Missed version added Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com> --- telegram/ext/filters.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index ec882163a35..eae0931d193 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -2198,7 +2198,10 @@ def filter(self, message: Message) -> bool: return bool(message.refunded_payment) REFUNDED_PAYMENT = _RefundedPayment("filters.StatusUpdate.REFUNDED_PAYMENT") - """Messages that contain :attr:`telegram.Message.refunded_payment`.""" + """Messages that contain :attr:`telegram.Message.refunded_payment`. + + .. versionadded:: NEXT.VERSION + """ class _UserShared(MessageFilter): __slots__ = () From e2d3b6938a75b5b97248be5dd95c7bfd2d68109c Mon Sep 17 00:00:00 2001 From: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com> Date: Mon, 8 Jul 2024 09:33:04 +0200 Subject: [PATCH 6/6] Update telegram/ext/filters.py --- telegram/ext/filters.py | 1 - 1 file changed, 1 deletion(-) diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index eae0931d193..fa260ad7476 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -2199,7 +2199,6 @@ def filter(self, message: Message) -> bool: REFUNDED_PAYMENT = _RefundedPayment("filters.StatusUpdate.REFUNDED_PAYMENT") """Messages that contain :attr:`telegram.Message.refunded_payment`. - .. versionadded:: NEXT.VERSION """