Skip to content

Commit bd357a4

Browse files
renzonrenzon
renzon
authored andcommitted
Created command to sync active subscriptions
Part of #4764
1 parent ba77d1a commit bd357a4

File tree

7 files changed

+417
-53
lines changed

7 files changed

+417
-53
lines changed

pythonpro/core/tests/test_view_unsubscribe.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33

44
def test_status_code(client):
5-
return client.get(reverse('core:unsubscribe'))
5+
assert client.get(reverse('core:unsubscribe'))

pythonpro/discourse/tests/test_sso.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,15 @@ def test_redirect_payload_lead_data(logged_user, nonce, response_with_lead):
120120
)
121121
def test_status_invalid_data(client_with_member, invalid_data):
122122
response = client_with_member.get(reverse('discourse:sso'), data=invalid_data)
123-
return response.status_code == 400
123+
assert response.status_code == 400
124124

125125

126126
def test_payload_without_nonce(response_without_nonce):
127-
return response_without_nonce.status_code == 400
127+
assert response_without_nonce.status_code == 400
128128

129129

130130
def test_payload_with_mismatch_signature(response_with_wrong_sig):
131-
return response_with_wrong_sig.status_code == 400
131+
assert response_with_wrong_sig.status_code == 400
132132

133133

134134
def test_user_not_logged_status_code(client):

pythonpro/memberkit/api.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,14 @@ def _configure_api_key(func, *args, api_key=_ApiKeyNone, **kwargs):
4545
@_configure_api_key
4646
def list_membership_levels(*, api_key=_ApiKeyNone):
4747
response = requests.get(f'{_base_url}/api/v1/membership_levels?api_key={api_key}')
48+
response.raise_for_status()
4849
return response.json()
4950

5051

5152
@_configure_api_key
5253
def user_detail(email_or_memberkit_user_id, *, api_key=_ApiKeyNone):
5354
response = requests.get(f'{_base_url}/api/v1/users/{email_or_memberkit_user_id}?api_key={api_key}')
55+
response.raise_for_status()
5456
return response.json()
5557

5658

