diff --git a/mercadopago/resources/__init__.py b/mercadopago/resources/__init__.py index f6b1cc1..ac555f8 100644 --- a/mercadopago/resources/__init__.py +++ b/mercadopago/resources/__init__.py @@ -6,23 +6,26 @@ from mercadopago.resources.advanced_payment import AdvancedPayment from mercadopago.resources.card import Card from mercadopago.resources.card_token import CardToken +from mercadopago.resources.chargeback import Chargeback from mercadopago.resources.customer import Customer from mercadopago.resources.disbursement_refund import DisbursementRefund from mercadopago.resources.identification_type import IdentificationType from mercadopago.resources.merchant_order import MerchantOrder from mercadopago.resources.payment import Payment from mercadopago.resources.payment_methods import PaymentMethods +from mercadopago.resources.plan import Plan from mercadopago.resources.preapproval import PreApproval from mercadopago.resources.preference import Preference from mercadopago.resources.refund import Refund +from mercadopago.resources.subscription import Subscription from mercadopago.resources.user import User -from mercadopago.resources.chargeback import Chargeback __all__ = ( 'AdvancedPayment', 'Card', 'CardToken', + 'Chargeback', 'Customer', 'DisbursementRefund', 'HttpClient', @@ -30,10 +33,11 @@ 'MerchantOrder', 'Payment', 'PaymentMethods', + 'Plan', 'PreApproval', 'Preference', 'Refund', 'RequestOptions', + 'Subscription', 'User', - 'Chargeback', ) diff --git a/mercadopago/resources/plan.py b/mercadopago/resources/plan.py new file mode 100644 index 0000000..abd77ee --- /dev/null +++ b/mercadopago/resources/plan.py @@ -0,0 +1,84 @@ +""" + Module: plan +""" +from mercadopago.core import MPBase + + +class Plan(MPBase): + """ + This class provides the methods to access the API that will allow you to create, search, get and update Plans to be + used as templates for Subscriptions. + + [Click here for more info](https://www.mercadopago.com.br/developers/en/docs/subscriptions/integration-configuration/subscriptions-associated-plan) # pylint: disable=line-too-long + """ + + def search(self, filters=None, request_options=None): + """[Click here for more info](https://www.mercadopago.com.co/developers/es/reference/subscriptions/_preapproval_plan_search/get) # pylint: disable=line-too-long + + Args: + request_options (mercadopago.config.request_options, optional): An instance of + RequestOptions can be pass changing or adding custom options to ur REST call. + Defaults to None. + + Returns: + dict: Plans found + """ + return self._get(uri="/preapproval_plan/search", filters=filters, + request_options=request_options) + + def get(self, plan_id, request_options=None): + """[Click here for more info](https://www.mercadopago.com.co/developers/es/reference/subscriptions/_preapproval_plan_id/get) # pylint: disable=line-too-long + + Args: + plan_id (str): The plan ID + request_options (mercadopago.config.request_options, optional): An instance of + RequestOptions can be pass changing or adding custom options to ur REST call. + Defaults to None. + + Returns: + dict: Plan found + """ + return self._get(uri="/preapproval_plan/" + str(plan_id), request_options=request_options) + + def create(self, plan_object, request_options=None): + """[Click here for more info](https://www.mercadopago.com.co/developers/es/reference/subscriptions/_preapproval_plan/post) # pylint: disable=line-too-long + + Args: + plan_object (dict): Plan to be created + request_options (mercadopago.config.request_options, optional): An instance of + RequestOptions can be pass changing or adding custom options to ur REST call. + Defaults to None. + + Raises: + ValueError: Param plan_object must be a Dictionary + + Returns: + dict: Plan creation response + """ + if not isinstance(plan_object, dict): + raise ValueError("Param plan_object must be a Dictionary") + + return self._post(uri="/preapproval_plan", data=plan_object, + request_options=request_options) + + def update(self, plan_id, plan_object, request_options=None): + """[Click here for more info](https://www.mercadopago.com.co/developers/es/reference/subscriptions/_preapproval_plan_id/put) # pylint: disable=line-too-long + + Args: + plan_id (str): The plan ID to be updated + plan_object (dict): Plan information to be updated + request_options (mercadopago.config.request_options, optional): An instance of + RequestOptions can be pass changing or adding custom options to ur REST call. + Defaults to None. + + Raises: + ValueError: Param plan_object must be a Dictionary + + Returns: + dict: Plan modification response + """ + if not isinstance(plan_object, dict): + raise ValueError("Param plan_object must be a Dictionary") + + return self._put(uri="/preapproval_plan/" + str(plan_id), + data=plan_object, request_options=request_options) diff --git a/mercadopago/resources/subscription.py b/mercadopago/resources/subscription.py new file mode 100644 index 0000000..955be0c --- /dev/null +++ b/mercadopago/resources/subscription.py @@ -0,0 +1,83 @@ +""" + Module: subscriptions +""" +from mercadopago.core import MPBase + + +class Subscription(MPBase): + """ + This class provides the methods to access the API that will allow you to create, search, get and update + Subscriptions. + + [Click here for more info](https://www.mercadopago.com.br/developers/en/docs/subscriptions/landing) # pylint: disable=line-too-long + """ + + def search(self, filters=None, request_options=None): + """[Click here for more info](https://www.mercadopago.com.co/developers/en/reference/subscriptions/_preapproval_search/get) # pylint: disable=line-too-long + + Args: + request_options (mercadopago.config.request_options, optional): An instance of + RequestOptions can be pass changing or adding custom options to ur REST call. + Defaults to None. + + Returns: + dict: Subscriptions found + """ + return self._get(uri="/preapproval/search", filters=filters, request_options=request_options) + + def get(self, subscription_id, request_options=None): + """[Click here for more info](https://www.mercadopago.com.co/developers/es/reference/subscriptions/_preapproval_id/get) # pylint: disable=line-too-long + + Args: + subscription_id (str): The subscription ID + request_options (mercadopago.config.request_options, optional): An instance of + RequestOptions can be pass changing or adding custom options to ur REST call. + Defaults to None. + + Returns: + dict: Subscription found + """ + return self._get(uri="/preapproval/" + str(subscription_id), request_options=request_options) + + def create(self, subscription_object, request_options=None): + """[Click here for more info](https://www.mercadopago.com/developers/en/reference/payments/_payments/post/) # pylint: disable=line-too-long + + Args: + subscription_object (dict): Subscription to be created + request_options (mercadopago.config.request_options, optional): An instance of + RequestOptions can be pass changing or adding custom options to ur REST call. + Defaults to None. + + Raises: + ValueError: Param payment_object must be a Dictionary + + Returns: + dict: Subscription creation response + """ + if not isinstance(subscription_object, dict): + raise ValueError("Param subscription_object must be a Dictionary") + + return self._post(uri="/preapproval", data=subscription_object, request_options=request_options) + + def update(self, subscription_id, subscription_object, request_options=None): + """[Click here for more info](https://www.mercadopago.com.co/developers/es/reference/subscriptions/_preapproval_id/put) # pylint: disable=line-too-long + + Args: + subscription_id (str): The subscription ID to be updated + subscription_object (dict): Subscription information to be updated + request_options (mercadopago.config.request_options, optional): An instance of + RequestOptions can be pass changing or adding custom options to ur REST call. + Defaults to None. + + Raises: + ValueError: Param subscription_object must be a Dictionary + + Returns: + dict: Subscription modification response + """ + if not isinstance(subscription_object, dict): + raise ValueError("Param subscription_object must be a Dictionary") + + return self._put( + uri="/preapproval_plan/" + str(subscription_id), data=subscription_object, request_options=request_options + ) diff --git a/mercadopago/sdk.py b/mercadopago/sdk.py index 46df77a..0536b58 100644 --- a/mercadopago/sdk.py +++ b/mercadopago/sdk.py @@ -7,17 +7,19 @@ AdvancedPayment, Card, CardToken, + Chargeback, Customer, DisbursementRefund, IdentificationType, MerchantOrder, Payment, PaymentMethods, + Plan, PreApproval, Preference, Refund, + Subscription, User, - Chargeback, ) @@ -38,6 +40,8 @@ class SDK: 12. Refund 13. User 14. Chargeback + 15. Subscription + 16. Plan """ def __init__( @@ -166,6 +170,20 @@ def chargeback(self, request_options=None): return Chargeback(request_options is not None and request_options or self.request_options, self.http_client) + def subscription(self, request_options=None): + """ + Returns the attribute value of the function + """ + return Subscription(request_options is not None and request_options + or self.request_options, self.http_client) + + def plan(self, request_options=None): + """ + Returns the attribute value of the function + """ + return Plan(request_options is not None and request_options + or self.request_options, self.http_client) + @property def request_options(self): """ diff --git a/tests/test_plan.py b/tests/test_plan.py new file mode 100644 index 0000000..e71f74c --- /dev/null +++ b/tests/test_plan.py @@ -0,0 +1,84 @@ +""" + Module: test_plan +""" +import unittest + +import mercadopago +import random + + +class TestPlan(unittest.TestCase): + """ + Test Module: Preference + """ + + sdk = mercadopago.SDK( + "APP_USR-558881221729581-091712-44fdc612e60e3e638775d8b4003edd51-471763966") + + def test_all(self): + """ + Test Module: Plan + """ + random_reason_number = random.randint(100000, 999999) + plan_object_all_options_payload = { + "auto_recurring": { + "frequency": 1, + "frequency_type": "months", + "repetitions": 12, + "billing_day": 5, + "free_trial": { + "frequency": 2, + "frequency_type": "days" + }, + "transaction_amount": 60, + "currency_id": "BRL", + }, + "back_url": "https://www.mercadopago.com.co/subscriptions", + "reason": f"Test Plan #{random_reason_number}", + } + plan_object_mandatory_options_payload = { + "auto_recurring": { + "frequency": 1, + "frequency_type": "months", + "transaction_amount": 60, + "currency_id": "BRL", + }, + "back_url": "https://www.mercadopago.com.co/subscriptions", + "reason": f"Test Plan (mandatory) #{random_reason_number}", + } + + plan_response = self.sdk.plan().create(plan_object_all_options_payload) + self.assertEqual(plan_response["status"], 201) + + plan_object = plan_response["response"] + self.assertEqual(plan_object["status"], "active") + + # Validate it works with minimal required options + plan_mandatory_options = self.sdk.plan().create( + plan_object_mandatory_options_payload) + self.assertEqual(plan_mandatory_options["status"], 201) + self.assertEqual( + plan_mandatory_options["response"]["status"], "active") + + plan_object["reason"] = "MercadoPago API Test" + update_response = self.sdk.plan().update( + plan_object["id"], plan_object) + self.assertEqual(update_response["status"], 200) + update_object = update_response["response"] + self.assertEqual(update_object["reason"], plan_object["reason"]) + self.assertEqual(update_object["status"], "active") + + get_response = self.sdk.plan().get(plan_object["id"]) + self.assertEqual(get_response["status"], 200) + get_object = get_response["response"] + self.assertEqual(get_object["id"], plan_object["id"]) + + search_response = self.sdk.plan().search() + self.assertEqual(search_response["status"], 200) + search_object = search_response["response"] + self.assertTrue("results" in search_object) + self.assertTrue(isinstance(search_object["results"], list)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_subscription.py b/tests/test_subscription.py new file mode 100644 index 0000000..2db4c29 --- /dev/null +++ b/tests/test_subscription.py @@ -0,0 +1,173 @@ +""" + Module: test_plan +""" +from datetime import datetime +import unittest + +import mercadopago +import random + + +class TestSubscription(unittest.TestCase): + """ + Test Module: Preference + """ + _customer_id = None + _customer_email = None + _plan_id = None + sdk = mercadopago.SDK( + "APP_USR-558881221729581-091712-44fdc612e60e3e638775d8b4003edd51-471763966") + + @classmethod + def setUpClass(cls): + customer_data = cls.create_customer() + cls._customer_id = customer_data["response"]["id"] + cls._customer_email = customer_data["response"]["email"] + plan_data = cls.create_plan() + cls._plan_id = plan_data["response"]["id"] + + @classmethod + def tearDownClass(cls): + cls.delete_customer() + # TODO: Can we delete the created plans and card tokens. The API doens't show any documentation about it. + + def test_all(self): + """ + Test Module: Subscription + """ + card_token = self.create_card_token() + card_token_id = card_token['response']['id'] + + random_reason_number = random.randint(100000, 999999) + subscription_payload = { + "back_url": "https://www.mercadopago.com.co/subscriptions", + "reason": f"MercadoPago API Subscription #{random_reason_number}", + "external_reference": "CustomIdentifier", + "payer_email": self._customer_email, + "preapproval_plan_id": self._plan_id, + "card_token_id": card_token_id, + "status": "authorized" + } + + subscription_response = self.sdk.subscription().create(subscription_payload) + self.assertEqual(subscription_response["status"], 201) + + subscription_object = subscription_response['response'] + self.assertIn('init_point', subscription_object) + self.assertEqual( + subscription_object["external_reference"], subscription_payload["external_reference"]) + self.assertEqual(subscription_object["status"], "authorized") + + # TODO: WHENEVER I TRY TO UPDATE A SUBSCRIPTION THE API TRHOWSa 404 WITH THE FOLLOWING MESSAGE + # ! "The template with id {plan_id} does not exist" + # update_payload = { + # "reason": f"MercadoPago API Subscription A #{random_reason_number}", + # } + # update_response = self.sdk.subscription().update( + # subscription_object["id"], update_payload) + # self.assertEqual(update_response["status"], 200) + # update_object = update_response["response"] + # self.assertEqual(update_object["reason"], update_payload["reason"]) + + get_response = self.sdk.subscription().get(subscription_object["id"]) + self.assertEqual(get_response["status"], 200) + get_object = get_response["response"] + self.assertEqual(get_object["id"], subscription_object["id"]) + + search_response = self.sdk.subscription().search() + self.assertEqual(search_response["status"], 200) + search_object = search_response["response"] + self.assertTrue("results" in search_object) + self.assertTrue(isinstance(search_object["results"], list)) + + def test_create_subscriptions_without_a_plan(self): + """ + Test Module: Subscription + + Test subscription creation without a plan + """ + card_token = self.create_card_token() + card_token_id = card_token['response']['id'] + + random_reason_number = random.randint(100000, 999999) + subscription_payload = { + "back_url": "https://www.mercadopago.com.co/subscriptions", + "reason": f"MercadoPago API Subscription B #{random_reason_number}", + "external_reference": "CustomIdentifier", + "payer_email": self._customer_email, + "card_token_id": card_token_id, + "auto_recurring": { + "frequency": 1, + "frequency_type": "months", + "transaction_amount": 60, + "currency_id": "BRL", + }, + "status": "authorized" + } + + subscription_response = self.sdk.subscription().create(subscription_payload) + self.assertEqual(subscription_response["status"], 201) + + subscription_object = subscription_response['response'] + self.assertIn('init_point', subscription_object) + self.assertEqual( + subscription_object["external_reference"], subscription_payload["external_reference"]) + self.assertEqual(subscription_object["status"], "authorized") + + @classmethod + def create_card_token(cls): + card_token_object = { + "card_number": "4074090000000004", + "security_code": "123", + "expiration_year": datetime.now().strftime("%Y"), + "expiration_month": "12", + "cardholder": { + "name": "APRO", + "identification": { + "CPF": "19119119100" + } + } + } + return cls.sdk.card_token().create(card_token_object) + + @classmethod + def create_customer(cls): + random_email_id = random.randint(100000, 999999) + customer_object = { + "email": f"test_payer_{random_email_id}@testuser.com", + "first_name": "Python", + "last_name": "Mercado", + "phone": { + "area_code": "03492", + "number": "432334" + }, + "identification": { + "type": "DNI", + "number": "29804555" + }, + "description": "customer description" + } + + return cls.sdk.customer().create(customer_object) + + @classmethod + def delete_customer(cls): + cls.sdk.customer().delete(cls._customer_id) + + @classmethod + def create_plan(cls): + plan_object = { + "auto_recurring": { + "frequency": 1, + "frequency_type": "months", + "transaction_amount": 60, + "currency_id": "BRL", + }, + "back_url": "https://www.mercadopago.com.co/subscriptions", + "reason": f"Test Plan #{random.randint(100000, 999999)}", + } + return cls.sdk.plan().create(plan_object) + + +if __name__ == "__main__": + unittest.main()