Merge pull request #631 from jeffffc/paymenthandlers

Add both handlers for queries from new Payment API
This commit is contained in:
Noam Meltzer 2017-06-09 18:23:29 +03:00 committed by GitHub
commit da8a3cee44
10 changed files with 279 additions and 11 deletions

View file

@ -30,6 +30,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `Hugo Damer <https://github.com/HakimusGIT>`_
- `Jacob Bom <https://github.com/bomjacob>`_
- `JASON0916 <https://github.com/JASON0916>`_
- `jeffffc <https://github.com/jeffffc>`_
- `jh0ker <https://github.com/jh0ker>`_
- `John Yong <https://github.com/whipermr5>`_
- `jossalgon <https://github.com/jossalgon>`_

View file

@ -1835,6 +1835,7 @@ class Bot(TelegramObject):
photo_height=None,
need_name=None,
need_phone_number=None,
need_email=None,
need_shipping_address=None,
is_flexible=None,
disable_notification=False,
@ -1867,6 +1868,8 @@ class Bot(TelegramObject):
the order
need_phone_number (Optional[bool]): Pass True, if you require the user's phone number
to complete the order
need_email (Optional[bool]): Pass True, if you require the user's email to
complete the order
need_shipping_address (Optional[bool]): Pass True, if you require the user's shipping
address to complete the order
is_flexible (Optional[bool]): Pass True, if the final price depends on the shipping
@ -1915,6 +1918,8 @@ class Bot(TelegramObject):
data['need_name'] = need_name
if need_phone_number is not None:
data['need_phone_number'] = need_phone_number
if need_email is not None:
data['need_email'] = need_email
if need_shipping_address is not None:
data['need_shipping_address'] = need_shipping_address
if is_flexible is not None:
@ -1926,7 +1931,9 @@ class Bot(TelegramObject):
shipping_query_id,
ok,
shipping_options=None,
error_message=None):
error_message=None,
timeout=None,
**kwargs):
"""
If you sent an invoice requesting a shipping address and the parameter is_flexible was
specified, the Bot API will send an Update with a shipping_query field to the bot. Use
@ -1943,6 +1950,7 @@ class Bot(TelegramObject):
form that explains why it is impossible to complete the order (e.g. "Sorry,
delivery to your desired address is unavailable'). Telegram will display this
message to the user.
**kwargs (dict): Arbitrary keyword arguments.
Returns:
bool: On success, `True` is returned.
@ -1951,18 +1959,32 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0]/answerShippingQuery'.format(self.base_url)
if ok is True and (shipping_options is None or error_message is not None):
raise TelegramError(
'answerShippingQuery: If ok is True, shipping_options '
'should not be empty and there should not be error_message')
if ok is False and (shipping_options is not None or error_message is None):
raise TelegramError(
'answerShippingQuery: If ok is False, error_message '
'should not be empty and there should not be shipping_options')
url_ = '{0}/answerShippingQuery'.format(self.base_url)
data = {'shipping_query_id': shipping_query_id, 'ok': ok}
if shipping_options is not None:
if ok is True:
data['shipping_options'] = shipping_options
if error_message is not None:
data['error_message'] = error_message
return url, data
result = self._request.post(url_, data, timeout=timeout)
def answer_pre_checkout_query(self, pre_checkout_query_id, ok, error_message=None):
return result
def answer_pre_checkout_query(self, pre_checkout_query_id, ok,
error_message=None, timeout=None, **kwargs):
"""
If you sent an invoice requesting a shipping address and the parameter is_flexible was
specified, the Bot API will send an Update with a shipping_query field to the bot.
@ -1977,6 +1999,7 @@ class Bot(TelegramObject):
"Sorry, somebody just bought the last of our amazing black T-shirts while you were
busy filling out your payment details. Please choose a different color or
garment!"). Telegram will display this message to the user.
**kwargs (dict): Arbitrary keyword arguments.
Returns:
bool: On success, `True` is returned.
@ -1985,14 +2008,23 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0]/answerPreCheckoutQuery'.format(self.base_url)
if not (ok ^ (error_message is None)):
raise TelegramError(
'answerPreCheckoutQuery: If ok is True, there should '
'not be error_message; if ok is False, error_message '
'should not be empty')
url_ = '{0}/answerPreCheckoutQuery'.format(self.base_url)
data = {'pre_checkout_query_id': pre_checkout_query_id, 'ok': ok}
if error_message is not None:
data['error_message'] = error_message
return url, data
result = self._request.post(url_, data, timeout=timeout)
return result
@staticmethod
def de_json(data, bot):

