LIL-583 реферальная система

remotes/origin/hotfix/LIL-691
gzbender 8 years ago
parent 397bc9c2d3
commit d63709b252
  1. 12
      api/v1/views.py
  2. 7
      apps/auth/views.py
  3. 103
      apps/payment/models.py
  4. 8
      apps/user/models.py
  5. 14
      project/context_processors.py
  6. 1
      project/settings.py
  7. 32
      project/templates/blocks/popup_auth.html
  8. 9
      project/templates/lilcity/index.html
  9. 10
      project/templates/lilcity/main.html
  10. 5
      project/views.py
  11. 8
      web/src/js/modules/api.js
  12. 27
      web/src/js/modules/popup.js

@ -1,7 +1,7 @@
from django.contrib.auth import get_user_model
from rest_framework import status, views, viewsets, generics
from rest_framework.decorators import detail_route, list_route
from rest_framework.decorators import detail_route, list_route, action
from rest_framework.response import Response
from . import ExtendedModelViewSet
@ -429,3 +429,13 @@ class PaymentViewSet(ExtendedModelViewSet):
'amount', 'created_at',
)
search_fields = ('user__email', 'user__first_name', 'user__last_name',)
@action(methods=['get'], detail=False, url_path='calc-amount')
def calc_amount(self, request, pk=None):
user = request.query_params.get('user')
course = request.query_params.get('course')
weekdays = request.query_params.getlist('weekdays[]')
user = user and User.objects.get(pk=user)
course = course and Course.objects.get(pk=course)
return Response(Payment.calc_amount(user=user, course=course, weekdays=weekdays))

@ -19,6 +19,7 @@ from django.shortcuts import redirect
from apps.notification.utils import send_email
from apps.config.models import Config
from apps.user.models import Referral
from .forms import LearnerRegistrationForm
from .tokens import verification_email_token
@ -51,6 +52,11 @@ class LearnerRegistrationView(FormView):
user.last_name = last_name
user.set_password(password)
referrer = self.request.session.get('referrer')
if referrer:
Referral.objects.create(referral=user, referrer=referrer, discount=Config.REFERRAL_DISCOUNT,
referrer_cashback=Config.REFERRER_CASHBACK)
self.request.session['referrer'] = None
user.save()
login(self.request, user)
@ -81,6 +87,7 @@ class LoginView(FormView):
def form_valid(self, form):
login(self.request, form.get_user())
self.request.session['referrer'] = None
return JsonResponse({"success": True})
def form_invalid(self, form):

