diff --git a/pythonpro/domain/checkout_domain.py b/pythonpro/domain/checkout_domain.py index f49807f5..40356cfd 100644 --- a/pythonpro/domain/checkout_domain.py +++ b/pythonpro/domain/checkout_domain.py @@ -6,6 +6,7 @@ from pythonpro.domain import user_domain from pythonpro.domain.hotzapp_domain import verify_purchase, send_purchase_notification from pythonpro.email_marketing import facade as email_marketing_facade +from pythonpro.memberkit import facade as memberkit_facade __all__ = ['contact_info_listener', 'user_factory', 'payment_handler_task', 'payment_change_handler'] @@ -56,6 +57,7 @@ def payment_handler_task(payment_id): else: status = payment.status() if status == django_pagarme_facade.PAID: + memberkit_facade.create_new_subscription(payment, 'Criação como resposta de pagamento no Pagarme') user = payment.user if payment.payment_method == django_pagarme_facade.BOLETO: email_marketing_facade.remove_tags.delay(user.email, user.id, f'{slug}-boleto', f'{slug}-refused') diff --git a/pythonpro/domain/tests/test_checkout/conftest.py b/pythonpro/domain/tests/test_checkout/conftest.py index 199dd3f8..a7ad7987 100644 --- a/pythonpro/domain/tests/test_checkout/conftest.py +++ b/pythonpro/domain/tests/test_checkout/conftest.py @@ -3,8 +3,10 @@ import pytest from django_pagarme import facade from django_pagarme.models import PagarmeFormConfig, PagarmeItemConfig +from model_bakery import baker # Workaround since module beginning with number can't be imported in regular way +from pythonpro.memberkit.models import SubscriptionType, PaymentItemConfigToSubscriptionType migration_module = import_module('pythonpro.checkout.migrations.0001_payment_setup') webdev_migration_module = import_module('pythonpro.checkout.migrations.0002_webdev_setup') @@ -60,6 +62,13 @@ def execute_migration(db, pytestconfig): webinar_migration_module.setup_payment_configs_function(PagarmeFormConfig, PagarmeItemConfig) webserie_migration_module.setup_payment_configs_function(PagarmeFormConfig, PagarmeItemConfig) thiago_avelino_migration_module.setup_payment_configs_function(PagarmeFormConfig, PagarmeItemConfig) + subscription_type = baker.make(SubscriptionType) + PaymentItemConfigToSubscriptionType.objects.bulk_create( + [ + PaymentItemConfigToSubscriptionType(payment_item=config, subscription_type=subscription_type) + for config in PagarmeItemConfig.objects.all() + ] + ) @pytest.fixture(autouse=True) diff --git a/pythonpro/domain/tests/test_checkout/test_boleto_generation.py b/pythonpro/domain/tests/test_checkout/test_boleto_generation.py index 6b61b290..958e58b5 100644 --- a/pythonpro/domain/tests/test_checkout/test_boleto_generation.py +++ b/pythonpro/domain/tests/test_checkout/test_boleto_generation.py @@ -142,8 +142,7 @@ def remove_tags_mock(mocker): def test_payment_tag_removed_after_payment(resp_logged_user, active_product_item, remove_tags_mock, logged_user, - tag_as_mock, - mocker): + tag_as_mock, mocker): payment = django_pagarme_facade.find_payment_by_transaction(TRANSACTION_ID) baker.make(django_pagarme_facade.PagarmeNotification, status=django_pagarme_facade.PAID, payment=payment) promote_mock = mocker.patch('pythonpro.domain.checkout_domain._promote') diff --git a/pythonpro/domain/tests/test_checkout/test_payment_handler.py b/pythonpro/domain/tests/test_checkout/test_payment_handler.py index c189db33..56fe5c56 100644 --- a/pythonpro/domain/tests/test_checkout/test_payment_handler.py +++ b/pythonpro/domain/tests/test_checkout/test_payment_handler.py @@ -4,6 +4,7 @@ from model_bakery import baker from pythonpro.domain import checkout_domain +from pythonpro.memberkit.models import SubscriptionType, Subscription, PaymentItemConfigToSubscriptionType @pytest.fixture @@ -31,6 +32,9 @@ def test_pagarme_payment_paid_boleto(db, tag_as_mock, remove_tags_mock, promote_ payment = baker.make(PagarmePayment, payment_method=facade.BOLETO, user=logged_user) baker.make(PagarmeNotification, status=facade.PAID, payment=payment) config = baker.make(PagarmeItemConfig, payments=[payment]) + subscription_type = baker.make(SubscriptionType) + PaymentItemConfigToSubscriptionType.objects.create(payment_item=config, subscription_type=subscription_type) + checkout_domain.payment_handler_task(payment.id) assert tag_as_mock.called is False remove_tags_mock.assert_called_once_with( @@ -40,11 +44,31 @@ def test_pagarme_payment_paid_boleto(db, tag_as_mock, remove_tags_mock, promote_ send_purchase_notification_mock.asser_called_once_with(payment.id) +def test_subscription_creation(db, tag_as_mock, remove_tags_mock, promote_user_mock, logged_user, + send_purchase_notification_mock): + payment = baker.make(PagarmePayment, payment_method=facade.BOLETO, user=logged_user) + baker.make(PagarmeNotification, status=facade.PAID, payment=payment) + config = baker.make(PagarmeItemConfig, payments=[payment]) + subscription_type = baker.make(SubscriptionType) + PaymentItemConfigToSubscriptionType.objects.create(payment_item=config, subscription_type=subscription_type) + checkout_domain.payment_handler_task(payment.id) + subscription = Subscription.objects.first() + assert subscription is not None + assert subscription.subscriber_id == payment.user_id + assert subscription.status == Subscription.Status.INACTIVE + assert subscription.payment_id == payment.id + assert list(subscription.subscription_types.all()) == [subscription_type] + assert subscription.responsible is None + assert subscription.observation == 'Criação como resposta de pagamento no Pagarme' + + def test_pagarme_payment_paid_credit_card(db, tag_as_mock, remove_tags_mock, promote_user_mock, logged_user, send_purchase_notification_mock): payment = baker.make(PagarmePayment, payment_method=facade.CREDIT_CARD, user=logged_user) baker.make(PagarmeNotification, status=facade.PAID, payment=payment) config = baker.make(PagarmeItemConfig, payments=[payment]) + subscription_type = baker.make(SubscriptionType) + PaymentItemConfigToSubscriptionType.objects.create(payment_item=config, subscription_type=subscription_type) checkout_domain.payment_handler_task(payment.id) assert tag_as_mock.called is False remove_tags_mock.assert_called_once_with( diff --git a/pythonpro/memberkit/admin.py b/pythonpro/memberkit/admin.py index eb0f9ce1..3693d35d 100644 --- a/pythonpro/memberkit/admin.py +++ b/pythonpro/memberkit/admin.py @@ -6,7 +6,7 @@ # Register your models here. from pythonpro.memberkit import facade -from pythonpro.memberkit.models import SubscriptionType, PaymentItemConfigToSubscriptionType +from pythonpro.memberkit.models import SubscriptionType, PaymentItemConfigToSubscriptionType, Subscription class PaymentItemConfigInline(admin.TabularInline): @@ -45,3 +45,15 @@ def has_delete_permission(self, request, obj=None): @admin.register(PagarmeItemConfig) class NewPagarmeItemConfigAdmin(PagarmeItemConfigAdmin): inlines = [PaymentItemConfigInline] + + +@admin.register(Subscription) +class SubscriptionAdmin(admin.ModelAdmin): + fields = ['subscriber', 'subscription_types', 'observation'] + list_display = ['subscriber', 'responsible', 'status', 'created_at', 'updated_at'] + autocomplete_fields = ['subscriber'] + search_fields = ['subscriber__email'] + list_filter = ['status', 'subscription_types'] + + def has_delete_permission(self, request, obj=None): + return False diff --git a/pythonpro/memberkit/facade.py b/pythonpro/memberkit/facade.py index 76d525ec..2869b086 100644 --- a/pythonpro/memberkit/facade.py +++ b/pythonpro/memberkit/facade.py @@ -1,7 +1,7 @@ from typing import List from pythonpro.memberkit import api -from pythonpro.memberkit.models import SubscriptionType +from pythonpro.memberkit.models import SubscriptionType, Subscription def synchronize_subscription_types() -> List[SubscriptionType]: @@ -9,3 +9,18 @@ def synchronize_subscription_types() -> List[SubscriptionType]: SubscriptionType.objects.update_or_create(id=dct['id'], name=dct['name'])[0] for dct in api.list_membership_levels() ] + + +def create_new_subscription(payment, observation: str = '') -> Subscription: + subscription_types = [item.subscription_type_relation.subscription_type for item in payment.items.all()] + if len(subscription_types) == 0: + raise ValueError(f"Payment {payment} doesn't have subscription types") + + subscription = Subscription.objects.create( + status=Subscription.Status.INACTIVE, + payment=payment, + subscriber=payment.user, + observation=observation + ) + subscription.subscription_types.set(subscription_types) + return subscription diff --git a/pythonpro/memberkit/migrations/0003_subscription.py b/pythonpro/memberkit/migrations/0003_subscription.py new file mode 100644 index 00000000..ec9f05d2 --- /dev/null +++ b/pythonpro/memberkit/migrations/0003_subscription.py @@ -0,0 +1,38 @@ +# Generated by Django 3.2.4 on 2021-06-05 20:15 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('django_pagarme', '0004_pagarme_item_config_available_until'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('memberkit', '0002_create_relationship_with_payment_config_item'), + ] + + operations = [ + migrations.CreateModel( + name='Subscription', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('status', models.CharField(choices=[('A', 'Ativo'), ('I', 'Inativo')], max_length=1)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('observation', models.TextField(blank=True, default='', verbose_name='Observação')), + ('payment', models.OneToOneField(null=True, on_delete=django.db.models.deletion.DO_NOTHING, + to='django_pagarme.pagarmepayment')), + ('responsible', models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, + related_name='created_subscriptions', to=settings.AUTH_USER_MODEL)), + ('subscriber', models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, + related_name='subscriptions', to=settings.AUTH_USER_MODEL)), + ('subscription_types', + models.ManyToManyField(related_name='subscriptions', to='memberkit.SubscriptionType')), + ], + options={ + 'verbose_name': 'Assinatura', + 'verbose_name_plural': 'Assinaturas', + }, + ), + ] diff --git a/pythonpro/memberkit/models.py b/pythonpro/memberkit/models.py index 752f9ec1..a933c5ff 100644 --- a/pythonpro/memberkit/models.py +++ b/pythonpro/memberkit/models.py @@ -1,3 +1,4 @@ +from django.contrib.auth import get_user_model from django.db import models @@ -22,8 +23,23 @@ class PaymentItemConfigToSubscriptionType(models.Model): def __str__(self): return f'{self.payment_item} -> {self.subscription_type}' -# class Subscription(models.Model): -# created_at = models.DateTimeField(auto_now_add=True) -# updated_at = models.DateTimeField(auto_now_add=True) -# payment = models.OneToOneField('PagarmePayment', on_delete=models.DO_NOTHING, ) -# subscription_type = models.OneToOneField(SubscriptionType, null=False, on_delete=models.DO_NOTHING) + +class Subscription(models.Model): + class Meta: + verbose_name = 'Assinatura' + verbose_name_plural = 'Assinaturas' + + class Status(models.TextChoices): + ACTIVE = 'A', 'Ativo' + INACTIVE = 'I', 'Inativo' + + status = models.CharField(max_length=1, choices=Status.choices) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + payment = models.OneToOneField('django_pagarme.PagarmePayment', on_delete=models.DO_NOTHING, null=True) + subscription_types = models.ManyToManyField(SubscriptionType, related_name='subscriptions') + subscriber = models.ForeignKey(get_user_model(), on_delete=models.DO_NOTHING, null=True, + related_name='subscriptions') + responsible = models.ForeignKey(get_user_model(), on_delete=models.DO_NOTHING, null=True, + related_name='created_subscriptions') + observation = models.TextField(verbose_name='Observação', blank=True, default='')