View file

@ -33,8 +33,11 @@ from .stringcommandhandler import StringCommandHandler
from .stringregexhandler import StringRegexHandler
from .typehandler import TypeHandler
from .conversationhandler import ConversationHandler
from .precheckoutqueryhandler import PreCheckoutQueryHandler
from .shippingqueryhandler import ShippingQueryHandler
__all__ = ('Dispatcher', 'JobQueue', 'Job', 'Updater', 'CallbackQueryHandler',
'ChosenInlineResultHandler', 'CommandHandler', 'Handler', 'InlineQueryHandler',
'MessageHandler', 'BaseFilter', 'Filters', 'RegexHandler', 'StringCommandHandler',
'StringRegexHandler', 'TypeHandler', 'ConversationHandler')
'StringRegexHandler', 'TypeHandler', 'ConversationHandler',
'PreCheckoutQueryHandler', 'ShippingQueryHandler')

View file

@ -0,0 +1,69 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# 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 the PreCheckoutQueryHandler class """
from telegram import Update
from .handler import Handler
class PreCheckoutQueryHandler(Handler):
"""
Handler class to handle Telegram PreCheckout callback queries.
Args:
callback (function): A function that takes ``bot, update`` as
positional arguments. It will be called when the ``check_update``
has determined that an update should be processed by this handler.
pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can
be used to insert updates. Default is ``False``.
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a ``JobQueue``
instance created by the ``Updater`` which can be used to schedule new jobs.
Default is ``False``.
pass_user_data (optional[bool]): If set to ``True``, a keyword argument called
``user_data`` will be passed to the callback function. It will be a ``dict`` you
can use to keep any data related to the user that sent the update. For each update of
the same user, it will be the same ``dict``. Default is ``False``.
pass_chat_data (optional[bool]): If set to ``True``, a keyword argument called
``chat_data`` will be passed to the callback function. It will be a ``dict`` you
can use to keep any data related to the chat that the update was sent in.
For each update in the same chat, it will be the same ``dict``. Default is ``False``.
"""
def __init__(self,
callback,
pass_update_queue=False,
pass_job_queue=False,
pass_user_data=False,
pass_chat_data=False):
super(PreCheckoutQueryHandler, self).__init__(
callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue,
pass_user_data=pass_user_data,
pass_chat_data=pass_chat_data)
def check_update(self, update):
return isinstance(update, Update) and update.pre_checkout_query
def handle_update(self, update, dispatcher):
optional_args = self.collect_optional_args(dispatcher, update)
return self.callback(dispatcher.bot, update, **optional_args)

View file

@ -0,0 +1,69 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# 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 the ShippingQueryHandler class """
from telegram import Update
from .handler import Handler
class ShippingQueryHandler(Handler):
"""
Handler class to handle Telegram shipping callback queries.
Args:
callback (function): A function that takes ``bot, update`` as
positional arguments. It will be called when the ``check_update``
has determined that an update should be processed by this handler.
pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can
be used to insert updates. Default is ``False``.
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a ``JobQueue``
instance created by the ``Updater`` which can be used to schedule new jobs.
Default is ``False``.
pass_user_data (optional[bool]): If set to ``True``, a keyword argument called
``user_data`` will be passed to the callback function. It will be a ``dict`` you
can use to keep any data related to the user that sent the update. For each update of
the same user, it will be the same ``dict``. Default is ``False``.
pass_chat_data (optional[bool]): If set to ``True``, a keyword argument called
``chat_data`` will be passed to the callback function. It will be a ``dict`` you
can use to keep any data related to the chat that the update was sent in.
For each update in the same chat, it will be the same ``dict``. Default is ``False``.
"""
def __init__(self,
callback,
pass_update_queue=False,
pass_job_queue=False,
pass_user_data=False,
pass_chat_data=False):
super(ShippingQueryHandler, self).__init__(
callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue,
pass_user_data=pass_user_data,
pass_chat_data=pass_chat_data)
def check_update(self, update):
return isinstance(update, Update) and update.shipping_query
def handle_update(self, update, dispatcher):
optional_args = self.collect_optional_args(dispatcher, update)
return self.callback(dispatcher.bot, update, **optional_args)

