LIL-263,269,270,272,274

remotes/origin/hasaccess
Ivlev Denis 8 years ago
parent 0f5b11f3d7
commit 34cf929b60
  1. 8
      apps/notification/templates/notification/email/decline_withdrawal.html
  2. 36
      apps/payment/migrations/0013_auto_20180304_1757.py
  3. 47
      apps/payment/models.py
  4. 8
      apps/payment/views.py
  5. 25
      apps/user/fields.py
  6. 7
      apps/user/forms.py
  7. 75
      apps/user/templates/user/payment-history.html
  8. 41
      apps/user/views.py

@ -0,0 +1,8 @@
{% extends "notification/email/_base.html" %}
{% block content %}
<p style="margin: 0 0 20px">К сожалению вам отказано в выводе средств!</p>
<div style="margin-bottom: 10px;">
<p>{{ author_balance.cause }}</p>
</div>
{% endblock content %}

@ -0,0 +1,36 @@
# Generated by Django 2.0.2 on 2018-03-04 17:57
import django.core.validators
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('payment', '0012_auto_20180302_0740'),
]
operations = [
migrations.AddField(
model_name='authorbalance',
name='card',
field=models.CharField(blank=True, max_length=20, null=True, validators=[django.core.validators.RegexValidator(regex='^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\\\d{3})\\d{11})$')]),
),
migrations.AddField(
model_name='authorbalance',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='authorbalance',
name='declined_send_at',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='authorbalance',
name='update_at',
field=models.DateTimeField(auto_now=True),
),
]