@ -1,5 +1,5 @@
from decimal import Decimal
import arrow
from paymentwall import Pingback
from polymorphic.models import PolymorphicModel
from polymorphic.managers import PolymorphicManager
@ -108,6 +108,62 @@ class Payment(PolymorphicModel):
verbose_name_plural = 'Платежи'
ordering = ('created_at',)
@classmethod
def calc_amount(cls, course_payment=None, school_payment=None, user=None, course=None, weekdays=None,
add_days=False, date_start=None, date_end=None):
if course_payment:
course = course_payment.course
user = course_payment.user
if school_payment:
user = school_payment.user
weekdays = school_payment.weekdays
add_days = school_payment.add_days
date_start = school_payment.date_start
date_end = school_payment.date_end
referral_discount = 0
referrer_cashback = 0
if hasattr(user, 'referral') and not user.referral.payment:
referral_discount = user.referral.discount
referrer_cashback = user.referral.referrer_cashback
discount = 0
if course:
price = course.price
else:
aggregate = SchoolSchedule.objects.filter(
weekday__in=weekdays,
).aggregate(
models.Sum('month_price'),
)
if add_days:
_school_payment = SchoolPayment.objects.filter(
add_days=False,
date_start__lte=date_start,
date_end__gte=date_end,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
).last()
weekday_count = weekday_in_date_range(date_start, date_end, weekdays[0])
all_weekday_count = weekday_in_date_range(_school_payment.date_start, _school_payment.date_end,
weekdays[0])
price = Decimal(aggregate.get('month_price__sum', 0) * weekday_count // all_weekday_count)
else:
price = Decimal(aggregate.get('month_price__sum', 0))
if price >= config.SERVICE_DISCOUNT_MIN_AMOUNT:
discount = config.SERVICE_DISCOUNT
referral_discount = (price - discount) * referral_discount / 100
referrer_cashback = (price - discount) * referrer_cashback / 100
amount = price - discount - referral_discount
return {
'price': price,
'amount': amount,
'referral_discount': referral_discount,
'referrer_cashback': referrer_cashback,
'discount': discount,
}
def calc_commission(self):
return self.amount * config.SERVICE_COMMISSION / 100
@ -136,7 +192,8 @@ class CoursePayment(Payment):
verbose_name_plural = 'Платежи за курсы'
def save(self, *args, **kwargs):
self.amount = self.course.price
amount_data = Payment.calc_amount(course_payment=self)
self.amount = amount_data.get('amount')
super().save(*args, **kwargs)
author_balance = getattr(self, 'authorbalance', None)
if not author_balance:
@ -148,6 +205,13 @@ class CoursePayment(Payment):
else:
author_balance.amount = self.amount
author_balance.save()
# Если юзер реферал и нет платежа, где применялась скидка
if hasattr(self.user, 'referral') and not self.user.referral.payment:
# Отправляем кэшбэк
self.user.referral.cashback(amount_data.get('referrer_cashback'))
# Платеж - как сигнал, что скидка применилась
self.user.referral.payment = self
self.user.referral.save()
class SchoolPayment(Payment):
@ -168,33 +232,16 @@ class SchoolPayment(Payment):
return days
def save(self, *args, **kwargs):
aggregate = SchoolSchedule.objects.filter(
weekday__in=self.weekdays,
).aggregate(
models.Sum('month_price'),
)
if self.add_days:
_school_payment = SchoolPayment.objects.filter(
add_days=False,
date_start__lte=self.date_start,
date_end__gte=self.date_end,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
).last()
weekday_count = weekday_in_date_range(self.date_start, self.date_end, self.weekdays[0])
all_weekday_count = weekday_in_date_range(_school_payment.date_start, _school_payment.date_end, self.weekdays[0])
month_price_sum = aggregate.get('month_price__sum', 0) * weekday_count // all_weekday_count
else:
month_price_sum = aggregate.get('month_price__sum', 0)
if month_price_sum >= config.SERVICE_DISCOUNT_MIN_AMOUNT:
discount = config.SERVICE_DISCOUNT
else:
discount = 0
self.amount = month_price_sum - discount
amount_data = Payment.calc_amount(school_payment=self)
self.amount = amount_data.get('amount')
super().save(*args, **kwargs)
# Если юзер реферал и нет платежа, где применялась скидка
if not self.add_days and hasattr(self.user, 'referral') and not self.user.referral.payment:
# Отправляем кэшбэк
self.user.referral.cashback(amount_data.get('referrer_cashback'))
# Платеж - как сигнал, что скидка применилась
self.user.referral.payment = self
self.user.referral.save()
@property
def date_end_humanize(self):

@ -12,7 +12,6 @@ from django.utils.translation import gettext_lazy as _
from api.v1 import serializers
from apps.notification.utils import send_email
from apps.user.tasks import user_to_mixpanel
@ -259,11 +258,16 @@ class EmailSubscription(models.Model):
class Referral(models.Model):
referral = models.OneToOneField(User, on_delete=models.CASCADE)
referral = models.OneToOneField(User, on_delete=models.CASCADE, related_name='referral')
referrer = models.ForeignKey(User, on_delete=models.CASCADE, related_name='referrals')
discount = models.IntegerField()
referrer_cashback = models.IntegerField()
payment = models.OneToOneField('payment.Payment', null=True, blank=True, on_delete=models.CASCADE)
class Meta:
verbose_name = 'Реферал'
verbose_name_plural = 'Рефералы'
def cashback(self, amount):
# TODO
pass

@ -1,6 +1,10 @@
from django.contrib.auth import get_user_model
from apps.config.models import Config
from apps.content.models import Baner
User = get_user_model()
def config(request):
return {"config": Config.load()}
@ -8,3 +12,13 @@ def config(request):
def baner(request):
return {'baner': Baner.objects.filter(use=True).first()}
def referrer(request):
referrer = request.session.get('referrer')
if referrer:
try:
referrer = User.objects.get(pk=referrer)
except:
referrer = None
return {'referrer': referrer}

@ -93,6 +93,7 @@ TEMPLATES = [
'context_processors': [
'project.context_processors.config',
'project.context_processors.baner',
'project.context_processors.referrer',
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',

@ -1,5 +1,5 @@
{% load static %}
<div class="popup js-popup-auth {% if referrer %}open visible{% endif %}">
<div class="popup js-popup-auth {% if is_registration %}open visible{% endif %}">
<div class="popup__wrap js-popup-wrap">
<button class="popup__close js-popup-close">
<svg class="icon icon-close">
@ -10,25 +10,14 @@
<div class="auth js-auth">
<div class="auth__login js-auth-login">
<div class="auth__nav">
<a class="auth__type js-auth-type {% if not referrer %}active{% endif %}" href="#">Войти</a>
<a class="auth__type js-auth-type {% if referrer %}active{% endif %}" href="#">РЕГИСТРАЦИЯ</a>
<a class="auth__type js-auth-type {% if not is_registration %}active{% endif %}" href="#">Войти</a>
<a class="auth__type js-auth-type {% if is_registration %}active{% endif %}" href="#">РЕГИСТРАЦИЯ</a>
</div>
<div class="auth__body">
<form id="learner-auth-form" method="post" action="{% url 'lilcity:login' %}">
{% csrf_token %}
<div class="auth__tab js-auth-tab" style="display: block;">
<div class="auth__tab js-auth-tab" {% if not is_registration %}style="display: block;"{% endif %}>
<div class="auth__enter js-auth-enter">
{% if referrer %}
<input type="hidden" name="referrer" value="{{ referrer.id }}">
<div style="margin: 0 0 20px;">
<a href="{% url 'user' referrer.id %}" target="_blank" style="
font-family: 'ProximaNova-Bold', serif;
font-size: 10px;
letter-spacing: 1px;
text-transform: uppercase;">
{{ referrer.get_full_name }}</a> дарит вам скидку 10% на первую покупку!
</div>
{% endif %}
<div id="learner-auth-field__username" class="auth__field field learner-auth-form__field">
<div class="field__label">ПОЧТА</div>
<div class="field__wrap">
@ -74,7 +63,18 @@
</form>
<form id="learner-registration-form" method="post" action="{% url 'lilcity:registration-learner' %}">
{% csrf_token %}
<div class="auth__tab js-auth-tab">
<div class="auth__tab js-auth-tab" {% if is_registration %}style="display: block;"{% endif %}>
{% if referrer %}
<input type="hidden" name="referrer" value="{{ referrer.id }}">
<div style="margin: 0 0 20px;">
<a href="{% url 'user' referrer.id %}" target="_blank" style="
font-family: 'ProximaNova-Bold', serif;
font-size: 10px;
letter-spacing: 1px;
text-transform: uppercase;">
{{ referrer.get_full_name }}</a> дарит вам скидку 10% на первую покупку!
</div>
{% endif %}
<div class="auth__fieldset">
<div id="learner-registration-field__first-name" class="auth__field field learner-registration-form__field">
<div class="field__label">ИМЯ</div>

@ -130,6 +130,14 @@
{% include "templates/blocks/popup_course_lock.html" %}
{% include "templates/blocks/popup_subscribe.html" %}
</div>
<script>
window.STORE = {
accessToken: '{{ request.user.auth_token }}',
user: {
id: '{{ request.user.id }}',
}
};
</script>
<script type="text/javascript" src={% static "app.js" %}></script>
<script>
var schoolDiscount = parseFloat({{ config.SERVICE_DISCOUNT }});
@ -140,5 +148,6 @@
(function(w, d, s, h, id) { w.roistatProjectId = id; w.roistatHost = h; var p = d.location.protocol == "https:" ? "https://" : "http://"; var u = /^.*roistat_visit=[^;]+(.*)?$/.test(d.cookie) ? "/dist/module.js" : "/api/site/1.0/"+id+"/init"; var js = d.createElement(s); js.charset="UTF-8"; js.async = 1; js.src = p+h+u; var js2 = d.getElementsByTagName(s)[0]; js2.parentNode.insertBefore(js, js2);})(window, document, 'script', 'cloud.roistat.com', '{% setting "ROISTAT_COUNTER_ID" %}');
</script>
{% block foot %}{% endblock foot %}
{% block body_js %}{% endblock body_js %}
</body>
</html>

@ -13,3 +13,13 @@
{% include "templates/blocks/teachers.html" %}
{% include "templates/blocks/partners.html" %}
{% endblock content %}
{% block body_js %}
{% if is_registration %}
<script>
$(document).ready(function () {
$('.js-header-enter').click();
});
</script>
{% endif %}
{% endblock body_js %}

@ -75,12 +75,13 @@ class IndexView(TemplateView):
if referrer:
try:
referrer = short_url.decode_url(referrer)
referrer = User.objects.get(pk=referrer)
referrer = User.objects.get(pk=referrer).id
except:
referrer = None
self.request.session['referrer'] = referrer
context.update({
'referrer': referrer,
'is_registration': bool(referrer),
'online': online,
'online_coming_soon': online_coming_soon,
'school_schedule': school_schedule,

@ -502,4 +502,12 @@ export const api = {
}
});
},
getPaymentAmount: (params) => {
return api.get('/api/v1/payments/calc-amount', {
params: params,
headers: {
'Authorization': `Token ${window.STORE.accessToken}`,
}
});
}
};

@ -1,9 +1,10 @@
import $ from 'jquery';
import {api} from './api';
var selectedWeekdays = {};
$(document).ready(function () {
let body = $('body'),
popup;
popup = $('.popup.visible.open');
body.on('click', '[data-popup]', function(e){
e.preventDefault();
@ -100,6 +101,7 @@ $(document).ready(function () {
});
function updateCart(){
var $orderPrice = $('.order_price_text');
var days = ['', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье'];
var weekdays = [], daysText = [], price = 0;
for(var i in selectedWeekdays) {
@ -108,13 +110,22 @@ $(document).ready(function () {
daysText.push(days[i]);
}
var text = '';
if(schoolAmountForDiscount <= price) {
text = '<del>'+price+'</del> '+(price-schoolDiscount)+'р.';
} else {
text = price+'p.';
if(weekdays.length){
api.getPaymentAmount({ user: window.STORE.user.id, weekdays: weekdays })
.then((response) => {
var text = '';
if(response.data.price != response.data.amount) {
text = '<del>'+response.data.price+'</del> '+response.data.amount+'р.';
} else {
text = response.data.amount+'p.';
}
$orderPrice.html(text);
});
}
$('.order_price_text').html(text);
else {
$orderPrice.html('0p.');
}
$('.order__days').html((daysText.length) ? daysText.join(', '):'Ничего не выбрано');
var link = $('.but_btn_popup').data('link');
@ -124,4 +135,4 @@ $(document).ready(function () {
}
updateCart();
});
});

Loading…
Cancel
Save