import csv import logging import requests from django.contrib.auth import get_user_model from django.core.mail import EmailMessage from django.db import IntegrityError from django.db.models import Q from django.http import HttpResponse, HttpResponseForbidden from django.shortcuts import redirect from rest_framework.renderers import JSONRenderer from rest_framework.response import Response from rest_framework.views import APIView from yandex_money.models import Payment from django.conf import settings from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string from django.utils.html import strip_tags from courses.models import Course from courses.api import CourseParamsApi from finance.models import Bill, Invoice, InvoiceRebilling from finance.serializers import BillSerializer, InvoiceSerializer from finance.tasks import setup_periodic_billing from lms.global_decorators import transaction_decorator from lms.tools import get_real_name from django.utils import timezone logger_yandex = logging.getLogger('yandex_money') class BillListView(APIView): renderer_classes = (JSONRenderer,) @staticmethod def get(request): if request.user.is_authenticated: return Response( [BillSerializer(i).data for i in Bill.objects.filter(Q(user=request.user) | Q(opener=request.user))], status=200, ) return Response("Permission denied", status=403) @transaction_decorator def post(self, request): if request.user.is_authenticated and (request.user.groups.filter(name__in=['managers','lead_managers']).exists() or request.user.is_superuser): user = get_user_model().objects.get(email=request.JSON.get('user')) opener = get_user_model().objects.get(email=request.JSON.get('opener')) description = request.JSON.get('description', None) comment = request.JSON.get('comment', None) course_token = request.JSON.get('course_token', None) if course_token is None: return Response("Идентификатор курса не передан", status=400) try: bill_obj = Bill.objects.get(user=user, course_token=course_token) except Bill.DoesNotExist: try: bill_obj = Bill.objects.create(user=user, course_token=course_token) except IntegrityError: return Response("У пользователя уже есть счёт на этот курс", status=400) bill_obj.opener = bill_obj.opener if opener is None else opener bill_obj.description = bill_obj.description if description is None else description bill_obj.comment = bill_obj.comment if comment is None else comment bill_obj.save() return Response(bill_obj.id, status=200) return Response("Ошибка доступа, возможно вы разлогинились из другой вкладки браузера", status=403) class InvoiceDetailView(APIView): renderer_classes = (JSONRenderer,) @staticmethod def delete(request, invoice_id): try: i = Invoice.objects.get(id=invoice_id) if not i.status == "F" and not (i.bill.check_pay() and i.is_open): i.delete() except Invoice.DoesNotExist: pass return Response(status=204) @staticmethod def post(request, invoice_id): if request.user.is_authenticated and (request.user.groups.filter(name__in=['managers','lead_managers']).exists() or request.user.is_superuser): invoice_id = int(invoice_id) bill_id = request.JSON.get('bill', None) is_open = request.JSON.get('is_open', None) method = request.JSON.get('method', None) status = request.JSON.get('status', None) price = request.JSON.get('price', None) comment = request.JSON.get('comment', None) real_price = request.JSON.get('real_price', None) rebilling_on = request.JSON.get('is_rebilling', False) pay_count = request.JSON.get('pay_count', None) if bill_id is None: return Response("Не передан id счёта", status=400) if is_open is None or method is None or status is None or price is None: return Response("Не передан один из пораметров is_open, method, status, price", status=400) try: bill = Bill.objects.get(id=bill_id) except Bill.DoesNotExist: return Response('Не найден счёт с id=%s' % bill_id, status=404) method = get_real_name(elem=method[0], array=Invoice.BILL_METHOD) status = get_real_name(elem=status[0], array=Invoice.BILL_STATUSES) if bill.check_validate(invoice_id) and is_open: return Response("Уже есть платёж открывающий курс", status=400) if rebilling_on: invoice = InvoiceRebilling.objects.create( bill=bill, method=method, status=status, is_open=is_open, pay_count=pay_count, ) else: try: invoice = Invoice.objects.get(id=invoice_id) except Invoice.DoesNotExist: if not invoice_id == 0: return Response("Платёж не найден", status=404) if bill.check_pay(): return Response( "Нельзя добавить новый платёж, так как один из платежей по счёту уже оплачен", status=400) invoice = Invoice.objects.create( bill=bill, method=method, status=status, is_open=is_open, ) if invoice.status == "F": return Response(InvoiceSerializer(invoice).data, status=200) invoice.real_price = None invoice.method = method invoice.status = status if invoice.status == "F": invoice.real_price = invoice.real_price if real_price is None else real_price if bill.check_pay() and (invoice.price < price): return Response("""Нельзя менять стоимость по счёту в большую сторону, когда один из платежей оплачен""", status=400) invoice.price = price invoice.is_open = is_open invoice.comment = comment if invoice.method == 'Y' and invoice.yandex_pay is None: yandex_pay = Payment.objects.create( order_amount=invoice.price, shop_amount=0, customer_number=bill.user.id, user=bill.user, cps_email=bill.user.email, ) invoice.yandex_pay = yandex_pay invoice.send_link() context = { 'user_email': invoice.bill.user.email, 'opener_full_name': invoice.bill.opener.get_full_name(), 'course_title': Course.objects.get(token=invoice.bill.course_token).title, 'date': str(invoice.date), 'price': invoice.price, } subject, to = 'Выставлен новый счёт', invoice.bill.opener.email html_content = render_to_string('mail/sales/back_set_bill.html', context) text_content = strip_tags(html_content) msg = EmailMultiAlternatives(subject, text_content, to=[to], bcc=['dmitry.dolya@skillbox.ru']) msg.attach_alternative(html_content, "text/html") msg.send() invoice.save() return Response(InvoiceSerializer(invoice).data, status=200) return Response("Invoice detail access only for manager users", status=403) class BillDetailView(APIView): renderer_classes = (JSONRenderer,) status_code = 200 def get(self, request, pk): if request.user.is_authenticated: try: bill = Bill.objects.get(id=pk) except Bill.DoesNotExist: return Response("Bill not found", status=404) res = { "bill": BillSerializer(bill).data, "children": [InvoiceSerializer(i).data for i in bill.invoice_set.all()], } return Response( res, status=self.status_code, ) return Response("Permission denied", status=403) class FindBillView(APIView): renderer_classes = (JSONRenderer,) status_code = 200 @staticmethod def get(request): if request.user.is_authenticated() and \ (request.user.is_superuser or request.user.groups.filter(name__in=['managers', 'lead_managers']).exists()): key = request.GET.get('key', None) count = int(request.GET.get('count', '10')) if key: res = Bill.objects.filter( Q(opener__email__contains=key.lower()) | Q(user__email__contains=key.lower()) | Q(id__contains=key) ) else: res = Bill.objects.all() res = res[:(count if len(res) > count else len(res))] return Response( [BillSerializer(i).data for i in res], status=200 ) return Response('Permission denied', status=403) class YandexPay(APIView): renderer_classes = (JSONRenderer,) @staticmethod def get(request, pk): try: pay = Payment.objects.get(id=pk) try: inv = InvoiceRebilling.objects.get(yandex_pay=pay) except InvoiceRebilling: inv = None r = requests.post('https://money.yandex.ru/eshop.xml', data={ 'shopId': pay.shop_id, 'scid': pay.scid, 'sum': pay.order_amount, 'customerNumber': pay.customer_number, 'orderNumber': pay.order_number, 'cps_email': pay.cps_email, 'rebillingOn': False if inv is None else inv.rebilling_on, 'shopSuccessURL': settings.YANDEX_MONEY_SUCCESS_URL, 'shopFailURL': settings.YANDEX_MONEY_FAIL_URL, }) msg = EmailMessage( 'Пользователь перешёл на страницу оплаты.', '''Пользователь "%s" перешёл на страницу оплаты курса "%s".''' % (pay.invoice.bill.user.email, Course.objects.get(token=pay.invoice.bill.course_token).title), 'robo@skillbox.ru', [pay.invoice.bill.opener.email], bcc=['dmitry.dolya@skillbox.ru'], ) msg.send() return redirect(r.url) except Payment.DoesNotExist: return Response('Payment not found', status=404) def get_invoices(request): if not request.user.is_authenticated and (request.user.groups.filter(name="finance") or request.user.is_superuser): return HttpResponseForbidden() date_from = request.GET.get('from', None) date_to = request.GET.get('to', None) file_name = "invoices" file_name = file_name + "__from_%s" % date_from if date_from else file_name file_name = file_name + "__to_%s" % date_to if date_to else file_name invoices = Invoice.objects.filter(method="Y", status="F") invoices = invoices.filter(yandex_pay__performed_datetime__lt=date_to) if date_to else invoices invoices = invoices.filter(yandex_pay__performed_datetime__gte=date_from) if date_from else invoices response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="%s.csv"' % file_name writer = csv.writer(response) writer.writerow(['date', 'time', 'student_email', 'full_name', 'course', 'price', 'real_price', 'key']) for i in invoices.order_by('-date'): course_api = CourseParamsApi(i.bill.course_token) writer.writerow([ i.yandex_pay.performed_datetime.date(), i.yandex_pay.performed_datetime.time(), i.bill.user.email, i.bill.user.get_full_name(), course_api.get_slug_and_title()['title'], i.price, i.real_price, i.key, ]) return response class YandexCheckView(APIView): renderer_classes = (JSONRenderer,) @staticmethod def post(request): data = dict() for i in request.body.decode('utf-8').split('&'): key = i.split('=')[0] val = i.split('=')[1] data[key] = val logger_yandex.info('Проверка платежа запрос', exc_info=True, extra={ 'request': data, }) try: pay = Payment.objects.get(order_number=data['orderNumber']) except Payment.DoesNotExist: logger_yandex.error('Ошибка проверки платежа', exc_info=True, extra={ 'request': "Payment with id=%s not found" % data['orderNumber'], }) return Response(status=204) if not pay.status == Payment.STATUS.PROCESSED: logger_yandex.error('Ошибка проверки платежа', exc_info=True, extra={ 'request': "Payment with id=%s have status %s" % (data['orderNumber'], pay.status), }) return Response(status=204) if not pay.shop_id == int(data['shopId']): logger_yandex.error('Ошибка проверки платежа', exc_info=True, extra={ 'request': "ShopId=%s not match" % (data['shopId'],), }) return Response(status=204) if not pay.scid == int(data['scid']): logger_yandex.error('Ошибка проверки платежа', exc_info=True, extra={ 'request': "scid=%s not match" % (data['scid'],) }) return Response(status=204) if not pay.order_amount == float(data['orderSumAmount']): logger_yandex.error('Ошибка проверки платежа', exc_info=True, extra={ 'request': "Expected amount is %s received amount is %s" % (pay.order_amount, data['orderSumAmount']), }) return Response(status=204) now = timezone.now() pay.performed_datetime = now.isoformat() pay.save() xml_res = """ """ % (pay.performed_datetime, str(data['invoiceId']), str(pay.shop_id)) logger_yandex.info('Проверка платежа ответ', exc_info=True, extra={ 'response': xml_res, }) return HttpResponse(xml_res, content_type='application/xml') class YandexAvisoView(APIView): renderer_classes = (JSONRenderer,) @staticmethod def post(request): data = dict() for i in request.body.decode('utf-8').split('&'): key = i.split('=')[0] val = i.split('=')[1] data[key] = val try: pay = Payment.objects.get(order_number=data['orderNumber']) except Payment.DoesNotExist: logger_yandex.error('Ошибка подтверждения платежа', exc_info=True, extra={ 'request': "Payment with invoice_id=%s not found" % data['orderNumber'], }) return Response(status=204) logger_yandex.info('Подтверждение платежа запрос', exc_info=True, extra={ 'request': 'Get success pay with invoice_id(yandex) %s' % str(data['invoiceId']), }) pay.shop_amount = data['shopSumAmount'] pay.status = Payment.STATUS.SUCCESS now = timezone.now() pay.performed_datetime = now.isoformat() pay.save() xml_res = """ """ % (pay.performed_datetime, str(data['invoiceId']), str(pay.shop_id)) logger_yandex.info('Подтверждение платежа ответ', exc_info=True, extra={ 'response': xml_res, }) context = { 'user_email': pay.invoice.bill.user.email, 'opener_full_name': pay.invoice.bill.opener.get_full_name(), 'course_title': Course.objects.get(token=pay.invoice.bill.course_token).title, 'date': str(pay.invoice.date), 'price': pay.invoice.price, 'finish_date': pay.performed_datetime, } subject, to = 'Счёт оплачен', pay.invoice.bill.opener.email html_content = render_to_string('mail/sales/pay_access.html', context) text_content = strip_tags(html_content) msg = EmailMultiAlternatives( subject, text_content, to=[to], bcc=['dmitry.dolya@skillbox.ru', 'vera.procenko@skillbox.ru']) msg.attach_alternative(html_content, "text/html") msg.send() try: InvoiceRebilling.objects.get(yandex_pay=pay) setup_periodic_billing(pay.order_number) except InvoiceRebilling.DoesNotExist: pass return HttpResponse(xml_res, content_type='application/xml') class YandexFailView(APIView): renderer_classes = (JSONRenderer,) @staticmethod def post(request): data = dict() for i in request.body.decode('utf-8').split('&'): key = i.split('=')[0] val = i.split('=')[1] data[key] = val logger_yandex.error(data) return redirect(to=settings.DOMAIN) class DemoYandexCheckView(YandexCheckView): """для тестирования платежей""" pass class DemoYandexAvisoView(YandexAvisoView): pass