@ -1,6 +1,7 @@
from django.db import models from django.db import models
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.postgres.fields import ArrayField, JSONField from django.contrib.postgres.fields import ArrayField, JSONField
from django.core.validators import RegexValidator
from constance import config from constance import config
from paymentwall import Pingback from paymentwall import Pingback
@ -8,9 +9,12 @@ from polymorphic.models import PolymorphicModel
from apps.course.models import Course from apps.course.models import Course
from apps.school.models import SchoolSchedule from apps.school.models import SchoolSchedule
from apps.notification.utils import send_email
User = get_user_model() User = get_user_model()
CREDIT_CARD_RE = r'^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\d{11})$'
class AuthorBalance(models.Model): class AuthorBalance(models.Model):
IN = 0 IN = 0
@ -35,11 +39,32 @@ class AuthorBalance(models.Model):
status = models.PositiveSmallIntegerField('Статус', choices=STATUS_CHOICES, default=0) status = models.PositiveSmallIntegerField('Статус', choices=STATUS_CHOICES, default=0)
payment = models.OneToOneField('Payment', on_delete=models.CASCADE, null=True, blank=True, verbose_name='Платёж') payment = models.OneToOneField('Payment', on_delete=models.CASCADE, null=True, blank=True, verbose_name='Платёж')
cause = models.TextField('Причина отказа', null=True, blank=True) cause = models.TextField('Причина отказа', null=True, blank=True)
card = models.CharField(max_length=20, validators=[RegexValidator(regex=CREDIT_CARD_RE)], null=True, blank=True)
declined_send_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
class Meta: class Meta:
verbose_name = 'Баланс' verbose_name = 'Баланс'
verbose_name_plural = 'Балансы' verbose_name_plural = 'Балансы'
def save(self, *args, **kwargs):
self.commission = self.calc_commission()
if self.type == self.OUT:
if self.status == self.DECLINED and not self.declined_send_at:
send_email(
'Отказ вывода средств',
self.author.email,
'notification/email/decline_withdrawal.html',
author_balance=self,
)
self.declined_send_at = now()
super.save(*args, **kwargs)
def calc_commission(self):
return self.amount * config.SERVICE_COMMISSION / 100
class Payment(PolymorphicModel): class Payment(PolymorphicModel):
PW_STATUS_CHOICES = ( PW_STATUS_CHOICES = (
@ -66,6 +91,22 @@ class Payment(PolymorphicModel):
def calc_commission(self): def calc_commission(self):
return self.amount * config.SERVICE_COMMISSION / 100 return self.amount * config.SERVICE_COMMISSION / 100
def is_deliverable(self):
return self.status in [
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
]
def is_under_review(self):
return self.status == Pingback.PINGBACK_TYPE_RISK_UNDER_REVIEW
def is_cancelable(self):
return self.status in [
Pingback.PINGBACK_TYPE_NEGATIVE,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_DECLINED,
]
class CoursePayment(Payment): class CoursePayment(Payment):
course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name='Курс', related_name='payments') course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name='Курс', related_name='payments')
@ -83,11 +124,9 @@ class CoursePayment(Payment):
author=self.course.author, author=self.course.author,
amount=self.amount, amount=self.amount,
payment=self, payment=self,
commission=self.calc_commission(),
) )
else: else:
author_balance.amount = self.amount author_balance.amount = self.amount
author_balance.commission = self.calc_commission()
author_balance.save() author_balance.save()
@ -100,6 +139,10 @@ class SchoolPayment(Payment):
verbose_name = 'Платеж за школу' verbose_name = 'Платеж за школу'
verbose_name_plural = 'Платежи за школу' verbose_name_plural = 'Платежи за школу'
def __str__(self):
days = ', '.join([dict(SchoolSchedule.WEEKDAY_CHOICES).get(weekday, '') for weekday in sorted(self.weekdays)])
return days
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
aggregate = SchoolSchedule.objects.filter( aggregate = SchoolSchedule.objects.filter(
weekday__in=self.weekdays, weekday__in=self.weekdays,

@ -1,6 +1,8 @@
import logging import logging
import json import json
from datetime import timedelta
from django.contrib import messages from django.contrib import messages
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
@ -8,6 +10,7 @@ from django.views.generic import View, TemplateView
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.timezone import now
from paymentwall import Pingback, Product, Widget from paymentwall import Pingback, Product, Widget
@ -123,10 +126,13 @@ class PaymentwallCallbackView(View):
) )
payment.status = pingback.get_type() payment.status = pingback.get_type()
payment.data = payment_raw_data payment.data = payment_raw_data
if pingback.is_deliverable() and product_type_name == 'school':
payment.date_start = now()
payment.date_end = now() + timedelta(days=30)
payment.save() payment.save()
author_balance = getattr(payment, 'author_balance', None) author_balance = getattr(payment, 'author_balance', None)
if author_balance: if author_balance and author_balance.type == AuthorBalance.IN:
if pingback.is_deliverable(): if pingback.is_deliverable():
payment.author_balance.status = AuthorBalance.ACCEPTED payment.author_balance.status = AuthorBalance.ACCEPTED
elif pingback.is_under_review(): elif pingback.is_under_review():

@ -0,0 +1,25 @@
import re
from django import forms
from django.utils.translation import ugettext_lazy as _
CREDIT_CARD_RE = r'^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\d{11})$'
class CreditCardField(forms.CharField):
"""
Form field that validates credit card numbers.
"""
default_error_messages = {
'required': _(u'Номер карты обязателен.'),
'invalid': _(u'Неверный номер карты.'),
}
def clean(self, value):
value = value.replace(' ', '').replace('-', '')
if self.required and not value:
raise forms.utils.ValidationError(self.error_messages['required'])
if value and not re.match(CREDIT_CARD_RE, value):
raise forms.utils.ValidationError(self.error_messages['invalid'])
return value

@ -1,6 +1,8 @@
from django import forms from django import forms
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from .fields import CreditCardField
User = get_user_model() User = get_user_model()
@ -47,3 +49,8 @@ class UserEditForm(forms.ModelForm):
'vkontakte', 'vkontakte',
'photo', 'photo',
) )
class WithdrawalForm(forms.Form):
amount = forms.DecimalField(required=True, min_value=2000)
card = CreditCardField(required=True)

