diff --git a/docs/tutorial.rst b/docs/tutorial.rst index ff40039..c8cea24 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -249,6 +249,36 @@ Efetue o checkout transparent:: >>> data = api.checkout() +==================================== +Trabalhando com a API de Assinaturas +==================================== + +Criando um Plano :: + + >>> from pagseguro.api import PagSeguroApiPreApproval + >>> from django.utils import timezone + >>> + >>> pagseguro_api = PagSeguroApiPreApproval(reference='id-unico-de-referencia-do-seu-sistema') + >>> final_date = timezone.now() + timezone.timedelta(days=180) + >>> pre_approval_plan_data = { + ... 'name': 'Seguro contra roubo de Notebook', + ... 'amount_per_payment': 100, + ... 'period': 'Monthly', + ... 'final_date': final_date, + ... 'max_total_amount': 300, + ... 'charge': 'auto', + ... 'details': 'Todo dia 28 será cobrado o valor de R100,00', + ... } + >>> pagseguro_api.create_plan(**pre_approval_plan_data) + +Cancelando uma assinatura via vendedor :: + + >>> from pagseguro.api import PagSeguroApiPreApproval + >>> + >>> pagseguro_api = PagSeguroApiPreApproval() + >>> data = pagseguro_api.pre_approval_cancel('codigo') + + =================================== Trabalhando com Signals de checkout =================================== diff --git a/pagseguro/admin.py b/pagseguro/admin.py index 5f32d18..8693fc4 100644 --- a/pagseguro/admin.py +++ b/pagseguro/admin.py @@ -2,7 +2,10 @@ from __future__ import unicode_literals from django.contrib import admin -from pagseguro.models import Checkout, Transaction, TransactionHistory +from pagseguro.models import ( + Checkout, Transaction, TransactionHistory, PreApprovalPlan, + PreApproval, PreApprovalHistory +) class CheckoutAdmin(admin.ModelAdmin): @@ -25,14 +28,50 @@ class TransactionHistoryInline(admin.TabularInline): class TransactionAdmin(admin.ModelAdmin): - list_display = ('code', 'reference', 'status', 'date', 'last_event_date') + list_display = ( + 'code', 'reference', 'status', 'date', 'last_event_date', + 'transaction_type', + ) list_display_links = ('code', ) search_fields = ['code', 'reference'] - list_filter = ('status', 'date', 'last_event_date') + list_filter = ('status', 'date', 'last_event_date', 'transaction_type') inlines = [ TransactionHistoryInline, ] +class PreApprovalPlanAdmin(admin.ModelAdmin): + + list_display = ( + 'id', 'name', 'amount_per_payment', 'reference', 'period', + 'redirect_code' + ) + list_display_links = ('id', 'name') + search_fields = ['redirect_code', 'name', 'reference'] + list_filter = ('charge', 'period') + +class PreApprovalHistoryInline(admin.TabularInline): + + list_display = ('id', 'pre_approval', 'status', 'date') + list_display_links = ('id', ) + search_fields = ['pre_approval__code', ] + list_filter = ('status', 'date') + model = PreApprovalHistory + extra = 0 + +class PreApprovalAdmin(admin.ModelAdmin): + + list_display = ( + 'code', 'tracker', 'reference', 'status', 'date', 'last_event_date' + ) + list_display_links = ('code', ) + search_fields = ['code', 'reference'] + list_filter = ('status', 'date', 'last_event_date') + inlines = [ + PreApprovalHistoryInline, + ] + admin.site.register(Checkout, CheckoutAdmin) admin.site.register(Transaction, TransactionAdmin) +admin.site.register(PreApprovalPlan, PreApprovalPlanAdmin) +admin.site.register(PreApproval, PreApprovalAdmin) diff --git a/pagseguro/api.py b/pagseguro/api.py index 7ca7154..66c603a 100644 --- a/pagseguro/api.py +++ b/pagseguro/api.py @@ -7,11 +7,16 @@ from pagseguro.settings import ( PAGSEGURO_EMAIL, PAGSEGURO_TOKEN, CHECKOUT_URL, PAYMENT_URL, - NOTIFICATION_URL, TRANSACTION_URL, SESSION_URL + NOTIFICATION_URL, TRANSACTION_URL, SESSION_URL, PRE_APPROVAL_URL, + PRE_APPROVAL_REQUEST_URL, PRE_APPROVAL_REDIRECT_URL, + PRE_APPROVAL_NOTIFICATION_URL, PRE_APPROVAL_CANCEL_URL ) from pagseguro.signals import ( notificacao_recebida, NOTIFICATION_STATUS, checkout_realizado, - checkout_realizado_com_sucesso, checkout_realizado_com_erro + checkout_realizado_com_sucesso, checkout_realizado_com_erro, + PRE_APPROVAL_NOTIFICATION_STATUS, pre_approval_notification, + pre_approval_status_cancelled, pre_approval_create_plan, + pre_approval_create_plan_error ) from pagseguro.forms import PagSeguroItemForm @@ -54,6 +59,11 @@ class PagSeguroApi(object): redirect_url = PAYMENT_URL notification_url = NOTIFICATION_URL transaction_url = TRANSACTION_URL + pre_approval_url = PRE_APPROVAL_URL + pre_approval_request_url = PRE_APPROVAL_REQUEST_URL + pre_approval_redirect_url = PRE_APPROVAL_REDIRECT_URL + pre_approval_notification_url = PRE_APPROVAL_NOTIFICATION_URL + pre_approval_cancel_url = PRE_APPROVAL_CANCEL_URL def __init__(self, **kwargs): self.base_params = { @@ -131,9 +141,11 @@ def checkout(self): return data - def get_notification(self, notification_id): + def _get_notification(self, notification_id, url, notification_type, + notification_signal, notification_status): + response = requests.get( - self.notification_url + '/{0}'.format(notification_id), + '{0}/{1}'.format(url, notification_id), params={ 'email': self.base_params['email'], 'token': self.base_params['token'] @@ -142,20 +154,37 @@ def get_notification(self, notification_id): if response.status_code == 200: root = xmltodict.parse(response.text) - transaction = root['transaction'] - notificacao_recebida.send( + transaction = root[notification_type] + notification_signal.send( sender=self, transaction=transaction ) status = transaction['status'] - if status in NOTIFICATION_STATUS: - signal = NOTIFICATION_STATUS[status] - signal.send( - sender=self, transaction=transaction - ) + if status in notification_status: + signal = notification_status[status] + signal.send(sender=self, transaction=transaction) return response + def get_notification(self, notification_id, notification_type='transaction'): + if notification_type == 'transaction': + response = self._get_notification( + notification_id, + self.notification_url, + 'transaction', + notificacao_recebida, + NOTIFICATION_STATUS, + ) + else: + response = self._get_notification( + notification_id, + self.pre_approval_notification_url, + 'preApproval', + pre_approval_notification, + PRE_APPROVAL_NOTIFICATION_STATUS, + ) + return response + def get_transaction(self, transaction_id): response = requests.get( self.transaction_url + '/{0}'.format(transaction_id), @@ -185,6 +214,35 @@ def get_transaction(self, transaction_id): return data + def get_pre_approval(self, pre_approval_id): + response = requests.get( + '{0}/{1}'.format(self.pre_approval_url, pre_approval_id), + params={ + 'email': self.base_params['email'], + 'token': self.base_params['token'] + } + ) + + if response.status_code == 200: + root = xmltodict.parse(response.text) + transaction = root['preApproval'] + + data = { + 'transaction': transaction, + 'status_code': response.status_code, + 'success': True, + 'date': timezone.now() + } + else: + data = { + 'status_code': response.status_code, + 'message': response.text, + 'success': False, + 'date': timezone.now() + } + + return data + class PagSeguroApiTransparent(PagSeguroApi): @@ -329,3 +387,113 @@ def get_session_id(self): } return data + + +class PagSeguroApiPreApproval(PagSeguroApi): + + def create(self, name, amount_per_payment, period, max_total_amount, + final_date='', max_amount_per_payment='', charge='auto', + details='', redirect_code=''): + + from pagseguro.models import PreApprovalPlan + + pre_approval = PreApprovalPlan( + name=name, + amount_per_payment=amount_per_payment, + period=period.upper(), + final_date=final_date, + max_total_amount=max_total_amount, + charge=charge, + details=details, + reference=self.base_params.get('reference', ''), + redirect_code=redirect_code, + ) + pre_approval.save() + + def set_pre_approval_data(self, name, amount_per_payment, period, + max_total_amount, final_date, charge='auto', + details=''): + + self.params['preApprovalName'] = name + self.params['preApprovalAmountPerPayment'] = amount_per_payment + self.params['preApprovalPeriod'] = period + self.params['preApprovalFinalDate'] = final_date.isoformat() + self.params['preApprovalMaxTotalAmount'] = max_total_amount + self.params['preApprovalCharge'] = charge + self.params['preApprovalDetails'] = details + + def create_plan(self, *args, **kwargs): + kwargs['max_total_amount'] = '{0:.2f}'.format( + kwargs['max_total_amount'] + ) + kwargs['amount_per_payment'] = '{0:.2f}'.format( + kwargs['amount_per_payment'] + ) + + self.set_pre_approval_data(**kwargs) + + self.build_params() + headers = { + 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' + } + response = requests.post( + self.pre_approval_request_url, self.params, headers=headers + ) + + if response.status_code == 200: + root = xmltodict.parse(response.text) + pre_approval = root['preApprovalRequest'] + + self.create(redirect_code=pre_approval['code'], **kwargs) + + # FIXME + data = { + 'pre_approval': pre_approval, + 'status_code': response.status_code, + 'success': True, + 'date': parse(pre_approval['date']), + 'code': pre_approval['code'], + 'redirect_url': '{0}?code={1}'.format( + self.pre_approval_redirect_url, pre_approval['code'] + ), + } + pre_approval_create_plan.send(sender=self, data=data) + else: + # FIXME + data = { + 'status_code': response.status_code, + 'message': response.text, + 'success': False, + 'date': timezone.now() + } + pre_approval_create_plan_error.send(sender=self, data=data) + + return data + + def pre_approval_cancel(self, pre_approval_code): + # FIXME + headers = { + 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' + } + + params = { + 'email': self.base_params['email'], + 'token': self.base_params['token'], + } + + url = '{0}/{1}'.format(self.pre_approval_cancel_url, pre_approval_code) + response = requests.get(url, params, headers=headers) + + if response.status_code == 200: + data = self.get_pre_approval(pre_approval_code).get('transaction') + pre_approval_notification.send(sender=self, transaction=data) + pre_approval_status_cancelled.send(sender=self, transaction=data) + else: + data = { + 'status_code': response.status_code, + 'message': response.text, + 'success': False, + 'date': timezone.now(), + } + + return data diff --git a/pagseguro/migrations/0003_auto_20171004_1528.py b/pagseguro/migrations/0003_auto_20171004_1528.py new file mode 100644 index 0000000..c2f122c --- /dev/null +++ b/pagseguro/migrations/0003_auto_20171004_1528.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2017-10-04 18:28 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('pagseguro', '0002_auto_20150506_0220'), + ] + + operations = [ + migrations.CreateModel( + name='PreApproval', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('code', models.CharField(db_index=True, help_text='O c\xf3digo da transa\xe7\xe3o.', max_length=100, unique=True, verbose_name='c\xf3digo')), + ('tracker', models.CharField(db_index=True, help_text='C\xf3digo identificador p\xfablico.', max_length=100, unique=True, verbose_name='identificador p\xfablico')), + ('reference', models.CharField(blank=True, db_index=True, help_text='A refer\xeancia passada na transa\xe7\xe3o.', max_length=200, verbose_name='refer\xeancia')), + ('status', models.CharField(choices=[('PENDING', 'Aguardando processamento do pagamento'), ('ACTIVE', 'Ativa'), ('CANCELLED', 'Cancelada'), ('CANCELLED_BY_RECEIVER', 'Cancelada pelo Vendedor'), ('CANCELLED_BY_SENDER', 'Cancelada pelo Comprador'), ('EXPIRED', 'Expirada')], db_index=True, help_text='Status atual da transa\xe7\xe3o.', max_length=20, verbose_name='Status')), + ('date', models.DateTimeField(help_text='Data em que a transa\xe7\xe3o foi criada.', verbose_name='Data')), + ('last_event_date', models.DateTimeField(help_text='Data da \xfaltima altera\xe7\xe3o na transa\xe7\xe3o.', verbose_name='\xdaltima altera\xe7\xe3o')), + ('content', models.TextField(help_text='Transa\xe7\xe3o no formato json.', verbose_name='Transa\xe7\xe3o')), + ], + options={ + 'ordering': ['-date'], + 'verbose_name': 'Assinatura: Transa\xe7\xe3o', + 'verbose_name_plural': 'Assinaturas: Transa\xe7\xf5es', + }, + ), + migrations.CreateModel( + name='PreApprovalHistory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('status', models.CharField(choices=[('PENDING', 'Aguardando processamento do pagamento'), ('ACTIVE', 'Ativa'), ('CANCELLED', 'Cancelada'), ('CANCELLED_BY_RECEIVER', 'Cancelada pelo Vendedor'), ('CANCELLED_BY_SENDER', 'Cancelada pelo Comprador'), ('EXPIRED', 'Expirada')], help_text='Status da transa\xe7\xe3o.', max_length=20, verbose_name='Status')), + ('date', models.DateTimeField(verbose_name='Data')), + ('pre_approval', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pagseguro.PreApproval', verbose_name='Transa\xe7\xe3o')), + ], + options={ + 'ordering': ['date'], + 'verbose_name': 'Assinatura: Hist\xf3rico da transa\xe7\xe3o', + 'verbose_name_plural': 'Assinaturas: Hist\xf3ricos de transa\xe7\xf5es', + }, + ), + migrations.CreateModel( + name='PreApprovalPlan', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('charge', models.CharField(choices=[('auto', 'Autom\xe1tica'), ('manual', 'Manual')], db_index=True, help_text='Indica se a assinatura ser\xe1 gerenciada pelo PagSeguro (autom\xe1tica) ou pelo Vendedor (manual)', max_length=20, verbose_name='Cobran\xe7a')), + ('name', models.CharField(db_index=True, help_text='Nome/Identificador da assinatura', max_length=100, unique=True, verbose_name='Nome')), + ('details', models.TextField(blank=True, help_text='Detalhes/Descri\xe7\xe3o da assinatura', max_length=255, verbose_name='Detalhes')), + ('amount_per_payment', models.DecimalField(blank=True, decimal_places=2, help_text='Valor exato de cada cobran\xe7a', max_digits=9, null=True, verbose_name='Valor da cobran\xe7a')), + ('max_amount_per_payment', models.DecimalField(blank=True, decimal_places=2, help_text='Valor m\xe1ximo de cada cobran\xe7a', max_digits=9, null=True, verbose_name='Valor m\xe1ximo de cada cobran\xe7a')), + ('period', models.CharField(choices=[('WEEKLY', 'Semanal'), ('MONTHLY', 'Mensal'), ('BIMONTHLY', '2 vezes ao m\xeas'), ('TRIMONTHLY', '3 vezes por m\xeas'), ('SEMIANNUALLY', 'A cada 6 meses'), ('YEARLY', 'Anualmente')], db_index=True, help_text='Periodicidade da cobran\xe7a', max_length=20, verbose_name='Periodicidade')), + ('final_date', models.DateTimeField(blank=True, db_index=True, help_text='Fim da vig\xeancia da assinatura', null=True, verbose_name='Data Final')), + ('max_total_amount', models.DecimalField(blank=True, decimal_places=2, help_text='Valor m\xe1ximo de cada cobran\xe7a', max_digits=9, null=True, verbose_name='Valor m\xe1ximo de cada cobran\xe7a')), + ('reference', models.CharField(blank=True, db_index=True, help_text='A refer\xeancia passada na transa\xe7\xe3o.', max_length=200, null=True, verbose_name='Refer\xeancia')), + ('redirect_code', models.CharField(blank=True, help_text='C\xf3digo gerado para redirecionamento.', max_length=100, null=True, verbose_name='c\xf3digo')), + ], + options={ + 'verbose_name': 'Assinatura: Plano', + 'verbose_name_plural': 'Assinaturas: Planos', + }, + ), + migrations.AddField( + model_name='transaction', + name='transaction_type', + field=models.CharField(choices=[('1', 'Pagamento'), ('11', 'Recorr\xeancia')], db_index=True, default='1', help_text='Representa o tipo da transa\xe7\xe3o recebida.', max_length=2, verbose_name='tipo'), + ), + ] diff --git a/pagseguro/models.py b/pagseguro/models.py index 407d0a8..758ab8a 100644 --- a/pagseguro/models.py +++ b/pagseguro/models.py @@ -6,10 +6,15 @@ from pagseguro.settings import PAGSEGURO_LOG_IN_MODEL from pagseguro.signals import ( checkout_realizado, notificacao_recebida, save_checkout, - update_transaction + update_transaction, pre_approval_notification, update_pre_approval, ) +TRANSACTION_TYPE = ( + ('1', 'Pagamento'), + ('11', 'Recorrência'), +) + TRANSACTION_STATUS_CHOICES = ( ('aguardando', 'Aguardando'), ('em_analise', 'Em análise'), @@ -20,6 +25,29 @@ ('cancelado', 'Cancelado') ) +PRE_APPROVAL_PERIOD_CHOICES = ( + ('WEEKLY', 'Semanal'), + ('MONTHLY', 'Mensal'), + ('BIMONTHLY', '2 vezes ao mês'), + ('TRIMONTHLY', '3 vezes por mês'), + ('SEMIANNUALLY', 'A cada 6 meses'), + ('YEARLY', 'Anualmente'), +) + +PRE_APPROVAL_CHARGE_CHOICES = ( + ('auto', 'Automática'), + ('manual', 'Manual'), +) + +PRE_APPROVAL_STATUS_CHOICES = ( + ('PENDING', 'Aguardando processamento do pagamento'), + ('ACTIVE', 'Ativa'), + ('CANCELLED', 'Cancelada'), + ('CANCELLED_BY_RECEIVER', 'Cancelada pelo Vendedor'), + ('CANCELLED_BY_SENDER', 'Cancelada pelo Comprador'), + ('EXPIRED', 'Expirada'), +) + @python_2_unicode_compatible class Checkout(models.Model): @@ -61,6 +89,15 @@ class Meta: @python_2_unicode_compatible class Transaction(models.Model): + transaction_type = models.CharField( + 'tipo', + default='1', + max_length=2, + db_index=True, + choices=TRANSACTION_TYPE, + help_text='Representa o tipo da transação recebida.' + ) + code = models.CharField( 'código', max_length=100, @@ -140,7 +177,190 @@ class Meta: verbose_name_plural = 'Históricos de transações' +@python_2_unicode_compatible +class PreApprovalPlan(models.Model): + + charge = models.CharField( + 'Cobrança', + max_length=20, + db_index=True, + choices=PRE_APPROVAL_CHARGE_CHOICES, + help_text='Indica se a assinatura será gerenciada pelo PagSeguro (automática) ou pelo Vendedor (manual)', + ) + + name = models.CharField( + 'Nome', + max_length=100, + unique=True, + db_index=True, + help_text='Nome/Identificador da assinatura', + ) + + details = models.TextField( + 'Detalhes', + max_length=255, + blank=True, + help_text='Detalhes/Descrição da assinatura', + ) + + amount_per_payment = models.DecimalField( + 'Valor da cobrança', + max_digits=9, + decimal_places=2, + blank=True, + null=True, + help_text='Valor exato de cada cobrança', + ) + + max_amount_per_payment = models.DecimalField( + 'Valor máximo de cada cobrança', + max_digits=9, + decimal_places=2, + blank=True, + null=True, + help_text='Valor máximo de cada cobrança', + ) + + period = models.CharField( + 'Periodicidade', + max_length=20, + db_index=True, + choices=PRE_APPROVAL_PERIOD_CHOICES, + help_text='Periodicidade da cobrança', + ) + + final_date = models.DateTimeField( + 'Data Final', + db_index=True, + blank=True, + null=True, + help_text='Fim da vigência da assinatura', + ) + + max_total_amount = models.DecimalField( + 'Valor máximo de cada cobrança', + max_digits=9, + decimal_places=2, + blank=True, + null=True, + help_text='Valor máximo de cada cobrança', + ) + + reference = models.CharField( + 'Referência', + max_length=200, + db_index=True, + blank=True, + null=True, + help_text='A referência passada na transação.' + ) + + redirect_code = models.CharField( + 'código', + max_length=100, + blank=True, + null=True, + help_text='Código gerado para redirecionamento.' + ) + + def __str__(self): + return '{0}'.format(self.name) + + class Meta: + verbose_name = 'Assinatura: Plano' + verbose_name_plural = 'Assinaturas: Planos' + +@python_2_unicode_compatible +class PreApproval(models.Model): + + code = models.CharField( + 'código', + max_length=100, + unique=True, + db_index=True, + help_text='O código da transação.' + ) + + tracker = models.CharField( + 'identificador público', + max_length=100, + unique=True, + db_index=True, + help_text='Código identificador público.' + ) + + reference = models.CharField( + 'referência', + max_length=200, + db_index=True, + blank=True, + help_text='A referência passada na transação.' + ) + + status = models.CharField( + 'Status', + max_length=20, + db_index=True, + choices=PRE_APPROVAL_STATUS_CHOICES, + help_text='Status atual da transação.' + ) + + date = models.DateTimeField( + 'Data', + help_text='Data em que a transação foi criada.' + ) + + last_event_date = models.DateTimeField( + 'Última alteração', + help_text='Data da última alteração na transação.' + ) + + content = models.TextField( + 'Transação', + help_text='Transação no formato json.' + ) + + def __str__(self): + return self.code + + class Meta: + ordering = ['-date'] + verbose_name = 'Assinatura: Transação' + verbose_name_plural = 'Assinaturas: Transações' + +@python_2_unicode_compatible +class PreApprovalHistory(models.Model): + + pre_approval = models.ForeignKey( + PreApproval, + on_delete=models.CASCADE, + verbose_name='Transação' + ) + + status = models.CharField( + 'Status', + max_length=20, + choices=PRE_APPROVAL_STATUS_CHOICES, + help_text='Status da transação.' + ) + + date = models.DateTimeField( + 'Data' + ) + + def __str__(self): + return '{0} - {1} - {2}'.format( + self.pre_approval, self.status, self.date + ) + + class Meta: + ordering = ['date'] + verbose_name = 'Assinatura: Histórico da transação' + verbose_name_plural = 'Assinaturas: Históricos de transações' + + # Signals if PAGSEGURO_LOG_IN_MODEL: checkout_realizado.connect(save_checkout) notificacao_recebida.connect(update_transaction) + pre_approval_notification.connect(update_pre_approval) diff --git a/pagseguro/settings.py b/pagseguro/settings.py index 3f9b635..19e4f46 100644 --- a/pagseguro/settings.py +++ b/pagseguro/settings.py @@ -13,12 +13,22 @@ NOTIFICATION_URL = 'https://ws.sandbox.pagseguro.uol.com.br/v2/transactions/notifications' TRANSACTION_URL = 'https://ws.sandbox.pagseguro.uol.com.br/v2/transactions' SESSION_URL = 'https://ws.sandbox.pagseguro.uol.com.br/v2/sessions/' + PRE_APPROVAL_URL = 'https://ws.sandbox.pagseguro.uol.com.br/v2/pre-approvals' + PRE_APPROVAL_REQUEST_URL = 'https://ws.sandbox.pagseguro.uol.com.br/v2/pre-approvals/request' + PRE_APPROVAL_REDIRECT_URL = 'https://sandbox.pagseguro.uol.com.br/v2/pre-approvals/request.html' + PRE_APPROVAL_NOTIFICATION_URL = 'https://ws.sandbox.pagseguro.uol.com.br/v2/pre-approvals/notifications' + PRE_APPROVAL_CANCEL_URL = 'https://ws.sandbox.pagseguro.uol.com.br/v2/pre-approvals/cancel' else: CHECKOUT_URL = 'https://ws.pagseguro.uol.com.br/v2/checkout' PAYMENT_URL = 'https://pagseguro.uol.com.br/v2/checkout/payment.html' NOTIFICATION_URL = 'https://ws.pagseguro.uol.com.br/v2/transactions/notifications' TRANSACTION_URL = 'https://ws.pagseguro.uol.com.br/v2/transactions' SESSION_URL = 'https://ws.pagseguro.uol.com.br/v2/sessions/' + PRE_APPROVAL_URL = 'https://ws.pagseguro.uol.com.br/v2/pre-approvals' + PRE_APPROVAL_REQUEST_URL = 'https://ws.pagseguro.uol.com.br/v2/pre-approvals/request' + PRE_APPROVAL_REDIRECT_URL = 'https://ws.pagseguro.uol.com.br/v2/pre-approvals/request.html' + PRE_APPROVAL_NOTIFICATION_URL = 'https://ws.pagseguro.uol.com.br/v2/pre-approvals/notifications' + PRE_APPROVAL_CANCEL_URL = 'https://ws.pagseguro.uol.com.br/v2/pre-approvals/cancel' TRANSACTION_STATUS = { '1': 'aguardando', diff --git a/pagseguro/signals.py b/pagseguro/signals.py index ddec4b0..6fdb756 100644 --- a/pagseguro/signals.py +++ b/pagseguro/signals.py @@ -21,7 +21,6 @@ notificacao_status_devolvido = Signal(providing_args=['transaction']) notificacao_status_cancelado = Signal(providing_args=['transaction']) - NOTIFICATION_STATUS = { '1': notificacao_status_aguardando, '2': notificacao_status_em_analise, @@ -32,6 +31,25 @@ '7': notificacao_status_cancelado } +pre_approval_notification = Signal(providing_args=['transaction']) +pre_approval_create_plan = Signal(providing_args=['data']) +pre_approval_create_plan_error = Signal(providing_args=['data']) +pre_approval_status_pending = Signal(providing_args=['transaction']) +pre_approval_status_active = Signal(providing_args=['transaction']) +pre_approval_status_cancelled = Signal(providing_args=['transaction']) +pre_approval_status_cancelled_by_receiver = Signal(providing_args=['transaction']) +pre_approval_status_cancelled_by_sender = Signal(providing_args=['transaction']) +pre_approval_status_expired = Signal(providing_args=['transaction']) + +PRE_APPROVAL_NOTIFICATION_STATUS = { + 'PENDING': pre_approval_status_pending, + 'ACTIVE': pre_approval_status_active, + 'CANCELLED': pre_approval_status_cancelled, + 'CANCELLED_BY_RECEIVER': pre_approval_status_cancelled_by_receiver, + 'CANCELLED_BY_SENDER': pre_approval_status_cancelled_by_sender, + 'EXPIRED': pre_approval_status_expired, +} + def save_checkout(sender, data, **kwargs): from pagseguro.models import Checkout @@ -59,6 +77,7 @@ def update_transaction(sender, transaction, **kwargs): except Transaction.DoesNotExist: transaction = Transaction.objects.create( code=trans.get('code'), + transaction_type = trans.get('type'), status=TRANSACTION_STATUS[trans.get('status')], date=parse(trans.get('date')), last_event_date=parse(trans.get('lastEventDate')), @@ -76,3 +95,32 @@ def update_transaction(sender, transaction, **kwargs): status=TRANSACTION_STATUS[trans.get('status')], date=parse(trans.get('lastEventDate')) ) + +def update_pre_approval(sender, transaction, **kwargs): + from pagseguro.models import ( + PreApproval, PreApprovalHistory + ) + + try: + pre_approval = PreApproval.objects.get(code=transaction.get('code')) + except PreApproval.DoesNotExist: + pre_approval = PreApproval.objects.create( + code=transaction.get('code'), + tracker=transaction.get('tracker'), + status=transaction.get('status'), + date=parse(transaction.get('date')), + last_event_date=parse(transaction.get('lastEventDate')), + content=json.dumps(transaction, indent=2), + reference=transaction.get('reference', '') + ) + + pre_approval.status = transaction.get('status') + pre_approval.last_event_date = parse(transaction.get('lastEventDate')) + pre_approval.content = json.dumps(transaction, indent=2) + pre_approval.save() + + PreApprovalHistory.objects.create( + pre_approval=pre_approval, + status=transaction.get('status'), + date=parse(transaction.get('lastEventDate')) + ) diff --git a/pagseguro/tests/__init__.py b/pagseguro/tests/__init__.py index 01daf0a..ed3e15b 100644 --- a/pagseguro/tests/__init__.py +++ b/pagseguro/tests/__init__.py @@ -1,4 +1,5 @@ from .test_api import * from .test_signals import * +from .test_signals_pre_approval import * from .test_views import * from .test_models import * diff --git a/pagseguro/tests/test_api.py b/pagseguro/tests/test_api.py index 3ac551d..5857e62 100644 --- a/pagseguro/tests/test_api.py +++ b/pagseguro/tests/test_api.py @@ -5,10 +5,14 @@ import httpretty from dateutil.parser import parse -from pagseguro.api import PagSeguroItem, PagSeguroApi, PagSeguroApiTransparent +from pagseguro.api import ( + PagSeguroItem, PagSeguroApi, PagSeguroApiTransparent, + PagSeguroApiPreApproval +) from pagseguro.settings import ( - CHECKOUT_URL, PAYMENT_URL, NOTIFICATION_URL, - TRANSACTION_URL, SESSION_URL + CHECKOUT_URL, PAYMENT_URL, NOTIFICATION_URL, TRANSACTION_URL, SESSION_URL, + PRE_APPROVAL_URL, PRE_APPROVAL_CANCEL_URL, PRE_APPROVAL_REQUEST_URL, + PRE_APPROVAL_REDIRECT_URL, PRE_APPROVAL_NOTIFICATION_URL ) @@ -158,6 +162,36 @@ def test_valid_item(self): ''' +notification_pre_approval_response_xml = ''' + + Seguro de Notebook + FB8389D1F6F6A258849E9F8000C2E173 + 2017-10-04T21:09:52-03:00 + 139095 + PENDING + meu-id + 2017-10-04T21:10:14-03:00 + AUTO + + Comprador de Testes + testes@sandbox.pagseguro.com.br + + 11 + 99999999 + +
+ Rua teste + 400 + + Bairro + Cidade + Estado + BRA + CEP +
+
+
''' + transaction_response_xml = ''' 2011-02-05T15:46:12.000-02:00 @@ -275,6 +309,37 @@ def test_valid_item(self): ''' +pre_approval_response_xml = ''' + + Seguro contra roubo do Notebook Prata + 97A07B380D0DE04994DECF8D03896C98 + 2011-11-23T13:40:23.000-02:00 + 538C53 + ACTIVE + REF1234 + 2011-11-25T20:04:23.000-02:00 + auto + + Nome Comprador + comprador@uol.com + + 11 + 30389678 + +
+ ALAMEDA ITU + 78 + ap. 2601 + Jardim Paulista + SAO PAULO + SP + BRASIL + 01421000 +
+
+
+''' + class PagSeguroApiTest(TestCase): def setUp(self): @@ -400,6 +465,24 @@ def test_get_valid_notification(self): ) self.assertEqual(response.status_code, 200) + @httpretty.activate + def test_get_valid_pre_approval_notification(self): + # mock requests + httpretty.register_uri( + httpretty.GET, + PRE_APPROVAL_NOTIFICATION_URL + '/{0}'.format( + '74C79677-60C2-4564-B093-504565A52446' + ), + body=notification_pre_approval_response_xml, + status=200, + ) + + response = self.pagseguro_api.get_notification( + '74C79677-60C2-4564-B093-504565A52446', + 'preApproval', + ) + self.assertEqual(response.status_code, 200) + @httpretty.activate def test_get_invalid_transaction(self): # mock requests @@ -457,6 +540,44 @@ def test_items_is_instance_variable(self): api2 = PagSeguroApi() self.assertIsNot(api1.items, api2.items) + @httpretty.activate + def test_get_valid_pre_approval(self): + code = '97A07B380D0DE04994DECF8D03896C98' + # mock requests + httpretty.register_uri( + httpretty.GET, + '{0}/{1}'.format(PRE_APPROVAL_URL, code), + body=pre_approval_response_xml, + status=200, + ) + + api = PagSeguroApi() + data = api.get_pre_approval(code) + + self.assertEqual(data['transaction']['code'], code) + self.assertTrue(data['success']) + self.assertEqual(data['status_code'], 200) + + @httpretty.activate + def test_get_invalid_pre_approval(self): + code = 'xx' + # mock requests + httpretty.register_uri( + httpretty.GET, + '{0}/{1}'.format(PRE_APPROVAL_URL, code), + body='Not Found', + status=404, + ) + + api = PagSeguroApi() + data = api.get_pre_approval(code) + + self.assertNotIn('transaction', data) + self.assertEqual(data['status_code'], 404) + self.assertFalse(data['success']) + self.assertEqual(data['status_code'], 404) + self.assertEqual(data['message'], 'Not Found') + session_response_xml = ''' @@ -670,3 +791,191 @@ def test_invalid_checkout(self): self.assertEqual(data['success'], False) self.assertEqual(data['message'], 'Unauthorized') self.assertEqual(data['status_code'], 401) + + +pre_approval_create_plan_response_xml = ''' + + 97A07B380D0DE04994DECF8D03896C98 + 2017-10-03T18:43:22-03:00 + +''' + +pre_approval_create_plan_invalid_response_xml = ''' + + + 11072 + preApprovalFinalDate invalid value. + + +''' + +pre_approval_cancel_response_ok_xml = ''' + + 2017-10-03T18:45:22.000-03:00 + OK + +''' + +pre_approval_cancel_response_error_xml = ''' + + + 17022 + invalid pre-approval status to execute the requested operation. Preapproval status is CANCELLED_BY_RECEIVER. + + +''' + +pre_approval_cancelled_response_xml = ''' + + Seguro contra roubo do Notebook Prata + 97A07B380D0DE04994DECF8D03896C98 + 2011-11-23T13:40:23.000-02:00 + 538C53 + CANCELLED + REF1234 + 2011-11-25T20:04:23.000-02:00 + auto + + Nome Comprador + comprador@uol.com + + 11 + 30389678 + +
+ ALAMEDA ITU + 78 + ap. 2601 + Jardim Paulista + SAO PAULO + SP + BRASIL + 01421000 +
+
+
+''' + + +class PagSeguroApiPreApprovalTest(TestCase): + + def setUp(self): + self.pagseguro_api = PagSeguroApiPreApproval() + self.pre_approval_code = '97A07B380D0DE04994DECF8D03896C98' + + def test_set_pre_approval_data(self): + data = { + 'name': 'Seguro contra roubo de Notebook', + 'amount_per_payment': '100.00', + 'period': 'Monthly', + 'final_date': parse('2020-11-27 00:00:00'), + 'max_total_amount': 2400.00, + 'charge': 'auto', + 'details': 'Todo dia 28 será cobrado o valor de R$100,00', + } + self.pagseguro_api.set_pre_approval_data(**data) + params = self.pagseguro_api.params + self.assertEqual(params['preApprovalName'], data['name']) + self.assertEqual( + params['preApprovalAmountPerPayment'], data['amount_per_payment'] + ) + self.assertEqual(params['preApprovalPeriod'], data['period']) + self.assertEqual( + params['preApprovalFinalDate'], data['final_date'].isoformat() + ) + self.assertEqual( + params['preApprovalMaxTotalAmount'], + data['max_total_amount'] + ) + self.assertEqual(params['preApprovalCharge'], data['charge']) + self.assertEqual(params['preApprovalDetails'], data['details']) + + @httpretty.activate + def test_pre_approval_cancel_with_success(self): + # mock requests + httpretty.register_uri( + httpretty.GET, + '{0}/{1}'.format(PRE_APPROVAL_CANCEL_URL, self.pre_approval_code), + body=pre_approval_cancel_response_ok_xml, + status=200, + ) + httpretty.register_uri( + httpretty.GET, + '{0}/{1}'.format(PRE_APPROVAL_URL, self.pre_approval_code), + body=pre_approval_cancelled_response_xml, + status=200, + ) + + data = self.pagseguro_api.pre_approval_cancel(self.pre_approval_code) + self.assertEqual(data.get('status'), 'CANCELLED') + self.assertEqual(data.get('code'), self.pre_approval_code) + + @httpretty.activate + def test_pre_approval_cancel_with_error(self): + # mock requests + httpretty.register_uri( + httpretty.GET, + '{0}/{1}'.format(PRE_APPROVAL_CANCEL_URL, self.pre_approval_code), + body=pre_approval_cancel_response_error_xml, + status=400, + ) + + data = self.pagseguro_api.pre_approval_cancel(self.pre_approval_code) + + self.assertEqual(data.get('status_code'), 400) + self.assertFalse(data.get('success')) + + @httpretty.activate + def test_create_valid_plan(self): + httpretty.register_uri( + httpretty.POST, + PRE_APPROVAL_REQUEST_URL, + body=pre_approval_create_plan_response_xml, + status=200, + ) + pre_approval_data = { + 'name': '2 mês de treinamento virtual', + 'amount_per_payment': 9.90, + 'period': 'Monthly', + 'final_date': parse('2017-11-03T18:43:22-03:00'), + 'max_total_amount': 20.00, + 'charge': 'auto', + 'details': 'Todo dia 02 será cobrado o valor de R9,90', + } + data = self.pagseguro_api.create_plan(**pre_approval_data) + + self.assertEqual(data.get('status_code'), 200) + self.assertTrue(data.get('success')) + self.assertEqual(data.get('date'), parse('2017-10-03T18:43:22-03:00')) + self.assertEqual(data.get('code'), self.pre_approval_code) + self.assertEqual( + data.get('redirect_url'), + '{0}?code={1}'.format( + PRE_APPROVAL_REDIRECT_URL, self.pre_approval_code + ) + ) + + @httpretty.activate + def test_create_invalid_plan(self): + httpretty.register_uri( + httpretty.POST, + PRE_APPROVAL_REQUEST_URL, + body=pre_approval_create_plan_invalid_response_xml, + status=400, + ) + pre_approval_data = { + 'name': '2 mês de treinamento virtual', + 'amount_per_payment': 9.90, + 'period': 'Monthly', + 'final_date': parse('2017-10-03T18:43:22-03:00'), + 'max_total_amount': 20.00, + 'charge': 'auto', + 'details': 'Todo dia 02 será cobrado o valor de R9,90', + } + + # FIXME: remove final_date + + data = self.pagseguro_api.create_plan(**pre_approval_data) + + self.assertEqual(data.get('status_code'), 400) + self.assertFalse(data.get('success')) diff --git a/pagseguro/tests/test_models.py b/pagseguro/tests/test_models.py index 2112d16..c5991eb 100644 --- a/pagseguro/tests/test_models.py +++ b/pagseguro/tests/test_models.py @@ -6,7 +6,10 @@ except ImportError: from django.utils.encoding import smart_text as smart_unicode -from pagseguro.models import Checkout, Transaction, TransactionHistory +from pagseguro.models import ( + Checkout, Transaction, TransactionHistory, PreApprovalPlan, PreApproval, + PreApprovalHistory +) class CheckoutTest(TestCase): @@ -58,3 +61,62 @@ def test_create_model(self): tx_history.transaction, tx_history.status, tx_history.date ) ) + + +class PreApprovalPlanTest(TestCase): + + def test_create_pre_approval_plan(self): + pre_approval_plan = PreApprovalPlan.objects.create( + name='Seguro contra roubo de Notebook', + amount_per_payment='100.00', + period='Monthly', + final_date=timezone.now(), + max_total_amount=2400.00, + charge='auto', + details='Todo dia 28 será cobrado o valor de R$100,00', + ) + self.assertEqual( + smart_unicode(pre_approval_plan), pre_approval_plan.name + ) + + +class PreApprovalTest(TestCase): + + def test_create_pre_approval(self): + pre_approval = PreApproval.objects.create( + code='007', + tracker='abc', + reference='nothing', + status='aguardando', + date=timezone.now(), + last_event_date=timezone.now() + ) + self.assertEqual( + smart_unicode(pre_approval), pre_approval.code + ) + + +class PreApprovalHistoryTest(TestCase): + + def test_create_pre_approval_history(self): + pre_approval = PreApproval.objects.create( + code='007', + tracker='abc', + reference='nothing', + status='aguardando', + date=timezone.now(), + last_event_date=timezone.now() + ) + pre_approval_history = PreApprovalHistory.objects.create( + pre_approval=pre_approval, + date=timezone.now(), + status='aguardando' + ) + self.assertEqual( + smart_unicode(pre_approval_history), + '{0} - {1} - {2}'.format( + pre_approval_history.pre_approval, + pre_approval_history.status, + pre_approval_history.date + ) + ) diff --git a/pagseguro/tests/test_signals_pre_approval.py b/pagseguro/tests/test_signals_pre_approval.py new file mode 100644 index 0000000..b96fdf6 --- /dev/null +++ b/pagseguro/tests/test_signals_pre_approval.py @@ -0,0 +1,268 @@ +# -*- coding: utf-8 -*- + +import httpretty +from django.test import TestCase +from django.core.urlresolvers import reverse +from django.utils import timezone +from dateutil.parser import parse +from mock import patch + +from pagseguro.settings import ( + PRE_APPROVAL_NOTIFICATION_URL, PRE_APPROVAL_REQUEST_URL +) +from pagseguro.api import PagSeguroApiPreApproval + +# PreApproval, PreApprovalHistory + + +notification_response_xml = ''' + + Seguro de Notebook + FB8389D1F6F6A258849E9F8000C2E173 + 2017-10-04T21:09:52-03:00 + 139095 + MY-STATUS + meu-id + 2017-10-04T21:10:14-03:00 + AUTO + + Comprador de Testes + testes@sandbox.pagseguro.com.br + + 11 + 99999999 + +
+ Rua teste + 400 + + Bairro + Cidade + Estado + BRA + CEP +
+
+
''' + +# FIXME +""" +pre_approval_request = ''' + + http://www.seusite.com.br/retorno.php + http://www.seusite.com.br/revisao.php + REF1234 + + Nome do Cliente + cliente@uol.com.br + + 11 + 56273440 + +
+ Avenida Brigadeiro Faria Lima + 1384 + 1 Andar + Jardim Paulistano + 01452002 + São Paulo + SP + BRA +
+
+ + auto + Seguro contra roubo do Notebook +
Todo dia 28 será cobrado o valor de R$100,00 referente ao seguro contra roubo de Notebook
+ 100.00 + Monthly + 2014-01-21T00:00:000-03:00 + 2400.00 +
+
''' +""" + +pre_approval_request_response = ''' + + A403A4EDD9D9957FF46BCFA89B3DB9F1 + 2017-10-11T18:40:23-03:00 +''' + + +pre_approval_request_error_response = ''' + + + 11106 + preApprovalCharge invalid value. + +''' + + +class PagSeguroPreApprovalSignalsTest(TestCase): + + def setUp(self): + self.url = reverse('pagseguro_receive_notification') + self.notification_code = 'A5182C-C9EF48EF48D2-1FF4AF6FAC82-EB2948' + self.post_params = { + 'notificationCode': 'A5182C-C9EF48EF48D2-1FF4AF6FAC82-EB2948', + 'notificationType': 'preApproval' + } + + @httpretty.activate + def _notification(self, status): + # mock requests + httpretty.register_uri( + httpretty.GET, + '{0}/{1}'.format( + PRE_APPROVAL_NOTIFICATION_URL, self.notification_code + ), + body=notification_response_xml.replace( + 'MY-STATUS', '{0}'.format(status) + ), + status=200, + ) + + # load notification + response = self.client.post(self.url, self.post_params) + self.assertEqual(response.status_code, 200) + + def _assert_status(self, signal_mock, status): + _, kwargs = signal_mock.call_args + transaction_status = kwargs.get('transaction', {}).get('status') + self.assertEqual(transaction_status, status) + + @patch('pagseguro.signals.pre_approval_notification.send') + def test_pre_approval_notification(self, signal_mock): + self._notification('MY-STATUS') + self.assertTrue(signal_mock.called) + self._assert_status(signal_mock, 'MY-STATUS') + + @patch('pagseguro.signals.pre_approval_status_pending.send') + def test_pre_approval_status_pending(self, signal_mock): + self._notification('PENDING') + self.assertTrue(signal_mock.called) + self._assert_status(signal_mock, 'PENDING') + + @patch('pagseguro.signals.pre_approval_status_active.send') + def test_pre_approval_status_active(self, signal_mock): + self._notification('ACTIVE') + self.assertTrue(signal_mock.called) + self._assert_status(signal_mock, 'ACTIVE') + + @patch('pagseguro.signals.pre_approval_status_cancelled.send') + def test_pre_approval_status_cancelled(self, signal_mock): + self._notification('CANCELLED') + self.assertTrue(signal_mock.called) + self._assert_status(signal_mock, 'CANCELLED') + + @patch('pagseguro.signals.pre_approval_status_cancelled_by_receiver.send') + def test_pre_approval_status_cancelled_by_receiver(self, signal_mock): + self._notification('CANCELLED_BY_RECEIVER') + self.assertTrue(signal_mock.called) + self._assert_status(signal_mock, 'CANCELLED_BY_RECEIVER') + + @patch('pagseguro.signals.pre_approval_status_cancelled_by_sender.send') + def test_pre_approval_status_cancelled_by_sender(self, signal_mock): + self._notification('CANCELLED_BY_SENDER') + self.assertTrue(signal_mock.called) + self._assert_status(signal_mock, 'CANCELLED_BY_SENDER') + + @patch('pagseguro.signals.pre_approval_status_expired.send') + def test_pre_approval_status_expired(self, signal_mock): + self._notification('EXPIRED') + self.assertTrue(signal_mock.called) + self._assert_status(signal_mock, 'EXPIRED') + + def test_pre_approval_invalid_status(self): + with patch('pagseguro.signals.pre_approval_status_pending.send') as signal_mock: + self._notification('METAL') + self.assertFalse(signal_mock.called) + with patch('pagseguro.signals.pre_approval_status_active.send') as signal_mock: + self._notification('METAL') + self.assertFalse(signal_mock.called) + with patch('pagseguro.signals.pre_approval_status_cancelled.send') as signal_mock: + self._notification('METAL') + self.assertFalse(signal_mock.called) + with patch('pagseguro.signals.pre_approval_status_cancelled_by_receiver.send') as signal_mock: + self._notification('METAL') + self.assertFalse(signal_mock.called) + with patch('pagseguro.signals.pre_approval_status_cancelled_by_sender.send') as signal_mock: + self._notification('METAL') + self.assertFalse(signal_mock.called) + with patch('pagseguro.signals.pre_approval_status_expired.send') as signal_mock: + self._notification('METAL') + self.assertFalse(signal_mock.called) + with patch('pagseguro.signals.pre_approval_notification.send') as signal_mock: + self._notification('METAL') + self.assertTrue(signal_mock.called) + self._assert_status(signal_mock, 'METAL') + + @httpretty.activate + @patch('pagseguro.signals.pre_approval_create_plan.send') + def test_pre_approval_create_plan(self, pre_approval_create_plan_mock): + # mock requests + httpretty.register_uri( + httpretty.POST, + PRE_APPROVAL_REQUEST_URL, + body=pre_approval_request_response, + status=200, + ) + + pagseguro_api = PagSeguroApiPreApproval(reference='id-unico') + final_date = timezone.now() + timezone.timedelta(days=180) + pre_approval_plan_data = { + 'name': 'Seguro contra roubo de Notebook', + 'amount_per_payment': 100, + 'period': 'Monthly', + 'final_date': final_date, + 'max_total_amount': 300, + 'charge': 'auto', + 'details': 'Todo dia 28 será cobrado o valor de R100,00', + } + pagseguro_api.create_plan(**pre_approval_plan_data) + + _, kwargs = pre_approval_create_plan_mock.call_args + + code = 'A403A4EDD9D9957FF46BCFA89B3DB9F1' + data = kwargs.get('data', {}) + + self.assertTrue(data['success']) + self.assertEqual(data['code'], code) + self.assertEqual(data['status_code'], 200) + self.assertEqual(data['date'], parse('2017-10-11T18:40:23-03:00')) + self.assertEqual( + data['redirect_url'], + 'https://sandbox.pagseguro.uol.com.br/v2/pre-approvals/request.html?code={0}'.format(code) + ) + + @httpretty.activate + @patch('pagseguro.signals.pre_approval_create_plan_error.send') + def test_pre_approval_create_plan_error(self, pre_approval_create_plan_error_mock): + # mock requests + httpretty.register_uri( + httpretty.POST, + PRE_APPROVAL_REQUEST_URL, + body=pre_approval_request_error_response, + status=400, + ) + + pagseguro_api = PagSeguroApiPreApproval(reference='id-unico') + final_date = timezone.now() + timezone.timedelta(days=180) + pre_approval_plan_data = { + 'name': 'Seguro contra roubo de Notebook', + 'amount_per_payment': 100, + 'period': 'Monthly', + 'final_date': final_date, + 'max_total_amount': 300, + 'charge': 'metal', + 'details': 'Todo dia 28 será cobrado o valor de R100,00', + } + pagseguro_api.create_plan(**pre_approval_plan_data) + + _, kwargs = pre_approval_create_plan_error_mock.call_args + data = kwargs.get('data', {}) + + self.assertFalse(data['success']) + self.assertEqual(data['status_code'], 400) + self.assertIsNone(data.get('redirect_url')) + self.assertIsNone(data.get('pre_approval')) diff --git a/pagseguro/views.py b/pagseguro/views.py index 3bca96d..d7abb30 100644 --- a/pagseguro/views.py +++ b/pagseguro/views.py @@ -15,9 +15,13 @@ def receive_notification(request): notification_code = request.POST.get('notificationCode', None) notification_type = request.POST.get('notificationType', None) - if notification_code and notification_type == 'transaction': + notifications_type = ['transaction', 'preApproval'] + + if notification_code and notification_type in notifications_type: pagseguro_api = PagSeguroApi() - response = pagseguro_api.get_notification(notification_code) + response = pagseguro_api.get_notification( + notification_code, notification_type + ) if response.status_code == 200: if six.PY2: