Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pythonpro/domain/checkout_domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ def payment_handler_task(payment_id):
user = payment.user
email_marketing_facade.tag_as.delay(user.email, user.id, f'{slug}-boleto')
send_purchase_notification.delay(payment.id)
elif status in {django_pagarme_facade.REFUNDED, django_pagarme_facade.PENDING_REFUND}:
subscription_domain.inactivate_payment_subscription(payment)


def _promote(user, slug: str):
Expand Down
93 changes: 92 additions & 1 deletion pythonpro/domain/subscription_domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def subscribe_with_no_role(session_id, name: str, email: str, *tags, id='0', pho
def sync_user_on_discourse(subscription: Subscription):
"""
Synchronize user data on forum if API is configured
:param user_or_id: Django user or his id
:param subscription
:return: returns result of hitting Discourse api
"""
can_make_api_call = bool(settings.DISCOURSE_API_KEY and settings.DISCOURSE_API_USER)
Expand Down Expand Up @@ -59,6 +59,74 @@ def sync_user_on_discourse(subscription: Subscription):
requests.post(url, data={'sso': sso_payload, 'sig': signature}, headers=headers)


def remove_from_discourse(subscription: Subscription):
"""
Synchronize user data on forum if API is configured
:param subscription
:return: returns result of hitting Discourse api
"""
can_make_api_call = bool(settings.DISCOURSE_API_KEY and settings.DISCOURSE_API_USER)
can_work_without_sync = not (settings.DISCOURSE_BASE_URL or can_make_api_call)
if can_work_without_sync:
_logger.info('Discourse Integration not available')
return
elif not can_make_api_call:
raise MissingDiscourseAPICredentials('Must define both DISCOURSE_API_KEY and DISCOURSE_API_USER configs')

# https://meta.discourse.org/t/sync-sso-user-data-with-the-sync-sso-route/84398
subscriber = subscription.subscriber
params = {
'email': subscriber.email,
'external_id': subscriber.id,
'require_actisubscription.discourse_groups': 'false',
'remove_groups': ','.join(subscription.discourse_groups)
}
sso_payload, signature = discourse_facade.generate_sso_payload_and_signature(params)
# query_string = parse.urlencode()
url = f'{settings.DISCOURSE_BASE_URL}/admin/users/sync_sso'
headers = {
'content-type': 'multipart/form-data',
'Api-Key': settings.DISCOURSE_API_KEY,
'Api-Username': settings.DISCOURSE_API_USER,
}

requests.post(url, data={'sso': sso_payload, 'sig': signature}, headers=headers)


def remove_user_from_discourse(subscription: Subscription):
"""
Synchronize user data on forum if API is configured
:param user_or_id: Django user or his id
:return: returns result of hitting Discourse api
"""
can_make_api_call = bool(settings.DISCOURSE_API_KEY and settings.DISCOURSE_API_USER)
can_work_without_sync = not (settings.DISCOURSE_BASE_URL or can_make_api_call)
if can_work_without_sync:
_logger.info('Discourse Integration not available')
return
elif not can_make_api_call:
raise MissingDiscourseAPICredentials('Must define both DISCOURSE_API_KEY and DISCOURSE_API_USER configs')

# https://meta.discourse.org/t/sync-sso-user-data-with-the-sync-sso-route/84398
subscriber = subscription.subscriber
params = {
'email': subscriber.email,
'external_id': subscriber.id,
'require_actisubscription.discourse_groups': 'false',
'remove_groups': ','.join(subscription.discourse_groups)
}
sso_payload, signature = discourse_facade.generate_sso_payload_and_signature(params)
# query_string = parse.urlencode()
url = f'{settings.DISCOURSE_BASE_URL}/admin/users/sync_sso'
headers = {
'content-type': 'multipart/form-data',
'Api-Key': settings.DISCOURSE_API_KEY,
'Api-Username': settings.DISCOURSE_API_USER,
}

requests.post(url, data={'sso': sso_payload, 'sig': signature}, headers=headers)


def create_subscription_and_activate_services(payment: PagarmePayment) -> Subscription:
subscription = memberkit_facade.create_new_subscription(payment, 'Criação como resposta de pagamento no Pagarme')
phone = None
Expand Down Expand Up @@ -97,3 +165,26 @@ def activate_subscription_on_all_services(subscription: Subscription, responsibl
phone=phone
)
return subscription


def inactivate_subscription_on_all_services(subscription: Subscription, responsible=None,
observation='') -> Subscription:
"""
Inactivate user account on Memberkit, Active Campaign and Discourse
:param subscription:
:return:
"""
remove_user_from_discourse(subscription)
memberkit_facade.inactivate(subscription, responsible, observation)
subscriber = subscription.subscriber
tags = list(subscription.email_marketing_tags)
email_marketing_facade.remove_tags.delay(
subscriber.email,
subscriber.id,
*tags
)
return subscription


def inactivate_payment_subscription(payment: PagarmePayment):
inactivate_subscription_on_all_services(payment.subscription)
33 changes: 31 additions & 2 deletions pythonpro/domain/tests/test_checkout/test_payment_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,6 @@ def test_pagarme_payment_waiting_payment_boleto(mock_subscription_creation, db,
[
facade.PROCESSING,
facade.AUTHORIZED,
facade.REFUNDED,
facade.PENDING_REFUND,
]
)
def test_pagarme_payment_with_item_but_do_nothing_status(db, tag_as_mock, remove_tags_mock, promote_user_mock, status):
Expand All @@ -166,6 +164,37 @@ def test_pagarme_payment_with_item_but_do_nothing_status(db, tag_as_mock, remove
assert promote_user_mock.called is False


@pytest.fixture
def inactivate_subscription_on_all_services(mocker):
return mocker.patch(
'pythonpro.domain.checkout_domain.subscription_domain.inactivate_subscription_on_all_services')


@pytest.mark.parametrize(
'status',
[
facade.REFUNDED,
facade.PENDING_REFUND,
]
)
def test_pagarme_subscription_inactivation(
db, tag_as_mock, remove_tags_mock, promote_user_mock, status, inactivate_subscription_on_all_services):
payment = baker.make(PagarmePayment)
baker.make(PagarmeNotification, status=status, payment=payment)
config = baker.make(PagarmeItemConfig, payments=[payment])
subscription_type = baker.make(SubscriptionType)
subscription = baker.make(Subscription, payment=payment)
subscription.subscription_types.add(subscription_type)
PaymentItemConfigToSubscriptionType.objects.create(payment_item=config, subscription_type=subscription_type)

checkout_domain.payment_handler_task(payment.id)

assert tag_as_mock.called is False
assert remove_tags_mock.called is False
assert promote_user_mock.called is False
inactivate_subscription_on_all_services.assert_called_once_with(subscription)


@pytest.mark.parametrize(
'status',
[
Expand Down
16 changes: 15 additions & 1 deletion pythonpro/memberkit/admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from time import strftime

from django.contrib import admin
from django.shortcuts import redirect
from django.urls import path
Expand Down Expand Up @@ -76,7 +78,7 @@ class SubscriptionAdmin(admin.ModelAdmin):
list_filter = ['status', PaymentListFilter, 'subscription_types']
ordering = ['-updated_at']
readonly_fields = ['activated_at', 'memberkit_user_id']
actions = ['activate']
actions = ['activate', 'inactivate']

def get_queryset(self, request):
return Subscription.objects.select_related('payment').select_related('subscriber').select_related('responsible')
Expand Down Expand Up @@ -120,5 +122,17 @@ def activate(self, request, queryset):

activate.short_descriptions = 'Ativar'

def inactivate(self, request, queryset):
responsible = request.user
strftime('d%/%m/%Y H%:%M:%S')
for subscription in queryset:
subscription_domain.inactivate_subscription_on_all_services(
subscription,
responsible,
f'Desativada em via admin por Usuário com id {responsible.id} e email {responsible.email}'
)

inactivate.short_descriptions = 'Desativar'

def has_delete_permission(self, request, obj=None):
return False
21 changes: 19 additions & 2 deletions pythonpro/memberkit/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ def list_membership_levels(*, api_key=_ApiKeyNone):


@_configure_api_key
def user_detail(email, *, api_key=_ApiKeyNone):
response = requests.get(f'{_base_url}/api/v1/users/{email}?api_key={api_key}')
def user_detail(email_or_memberkit_user_id, *, api_key=_ApiKeyNone):
response = requests.get(f'{_base_url}/api/v1/users/{email_or_memberkit_user_id}?api_key={api_key}')
return response.json()


Expand All @@ -70,3 +70,20 @@ def activate_user(full_name: str, email: str, subscription_type_id: int, expires
}
requests.post(f'{_base_url}/api/v1/users?api_key={api_key}', json=data)
return user_detail(email)


@_configure_api_key
def inactivate_user(memberkit_user_id: int, subscription_type_id: int, *,
api_key=_ApiKeyNone):
user_json = user_detail(memberkit_user_id, api_key=api_key)
data = {
'full_name': user_json['full_name'],
'email': user_json['email'],
'status': 'expired',
'blocked': False,
'membership_level_id': subscription_type_id,
'unlimited': False,
'expires_at': date.today().strftime('%d/%m/%Y'),
}
response = requests.post(f'{_base_url}/api/v1/users?api_key={api_key}', json=data)
return response.json()
17 changes: 17 additions & 0 deletions pythonpro/memberkit/facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,20 @@ def activate(subscription, responsible=None, observation=''):
subscription.responsible = responsible
subscription.save()
return subscription


def inactivate(subscription, responsible=None, observation=''):
for subscription_type in subscription.subscription_types.all().only('id'):
api.inactivate_user(subscription.memberkit_user_id, subscription_type.id)
subscription.status = Subscription.Status.INACTIVE
subscription.activated_at = None
if responsible is not None:
subscription.responsible = responsible
if subscription.observation:
subscription.observation += f'\n\n {observation}'
else:
subscription.observation = observation
subscription.save(update_fields=[
'status', 'activated_at', 'responsible', 'observation'
])
return subscription
68 changes: 68 additions & 0 deletions pythonpro/memberkit/tests/test_user_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,71 @@ def test_activate_on_membership(django_user_model, logged_user, responses, api_k
assert subscription.memberkit_user_id == memberkit_user_id
assert subscription.observation == msg
assert subscription.responsible == logged_user


def test_deactivate_on_membership(django_user_model, logged_user, api_key, responses):
memberkit_user_id = 5047080
user_response = {
'bio': None,
'blocked': False,
'email': 'renzo@python.pro.br',
'enrollments': [],
'full_name': 'ktGsanrofXZhsxXakxkNtHTNiDRdQU',
'id': memberkit_user_id,
'memberships': [
{
'expire_date': '2200-01-01',
'membership_level_id': 4424,
'status': 'active'
}
],
'profile_image_url': None,
'unlimited': True
}
user = baker.make(django_user_model, email='renzo@python.pro.br')
responses.add(
responses.GET,
f'https://memberkit.com.br/api/v1/users/{memberkit_user_id}?api_key={api_key}',
json=user_response
)

inactive_user_response = {
'bio': None,
'blocked': False,
'email': 'renzo@python.pro.br',
'enrollments': [],
'full_name': 'ktGsanrofXZhsxXakxkNtHTNiDRdQU',
'id': memberkit_user_id,
'memberships': [
{
'expire_date': '2200-01-01',
'membership_level_id': 4424,
'status': 'expired'
}
],
'profile_image_url': None,
'unlimited': True
}
responses.add(
responses.POST,
f'https://memberkit.com.br/api/v1/users?api_key={api_key}',
json=inactive_user_response
)

subscription_type = SubscriptionType.objects.create(id=4424, name='Membros')
subscription = baker.make(
Subscription,
subscriber=user,
status=Subscription.Status.ACTIVE,
activated_at=timezone.now(),
memberkit_user_id=memberkit_user_id
)
subscription.subscription_types.add(subscription_type)
msg = 'Desativado por razão x'
facade.inactivate(subscription, logged_user, msg)
subscription.refresh_from_db()
assert subscription.status == Subscription.Status.INACTIVE
assert subscription.activated_at is None
assert subscription.memberkit_user_id == memberkit_user_id
assert subscription.observation == msg
assert subscription.responsible == logged_user