@ -22,59 +22,98 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if messages %}
<div class="section section_gray section_menu">
<div class="section__center center center_xs">
{% for message in messages %}
<div class="message message_{{ message.tags }}">{{ message }}</div>
{% endfor %}
</div>
</div>
{% endif %}
{% if request.user.role == 1 or request.user.role == 2 %}
<div class="section"> <div class="section">
<div class="section__center center center_xs"> <div class="section__center center center_xs">
<div class="title title_sm">Вывести деньги со счета</div> <div class="title title_sm">Вывести деньги со счета</div>
<div class="form"> <form class="form" method="POST">{% csrf_token %}
<div class="form__group"> <div class="form__group">
<div class="form__content">На вашем счету {{ request.user.balance }} руб.</div> <div class="form__content">На вашем счету {{ request.user.balance }} руб.</div>
<div class="form__field field"> <div class="form__field field{% if form.amount.errors %} error{% endif %}">
<div class="field__label">СУММА</div> <div class="field__label">СУММА</div>
<div class="field__wrap"> <div class="field__wrap">
<input class="field__input" type="text" placeholder="{{ request.user.balance }}"> <input class="field__input" name="amount" required="" id="id_amount" type="number" pattern="\d+(,\d{2})?" placeholder="{{ request.user.balance }}">
</div> </div>
<div class="field__error">Размер выводимой суммы не должно быть менее 2000 рублей.</div> <div class="field__error">Размер выводимой суммы не должно быть менее 2000 рублей.</div>
{% if form.amount.errors %}
<div class="field__error">{{ form.amount.errors }}</div>
{% endif %}
</div> </div>
<div class="form__field field"> <div class="form__field field{% if form.card.errors %} error{% endif %}">
<div class="field__label">НОМЕР КРЕДИТНОЙ КАРТЫ <div class="field__label">НОМЕР КРЕДИТНОЙ КАРТЫ
<span class="field__required">*</span> <span class="field__required">*</span>
</div> </div>
<div class="field__wrap"> <div class="field__wrap">
<input class="field__input" type="email" placeholder="0000 0000 0000 0000"> <input class="field__input" name="card" required="" id="id_card" type="text" autocomplete="off" placeholder="0000 0000 0000 0000">
<div class="field__preview"> <div class="field__preview">
<img class="field__pic" src="{% static 'img/paysys.png' %}"> <img class="field__pic" src="{% static 'img/paysys.png' %}">
</div> </div>
</div> </div>
{% if form.card.errors %}
<div class="field__error">{{ form.card.errors }}</div>
{% endif %}
</div> </div>
</div> </div>
<div class="form__foot"> <div class="form__foot">
<button class="form__btn btn btn_md">СОХРАНИТЬ</button> <button class="form__btn btn btn_md">СОХРАНИТЬ</button>
</div> </div>
</div> </form>
</div> </div>
</div> </div>
{% endif %}
<div class="section"> <div class="section">
<div class="section__center center center_xs"> <div class="section__center center center_xs">
<div class="title title_sm">История платежей</div> <div class="title title_sm">История платежей</div>
<div class="transactions"> <div class="transactions">
<div class="transactions__wrap"> <div class="transactions__wrap">
{% if request.user.role == 1 or request.user.role == 2 %}
{% for balance in request.user.balances.all %}
<div class="transactions__row">
<div class="transactions__cell">{{balance.payment.course.title}}</div>
<div class="transactions__cell">{{balance.amount}}</div>
<div class="transactions__cell">Получено</div>
</div>
{% endfor %}
{% else %}
{% for payment in request.user.payments.all %} {% for payment in request.user.payments.all %}
<div class="transactions__row"> <div class="transactions__row">
<div class="transactions__cell">{{payment.course.title}}</div> {% if payment.course %}
<div class="transactions__cell">Курс. {{payment.course.title}}</div>
{% else %}
<div class="transactions__cell">
Школа. {% if payment.date_start and payment.date_end %}{{ payment.date_start }} - {{ payment.date_end }}{% endif %}
{{ payment }}
</div>
{% endif %}
{% if payment.balance %}
<div class="transactions__cell">{{payment.balance.amount}}</div>
{% else %}
<div class="transactions__cell">{{payment.amount}}</div> <div class="transactions__cell">{{payment.amount}}</div>
<div class="transactions__cell">Получено</div> {% endif %}
{% if payment.balance.type == 1 %}
<div class="transactions__cell">
{% if payment.balance.status == 0 %}
Ожидается подтверждение выплаты
{% elif payment.balance.status == 1 %}
Выплачено
{% else %}
Выплата отменена
Причина: "{{ payment.balance.cause }}
{% endif %}
</div>
{% else %}
<div class="transactions__cell">
{% if payment.is_deliverable %}
Получено
{% elif payment.is_under_review %}
Ожидается подтверждение оплаты
{% else %}
Ошибка оплаты
{% endif %}
</div>
{% endif %}
</div> </div>
{% endfor %} {% endfor %}
{% endif %}
</div> </div>
<div class="transactions__load load"> <div class="transactions__load load">
<button class="load__btn btn">еще</button> <button class="load__btn btn">еще</button>

