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.contrib.auth import get_user_model
from django.contrib.postgres.fields import ArrayField, JSONField
from django.core.validators import RegexValidator
from constance import config
from paymentwall import Pingback
@ -8,9 +9,12 @@ from polymorphic.models import PolymorphicModel
from apps.course.models import Course
from apps.school.models import SchoolSchedule
from apps.notification.utils import send_email
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):
IN = 0
@ -35,11 +39,32 @@ class AuthorBalance(models.Model):
status = models.PositiveSmallIntegerField('Статус', choices=STATUS_CHOICES, default=0)
payment = models.OneToOneField('Payment', on_delete=models.CASCADE, null=True, blank=True, verbose_name='Платёж')
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:
verbose_name = 'Баланс'
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):
PW_STATUS_CHOICES = (
@ -66,6 +91,22 @@ class Payment(PolymorphicModel):
def calc_commission(self):
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):
course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name='Курс', related_name='payments')
@ -83,11 +124,9 @@ class CoursePayment(Payment):
author=self.course.author,
amount=self.amount,
payment=self,
commission=self.calc_commission(),
)
else:
author_balance.amount = self.amount
author_balance.commission = self.calc_commission()
author_balance.save()
@ -100,6 +139,10 @@ class SchoolPayment(Payment):
verbose_name = 'Платеж за школу'
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):
aggregate = SchoolSchedule.objects.filter(
weekday__in=self.weekdays,

@ -1,6 +1,8 @@
import logging
import json
from datetime import timedelta
from django.contrib import messages
from django.http import HttpResponse
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.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.utils.timezone import now
from paymentwall import Pingback, Product, Widget
@ -123,10 +126,13 @@ class PaymentwallCallbackView(View):
)
payment.status = pingback.get_type()
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()
author_balance = getattr(payment, 'author_balance', None)
if author_balance:
if author_balance and author_balance.type == AuthorBalance.IN:
if pingback.is_deliverable():
payment.author_balance.status = AuthorBalance.ACCEPTED
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.contrib.auth import get_user_model
from .fields import CreditCardField
User = get_user_model()
@ -47,3 +49,8 @@ class UserEditForm(forms.ModelForm):
'vkontakte',
'photo',
)
class WithdrawalForm(forms.Form):
amount = forms.DecimalField(required=True, min_value=2000)
card = CreditCardField(required=True)

@ -22,59 +22,98 @@
</div>
</div>
{% 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__center center center_xs">
<div class="title title_sm">Вывести деньги со счета</div>
<div class="form">
<form class="form" method="POST">{% csrf_token %}
<div class="form__group">
<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__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 class="field__error">Размер выводимой суммы не должно быть менее 2000 рублей.</div>
{% if form.amount.errors %}
<div class="field__error">{{ form.amount.errors }}</div>
{% endif %}
</div>
<div class="form__field field">
<div class="form__field field{% if form.card.errors %} error{% endif %}">
<div class="field__label">НОМЕР КРЕДИТНОЙ КАРТЫ
<span class="field__required">*</span>
</div>
<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">
<img class="field__pic" src="{% static 'img/paysys.png' %}">
</div>
</div>
{% if form.card.errors %}
<div class="field__error">{{ form.card.errors }}</div>
{% endif %}
</div>
</div>
<div class="form__foot">
<button class="form__btn btn btn_md">СОХРАНИТЬ</button>
</div>
</div>
</form>
</div>
</div>
{% endif %}
<div class="section">
<div class="section__center center center_xs">
<div class="title title_sm">История платежей</div>
<div class="transactions">
<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 %}
<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">Получено</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>
{% endfor %}
{% endif %}
</div>
<div class="transactions__load load">
<button class="load__btn btn">еще</button>

@ -1,9 +1,12 @@
from io import BytesIO
from PIL import Image
from os.path import splitext
from datetime import timedelta
from django.contrib.auth import login
from django.core.exceptions import ValidationError
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.auth import get_user_model
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.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.utils.timezone import now
from apps.auth.tokens import verification_email_token
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 .forms import UserEditForm
from .forms import UserEditForm, WithdrawalForm
User = get_user_model()
@ -31,6 +35,7 @@ def resend_email_verify(request):
return redirect('user-edit-profile', request.user.id)
@method_decorator(login_required, name='dispatch')
class UserView(DetailView):
model = User
template_name = 'user/profile.html'
@ -52,6 +57,7 @@ class UserView(DetailView):
return context
@method_decorator(login_required, name='dispatch')
class NotificationEditView(TemplateView):
template_name = 'user/notification-settings.html'
@ -59,13 +65,40 @@ class NotificationEditView(TemplateView):
return super().get(request)
class PaymentHistoryView(TemplateView):
@method_decorator(login_required, name='dispatch')
class PaymentHistoryView(FormView):
template_name = 'user/payment-history.html'
form_class = WithdrawalForm
def get(self, request, pk=None):
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):
model = User
template_name = 'user/profile-settings.html'

Loading…
Cancel
Save