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.
584 lines
22 KiB
584 lines
22 KiB
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, render_to_response
|
|
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 lms.global_decorators import transaction_decorator
|
|
from lms.tools import get_real_name
|
|
from django.utils import timezone
|
|
import uuid
|
|
|
|
logger_yandex = logging.getLogger('yandex_money')
|
|
|
|
|
|
def test_pay(request):
|
|
return render_to_response('yandex/test_pay.html', context={
|
|
'shop_id': settings.YANDEX_MONEY_SHOP_ID,
|
|
'sc_id': settings.YANDEX_MONEY_TEST_SCID,
|
|
'order_number': str(uuid.uuid4())[:12],
|
|
'user_email': request.user.email if request.user.is_authenticated else 'user@domain.com'
|
|
})
|
|
|
|
|
|
class FreezeView(APIView):
|
|
renderer_classes = (JSONRenderer,)
|
|
|
|
@staticmethod
|
|
def post(request, pk):
|
|
try:
|
|
bill = Bill.objects.get(id=pk)
|
|
except Bill.DoesNotExist:
|
|
return Response("Счёт не найден", status=404)
|
|
|
|
if request.user.is_authenticated and request.user.email == bill.user.email:
|
|
bill.freeze_course()
|
|
return Response(status=204)
|
|
return Response("Permission denied", status=403)
|
|
|
|
|
|
class UnFreezeView(APIView):
|
|
renderer_classes = (JSONRenderer,)
|
|
|
|
@staticmethod
|
|
def post(request, pk):
|
|
try:
|
|
bill = Bill.objects.get(id=pk)
|
|
except Bill.DoesNotExist:
|
|
return Response("Счёт не найден", status=404)
|
|
|
|
if request.user.is_authenticated and request.user.email == bill.user.email:
|
|
bill.unfreeze_course()
|
|
return Response(status=204)
|
|
return Response("Permission denied", status=403)
|
|
|
|
|
|
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)
|
|
pay_count = int(request.JSON.get('pay_count', '1'))
|
|
|
|
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 pay_count > 1:
|
|
invoice = InvoiceRebilling.objects.create(
|
|
bill=bill,
|
|
method=method,
|
|
status=status,
|
|
is_open=is_open,
|
|
rebilling_on=True,
|
|
)
|
|
|
|
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 pay_count > 1:
|
|
invoice.create_child_pays(pay_count)
|
|
|
|
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.DoesNotExist:
|
|
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 = """<checkOrderResponse performedDatetime="%s" code="0" invoiceId="%s" shopId="%s"/>
|
|
""" % (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']
|
|
invoice = pay.invoice
|
|
invoice.key = data['invoiceId']
|
|
invoice.save()
|
|
pay.status = Payment.STATUS.SUCCESS
|
|
now = timezone.now()
|
|
pay.performed_datetime = now.isoformat()
|
|
pay.save()
|
|
xml_res = """<paymentAvisoResponse performedDatetime="%s" code="0" invoiceId="%s" shopId="%s"/>
|
|
""" % (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()
|
|
|
|
return HttpResponse(xml_res, content_type='application/xml')
|
|
|
|
|
|
class YandexFailView(APIView):
|
|
renderer_classes = (JSONRenderer,)
|
|
|
|
@staticmethod
|
|
def get(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(APIView):
|
|
renderer_classes = (JSONRenderer,)
|
|
|
|
@staticmethod
|
|
def post(request):
|
|
# Деалем допущение, что customerNumber=abc1111111 иначе всё плохо
|
|
data = dict()
|
|
try:
|
|
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,
|
|
})
|
|
|
|
except IndexError:
|
|
logger_yandex.error("Непредвиденная ошибка проверки тестогого платежа")
|
|
return Response(status=204)
|
|
|
|
now = timezone.now()
|
|
try:
|
|
if data['customerNumber'] == "abc000":
|
|
xml_res = """<checkOrderResponse performedDatetime="%s" code="0" invoiceId="%s" shopId="%s"/>
|
|
""" % (now.isoformat(), str(data['invoiceId']), str(data['shopId']))
|
|
else:
|
|
xml_res = """<checkOrderResponse performedDatetime="%s" code="100" invoiceId="%s" shopId="%s"
|
|
message="Неверный номер ордера"/>""" % (now.isoformat(), str(data['invoiceId']), str(data['shopId']))
|
|
logger_yandex.warning(
|
|
"Ошибка проверки тестогого платежа" , exc_info=True, extra={
|
|
"response": xml_res
|
|
}
|
|
)
|
|
return HttpResponse(xml_res, content_type='application/xml')
|
|
except KeyError:
|
|
logger_yandex.error('Ошибка в данных checkYandex')
|
|
return Response(status=204)
|
|
|
|
|
|
class DemoYandexAvisoView(APIView):
|
|
|
|
@staticmethod
|
|
def post(request):
|
|
logger_yandex.info('Начало тестирования avisoYandex')
|
|
data = dict()
|
|
for i in request.body.decode('utf-8').split('&'):
|
|
key = i.split('=')[0]
|
|
val = i.split('=')[1]
|
|
data[key] = val
|
|
|
|
now = timezone.now()
|
|
if float(data['orderSumAmount']) < 1001:
|
|
xml_res = """<paymentAvisoResponse performedDatetime="%s" code="0" invoiceId="%s" shopId="%s"/>
|
|
""" % (now.isoformat(), str(data['invoiceId']), str(data['shopId']))
|
|
logger_yandex.info('Подтверждение демо платежа ответ', exc_info=True, extra={
|
|
'response': xml_res,
|
|
})
|
|
|
|
else:
|
|
xml_res = """<paymentAvisoResponse performedDatetime="%s" code="100" invoiceId="%s" shopId="%s"
|
|
message="Нам не позволяет совесть принять от вас более 1000 рублей"/
|
|
>""" % (now.isoformat(), str(data['invoiceId']), str(data['shopId']))
|
|
logger_yandex.warning(
|
|
"Ошибка подтверждения тестогого платежа", exc_info=True, extra={
|
|
"response": xml_res
|
|
}
|
|
)
|
|
return HttpResponse(xml_res, content_type='application/xml') |