@ -1,9 +1,12 @@
from io import BytesIO from io import BytesIO
from PIL import Image from PIL import Image
from os.path import splitext from os.path import splitext
from datetime import timedelta
from django.contrib.auth import login from django.contrib.auth import login
from django.core.exceptions import ValidationError
from django.shortcuts import render, reverse, redirect from django.shortcuts import render, reverse, redirect
from django.views.generic import DetailView, UpdateView, TemplateView from django.views.generic import DetailView, UpdateView, TemplateView, FormView
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
@ -11,13 +14,14 @@ from django.contrib.auth.hashers import check_password, make_password
from django.http import Http404 from django.http import Http404
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.timezone import now
from apps.auth.tokens import verification_email_token from apps.auth.tokens import verification_email_token
from apps.course.models import Course from apps.course.models import Course
from apps.payment.models import CoursePayment from apps.payment.models import AuthorBalance, CoursePayment
from apps.notification.utils import send_email from apps.notification.utils import send_email
from .forms import UserEditForm from .forms import UserEditForm, WithdrawalForm
User = get_user_model() User = get_user_model()
@ -31,6 +35,7 @@ def resend_email_verify(request):
return redirect('user-edit-profile', request.user.id) return redirect('user-edit-profile', request.user.id)
@method_decorator(login_required, name='dispatch')
class UserView(DetailView): class UserView(DetailView):
model = User model = User
template_name = 'user/profile.html' template_name = 'user/profile.html'
@ -52,6 +57,7 @@ class UserView(DetailView):
return context return context
@method_decorator(login_required, name='dispatch')
class NotificationEditView(TemplateView): class NotificationEditView(TemplateView):
template_name = 'user/notification-settings.html' template_name = 'user/notification-settings.html'
@ -59,13 +65,40 @@ class NotificationEditView(TemplateView):
return super().get(request) return super().get(request)
class PaymentHistoryView(TemplateView): @method_decorator(login_required, name='dispatch')
class PaymentHistoryView(FormView):
template_name = 'user/payment-history.html' template_name = 'user/payment-history.html'
form_class = WithdrawalForm
def get(self, request, pk=None): def get(self, request, pk=None):
return super().get(request) return super().get(request)
def post(self, request, pk=None):
form = self.get_form()
if AuthorBalance.objects.filter(created_at__gte=now() - timedelta(days=30)).exists():
messages.error(request, 'Запрос на вывод средств можно сделать только один раз в 30 дней.')
return self.form_invalid(form)
if form.is_valid():
if request.user.balance < form.cleaned_data['amount']:
form.errors['amount'] = 'Сумма для вывода не может быть меньше средств на счету'
return self.form_invalid(form)
AuthorBalance.objects.create(
author=request.user,
type=AuthorBalance.OUT,
amount=form.cleaned_data['amount'],
status=AuthorBalance.PENDING,
card=form.cleaned_data['amount'],
)
return self.form_valid(form)
else:
return self.form_invalid(form)
def get_success_url(self):
success_url = reverse_lazy('user-edit-payments', args=[self.request.user.id])
return success_url
@method_decorator(login_required, name='dispatch')
class UserEditView(UpdateView): class UserEditView(UpdateView):
model = User model = User
template_name = 'user/profile-settings.html' template_name = 'user/profile-settings.html'

Loading…
Cancel
Save