|
14 | 14 | import user.models |
15 | 15 | import user.permissions |
16 | 16 | import user.serializers |
| 17 | +import user.services |
17 | 18 |
|
18 | 19 |
|
19 | 20 | class UserSignUpView( |
@@ -296,138 +297,38 @@ def destroy(self, request, *args, **kwargs): |
296 | 297 |
|
297 | 298 |
|
298 | 299 | class PromoActivateView(rest_framework.views.APIView): |
299 | | - permission_classes = [rest_framework.permissions.IsAuthenticated] |
300 | | - allowed_methods = ['post', 'options', 'head'] |
301 | | - |
302 | | - def _validate_targeting(self, user_, promo): |
303 | | - user_age = user_.other.get('age') |
304 | | - user_country = user_.other.get('country').lower() |
305 | | - target = promo.target |
306 | | - |
307 | | - if not target: |
308 | | - return None |
309 | | - |
310 | | - if target.get('country') and user_country != target['country'].lower(): |
311 | | - return rest_framework.response.Response( |
312 | | - {'error': 'Targeting mismatch: country.'}, |
313 | | - status=rest_framework.status.HTTP_403_FORBIDDEN, |
314 | | - ) |
315 | | - if target.get('age_from') and ( |
316 | | - user_age is None or user_age < target['age_from'] |
317 | | - ): |
318 | | - return rest_framework.response.Response( |
319 | | - {'error': 'Targeting mismatch: age.'}, |
320 | | - status=rest_framework.status.HTTP_403_FORBIDDEN, |
321 | | - ) |
322 | | - if target.get('age_until') and ( |
323 | | - user_age is None or user_age > target['age_until'] |
324 | | - ): |
325 | | - return rest_framework.response.Response( |
326 | | - {'error': 'Targeting mismatch: age.'}, |
327 | | - status=rest_framework.status.HTTP_403_FORBIDDEN, |
328 | | - ) |
329 | | - return None |
330 | | - |
331 | | - def _validate_is_active(self, promo): |
332 | | - if not promo.active or not promo.is_active: |
333 | | - return rest_framework.response.Response( |
334 | | - {'error': 'Promo is not active.'}, |
335 | | - status=rest_framework.status.HTTP_403_FORBIDDEN, |
336 | | - ) |
337 | | - return None |
338 | | - |
339 | | - def _validate_antifraud(self, user_, promo): |
340 | | - antifraud_response = ( |
341 | | - user.antifraud_service.antifraud_service.get_verdict( |
342 | | - user_.email, |
343 | | - str(promo.id), |
344 | | - ) |
345 | | - ) |
346 | | - if not antifraud_response.get('ok'): |
347 | | - return rest_framework.response.Response( |
348 | | - {'error': 'Activation forbidden by anti-fraud system.'}, |
349 | | - status=rest_framework.status.HTTP_403_FORBIDDEN, |
350 | | - ) |
351 | | - return None |
| 300 | + """ |
| 301 | + Activates a promo code for the user. |
| 302 | + All business logic is encapsulated in PromoActivationService. |
| 303 | + """ |
352 | 304 |
|
353 | | - def _activate_code(self, user_, promo): |
354 | | - try: |
355 | | - with django.db.transaction.atomic(): |
356 | | - promo_for_update = ( |
357 | | - business.models.Promo.objects.select_for_update().get( |
358 | | - id=promo.id, |
359 | | - ) |
360 | | - ) |
361 | | - promo_code_value = None |
362 | | - |
363 | | - if ( |
364 | | - promo_for_update.mode |
365 | | - == business.constants.PROMO_MODE_COMMON |
366 | | - ): |
367 | | - if ( |
368 | | - promo_for_update.used_count |
369 | | - < promo_for_update.max_count |
370 | | - ): |
371 | | - promo_for_update.used_count += 1 |
372 | | - promo_for_update.save(update_fields=['used_count']) |
373 | | - promo_code_value = promo_for_update.promo_common |
374 | | - else: |
375 | | - raise ValueError('No common codes left.') |
376 | | - |
377 | | - elif ( |
378 | | - promo_for_update.mode |
379 | | - == business.constants.PROMO_MODE_UNIQUE |
380 | | - ): |
381 | | - unique_code = promo_for_update.unique_codes.filter( |
382 | | - is_used=False, |
383 | | - ).first() |
384 | | - if unique_code: |
385 | | - unique_code.is_used = True |
386 | | - unique_code.used_at = django.utils.timezone.now() |
387 | | - unique_code.save(update_fields=['is_used', 'used_at']) |
388 | | - promo_code_value = unique_code.code |
389 | | - else: |
390 | | - raise ValueError('No unique codes left.') |
391 | | - |
392 | | - if promo_code_value: |
393 | | - user.models.PromoActivationHistory.objects.create( |
394 | | - user=user_, |
395 | | - promo=promo, |
396 | | - ) |
397 | | - serializer = user.serializers.PromoActivationSerializer( |
398 | | - data={'promo': promo_code_value}, |
399 | | - ) |
400 | | - serializer.is_valid(raise_exception=True) |
401 | | - return rest_framework.response.Response( |
402 | | - serializer.data, |
403 | | - status=rest_framework.status.HTTP_200_OK, |
404 | | - ) |
405 | | - |
406 | | - raise ValueError('Promo code could not be activated.') |
407 | | - |
408 | | - except ValueError as e: |
409 | | - return rest_framework.response.Response( |
410 | | - {'error': str(e)}, |
411 | | - status=rest_framework.status.HTTP_403_FORBIDDEN, |
412 | | - ) |
| 305 | + permission_classes = [rest_framework.permissions.IsAuthenticated] |
413 | 306 |
|
414 | 307 | def post(self, request, id): |
415 | 308 | promo = django.shortcuts.get_object_or_404( |
416 | 309 | business.models.Promo, |
417 | 310 | id=id, |
418 | 311 | ) |
419 | | - user_ = request.user |
420 | 312 |
|
421 | | - if (response := self._validate_targeting(user_, promo)) is not None: |
422 | | - return response |
| 313 | + service = user.services.PromoActivationService( |
| 314 | + user=request.user, |
| 315 | + promo=promo, |
| 316 | + ) |
423 | 317 |
|
424 | | - if (response := self._validate_is_active(promo)) is not None: |
425 | | - return response |
| 318 | + try: |
| 319 | + promo_code = service.activate() |
426 | 320 |
|
427 | | - if (response := self._validate_antifraud(user_, promo)) is not None: |
428 | | - return response |
| 321 | + serializer = user.serializers.PromoActivationSerializer( |
| 322 | + data={'promo': promo_code}, |
| 323 | + ) |
| 324 | + serializer.is_valid(raise_exception=True) |
| 325 | + return rest_framework.response.Response(serializer.data) |
429 | 326 |
|
430 | | - return self._activate_code(user_, promo) |
| 327 | + except user.services.PromoActivationError as e: |
| 328 | + return rest_framework.response.Response( |
| 329 | + {'error': e.detail}, |
| 330 | + status=e.status_code, |
| 331 | + ) |
431 | 332 |
|
432 | 333 |
|
433 | 334 | class PromoHistoryView(rest_framework.generics.ListAPIView): |
|
0 commit comments