@@ -73,11 +75,13 @@ def activate_user(full_name: str, email: str, subscription_type_id: int, expires
7375

7476

7577
@_configure_api_key
76-
def update_user_subscription(memberkit_user_id: int, subscription_type_id: int, status: str, expires_at: date = None, *,
78+
def update_user_subscription(memberkit_user_id: int, subscription_type_id: int, status: str, expires_at: date, *,
7779
api_key=_ApiKeyNone):
80+
valid_statuses = {'inactive', 'pending', 'active', 'expired'}
81+
if status not in valid_statuses:
82+
raise ValueError(f'{status} is not on of valid statuses: {valid_statuses}')
83+
7884
user_json = user_detail(memberkit_user_id, api_key=api_key)
79-
if expires_at is None:
80-
expires_at = date(2200, 1, 1)
8185
data = {
8286
'full_name': user_json['full_name'],
8387
'email': user_json['email'],
@@ -88,6 +92,7 @@ def update_user_subscription(memberkit_user_id: int, subscription_type_id: int,
8892
'expires_at': expires_at.strftime('%d/%m/%Y'),
8993
}
9094
response = requests.post(f'{_base_url}/api/v1/users?api_key={api_key}', json=data)
95+
response.raise_for_status()
9196
return response.json()
9297

9398

@@ -105,13 +110,15 @@ def inactivate_user(memberkit_user_id: int, subscription_type_id: int, *,
105110
'expires_at': date.today().strftime('%d/%m/%Y'),
106111
}
107112
response = requests.post(f'{_base_url}/api/v1/users?api_key={api_key}', json=data)
113+
response.raise_for_status()
108114
return response.json()
109115

110116

111117
@_configure_api_key
112118
def delete_user(memberkit_user_id: int, *, api_key=_ApiKeyNone):
113119
response = requests.delete(f'{_base_url}/api/v1/users/{memberkit_user_id}?api_key={api_key}')
114120
response.raise_for_status()
121+
return response
115122

116123

117124
@_configure_api_key

pythonpro/memberkit/facade.py

+48-27
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from builtins import Exception
2-
from datetime import timedelta
32
from itertools import count
43
from logging import Logger
54
from typing import List
65

76
from celery import shared_task
7+
from django.db import transaction
88
from django.utils import timezone
9+
from requests import HTTPError
910

1011
from pythonpro.memberkit import api
1112
from pythonpro.memberkit.models import SubscriptionType, Subscription, YEAR_IN_DAYS, UserSubscriptionsSummary
@@ -62,25 +63,34 @@ def create_new_subscription(payment, observation: str = '') -> Subscription:
6263

6364
def activate(subscription, responsible=None, observation=''):
6465
user = subscription.subscriber
65-
if subscription.status == Subscription.Status.INACTIVE or subscription.activated_at is None:
66+
if subscription.activated_at is None:
67+
# Faz extensão da anualidade se for a primeira ativação
6668
subscription.activated_at = timezone.now()
67-
for subscription_type in subscription.subscription_types.all():
68-
expires_at = subscription.activated_at + timedelta(days=subscription_type.days_of_access)
69-
if subscription_type.id in IDS_COMUNIDADE_SUBSCRIPTION:
70-
active_comunidade_subscriptions = Subscription.objects.filter(
71-
subscriber_id=user.id,
72-
status=Subscription.Status.ACTIVE,
73-
subscription_types__in=IDS_COMUNIDADE_SUBSCRIPTION
74-
)
69+
for subscription_type in subscription.subscription_types.all():
70+
if subscription_type.id in IDS_COMUNIDADE_SUBSCRIPTION:
71+
active_comunidade_subscriptions = Subscription.objects.filter(
72+
subscriber_id=user.id,
73+
status=Subscription.Status.ACTIVE,
74+
subscription_types__in=IDS_COMUNIDADE_SUBSCRIPTION
75+
)
7576

76-
max_remaining_days = max(s.remaining_days for s in active_comunidade_subscriptions)
77-
expires_at += timedelta(days=max_remaining_days)
78-
subscription.days_of_access += max_remaining_days
77+
max_remaining_days = max(
78+
(s.remaining_days for s in active_comunidade_subscriptions),
79+
default=0
80+
)
81+
subscription.days_of_access += max_remaining_days
7982

80-
response_json = api.activate_user(
81-
user.get_full_name(), user.email, subscription_type.id, expires_at
82-
)
83-
subscription.memberkit_user_id = response_json['id']
83+
response_json = api.activate_user(
84+
user.get_full_name(), user.email, subscription_type.id, subscription.expires_at
85+
)
86+
subscription.memberkit_user_id = response_json['id']
87+
else:
88+
# Se for a segunda, só usa os dados já calculados
89+
for subscription_type in subscription.subscription_types.all():
90+
response_json = api.activate_user(
91+
user.get_full_name(), user.email, subscription_type.id, subscription.expires_at
92+
)
93+
subscription.memberkit_user_id = response_json['id']
8494
subscription.status = Subscription.Status.ACTIVE
8595
if subscription.observation:
8696
subscription.observation += f'\n\n {observation}'
@@ -96,15 +106,14 @@ def inactivate(subscription, responsible=None, observation=''):
96106
for subscription_type in subscription.subscription_types.all().only('id'):
97107
api.inactivate_user(subscription.memberkit_user_id, subscription_type.id)
98108
subscription.status = Subscription.Status.INACTIVE
99-
subscription.activated_at = None
100109
if responsible is not None:
101110
subscription.responsible = responsible
102111
if subscription.observation:
103112
subscription.observation += f'\n\n {observation}'
104113
else:
105114
subscription.observation = observation
106115
subscription.save(update_fields=[
107-
'status', 'activated_at', 'responsible', 'observation'
116+
'status', 'responsible', 'observation'
108117
])
109118
return subscription
110119

@@ -169,25 +178,37 @@ def process_expired_subscriptions(user_id):
169178
for subscription in active_subscriptions:
170179
if subscription.expires_at < now:
171180
subscription.status = Subscription.Status.INACTIVE
172-
subscription.save()
173181
inactive_subscriptions = [s for s in active_subscriptions if s.status == Subscription.Status.INACTIVE]
174182
active_subscriptions = [s for s in active_subscriptions if s.status == Subscription.Status.ACTIVE]
175183
if len(active_subscriptions) == 0:
176184
for memberkit_user_id in summary.memberkit_user_ids():
177185
_logger.info(f'Deleted memberkit account for user_id: {user_id}')
178-
api.delete_user(memberkit_user_id)
186+
try:
187+
api.delete_user(memberkit_user_id)
188+
except HTTPError as e:
189+
if e.response.status_code != 404:
190+
raise e
191+
192+
with transaction.atomic():
193+
for subscription in inactive_subscriptions:
194+
subscription.save()
179195
else:
180-
for inactive_subscription in inactive_subscriptions:
181-
_logger.info(f'Inactivated {inactive_subscription.name} for user_id: {user_id}')
182-
inactivate(inactive_subscription, observation='Inativada por processo de inativação')
183196
for active_subscription in active_subscriptions:
184-
for subscription_type in active_subscription.subscription_types.all().only('id'):
197+
for subscription_type_id in active_subscription.subscription_types.all().values_list('id', flat=True):
185198
_logger.info(f'Activated {active_subscription.name} for user_id: {user_id}')
186199
api.update_user_subscription(
187200
active_subscription.memberkit_user_id,
188-
subscription_type,
189-
'activate'
201+
subscription_type_id,
202+
'active',
203+
active_subscription.expires_at.date()
190204
)
205+
for inactive_subscription in inactive_subscriptions:
206+
_logger.info(f'Inactivated {inactive_subscription.name} for user_id: {user_id}')
207+
try:
208+
inactivate(inactive_subscription, observation='Inativada por data de experição7')
209+
except HTTPError as e:
210+
if e.response.status_code != 404:
211+
raise e
191212

192213

193214
def inactivate_expired_subscriptions():

0 commit comments

Comments
 (0)