diff --git a/docs/tutorial.rst b/docs/tutorial.rst
index 189b19c..3a7895f 100644
--- a/docs/tutorial.rst
+++ b/docs/tutorial.rst
@@ -250,6 +250,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/my_requirenments.txt b/my_requirenments.txt
new file mode 100644
index 0000000..8f297b3
--- /dev/null
+++ b/my_requirenments.txt
@@ -0,0 +1,24 @@
+arrow==0.10.0
+asn1crypto==0.23.0
+certifi==2017.7.27.1
+cffi==1.11.0
+chardet==3.0.4
+coverage==4.4.1
+cryptography==2.0.3
+Django==1.10
+django-cors-headers==2.1.0
+enum34==1.1.6
+httpretty==0.8.6
+idna==2.6
+ipaddress==1.0.18
+ndg-httpsclient==0.4.3
+pkg-resources==0.0.0
+pyasn1==0.3.6
+pycparser==2.18
+pyOpenSSL==17.3.0
+python-dateutil==2.6.1
+pytz==2017.2
+requests==2.7.0
+six==1.11.0
+urllib3==1.22
+xmltodict==0.11.0
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 5b868ba..8818067 100644
--- a/pagseguro/api.py
+++ b/pagseguro/api.py
@@ -8,11 +8,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
@@ -74,6 +79,12 @@ def __init__(self, checkout_url=None, redirect_url=None,
self.base_params.update(kwargs)
self.params = {}
self.items = []
+ # FIXME
+ self.pre_approval_url = PRE_APPROVAL_URL
+ self.pre_approval_request_url = PRE_APPROVAL_REQUEST_URL
+ self.pre_approval_redirect_url = PRE_APPROVAL_REDIRECT_URL
+ self.pre_approval_notification_url = PRE_APPROVAL_NOTIFICATION_URL
+ self.pre_approval_cancel_url = PRE_APPROVAL_CANCEL_URL
def add_item(self, item):
self.items.append(item)
@@ -141,7 +152,9 @@ def checkout(self):
logger.debug('operation=api_checkout, data={!r}'.format(data))
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 + '/{}'.format(notification_id),
params={
@@ -152,17 +165,15 @@ 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)
logger.debug(
'operation=api_get_notification, '
@@ -174,6 +185,25 @@ def get_notification(self, notification_id):
)
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 + '/{}'.format(transaction_id),
@@ -211,6 +241,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):
def __init__(self, session_url=None, **kwargs):
@@ -363,3 +422,113 @@ def get_session_id(self):
'data={!r}'.format(data)
)
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 ceaeef5..bdc5022 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):
@@ -127,7 +155,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 5a97c01..25784b6 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 5642e7d..df32928 100644
--- a/pagseguro/signals.py
+++ b/pagseguro/signals.py
@@ -23,7 +23,6 @@
notificacao_status_debitado = Signal(providing_args=['transaction'])
notificacao_status_retencao_temporaria = Signal(providing_args=['transaction'])
-
NOTIFICATION_STATUS = {
'1': notificacao_status_aguardando,
'2': notificacao_status_em_analise,
@@ -36,6 +35,25 @@
'9': notificacao_status_retencao_temporaria,
}
+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
@@ -63,6 +81,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')),
@@ -80,3 +99,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 6ac0858..27d4d39 100644
--- a/pagseguro/tests/test_api.py
+++ b/pagseguro/tests/test_api.py
@@ -5,10 +5,14 @@
from dateutil.parser import parse
from django.test import TestCase
-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)
+ @responses.activate
+ def test_get_valid_pre_approval_notification(self):
+ # mock requests
+ responses.add(
+ responses.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)
+
@responses.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)
+ @responses.activate
+ def test_get_valid_pre_approval(self):
+ code = '97A07B380D0DE04994DECF8D03896C98'
+ # mock requests
+ responses.add(
+ responses.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)
+
+ @responses.activate
+ def test_get_invalid_pre_approval(self):
+ code = 'xx'
+ # mock requests
+ responses.add(
+ responses.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 = '''
@@ -693,3 +814,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: