- Funcionalidad para generar codigos de invitacion - ya esta
- Una persona puede invitar a varias personas a unirse
- Por cada seis personas que se haya invitado, se sumaran 365 dias a tu suscripcion
- Una persona que uso un codigo de invitacion para crear su cuenta, recibe 15 dias a sus suscripcion
-
La logica de aplicacion de codigos esta en
-
src/apps/codes
- models.py: Coneccion a la tabla de la base de datos donde se almacena la informacion de aplicacion de codigos de invitacion
- serializers.py: Logica para serializar datos y volverlo compatibles con el modelo de la base de datos y el objeto JSON que se retornara al frontend
- urls.py: Direcciones de los servicios de codigos
- views.py: Logica del servicio de codigos
-
src/apps/subscriptions
- models.py: Modelo de base de datos
- serializers.py: serializador y calculo de dias restantes de tu suscripcion
- urls.py: Direccion de los servicios de suscripcion
- views.py: Servicio que devuelve datos de la suscripcion
-
src/apps/payments
- views.py: logica de cobros
class InvitationCode(models.Model):
'''
Modelo de la tabla de codigos (No es necesario modificarla)
'''
# ID del profile de usuario
profile = models.OneToOneField(Profile, on_delete=models.CASCADE, null=True)
# Codigo unico por profile
code = models.CharField(max_length=8, unique=True, db_index=True)
# Aqui se lleva la cuenta de cuantas veces han usado tu codigo de invitacion
application_count = models.IntegerField(default=0)
# Este campo no lo vamos a usar (es de la vieja logica de negocios)
credits = models.IntegerField(default=0)
# Este campo se cambia a True si usaste el codigo de invitacion de alguien
invitation = models.BooleanField(default=False)
# Este campo se cambia a True si se te añadieron tus 15 dias por haber sido invitado
invitation_used = models.BooleanField(default=False)
class InvitationCodeView(APIView):
permission_classes = (IsAuthenticated,)
def get_remaining_days(self, user, data):
'''
Esta funcion devuelve los dias restantes de tu suscripcion
'''
remaining_days = get_object_or_404(Subscription,
Q(profile__single=user) |
Q(profile__couple__id_person_one=user) |
Q(profile__couple__id_person_two=user)
)
days_serializer = SubscriptionSerializer(remaining_days)
data['days_remaining'] = days_serializer.data.get('days_remaining')
return data
def get(self, request):
'''Servicio para devolver tu codigo de invitacion'''
try:
code = InvitationCode.objects.get(
Q(profile__single__person_id=request.user.person_id) |
Q(profile__couple__id_person_one__person_id=request.user.person_id) |
Q(profile__couple__id_person_two__person_id=request.user.person_id)
)
except InvitationCode.DoesNotExist:
code = None
if code:
serializer = InvitationCodeSerializer(code)
data = self.get_remaining_days(request.user, serializer.data)
return Response(data)
data = dict(
profile=get_profile(request.user.person_id).id,
code=code_generator(length=8, chars=string.ascii_uppercase + string.digits),
application_count=0,
credits=0
)
serializer = InvitationCodeSerializer(data=data)
if serializer.is_valid():
serializer.save()
data = self.get_remaining_days(request.user, serializer.data)
return Response(data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def apply_code(self, invitation_code, person_id):
'''
Funcion para aplicacion de codigo
(Parte de la nueva logica de negocio va aqui)
'''
try:
his_code = get_object_or_404(InvitationCode, code=invitation_code)
my_code = get_object_or_404(InvitationCode, profile=get_profile(person_id).id)
if my_code.credits == 0 and my_code.application_count == 0:
his_code.credits += 1
his_code.application_count += 1
his_code.save()
my_code.credits += 1
my_code.invitation = True
my_code.save()
return my_code
except InvitationCode.DoesNotExist:
raise Http404
@error_handler
def post(self, request):
'''Servicio de aplicacion de codigo'''
invitation_code = request.data['code']
my_code = self.apply_code(invitation_code, request.user.person_id)
serializer = InvitationCodeSerializer(my_code)
return Response(serializer.data)
class ValidateCode(APIView):
permission_classes = (AllowAny,)
@error_handler
def post(self, request):
'''
Servicio para validar que un codigo exista
'''
try:
code = InvitationCode.objects.get(code=request.data['code'])
response = Response({'valid': True})
except InvitationCode.DoesNotExist:
response = Response({'valid': False})
return response
class ApplyDiscountView(APIView):
permission_classes = (IsAuthenticated,)
@error_handler
def patch(self, request):
'''
Antigua logica de necocio para aplicar descuentos
Funcionaba asi:
Por cada tres personas que hayas invitado aliberti, se te anexaban $149 pesos de descuento
Funcionalidad nueva:
Por cada seis personas que hayas invitado, se te suman 365 dias a tu tiempo de suscripcion
'''
credits = request.data['credits']
code = get_object_or_404(InvitationCode, profile=get_profile(request.user.person_id).id)
if code.credits >= credits and credits % 3 == 0:
code.credits -= credits
code.save()
serializer = InvitationCodeSerializer(code)
return Response(serializer.data)
raise ApplyDiscountException('The discount is not applicable')
class MakeMembershipAmountCalc(APIView):
permission_classes = (IsAuthenticated,)
@error_handler
def post(self, request):
'''
Antiguo servicio que daba el calculo del descuento total acumulado por cada tres personas que hayas invitado
Este servicio ya no va
'''
apply_discount = request.data['apply_discount']
membership = request.data['membership']
counter = 0
applicables = 0
if membership == '0799':
total_cost = 799
elif membership == '1899':
total_cost = 1899
if apply_discount:
invitation_code = InvitationCode.objects.get(
Q(profile__single=request.user) |
Q(profile__couple__id_person_one=request.user) |
Q(profile__couple__id_person_two=request.user)
)
if invitation_code.invitation == True and invitation_code.invitation_used == False:
counter += 50
invitation_code.invitation_used = True
if (invitation_code.credits // 3) > total_cost // 149:
if total_cost == 799:
applicables = 5
elif total_cost == 1899:
applicables = 12
else:
applicables = (invitation_code.credits // 3)
credits = applicables * 149
total_cost -= (counter + credits)
return Response({"total_cost": total_cost, "aplicable_credits": applicables})
class Subscription(models.Model):
'''
Modelo de la base de datos
'''
profile = models.OneToOneField(Profile, on_delete=models.CASCADE)
application_date = models.DateTimeField(auto_now_add=True)
expiration_date = models.DateTimeField()
credits = models.IntegerField(default=0)
membership_days = models.DateTimeField(null=True)
membership_application_date = models.DateTimeField(null=True)
lifetime = models.BooleanField(default=False)
def set_subscription_start(sender, **kwargs):
'''
Funcion usada como trigger para setear el inicio de una suscripcion
Se activa desde src/apps/swinger_profile/models.py linea 128
'''
if kwargs['created']:
new_user = kwargs['instance']
subscription = Subscription.objects.create(
profile=new_user,
expiration_date=free_month()
)
def current_time():
'''
Devuelve la fecha actual
'''
return timezone.now()
def free_month():
'''
Devuelve la cuenta de un mes gratis
'''
return current_time()+datetime.timedelta(days=30)
class SubscriptionSerializer(serializers.ModelSerializer):
'''
Serializador y calculo de dias restantes
'''
days_remaining = serializers.SerializerMethodField()
class Meta:
model = Subscription
fields = ('days_remaining',)
def get_days_remaining(self, obj):
'''
Aqui va la logica del calculo de dias restantes de una suscripcion
'''
if obj.lifetime == True:
return '∞'
# days = (obj.expiration_date - obj.application_date).days + 1
days = (obj.expiration_date - current_time()).days
if obj.membership_days != None:
membership_days = (obj.membership_days - obj.membership_application_date - current_time()).days
else:
membership_days = 0
total = days + membership_days
return str(total) if total > 0 else str(0)
class ChargesAPIView(APIView):
permission_classes = (IsAuthenticated,)
@property
def order_id(self):
return 'oid_' + str(uuid.uuid4().fields[-1])[:10]
@openpay_errors
def post(self, request):
'''Create a payment'''
customer_id = request.data.get('customer').get('id')
card_data = request.data.get('card')
membership = request.data.get('membership')
if membership not in ['0799', '1899']:
return Response(400, status=status.HTTP_400_BAD_REQUEST)
customer = openpay.Customer.retrieve(customer_id)
card_obj = get_object_or_404(Card, card__id=card_data.get('source_id'))
charge = customer.charges.create(
method=card_data.get('method'),
source_id=card_data.get('source_id'),
device_session_id=card_data.get('device_session_id'),
amount=card_data.get('amount'),
description=card_data.get('description'),
order_id=self.order_id
)
print(membership)
charge_data = {
'card': card_obj.id,
'charge': charge,
'membership': membership
}
serializer = ChargeSerializer(data=charge_data)
serializer.is_valid(raise_exception=True)
serializer.save()
self.apply_credits(request.user.person_id, membership, request.user.email)
return Response(serializer.data)
def apply_credits(self, person_id, membership, email):
'''
En esta funcion se realizaba la aplicacion de tiempo comprado dependiendo del paquete elegido
'''
profile = get_profile(person_id)
if membership == '0799':
self.anual(profile)
send_membership_mail(email, membership)
elif membership == '1899':
self.lifetime(profile)
send_membership_mail(email, membership)
else:
pass
def anual(self, profile):
'''
aplicacion de plan anual
'''
subscription = self.get_subscriptions(profile)
if subscription.membership_days != None:
remaining_days = (subscription.membership_days - timezone.now()).days
else:
remaining_days = 0
today = timezone.now()
nextD = today + datetime.timedelta(days=(365 + remaining_days))
subscription.membership_application_date = today
subscription.membership_days = nextD
subscription.save()
return subscription
def lifetime(self, profile):
'''
aplicacion de plan de por vida (Ya no va, ahora el plan debe ser de aplicacion mensual)
'''
subscription = self.get_subscriptions(profile)
subscription.lifetime = True
subscription.save()
return subscription
def get_subscriptions(self, profile):
subscription = get_object_or_404(Subscription, profile=profile)
return subscription