You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

473 lines
19 KiB

from decimal import Decimal
import short_url
import arrow
import json
import logging
from datetime import timedelta
from urllib.parse import urlsplit
import datetime
import calendar
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, 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, reverse
from django.utils.decorators import method_decorator
from django.utils.timezone import now
from django.conf import settings
from paymentwall import Pingback, Product, Widget
from apps.course.models import Course
from apps.school.models import SchoolSchedule
from apps.payment.tasks import transaction_to_mixpanel, product_payment_to_mixpanel, transaction_to_roistat
from .models import AuthorBalance, CoursePayment, SchoolPayment, Payment, UserBonus, GiftCertificate, \
GiftCertificatePayment, UserGiftCertificate
logger = logging.getLogger('django')
class DisallowedPingbackHost(Exception):
pass
@method_decorator(login_required, name='dispatch')
class CourseBuySuccessView(TemplateView):
template_name = 'payment/course_payment_success.html'
def get(self, request, pk=None, *args, **kwargs):
course = get_object_or_404(Course, pk=pk)
return self.render_to_response(context={'course': course})
@method_decorator(login_required, name='dispatch')
class SchoolBuySuccessView(TemplateView):
template_name = 'payment/payment_success.html'
def get(self, request, pk=None, *args, **kwargs):
return self.render_to_response(context={'school': True})
@method_decorator(login_required, name='dispatch')
class CourseBuyView(TemplateView):
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])
course = Course.objects.get(id=pk)
roistat_visit = request.COOKIES.get('roistat_visit', None)
if request.user == course.author:
messages.error(request, 'Вы не можете приобрести свой курс.')
return redirect(reverse_lazy('course', args=[course.id]))
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)
course_payment = CoursePayment.objects.create(
user=request.user,
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 = Payment.STATUS_PAID
else:
bonus = UserBonus.objects.create(amount= -request.user.bonus, user=request.user,
payment=course_payment)
course_payment.amount -= request.user.bonus
course_payment.bonus = bonus
course_payment.save()
if course_payment.is_paid():
return redirect(reverse_lazy('course_payment_success', args=[course.id]))
context = {
'payment': course_payment,
}
return self.render_to_response(context=context)
@method_decorator(login_required, name='dispatch')
class SchoolBuyView(TemplateView):
template_name = 'payment/cloudpayments_widget.html'
def get(self, request, *args, **kwargs):
host = urlsplit(self.request.META.get('HTTP_REFERER'))
host = str(host[0]) + '://' + str(host[1])
weekdays = set(request.GET.getlist('weekdays', []))
use_bonuses = request.GET.get('use_bonuses')
roistat_visit = request.COOKIES.get('roistat_visit', None)
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, 'Выберите несколько дней недели')
if request.is_ajax():
return JsonResponse({
'error': 'Выберите несколько дней недели'
})
return redirect('school:school')
try:
weekdays = [int(weekday) for weekday in weekdays]
except ValueError:
messages.error(request, 'Ошибка выбора дней недели')
if request.is_ajax():
return JsonResponse({
'error': 'Ошибка выбора дней недели'
})
return redirect('school:school')
prev_school_payment = SchoolPayment.objects.paid().filter(
user=request.user,
date_start__lte=date_start,
date_end__gte=date_start,
add_days=False,
).last()
add_days = bool(prev_school_payment)
if add_days:
school_payment = SchoolPayment.objects.create(
user=request.user,
weekdays=weekdays,
date_start=date_start,
date_end=prev_school_payment.date_end,
add_days=True,
roistat_visit=roistat_visit,
)
# Если произойдет ошибка и оплату бонусами повторят еще раз на те же дни, то вернет ошибку
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(
user=request.user,
weekdays=weekdays,
roistat_visit=roistat_visit,
date_start=date_start,
date_end=Payment.add_months(date_start),
)
if use_bonuses:
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 = Payment.STATUS_PAID
else:
bonus = UserBonus.objects.create(amount= -request.user.bonus, user=request.user,
payment=school_payment)
school_payment.amount -= request.user.bonus
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'))
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')
class PaymentwallCallbackView(View):
def get_request_ip(self):
x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = self.request.META.get('REMOTE_ADDR')
return ip
def get(self, request, *args, **kwargs):
payment_raw_data = request.GET.copy()
pingback = Pingback(payment_raw_data, self.get_request_ip())
if pingback.validate():
splitted = pingback.get_product().get_id().split('_')
product_type_name, payment_id = '_'.join(splitted[:-1]), splitted[-1]
if product_type_name == 'course':
product_payment_class = CoursePayment
elif product_type_name == 'school':
product_payment_class = SchoolPayment
elif product_type_name == 'gift_certificate':
product_payment_class = GiftCertificatePayment
else:
return HttpResponse(status=403)
try:
payment = product_payment_class.objects.get(pk=payment_id)
except product_payment_class.DoesNotExist:
return HttpResponse(status=403)
logger.info(
json.dumps(payment_raw_data),
)
payment.status = pingback.get_type()
payment.data = payment_raw_data
if pingback.is_deliverable():
effective_amount = payment_raw_data.get('effective_price_amount')
if effective_amount:
payment.amount = Decimal(effective_amount)
transaction_to_mixpanel.delay(
payment.user.id,
payment.amount,
now().strftime('%Y-%m-%dT%H:%M:%S'),
product_type_name,
)
if 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,
}
elif 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,
}
elif 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,
}
payment.save()
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'),
pingback.get_type(),
product_type_name,
payment.roistat_visit,
)
author_balance = getattr(payment, 'author_balance', None)
if author_balance and author_balance.type == AuthorBalance.IN:
if pingback.is_deliverable():
payment.author_balance.status = AuthorBalance.ACCEPTED
elif pingback.is_under_review():
payment.author_balance.status = AuthorBalance.PENDING
else:
payment.author_balance.status = AuthorBalance.DECLINED
payment.author_balance.save()
return HttpResponse('OK')
else:
raise DisallowedPingbackHost
return HttpResponse(status=403)
class GiftCertificatesView(TemplateView):
model = GiftCertificate
template_name = 'payment/gift_certificates.html'
def get(self, request, *args, **kwargs):
gift_certificates = GiftCertificate.objects.all()
context = self.get_context_data(**kwargs)
context['gift_certificates'] = gift_certificates
return self.render_to_response(context)
@method_decorator(login_required, name='dispatch')
class GiftCertificateBuyView(TemplateView):
model = GiftCertificate
template_name = 'payment/paymentwall_widget.html'
def get(self, request, pk, *args, **kwargs):
gift_certificate = get_object_or_404(GiftCertificate, pk=pk)
roistat_visit = request.COOKIES.get('roistat_visit', None)
gift_certificate_payment = GiftCertificatePayment.objects.create(
user=request.user,
gift_certificate=gift_certificate,
roistat_visit=roistat_visit,)
context = self.get_context_data(**kwargs)
product = Product(
f'gift_certificate_{gift_certificate_payment.id}',
gift_certificate_payment.amount,
'RUB',
'Подарочный сертификат',
)
host = urlsplit(self.request.META.get('HTTP_REFERER'))
host = str(host[0]) + '://' + str(host[1])
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('gift-certificate-payment-success', args=[gift_certificate_payment.id])),
'failure_url': host + str(reverse_lazy('payment-error')),
}
)
context['widget'] = widget.get_html_code()
return self.render_to_response(context)
@method_decorator(login_required, name='dispatch')
class GiftCertificateBuySuccessView(TemplateView):
template_name = 'payment/gift_certificate_payment_success.html'
def get(self, request, payment_id=None, *args, **kwargs):
try:
GiftCertificatePayment.objects.get(id=payment_id)
except:
raise Http404()
return self.render_to_response(context={'gift_certificate': True})
@method_decorator(login_required, name='dispatch')
class GiftCertificateGetView(TemplateView):
template_name = 'payment/gift_certificate_get.html'
def get(self, request, slug, *args, **kwargs):
try:
ugs = get_object_or_404(UserGiftCertificate, pk=short_url.decode_url(slug))
except:
raise Http404()
if UserBonus.objects.filter(payment=ugs.payment).exists():
raise Http404()
bonuses = UserBonus.objects.create(user=request.user, amount=ugs.gift_certificate.price,
payment=ugs.payment)
ugs.bonuses_sent = bonuses
ugs.save()
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
class CloudPaymentsTestView(TemplateView):
template_name = 'payment/cloud_payments_test.html'
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
context['invoice_id'] = random.randrange(1000, 100000000)
return self.render_to_response(context)
class PaymentSuccessView(View):
def post(self, request, payment_id):
payment_data = request.POST.get('payment_data')
try:
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')