Перейти с PaymentWall на cloudpayments

remotes/origin/feature/cloudpayments
gzbender 7 years ago
parent 7207527052
commit 8d24888d58
  1. 8
      api/v1/views.py
  2. 2
      apps/course/management/commands/fix_access_expire.py
  3. 5
      apps/course/templates/course/course.html
  4. 23
      apps/course/views.py
  5. 3
      apps/notification/tasks.py
  6. 16
      apps/payment/management/commands/roistat_set_statuses.py
  7. 6
      apps/payment/management/commands/send_transactions_to_roistat.py
  8. 18
      apps/payment/migrations/0033_payment_payment_platform.py
  9. 21
      apps/payment/migrations/0034_fill_payment_platform.py
  10. 18
      apps/payment/migrations/0035_auto_20190222_0233.py
  11. 87
      apps/payment/models.py
  12. 30
      apps/payment/templates/payment/cloud_payments_test.html
  13. 55
      apps/payment/templates/payment/cloudpayments_widget.html
  14. 188
      apps/payment/views.py
  15. 4
      apps/school/templates/school/summer_school.html
  16. 21
      apps/school/views.py
  17. 17
      apps/user/models.py
  18. 19
      apps/user/views.py
  19. 8
      project/context_processors.py
  20. 3
      project/settings.py
  21. 6
      project/urls.py
  22. 10
      project/views.py
  23. 1
      web/src/js/modules/popup.js

@ -1,4 +1,3 @@
import json
from datetime import datetime
from decimal import Decimal
@ -6,9 +5,8 @@ from django.contrib.auth import get_user_model
from django.db.models import Q
from rest_framework import status, views, viewsets, generics, mixins
from rest_framework.decorators import (detail_route, list_route, action,
permission_classes, authentication_classes as auth_classes)
permission_classes)
from rest_framework.response import Response
from rest_framework.settings import api_settings
from . import ExtendedModelViewSet, BothListFormatMixin
@ -80,7 +78,9 @@ User = get_user_model()
class AuthorBalanceViewSet(ExtendedModelViewSet):
queryset = AuthorBalance.objects.filter(
Q(type=1) | Q(payment__status__in=Payment.PW_PAID_STATUSES),
Q(type=AuthorBalance.OUT)
| Q(payment__payment_platform=Payment.PAYMENT_PLATFORM_PAYMENTWALL, payment__status__in=Payment.PAID_STATUSES)
| Q(payment__payment_platform=Payment.PAYMENT_PLATFORM_CLOUDPAYMENTS, payment__status=Payment.STATUS_PAID),
# author__role__in=[User.AUTHOR_ROLE, User.ADMIN_ROLE, User.TEACHER_ROLE],
)
serializer_class = AuthorBalanceCreateSerializer

@ -18,7 +18,7 @@ class Command(BaseCommand):
course.access_duration = access_duration
course.save()
for payment in CoursePayment.objects.filter(status__in=CoursePayment.PW_PAID_STATUSES):
for payment in CoursePayment.objects.paid():
payment.access_expire = payment.created_at.date() + timedelta(days=payment.course.access_duration)
payment.save()

@ -22,6 +22,11 @@
{% block ogdescription %}{{ course.short_description | striptags }}{% endblock ogdescription %}
{% block head %}
{{ block.super }}
<script src="https://widget.cloudpayments.ru/bundles/cloudpayments"></script>
{% endblock head %}
{% block content %}
<div class="section section_border course">
<div class="section__center center center_sm">

