diff --git a/api/v1/views.py b/api/v1/views.py
index f43ca18a..967dbb91 100644
--- a/api/v1/views.py
+++ b/api/v1/views.py
@@ -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
diff --git a/apps/course/management/commands/fix_access_expire.py b/apps/course/management/commands/fix_access_expire.py
index 2570e8e0..77c13e44 100644
--- a/apps/course/management/commands/fix_access_expire.py
+++ b/apps/course/management/commands/fix_access_expire.py
@@ -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()
diff --git a/apps/course/templates/course/course.html b/apps/course/templates/course/course.html
index fb9c351f..dcba1856 100644
--- a/apps/course/templates/course/course.html
+++ b/apps/course/templates/course/course.html
@@ -22,6 +22,11 @@
{% block ogdescription %}{{ course.short_description | striptags }}{% endblock ogdescription %}
+{% block head %}
+ {{ block.super }}
+
+{% endblock head %}
+
{% block content %}
diff --git a/apps/course/views.py b/apps/course/views.py
index 1f0a101f..77597acc 100644
--- a/apps/course/views.py
+++ b/apps/course/views.py
@@ -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()
# если это не автор или админ
diff --git a/apps/notification/tasks.py b/apps/notification/tasks.py
index 243be211..c41f16be 100644
--- a/apps/notification/tasks.py
+++ b/apps/notification/tasks.py
@@ -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,
diff --git a/apps/payment/management/commands/roistat_set_statuses.py b/apps/payment/management/commands/roistat_set_statuses.py
index f3ac3e11..5990c921 100644
--- a/apps/payment/management/commands/roistat_set_statuses.py
+++ b/apps/payment/management/commands/roistat_set_statuses.py
@@ -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',
},
diff --git a/apps/payment/management/commands/send_transactions_to_roistat.py b/apps/payment/management/commands/send_transactions_to_roistat.py
index f5569984..a2a93105 100644
--- a/apps/payment/management/commands/send_transactions_to_roistat.py
+++ b/apps/payment/management/commands/send_transactions_to_roistat.py
@@ -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))
diff --git a/apps/payment/migrations/0033_payment_payment_platform.py b/apps/payment/migrations/0033_payment_payment_platform.py
new file mode 100644
index 00000000..e34e1300
--- /dev/null
+++ b/apps/payment/migrations/0033_payment_payment_platform.py
@@ -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),
+ ),
+ ]
diff --git a/apps/payment/migrations/0034_fill_payment_platform.py b/apps/payment/migrations/0034_fill_payment_platform.py
new file mode 100644
index 00000000..b9b34c8f
--- /dev/null
+++ b/apps/payment/migrations/0034_fill_payment_platform.py
@@ -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),
+ ]
diff --git a/apps/payment/migrations/0035_auto_20190222_0233.py b/apps/payment/migrations/0035_auto_20190222_0233.py
new file mode 100644
index 00000000..fd3273fe
--- /dev/null
+++ b/apps/payment/migrations/0035_auto_20190222_0233.py
@@ -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),
+ ),
+ ]
diff --git a/apps/payment/models.py b/apps/payment/models.py
index 4e157d4a..fb064c9c 100644
--- a/apps/payment/models.py
+++ b/apps/payment/models.py
@@ -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')
diff --git a/apps/payment/templates/payment/cloud_payments_test.html b/apps/payment/templates/payment/cloud_payments_test.html
deleted file mode 100644
index c26a2345..00000000
--- a/apps/payment/templates/payment/cloud_payments_test.html
+++ /dev/null
@@ -1,30 +0,0 @@
-{% extends "templates/lilcity/index.html" %} {% load static %}
-
-{% block head %}
-
-{% endblock head %}
-
-{% block content %}
-
-{% endblock content %}
-
-{% block foot_js %}
-
-{% endblock foot_js %}
diff --git a/apps/payment/templates/payment/cloudpayments_widget.html b/apps/payment/templates/payment/cloudpayments_widget.html
new file mode 100644
index 00000000..3142316d
--- /dev/null
+++ b/apps/payment/templates/payment/cloudpayments_widget.html
@@ -0,0 +1,55 @@
+{% extends "templates/lilcity/index.html" %} {% load static settings %}
+
+{% block head %}
+
+{% endblock head %}
+
+{% block content %}
+
+{% endblock content %}
+
+{% block foot_js %}
+
+{% endblock foot_js %}
diff --git a/apps/payment/views.py b/apps/payment/views.py
index 50a7e4de..afa65d80 100644
--- a/apps/payment/views.py
+++ b/apps/payment/views.py
@@ -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')
diff --git a/apps/school/templates/school/summer_school.html b/apps/school/templates/school/summer_school.html
index e0e05514..d5a5457b 100644
--- a/apps/school/templates/school/summer_school.html
+++ b/apps/school/templates/school/summer_school.html
@@ -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 }}
+
+{% endblock head %}
{% block content %}
{% if not is_purchased and not prev_school_payments_exists %}
{% include "../summer/promo.html" %}
diff --git a/apps/school/views.py b/apps/school/views.py
index 3dc0f01a..4317614c 100644
--- a/apps/school/views.py
+++ b/apps/school/views.py
@@ -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,
)
diff --git a/apps/user/models.py b/apps/user/models.py
index 5713e6db..37da804e 100644
--- a/apps/user/models.py
+++ b/apps/user/models.py
@@ -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)
diff --git a/apps/user/views.py b/apps/user/views.py
index 1c244b01..9177f39c 100644
--- a/apps/user/views.py
+++ b/apps/user/views.py
@@ -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)
diff --git a/project/context_processors.py b/project/context_processors.py
index 6e954da7..6b966957 100644
--- a/project/context_processors.py
+++ b/project/context_processors.py
@@ -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
)
diff --git a/project/settings.py b/project/settings.py
index 9f835203..f635b6eb 100644
--- a/project/settings.py
+++ b/project/settings.py
@@ -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:
diff --git a/project/urls.py b/project/urls.py
index c29a1803..4054af90 100644
--- a/project/urls.py
+++ b/project/urls.py
@@ -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//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//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//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'),
]
diff --git a/project/views.py b/project/views.py
index 7404e317..4b8dcaee 100644
--- a/project/views.py
+++ b/project/views.py
@@ -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,
diff --git a/web/src/js/modules/popup.js b/web/src/js/modules/popup.js
index 8d3079ba..6a6b461e 100644
--- a/web/src/js/modules/popup.js
+++ b/web/src/js/modules/popup.js
@@ -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');