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 from django.contrib import messages from django.contrib.auth.decorators import login_required from django.http import HttpResponse, Http404 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.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/paymentwall_widget.html' def get(self, request, pk=None, *args, **kwargs): 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.filter(user=request.user, course=course, status__in=Payment.PW_PAID_STATUSES).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, ) 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 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])) 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()}) @method_decorator(login_required, name='dispatch') class SchoolBuyView(TemplateView): template_name = 'payment/paymentwall_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, 'Выберите несколько дней недели.') return redirect('school:school') try: weekdays = [int(weekday) for weekday in weekdays] except ValueError: messages.error(request, 'Ошибка выбора дней недели.') return redirect('school:school') prev_school_payment = SchoolPayment.objects.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: 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, 'Выбранные дни отсутствуют в оставшемся периоде подписки') 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 = Pingback.PINGBACK_TYPE_REGULAR 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(): 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()}) @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)