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')