View file

@ -35,6 +35,7 @@ class PreCheckoutQuery(TelegramObject):
invoice_payload (str): Bot specified invoice payload
shipping_option_id (Optional[str]): Identifier of the shipping option chosen by the user
order_info (Optional[:class:`telegram.OrderInfo`]): Order info provided by the user
bot (Optional[Bot]): The Bot to use for instance methods
**kwargs (dict): Arbitrary keyword arguments.
"""
@ -47,6 +48,7 @@ class PreCheckoutQuery(TelegramObject):
invoice_payload,
shipping_option_id=None,
order_info=None,
bot=None,
**kwargs):
self.id = id
self.from_user = from_user
@ -56,6 +58,8 @@ class PreCheckoutQuery(TelegramObject):
self.shipping_option_id = shipping_option_id
self.order_info = order_info
self.bot = bot
self._id_attrs = (self.id,)
@staticmethod
@ -88,3 +92,9 @@ class PreCheckoutQuery(TelegramObject):
data['from'] = data.pop('from_user', None)
return data
def answer(self, *args, **kwargs):
"""
Shortcut for ``bot.answerPreCheckoutQuery(update.pre_checkout_query.id, *args, **kwargs)``
"""
return self.bot.answerPreCheckoutQuery(self.id, *args, **kwargs)

View file

@ -32,16 +32,19 @@ class ShippingQuery(TelegramObject):
from_user (:class:`telegram.User`): User who sent the query
invoice_payload (str): Bot specified invoice payload
shipping_address (:class:`telegram.ShippingQuery`): User specified shipping address
bot (Optional[Bot]): The Bot to use for instance methods
**kwargs (dict): Arbitrary keyword arguments.
"""
def __init__(self, id, from_user, invoice_payload, shipping_address, **kwargs):
def __init__(self, id, from_user, invoice_payload, shipping_address, bot=None, **kwargs):
self.id = id
self.from_user = from_user
self.invoice_payload = invoice_payload
self.shipping_address = shipping_address
self.bot = bot
self._id_attrs = (self.id,)
@staticmethod
@ -74,3 +77,7 @@ class ShippingQuery(TelegramObject):
data['from'] = data.pop('from_user', None)
return data
def answer(self, *args, **kwargs):
"""Shortcut for ``bot.answerShippingQuery(update.shipping_query.id, *args, **kwargs)``"""
return self.bot.answerShippingQuery(self.id, *args, **kwargs)

View file

@ -18,7 +18,8 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Update."""
from telegram import (Message, TelegramObject, InlineQuery, ChosenInlineResult, CallbackQuery)
from telegram import (Message, TelegramObject, InlineQuery, ChosenInlineResult,
CallbackQuery, ShippingQuery, PreCheckoutQuery)
class Update(TelegramObject):
@ -38,6 +39,9 @@ class Update(TelegramObject):
text, photo, sticker, etc.
edited_channel_post (Optional[:class:`telegram.Message`]): New version of a channel post
that is known to the bot and was edited.
shipping_query (:class:`telegram.ShippingQuery`): New incoming shipping query.
pre_checkout_query (:class:`telegram.PreCheckoutQuery`): New incoming pre-checkout query.
Args:
update_id (int):
@ -48,6 +52,8 @@ class Update(TelegramObject):
callback_query (Optional[:class:`telegram.CallbackQuery`]):
channel_post (Optional[:class:`telegram.Message`]):
edited_channel_post (Optional[:class:`telegram.Message`]):
shipping_query (Optional[:class:`telegram.ShippingQuery`]):
pre_checkout_query (Optional[:class:`telegram.PreCheckoutQuery`]):
**kwargs: Arbitrary keyword arguments.
"""
@ -61,6 +67,8 @@ class Update(TelegramObject):
callback_query=None,
channel_post=None,
edited_channel_post=None,
shipping_query=None,
pre_checkout_query=None,
**kwargs):
# Required
self.update_id = int(update_id)
@ -70,6 +78,8 @@ class Update(TelegramObject):
self.inline_query = inline_query
self.chosen_inline_result = chosen_inline_result
self.callback_query = callback_query
self.shipping_query = shipping_query
self.pre_checkout_query = pre_checkout_query
self.channel_post = channel_post
self.edited_channel_post = edited_channel_post
@ -100,6 +110,8 @@ class Update(TelegramObject):
data['chosen_inline_result'] = ChosenInlineResult.de_json(
data.get('chosen_inline_result'), bot)
data['callback_query'] = CallbackQuery.de_json(data.get('callback_query'), bot)
data['shipping_query'] = ShippingQuery.de_json(data.get('shipping_query'), bot)
data['pre_checkout_query'] = PreCheckoutQuery.de_json(data.get('pre_checkout_query'), bot)
data['channel_post'] = Message.de_json(data.get('channel_post'), bot)
data['edited_channel_post'] = Message.de_json(data.get('edited_channel_post'), bot)
@ -132,6 +144,12 @@ class Update(TelegramObject):
elif self.callback_query:
user = self.callback_query.from_user
elif self.shipping_query:
user = self.shipping_query.from_user
elif self.pre_checkout_query:
user = self.pre_checkout_query.from_user
self._effective_user = user
return user