@ -1,6 +1,5 @@
from datetime import timedelta
from paymentwall import Pingback
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required
from django.db.models import Q
@ -234,13 +233,8 @@ class CourseView(DetailView):
if self.request.user.is_authenticated:
context['next'] = self.request.GET.get('next', None)
# берем последнюю оплату курса
payments = self.object.payments.filter(
user=self.request.user,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
])
payments = self.object.payments.paid().filter(
user=self.request.user)
payment = payments.filter(access_expire__gte=now().date()).order_by('-access_expire').first()
context['payment'] = payment
context['access_duration'] = ((payment.access_expire - now().date()).days + 1) if payment else self.object.access_duration
@ -249,7 +243,8 @@ class CourseView(DetailView):
payments.filter(access_expire__lt=now().date()).exists())
context['pending'] = self.object.payments.filter(
user=self.request.user,
status=Pingback.PINGBACK_TYPE_RISK_UNDER_REVIEW,
payment_platform=CoursePayment.PAYMENT_PLATFORM_PAYMENTWALL,
status=CoursePayment.STATUS_PINGBACK_TYPE_RISK_UNDER_REVIEW,
).exists()
context['only_lessons'] = self.only_lessons
if self.only_lessons:
@ -328,8 +323,7 @@ class CoursesView(ListView):
else:
context['age_name'] = ''
if self.request.user.is_authenticated:
can_buy_again_courses = list(CoursePayment.objects.filter(user=self.request.user,
status__in=CoursePayment.PW_PAID_STATUSES,
can_buy_again_courses = list(CoursePayment.objects.paid().filter(user=self.request.user,
access_expire__lte=now().date() + timedelta(7)).values_list('course_id', flat=True))
for course in context['course_items']:
if course.id in can_buy_again_courses:
@ -350,13 +344,8 @@ class LessonView(DetailView):
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
paid = request.user.is_authenticated and self.object.course.payments.filter(
paid = request.user.is_authenticated and self.object.course.payments.paid().filter(
user=self.request.user,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
access_expire__gte=now().date(),
).exists()
# если это не автор или админ

@ -84,8 +84,7 @@ def sendgrid_update_recipients():
for user in users:
last_course_purchase = course_payments.get(user.id) and course_payments.get(user.id).strftime(date_format)
last_school_purchase = school_payments.get(user.id) and school_payments.get(user.id).strftime(date_format)
courses_purchased = CoursePayment.objects.filter(user=user,
status__in=CoursePayment.PW_PAID_STATUSES).values_list('course_id', flat=True)
courses_purchased = CoursePayment.objects.paid().filter(user=user,).values_list('course_id', flat=True)
data.append({
'first_name': user.first_name,
'last_name': user.last_name,

@ -1,10 +1,10 @@
import requests
from paymentwall import Pingback
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from apps.payment.models import Payment
class Command(BaseCommand):
help = 'Update statuses on Roistat'
@ -12,32 +12,32 @@ class Command(BaseCommand):
def handle(self, *args, **options):
body = [
{
'id': str(Pingback.PINGBACK_TYPE_REGULAR),
'id': str(Payment.STATUS_PAID),
'name': 'PINGBACK_TYPE_REGULAR',
'type': 'paid',
},
{
'id': str(Pingback.PINGBACK_TYPE_GOODWILL),
'id': str(Payment.STATUS_PINGBACK_TYPE_GOODWILL),
'name': 'PINGBACK_TYPE_GOODWILL',
'type': 'paid',
},
{
'id': str(Pingback.PINGBACK_TYPE_NEGATIVE),
'id': str(Payment.STATUS_PINGBACK_TYPE_NEGATIVE),
'name': 'PINGBACK_TYPE_NEGATIVE',
'type': 'canceled',
},
{
'id': str(Pingback.PINGBACK_TYPE_RISK_UNDER_REVIEW),
'id': str(Payment.STATUS_PINGBACK_TYPE_RISK_UNDER_REVIEW),
'name': 'PINGBACK_TYPE_RISK_UNDER_REVIEW',
'type': 'progress',
},
{
'id': str(Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED),
'id': str(Payment.STATUS_PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED),
'name': 'PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED',
'type': 'paid',
},
{
'id': str(Pingback.PINGBACK_TYPE_RISK_REVIEWED_DECLINED),
'id': str(Payment.STATUS_PINGBACK_TYPE_RISK_REVIEWED_DECLINED),
'name': 'PINGBACK_TYPE_RISK_REVIEWED_DECLINED',
'type': 'canceled',
},

@ -1,6 +1,4 @@
from django.core.management.base import BaseCommand, CommandError
from django.utils.timezone import now
from paymentwall.pingback import Pingback
from django.core.management.base import BaseCommand
from apps.payment.tasks import transaction_to_roistat
from apps.payment.models import Payment
@ -22,7 +20,7 @@ class Command(BaseCommand):
start_id = options.get('start_id')
print('start_id=' + str(start_id))
payments = Payment.objects.filter(id__gte=start_id, status__in=Payment.PW_PAID_STATUSES)
payments = Payment.objects.paid().filter(id__gte=start_id)
for payment in payments:
print('TRANSACTION: ' + str(payment.id))

@ -0,0 +1,18 @@
# Generated by Django 2.0.7 on 2019-02-22 02:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('payment', '0032_auto_20190207_1233'),
]
operations = [
migrations.AddField(
model_name='payment',
name='payment_platform',
field=models.PositiveSmallIntegerField(choices=[(1, 'PaymentWall'), (2, 'Cloudpayments')], null=True),
),
]

@ -0,0 +1,21 @@
# Generated by Django 2.0.7 on 2019-02-22 02:14
from django.db import migrations
def forwards(apps, schema_editor):
Payment = apps.get_model('payment', 'Payment')
for payment in Payment.objects.filter(payment_platform__isnull=True):
payment.payment_platform = 1 # PAYMENT_PLATFORM_PAYMENTWALL
payment.save()
class Migration(migrations.Migration):
dependencies = [
('payment', '0033_payment_payment_platform'),
]
operations = [
migrations.RunPython(forwards),
]

@ -0,0 +1,18 @@
# Generated by Django 2.0.7 on 2019-02-22 02:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('payment', '0034_fill_payment_platform'),
]
operations = [
migrations.AlterField(
model_name='payment',
name='payment_platform',
field=models.PositiveSmallIntegerField(choices=[(1, 'PaymentWall'), (2, 'Cloudpayments')], default=2),
),
]

@ -1,9 +1,7 @@
from decimal import Decimal
import arrow
import short_url
from django.db.models import Func, F
from django.db.models import Func, F, Q
from paymentwall import Pingback
from polymorphic.models import PolymorphicModel
from polymorphic.managers import PolymorphicManager
@ -14,7 +12,6 @@ from django.core.validators import RegexValidator
from django.utils.timezone import now
from django.conf import settings
from apps.content.models import ImageObject
from project.utils import weekdays_in_date_range
from apps.course.models import Course
@ -85,24 +82,46 @@ class PaymentManger(PolymorphicManager):
def all(self):
return self.filter(status__isnull=False)
def paid(self):
return self.filter(
Q(payment_platform=Payment.PAYMENT_PLATFORM_PAYMENTWALL, status__in=Payment.PAID_STATUSES)
| Q(payment_platform=Payment.PAYMENT_PLATFORM_CLOUDPAYMENTS, status=Payment.STATUS_PAID))
class Payment(PolymorphicModel):
PW_PAID_STATUSES = [
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
STATUS_PAID = 0
STATUS_PINGBACK_TYPE_GOODWILL = 1
STATUS_PINGBACK_TYPE_NEGATIVE = 2
STATUS_PINGBACK_TYPE_RISK_UNDER_REVIEW = 200
STATUS_PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED = 201
STATUS_PINGBACK_TYPE_RISK_REVIEWED_DECLINED = 202
STATUS_PINGBACK_TYPE_RISK_AUTHORIZATION_VOIDED = 203
STATUS_PINGBACK_TYPE_SUBSCRIPTION_CANCELLATION = 12
STATUS_PINGBACK_TYPE_SUBSCRIPTION_EXPIRED = 13
STATUS_PINGBACK_TYPE_SUBSCRIPTION_PAYMENT_FAILED = 14
PAID_STATUSES = [
STATUS_PAID,
STATUS_PINGBACK_TYPE_GOODWILL,
STATUS_PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
]
PW_STATUS_CHOICES = (
(Pingback.PINGBACK_TYPE_REGULAR, 'regular',),
(Pingback.PINGBACK_TYPE_GOODWILL, 'goodwill',),
(Pingback.PINGBACK_TYPE_NEGATIVE, 'negative',),
(Pingback.PINGBACK_TYPE_RISK_UNDER_REVIEW, 'risk under review',),
(Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED, 'risk reviewed accepted',),
(Pingback.PINGBACK_TYPE_RISK_REVIEWED_DECLINED, 'risk reviewed declined',),
(Pingback.PINGBACK_TYPE_RISK_AUTHORIZATION_VOIDED, 'risk authorization voided',),
(Pingback.PINGBACK_TYPE_SUBSCRIPTION_CANCELLATION, 'subscription cancelation',),
(Pingback.PINGBACK_TYPE_SUBSCRIPTION_EXPIRED, 'subscription expired',),
(Pingback.PINGBACK_TYPE_SUBSCRIPTION_PAYMENT_FAILED, 'subscription payment failed',),
(STATUS_PAID, 'regular',),
(STATUS_PINGBACK_TYPE_GOODWILL, 'goodwill',),
(STATUS_PINGBACK_TYPE_NEGATIVE, 'negative',),
(STATUS_PINGBACK_TYPE_RISK_UNDER_REVIEW, 'risk under review',),
(STATUS_PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED, 'risk reviewed accepted',),
(STATUS_PINGBACK_TYPE_RISK_REVIEWED_DECLINED, 'risk reviewed declined',),
(STATUS_PINGBACK_TYPE_RISK_AUTHORIZATION_VOIDED, 'risk authorization voided',),
(STATUS_PINGBACK_TYPE_SUBSCRIPTION_CANCELLATION, 'subscription cancelation',),
(STATUS_PINGBACK_TYPE_SUBSCRIPTION_EXPIRED, 'subscription expired',),
(STATUS_PINGBACK_TYPE_SUBSCRIPTION_PAYMENT_FAILED, 'subscription payment failed',),
)
PAYMENT_PLATFORM_PAYMENTWALL = 1
PAYMENT_PLATFORM_CLOUDPAYMENTS = 2
PAYMENT_PLATFORM_CHOICES = (
(PAYMENT_PLATFORM_PAYMENTWALL, 'PaymentWall'),
(PAYMENT_PLATFORM_CLOUDPAYMENTS, 'Cloudpayments'),
)
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='Пользователь', related_name='payments')
amount = models.DecimalField('Итого', max_digits=8, decimal_places=2, default=0, editable=False)
@ -112,6 +131,7 @@ class Payment(PolymorphicModel):
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
bonus = models.ForeignKey('payment.UserBonus', null=True, on_delete=models.SET_NULL, related_name='purchase_payments')
payment_platform = models.PositiveSmallIntegerField(choices=PAYMENT_PLATFORM_CHOICES, default=PAYMENT_PLATFORM_CLOUDPAYMENTS)
objects = PaymentManger()
@ -152,15 +172,14 @@ class Payment(PolymorphicModel):
elif isinstance(payment, GiftCertificatePayment):
price = payment.gift_certificate.price
elif course:
paid_before = CoursePayment.objects.filter(user=user, course=course, status__in=Payment.PW_PAID_STATUSES).exists()
paid_before = CoursePayment.objects.paid().filter(user=user, course=course).exists()
price = course.price / 2 if paid_before else course.price
else:
if user:
school_payments = SchoolPayment.objects.filter(
school_payments = SchoolPayment.objects.paid().filter(
user=user,
date_start__lte=date_start,
date_end__gte=date_start,
status__in=Payment.PW_PAID_STATUSES,
)
school_schedules_purchased = school_payments.annotate(
joined_weekdays=Func(F('weekdays'), function='unnest', )
@ -206,26 +225,26 @@ class Payment(PolymorphicModel):
# TODO change to property
def is_paid(self):
return self.status in self.PW_PAID_STATUSES
if self.payment_platform == Payment.PAYMENT_PLATFORM_PAYMENTWALL:
return self.status in self.PAID_STATUSES
if self.payment_platform == Payment.PAYMENT_PLATFORM_CLOUDPAYMENTS:
return self.status == Payment.STATUS_PAID
# TODO? delete ? change to property
def is_deliverable(self):
return self.status in [
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
]
return self.is_paid()
# TODO change to property
def is_under_review(self):
return self.status == Pingback.PINGBACK_TYPE_RISK_UNDER_REVIEW
return (self.payment_platform == Payment.PAYMENT_PLATFORM_PAYMENTWALL
and self.status == self.STATUS_PINGBACK_TYPE_RISK_UNDER_REVIEW)
# TODO change to property
def is_cancelable(self):
return self.status in [
Pingback.PINGBACK_TYPE_NEGATIVE,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_DECLINED,
]
return (self.payment_platform == Payment.PAYMENT_PLATFORM_PAYMENTWALL and self.status in [
self.STATUS_PINGBACK_TYPE_NEGATIVE,
self.STATUS_PINGBACK_TYPE_RISK_REVIEWED_DECLINED,
])
def save(self, *args, **kwargs):
amount_data = Payment.calc_amount(payment=self)
@ -284,6 +303,10 @@ class SchoolPayment(Payment):
])
return days
@property
def weekdays_str(self):
return ', '.join([SchoolSchedule.WEEKDAY_SHORT_NAMES[wd - 1] for wd in self.weekdays])
@property
def date_end_humanize(self):
return arrow.get(self.date_end, settings.TIME_ZONE).humanize(locale='ru')

@ -1,30 +0,0 @@
{% extends "templates/lilcity/index.html" %} {% load static %}
{% block head %}
<script src="https://widget.cloudpayments.ru/bundles/cloudpayments"></script>
{% endblock head %}
{% block content %}
<button class="btn" id="pay_btn">Pay</button>
{% endblock content %}
{% block foot_js %}
<script>
document.getElementById('pay_btn').onclick = function() {
var widget = new cp.CloudPayments();
widget.charge({ // options
publicId: 'pk_9ae020cd5fed74499dee247067d17', //id из личного кабинета
description: 'Пример оплаты (деньги сниматься не будут)', //назначение
amount: 1, //сумма
currency: 'RUB', //валюта
invoiceId: '{{ invoice_id }}', //номер заказа (необязательно)
// accountId: 'user@example.com', //идентификатор плательщика (необязательно)
data: {
test: true //произвольный набор параметров
}
},
"{% url 'cloud_payments_callback_test' %}?invoice_id={{ invoice_id }}",
"{% url 'cloud_payments_callback_test' %}?invoice_id={{ invoice_id }}");
}
</script>
{% endblock foot_js %}

@ -0,0 +1,55 @@
{% extends "templates/lilcity/index.html" %} {% load static settings %}
{% block head %}
<script src="https://widget.cloudpayments.ru/bundles/cloudpayments"></script>
{% endblock head %}
{% block content %}
<!--<div class="section">
<div class="section__center center">
<div style="margin-bottom: 25px;">
{% if payment.course %}
Курс <b>{{ payment.course.title }}</b>
{% endif %}
{% if payment.gift_certificate %}
Подарочный сертификат на {{ payment.gift_certificate.price }} руб.
{% endif %}
{% if not payment.course and not payment.gift_certificate %}
Подписка на онлайн школу до {{ payment.date_end_humanize }}, {{ payment.weekdays_str }}
{% endif %}
</div>
<button class="btn" id="pay_btn">Оплатить {{ payment.amount }} руб.</button>
</div>
</div> -->
{% endblock content %}
{% block foot_js %}
<script>
function pay() {
var widget = new cp.CloudPayments();
widget.charge({ // options
publicId: '{% setting "CLOUD_PAYMENTS_PUBLIC_ID" %}', //id из личного кабинета
{% if payment.course %}
description: 'Оплата курса {{ payment.course.title }}',
{% endif %}
{% if payment.gift_certificate %}
description: 'Оплата подарочного сертификата на {{ payment.gift_certificate.price }} руб.',
{% endif %}
{% if not payment.course and not payment.gift_certificate %}
description: 'Оплата школы до {{ payment.date_end_humanize }}, {{ payment.weekdays_str }}',
{% endif %}
amount: {{ payment.amount|floatformat }}, //сумма
currency: 'RUB', //валюта
invoiceId: '{{ payment.id }}', //номер заказа (необязательно)
requireEmail: false,
},
function(payment_data){
var url = "{% url 'payment-success' payment.id %}";
},
function(reason){
showNotification(reason);
});
}
pay();
</script>
{% endblock foot_js %}

@ -12,11 +12,11 @@ import random
from cloudpayments import CloudPayments
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, Http404
from django.http import HttpResponse, Http404, JsonResponse
from django.shortcuts import redirect, get_object_or_404
from django.views.generic import View, TemplateView, DetailView
from django.views.decorators.csrf import csrf_exempt
from django.urls import reverse_lazy
from django.urls import reverse_lazy, reverse
from django.utils.decorators import method_decorator
from django.utils.timezone import now
from django.conf import settings
@ -56,9 +56,10 @@ class SchoolBuySuccessView(TemplateView):
@method_decorator(login_required, name='dispatch')
class CourseBuyView(TemplateView):
template_name = 'payment/paymentwall_widget.html'
template_name = 'payment/cloudpayments_widget.html'
def get(self, request, pk=None, *args, **kwargs):
#TODO: cloudpayments
use_bonuses = request.GET.get('use_bonuses')
host = urlsplit(self.request.META.get('HTTP_REFERER'))
host = str(host[0]) + '://' + str(host[1])
@ -67,8 +68,7 @@ class CourseBuyView(TemplateView):
if request.user == course.author:
messages.error(request, 'Вы не можете приобрести свой курс.')
return redirect(reverse_lazy('course', args=[course.id]))
prev_payment = CoursePayment.objects.filter(user=request.user, course=course,
status__in=Payment.PW_PAID_STATUSES).order_by('-access_expire').first()
prev_payment = CoursePayment.objects.paid().filter(user=request.user, course=course).order_by('-access_expire').first()
access_duration = course.access_duration or 90
access_expire = prev_payment.access_expire + timedelta(days=access_duration) if prev_payment \
else now().date() + timedelta(days=access_duration - 1)
@ -77,12 +77,13 @@ class CourseBuyView(TemplateView):
course=course,
access_expire=access_expire,
roistat_visit=roistat_visit,
payment_platform=CoursePayment.PAYMENT_PLATFORM_CLOUDPAYMENTS,
)
if use_bonuses:
if request.user.bonus >= course_payment.amount:
bonus = UserBonus.objects.create(amount= -course_payment.amount, user=request.user, payment=course_payment)
course_payment.amount = 0
course_payment.status = Pingback.PINGBACK_TYPE_REGULAR
course_payment.status = Payment.STATUS_PAID
else:
bonus = UserBonus.objects.create(amount= -request.user.bonus, user=request.user,
payment=course_payment)
@ -91,31 +92,15 @@ class CourseBuyView(TemplateView):
course_payment.save()
if course_payment.is_paid():
return redirect(reverse_lazy('course_payment_success', args=[course.id]))
product = Product(
f'course_{course_payment.id}',
course_payment.amount,
'RUB',
f'Курс "{course.title}"',
)
widget = Widget(
str(request.user.id),
'p1_1',
[product],
extra_params={
'lang': 'ru',
'evaluation': 1,
'demo': 1,
'test_mode': 1,
'success_url': host + str(reverse_lazy('course_payment_success', args=[course.id])),
'failure_url': host + str(reverse_lazy('payment-error')),
}
)
return self.render_to_response(context={'widget': widget.get_html_code()})
context = {
'payment': course_payment,
}
return self.render_to_response(context=context)
@method_decorator(login_required, name='dispatch')
class SchoolBuyView(TemplateView):
template_name = 'payment/paymentwall_widget.html'
template_name = 'payment/cloudpayments_widget.html'
def get(self, request, *args, **kwargs):
host = urlsplit(self.request.META.get('HTTP_REFERER'))
@ -126,23 +111,26 @@ class SchoolBuyView(TemplateView):
date_start = request.GET.get('date_start')
date_start = date_start and datetime.datetime.strptime(date_start, '%Y-%m-%d') or now().date()
if not weekdays:
messages.error(request, 'Выберите несколько дней недели.')
messages.error(request, 'Выберите несколько дней недели')
if request.is_ajax():
return JsonResponse({
'error': 'Выберите несколько дней недели'
})
return redirect('school:school')
try:
weekdays = [int(weekday) for weekday in weekdays]
except ValueError:
messages.error(request, 'Ошибка выбора дней недели.')
messages.error(request, 'Ошибка выбора дней недели')
if request.is_ajax():
return JsonResponse({
'error': 'Ошибка выбора дней недели'
})
return redirect('school:school')
prev_school_payment = SchoolPayment.objects.filter(
prev_school_payment = SchoolPayment.objects.paid().filter(
user=request.user,
date_start__lte=date_start,
date_end__gte=date_start,
add_days=False,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
).last()
add_days = bool(prev_school_payment)
if add_days:
@ -157,6 +145,10 @@ class SchoolBuyView(TemplateView):
# Если произойдет ошибка и оплату бонусами повторят еще раз на те же дни, то вернет ошибку
if school_payment.amount <= 0:
messages.error(request, 'Выбранные дни отсутствуют в оставшемся периоде подписки')
if request.is_ajax():
return JsonResponse({
'error': 'Выбранные дни отсутствуют в оставшемся периоде подписки'
})
return redirect(reverse_lazy('school:school'))
else:
school_payment = SchoolPayment.objects.create(
@ -170,7 +162,7 @@ class SchoolBuyView(TemplateView):
if request.user.bonus >= school_payment.amount:
bonus = UserBonus.objects.create(amount= -school_payment.amount, user=request.user, payment=school_payment)
school_payment.amount = 0
school_payment.status = Pingback.PINGBACK_TYPE_REGULAR
school_payment.status = Payment.STATUS_PAID
else:
bonus = UserBonus.objects.create(amount= -request.user.bonus, user=request.user,
payment=school_payment)
@ -178,27 +170,22 @@ class SchoolBuyView(TemplateView):
school_payment.bonus = bonus
school_payment.save()
if school_payment.is_paid():
if request.is_ajax():
return JsonResponse({
'status': 'ok',
'paid': True,
})
return redirect(reverse_lazy('payment-success'))
product = Product(
f'school_{school_payment.id}',
school_payment.amount,
'RUB',
'Школа',
)
widget = Widget(
str(request.user.id),
'p1_1',
[product],
extra_params={
'lang': 'ru',
'evaluation': 1,
'demo': 1,
'test_mode': 1,
'success_url': host + str(reverse_lazy('payment-success')),
'failure_url': host + str(reverse_lazy('payment-error')),
}
)
return self.render_to_response(context={'widget': widget.get_html_code()})
if request.is_ajax():
return JsonResponse({
'status': 'ok',
'amount': school_payment.amount,
'payment_id': school_payment.id,
'success_url': reverse('payment-success', args=[school_payment.id]),
'description': f'Оплата школы до {school_payment.date_end_humanize}, {school_payment.weekdays_str}',
})
return self.render_to_response(context={'payment': school_payment})
@method_decorator(csrf_exempt, name='dispatch')
@ -407,19 +394,80 @@ class CloudPaymentsTestView(TemplateView):
return self.render_to_response(context)
class CloudPaymentsCallbackTestView(TemplateView):
template_name = 'payment/cloud_payments_callback_test.html'
class PaymentSuccessView(View):
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
invoice_id = request.GET.get('invoice_id')
client = CloudPayments('pk_9ae020cd5fed74499dee247067d17', 'a7d5f88ea69ef59f8b5db2252e39cedf')
context['test_ok'] = bool(client.test())
def post(self, request, payment_id):
payment_data = request.POST.get('payment_data')
try:
t = client.find_payment(int(invoice_id))
print('transaction.status', t.status, 'transaction.reason', t.reason)
# status = 'Completed', reason = 'Approved'
context['transaction'] = t
except:
context['transaction'] = None
return self.render_to_response(context)
payment = Payment.objects.get(id=payment_id)
except Payment.DoesNotExist:
raise Http404()
client = CloudPayments(settings.CLOUD_PAYMENTS_PUBLIC_ID, settings.CLOUD_PAYMENTS_API_KRY)
transaction = client.find_payment(payment_id)
if transaction.status == 'Completed' and transaction.reason == 'Approved':
payment.data = payment_data
payment.status = Payment.STATUS_PAID
payment.save()
author_balance = getattr(payment, 'author_balance', None)
if author_balance and author_balance.type == AuthorBalance.IN:
payment.author_balance.status = AuthorBalance.DECLINED
payment.author_balance.save()
if isinstance(payment, CoursePayment):
product_type_name = 'course'
properties = {
'payment_id': payment.id,
'amount': payment.amount,
'status': payment.status,
'course': payment.course.id,
'created_at': payment.created_at,
'update_at': payment.update_at,
}
redirect_to = reverse('course_payment_success', args=[payment.course_id])
elif isinstance(payment, SchoolPayment):
product_type_name = 'school'
properties = {
'payment_id': payment.id,
'amount': payment.amount,
'status': payment.status,
'weekdays': payment.weekdays,
'add_days': payment.add_days,
'date_start': payment.date_start,
'date_end': payment.date_end,
'created_at': payment.created_at,
'update_at': payment.update_at,
}
redirect_to = reverse('payment-success')
elif isinstance(payment, GiftCertificatePayment):
product_type_name = 'gift_certificate'
properties = {
'payment_id': payment.id,
'amount': payment.amount,
'status': payment.status,
'gift_certificate': payment.gift_certificate.id,
'created_at': payment.created_at,
'update_at': payment.update_at,
}
redirect_to = reverse('gift-certificate-payment-success', args=[payment_id])
product_payment_to_mixpanel.delay(
payment.user.id,
f'{product_type_name.title()} payment',
now().strftime('%Y-%m-%dT%H:%M:%S'),
properties,
)
transaction_to_roistat.delay(
payment.user.id,
payment.id,
f'{product_type_name.title()} payment',
payment.amount,
now().strftime('%Y-%m-%d %H:%M:%S'),
payment.STATUS_PAID,
product_type_name,
payment.roistat_visit,
)
return redirect_to
return reverse('payment-error')

@ -1,6 +1,10 @@
{% extends "templates/lilcity/index.html" %} {% load static %}
{% block title %}Онлайн-школа LilCity{% endblock title%}
{% block ogimage %}http://{{request.META.HTTP_HOST}}{% static 'img/og_main.jpg' %}{% endblock %}
{% block head %}
{{ block.super }}
<script src="https://widget.cloudpayments.ru/bundles/cloudpayments"></script>
{% endblock head %}
{% block content %}
{% if not is_purchased and not prev_school_payments_exists %}
{% include "../summer/promo.html" %}

@ -1,5 +1,4 @@
from datetime import datetime, timedelta, date
from paymentwall import Pingback
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required, user_passes_test
@ -67,15 +66,10 @@ class LiveLessonsDetailView(DetailView):
if lesson_date:
self.object = LiveLesson.objects.get(date=datetime.strptime(lesson_date, '%d-%m-%y'))
if request.user.is_authenticated:
is_purchased = SchoolPayment.objects.filter(
is_purchased = SchoolPayment.objects.paid().filter(
user=request.user,
date_start__lte=now(),
date_end__gte=now() - timedelta(days=7),
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
weekdays__contains=[self.object.date.weekday() + 1],
).exists()
if not is_purchased and request.user.role not in [User.ADMIN_ROLE, User.TEACHER_ROLE]:
@ -142,17 +136,15 @@ class SchoolView(TemplateView):
prev_school_payments = None
prev_range = [date_now - timedelta(days=7), date_now - timedelta(days=1)]
if self.request.user.is_authenticated:
school_payment = SchoolPayment.objects.filter(
school_payment = SchoolPayment.objects.paid().filter(
user=self.request.user,
status__in=SchoolPayment.PW_PAID_STATUSES,
date_start__lte=date_now,
date_end__gte=date_now
)
school_payment_exists = school_payment.exists()
school_purchased_future = SchoolPayment.objects.filter(
school_purchased_future = SchoolPayment.objects.paid().filter(
user=self.request.user,
status__in=SchoolPayment.PW_PAID_STATUSES,
date_start__gt=date_now,
date_end__gt=date_now
)
@ -167,15 +159,10 @@ class SchoolView(TemplateView):
joined_weekdays=Func(F('weekdays'), function='unnest',)
).values_list('joined_weekdays', flat=True).distinct()
prev_school_payments = SchoolPayment.objects.filter(
prev_school_payments = SchoolPayment.objects.paid().filter(
date_start__lte=prev_range[1],
date_end__gte=prev_range[0],
user=self.request.user,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
weekdays__len__gt=0,
)

@ -110,13 +110,12 @@ class User(AbstractUser):
def balance(self):
from apps.payment.models import Payment, AuthorBalance
income = self.balances.filter(
type=AuthorBalance.IN,
payment__isnull=False,
payment__status__in=Payment.PW_PAID_STATUSES,
).aggregate(
models.Sum('amount'),
models.Sum('commission'),
)
Q(payment__payment_platform=Payment.PAYMENT_PLATFORM_PAYMENTWALL, payment__status__in=Payment.PAID_STATUSES)
| Q(payment__payment_platform=Payment.PAYMENT_PLATFORM_CLOUDPAYMENTS, payment__status=Payment.STATUS_PAID),
type=AuthorBalance.IN,).aggregate(
models.Sum('amount'),
models.Sum('commission'),
)
income_amount = income.get('amount__sum') or 0
income_commission = income.get('commission__sum') or 0
@ -129,7 +128,9 @@ class User(AbstractUser):
def bonus(self):
from apps.payment.models import Payment
return int(self.bonuses.filter(
Q(payment__isnull=False, payment__status__in=Payment.PW_PAID_STATUSES) | Q(is_service=True),
Q(payment__payment_platform=Payment.PAYMENT_PLATFORM_PAYMENTWALL, payment__status__in=Payment.PAID_STATUSES)
| Q(payment__payment_platform=Payment.PAYMENT_PLATFORM_CLOUDPAYMENTS, payment__status=Payment.STATUS_PAID)
| Q(is_service=True),
).aggregate(models.Sum('amount')).get('amount__sum') or 0)

@ -3,7 +3,6 @@ from PIL import Image
from uuid import uuid4
from os.path import splitext
from datetime import timedelta
from paymentwall import Pingback
import short_url
from django.conf import settings
@ -61,23 +60,13 @@ class ProfileView(TemplateView):
context['is_author'] = context['published'] or self.request.user.role == User.AUTHOR_ROLE
context['user_gift_certificates'] = UserGiftCertificate.objects.filter(user=self.request.user)
context['paid'] = Course.objects.filter(
payments__in=CoursePayment.objects.filter(
payments__in=CoursePayment.objects.paid().filter(
user=self.object,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
),
).distinct()
school_payment = SchoolPayment.objects.filter(
school_payment = SchoolPayment.objects.paid().filter(
user=self.object,
date_end__gte=now(),
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
)
context['is_school_purchased'] = school_payment.exists()
if context['is_school_purchased']:
@ -303,7 +292,9 @@ class BonusHistoryView(TemplateView):
config = Config.load()
context = self.get_context_data(**kwargs)
context['bonuses'] = request.user.bonuses.filter(
Q(payment__isnull=False, payment__status__in=Payment.PW_PAID_STATUSES) | Q(is_service=True),
Q(payment__payment_platform=Payment.PAYMENT_PLATFORM_PAYMENTWALL, payment__status__in=Payment.PAID_STATUSES)
| Q(payment__payment_platform=Payment.PAYMENT_PLATFORM_CLOUDPAYMENTS, payment__status=Payment.STATUS_PAID)
| Q(is_service=True)
)
context['referrer_url'] = 'https://%s%s?referrer=%s' % (
settings.MAIN_HOST, reverse('index'), short_url.encode_url(request.user.id)

@ -1,6 +1,5 @@
from django.db.models import Func, F
from django.utils.timezone import now
from paymentwall.pingback import Pingback
from django.contrib.auth import get_user_model
from django.conf import settings
from apps.config.models import Config
@ -21,13 +20,8 @@ def baner(request):
def school_purchased(request):
if request.user.is_authenticated:
n = now().date()
school_payment = SchoolPayment.objects.filter(
school_payment = SchoolPayment.objects.paid().filter(
user=request.user,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
date_start__lte=n,
date_end__gte=n
)

@ -264,6 +264,9 @@ CELERY_BEAT_SCHEDULE = {
},
}
CLOUD_PAYMENTS_PUBLIC_ID = os.getenv('CLOUD_PAYMENTS_PUBLIC_ID', 'pk_9ae020cd5fed74499dee247067d17')
CLOUD_PAYMENTS_API_KRY = os.getenv('CLOUD_PAYMENTS_API_KRY', 'a7d5f88ea69ef59f8b5db2252e39cedf')
try:
from paymentwall import *
except ImportError:

@ -35,7 +35,7 @@ from apps.payment.views import (
CourseBuySuccessView, CourseBuyView,
PaymentwallCallbackView, SchoolBuySuccessView,
SchoolBuyView, GiftCertificatesView, GiftCertificateBuyView,
GiftCertificateBuySuccessView, GiftCertificateGetView, CloudPaymentsTestView, CloudPaymentsCallbackTestView)
GiftCertificateBuySuccessView, GiftCertificateGetView, CloudPaymentsTestView, PaymentSuccessView)
from .views import AboutView, IndexView, SchoolSchedulesView
@ -66,6 +66,8 @@ urlpatterns = [
path('payments/course/<int:pk>/success', CourseBuySuccessView.as_view(), name='course_payment_success'),
path('payments/school/success', SchoolBuySuccessView.as_view(), name='payment-success'),
path('payments/error', TemplateView.as_view(template_name='payment/payment_error.html'), name='payment-error'),
path('payments/<int:payment_id>/success', PaymentSuccessView.as_view(), name='payment-success'),
path('cloud_payments_test', CloudPaymentsTestView.as_view(), name='cloud_payments_test'),
path('school/checkout', SchoolBuyView.as_view(), name='school-checkout'),
path('search/', SearchView.as_view(), name='search'),
path('user/profile/', ProfileView.as_view(), name='user-profile'),
@ -98,8 +100,6 @@ urlpatterns = [
name='gift-certificate-payment-success'),
path('gift-certificate/<str:slug>/get', GiftCertificateGetView.as_view(), name='gift-certificate-get'),
path('faq', FAQView.as_view(), name='faq'),
path('cloud_payments_callback_test', CloudPaymentsCallbackTestView.as_view(), name='cloud_payments_callback_test'),
path('cloud_payments_test', CloudPaymentsTestView.as_view(), name='cloud_payments_test'),
]

@ -6,7 +6,6 @@ from django.db.models import Min, Func, F
from django.contrib.auth import get_user_model
from django.views.generic import TemplateView
from django.utils.timezone import now
from paymentwall.pingback import Pingback
from apps.course.models import Course
from apps.school.models import SchoolSchedule
@ -50,15 +49,10 @@ class IndexView(TemplateView):
)
date_now = now_time.date()
if self.request.user.is_authenticated:
school_payment = SchoolPayment.objects.filter(
school_payment = SchoolPayment.objects.paid().filter(
user=self.request.user,
date_start__lte=date_now,
date_end__gte=date_now,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
)
school_payment_exists = school_payment.exists()
school_schedules_purchased = school_payment.annotate(
@ -105,7 +99,7 @@ class IndexView(TemplateView):
'school_schedules': SchoolSchedule.objects.all(),
'school_schedules_purchased': set(school_schedules_purchased),
'teachers': User.objects.filter(role=User.TEACHER_ROLE, show_in_mainpage=True),
'works_count': Payment.objects.filter(status__in=Payment.PW_PAID_STATUSES).count() * 5,
'works_count': Payment.objects.paid().count() * 5,
'subscription_ends': school_payment.filter(add_days=False).first().date_end if school_payment_exists else None,
'subscription_ends_humanize': school_payment.filter(add_days=False).first().date_end_humanize if school_payment_exists else None,

@ -215,6 +215,7 @@ $(document).ready(function () {
}
if(data === '.js-popup-course-buy'){
//TODO: cloudpayments
const updateCourseCart = () => {
const $orderPrice = popup.find('.order_price_text');
const useBonuses = $bonusesCheckbox.prop('checked');

Loading…
Cancel
Save