from decimal import Decimal import short_url import json import logging from datetime import timedelta from urllib.parse import urlsplit import datetime 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 from django.views.decorators.csrf import csrf_exempt from django.urls import reverse from django.utils.decorators import method_decorator from django.utils.timezone import now from paymentwall import Pingback, Product, Widget from apps.content.models import Package from apps.course.models import Course from apps.payment.tasks import transaction_to_mixpanel, product_payment_to_mixpanel, transaction_to_roistat from apps.notification.utils import send_email from .models import AuthorBalance, CoursePayment, SchoolPayment, Payment, UserBonus, GiftCertificate, \ GiftCertificatePayment, UserGiftCertificate, DrawingCampPayment logger = logging.getLogger('django') class DisallowedPingbackHost(Exception): pass class BuyMixin(object): template_name = 'payment/pay.html' def get(self, request, *args, **kwargs): roistat_visit = request.COOKIES.get('roistat_visit', None) use_bonuses = request.GET.get('use_bonuses') product_name = '' context = self.get_context_data(**kwargs) payment = None if self.product_type == 'gift_certificate': gift_certificate = get_object_or_404(GiftCertificate, pk=kwargs.get('pk')) payment = GiftCertificatePayment.objects.create( user=request.user, gift_certificate=gift_certificate, roistat_visit=roistat_visit, ) success_url = reverse('gift-certificate-payment-success', args=[payment.id]) product_name = 'Подарочный сертификат' if self.product_type == 'course': course = Course.objects.get(id=kwargs.get('pk')) if request.user == course.author: messages.error(request, 'Вы не можете приобрести свой курс.') return redirect(reverse('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) payment = CoursePayment.objects.create( user=request.user, course=course, access_expire=access_expire, roistat_visit=roistat_visit, ) success_url = reverse('course_payment_success', args=[course.id]) product_name = f'Курс "{course.title}"' context['course'] = course if self.product_type == 'school': date_start = request.GET.get('date_start') duration = request.GET.get('duration') payment_id = request.GET.get('payment_id') package = get_object_or_404(Package, duration=duration) date_start = date_start and datetime.datetime.strptime(date_start, '%Y-%m-%d').date() or now().date() prev_payment = SchoolPayment.objects.paid().filter( user=self.request.user, date_end__gte=now().date(), ).last() if prev_payment and prev_payment.date_end > date_start: date_start = prev_payment.date_end + timedelta(1) if payment_id: payment = get_object_or_404(SchoolPayment, id=payment_id) else: amount_data = SchoolPayment.calc_amount(package=package, user=request.user, date_start=date_start) payment = SchoolPayment.objects.create( user=request.user, weekdays=amount_data.get('weekdays'), roistat_visit=roistat_visit, date_start=amount_data.get('date_start'), date_end=amount_data.get('date_end'), package=package, ) if payment_id and payment.bonus and not use_bonuses: bonus = payment.bonus payment.amount += payment.bonus payment.bonus = None bonus.delete() success_url = '%s?duration=%s' % (reverse('payment-success'), duration) product_name = 'Подписка' context['school'] = True if self.product_type == 'drawing_camp': date_start = request.GET.get('date_start') date_start = date_start and datetime.datetime.strptime(date_start, '%Y-%m-%d').date() or now().date() date_start, date_end = Payment.get_date_range(date_start, months=1, is_camp=True) prev_payment = DrawingCampPayment.objects.filter( user=request.user, date_start__lte=date_start, date_end__gte=date_start, status__in=[ Pingback.PINGBACK_TYPE_REGULAR, Pingback.PINGBACK_TYPE_GOODWILL, Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED, ], ).exists() if prev_payment: return HttpResponse(status=403) payment = DrawingCampPayment.objects.create( user=request.user, roistat_visit=roistat_visit, date_start=date_start, date_end=date_end, ) success_url = reverse('camp-payment-success') product_name = 'Подписка' context['camp'] = True if use_bonuses and request.user.bonus: if request.user.bonus >= payment.amount: bonus = UserBonus.objects.create(amount=-payment.amount, user=request.user, payment=payment) payment.amount = 0 payment.status = Pingback.PINGBACK_TYPE_REGULAR else: bonus = UserBonus.objects.create(amount=-request.user.bonus, user=request.user, payment=payment) payment.amount -= request.user.bonus payment.bonus = bonus payment.save() if payment.is_paid(): self.after_buy(payment) return redirect(success_url) product = Product( f'course_{payment.id}', payment.amount, 'RUB', product_name, ) widget = Widget( str(request.user.id), 'p1_1', [product], extra_params={ 'email': request.user.email, 'lang': 'ru', 'evaluation': 1, 'demo': 1, 'test_mode': 1, 'success_url': request.build_absolute_uri(success_url), 'failure_url': request.build_absolute_uri(reverse('payment-error')), } ) if self.request.is_ajax(): self.template_name = 'payment/paymentwall_widget.html' attrs = {'height': '400'} if request.user_agent.is_mobile: attrs['width'] = '100%' attrs['height'] = '600' context['widget'] = widget.get_html_code(attrs) return self.render_to_response(context) def after_buy(self, payment): product_type_name = None if isinstance(payment, CoursePayment): product_type_name == 'course' elif isinstance(payment, SchoolPayment): product_type_name == 'school' elif isinstance(payment, DrawingCampPayment): product_type_name == 'drawing_camp' elif isinstance(payment, GiftCertificatePayment): product_type_name == 'gift_certificate' 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 == 'drawing_camp': properties = { 'payment_id': payment.id, 'amount': payment.amount, 'status': payment.status, '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, } 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, datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'), payment.status, product_type_name, payment.roistat_visit, ) if product_type_name == 'course': send_email.delay('Спасибо за покупку!', payment.user.email, 'notification/email/buy_email.html', product_type=product_type_name, url=payment.course.url, username=payment.user.get_full_name(), course_title=payment.course.title, access_duration=payment.access_duration) elif product_type_name != 'school': send_email.delay('Спасибо за покупку!', payment.user.email, 'notification/email/buy_email.html', product_type=product_type_name) elif product_type_name != 'drawing_camp': send_email.delay('Спасибо за покупку!', payment.user.email, 'notification/email/buy_email.html', product_type=product_type_name, date_start=payment.date_start, date_end=payment.date_end) author_balance = getattr(payment, 'author_balance', None) if author_balance and author_balance.type == AuthorBalance.IN: if payment.is_deliverable(): payment.author_balance.status = AuthorBalance.ACCEPTED elif payment.is_under_review(): payment.author_balance.status = AuthorBalance.PENDING else: payment.author_balance.status = AuthorBalance.DECLINED payment.author_balance.save() @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, is_camp=False, *args, **kwargs): context = { 'duration': request.GET.get('duration'), 'camp': is_camp, 'school': not is_camp } return self.render_to_response(context=context) @method_decorator(login_required, name='dispatch') class CourseBuyView(BuyMixin, TemplateView): template_name = 'payment/pay.html' product_type = 'course' @method_decorator(login_required, name='dispatch') class SchoolBuyView(BuyMixin, TemplateView): template_name = 'payment/pay.html' product_type = 'school' @method_decorator(login_required, name='dispatch') class DrawingCampBuyView(BuyMixin, TemplateView): template_name = 'payment/pay.html' product_type = 'drawing_camp' @method_decorator(csrf_exempt, name='dispatch') class PaymentwallCallbackView(BuyMixin, 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 == 'drawing_camp': product_payment_class = DrawingCampPayment 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, ) self.after_buy(payment) payment.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(BuyMixin, TemplateView): model = GiftCertificate product_type = 'gift_certificate' @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 request.user.bonuses.filter(payment=ugs.payment, referral__isnull=True).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)