View file

@ -118,6 +118,18 @@ class FiltersTest(BaseTest, unittest.TestCase):
self.message.game = None
self.assertFalse(Filters.game(self.message))
def test_filters_successful_payment(self):
self.message.successful_payment = 'test'
self.assertTrue(Filters.successful_payment(self.message))
self.message.successful_payment = None
self.assertFalse(Filters.successful_payment(self.message))
def test_filters_invoice(self):
self.message.invoice = 'test'
self.assertTrue(Filters.invoice(self.message))
self.message.invoice = None
self.assertFalse(Filters.invoice(self.message))
def test_filters_status_update(self):
self.assertFalse(Filters.status_update(self.message))

View file

@ -46,7 +46,8 @@ except ImportError:
sys.path.append('.')
from telegram import Update, Message, TelegramError, User, Chat, Bot, InlineQuery, CallbackQuery
from telegram import (Update, Message, TelegramError, User, Chat, Bot,
InlineQuery, CallbackQuery, ShippingQuery, PreCheckoutQuery)
from telegram.ext import *
from telegram.ext.dispatcher import run_async
from telegram.error import Unauthorized, InvalidToken
@ -119,6 +120,14 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self.received_message = update.callback_query
self.message_count += 1
def telegramShippingHandlerTest(self, bot, update):
self.received_message = update.shipping_query
self.message_count += 1
def telegramPreCheckoutHandlerTest(self, bot, update):
self.received_message = update.pre_checkout_query
self.message_count += 1
@run_async
def asyncHandlerTest(self, bot, update):
sleep(1)
@ -501,6 +510,44 @@ class UpdaterTest(BaseTest, unittest.TestCase):
sleep(.1)
self.assertTrue(None is self.received_message)
def test_addRemoveShippingQueryHandler(self):
self._setup_updater('', messages=0)
d = self.updater.dispatcher
handler = ShippingQueryHandler(self.telegramShippingHandlerTest)
d.add_handler(handler)
queue = self.updater.start_polling(0.01)
update = Update(update_id=0, shipping_query="testshipping")
queue.put(update)
sleep(.1)
self.assertEqual(self.received_message, "testshipping")
# Remove handler
d.remove_handler(handler)
self.reset()
queue.put(update)
sleep(.1)
self.assertTrue(None is self.received_message)
def test_addRemovePreCheckoutQueryHandler(self):
self._setup_updater('', messages=0)
d = self.updater.dispatcher
handler = PreCheckoutQueryHandler(self.telegramPreCheckoutHandlerTest)
d.add_handler(handler)
queue = self.updater.start_polling(0.01)
update = Update(update_id=0, pre_checkout_query="testprecheckout")
queue.put(update)
sleep(.1)
self.assertEqual(self.received_message, "testprecheckout")
# Remove handler
d.remove_handler(handler)
self.reset()
queue.put(update)
sleep(.1)
self.assertTrue(None is self.received_message)
def test_runAsync(self):
self._setup_updater('Test5', messages=2)
d = self.updater.dispatcher