Merge branch 'dev' into 'master'

Dev

See merge request !315
master
Andrey 8 years ago
commit 64442b7ca4
  1. 10
      finance/admin.py
  2. 37
      finance/migrations/0006_auto_20180330_1121.py
  3. 23
      finance/migrations/0007_auto_20180330_1452.py
  4. 28
      finance/migrations/0008_invoicerebilling.py
  5. 21
      finance/migrations/0009_invoicerebilling_pay_count.py
  6. 29
      finance/models.py
  7. 11
      finance/signals.py
  8. 86
      finance/tasks.py
  9. 45
      finance/views.py
  10. 3
      lms/settings.py

@ -1,7 +1,13 @@
# coding=utf-8
from django.contrib import admin
from finance.models import Bill, Invoice
from finance.models import Bill, Invoice, InvoiceRebilling
class InvoiceAdmin(admin.ModelAdmin):
list_display = ('__str__', 'rebilling_on',)
admin.site.register(Bill)
admin.site.register(Invoice)
admin.site.register(Invoice)
admin.site.register(InvoiceRebilling, InvoiceAdmin)

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-03-30 11:21
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('finance', '0005_auto_20180329_1346'),
]
operations = [
migrations.RemoveField(
model_name='installmentplan',
name='bill',
),
migrations.AddField(
model_name='invoice',
name='rebilling',
field=models.BooleanField(default=False, editable=False, verbose_name='Повторный платеж'),
),
migrations.AddField(
model_name='invoice',
name='rebilling_on',
field=models.BooleanField(default=False, editable=False, verbose_name='Повторять платеж'),
),
migrations.AlterField(
model_name='invoice',
name='date',
field=models.DateTimeField(auto_now_add=True),
),
migrations.DeleteModel(
name='InstallmentPlan',
),
]

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-03-30 14:52
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('finance', '0006_auto_20180330_1121'),
]
operations = [
migrations.RemoveField(
model_name='invoice',
name='rebilling',
),
migrations.RemoveField(
model_name='invoice',
name='rebilling_on',
),
]

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-03-30 14:52
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('finance', '0007_auto_20180330_1452'),
]
operations = [
migrations.CreateModel(
name='InvoiceRebilling',
fields=[
('invoice_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='finance.Invoice')),
('rebilling_on', models.BooleanField(default=False, editable=False, verbose_name='Повторять платеж')),
],
options={
'verbose_name': 'Повторный платёж',
'verbose_name_plural': 'Повторные платежи',
},
bases=('finance.invoice',),
),
]

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-03-30 15:03
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('finance', '0008_invoicerebilling'),
]
operations = [
migrations.AddField(
model_name='invoicerebilling',
name='pay_count',
field=models.SmallIntegerField(default=2, editable=False, verbose_name='Всего платежей'),
preserve_default=False,
),
]

@ -1,5 +1,6 @@
# coding=utf-8
from django.conf import settings
from django.core.mail import EmailMessage
from django.db import models
from yandex_money.models import Payment
@ -58,12 +59,23 @@ class Invoice(models.Model):
blank=True, editable=False)
bill = models.ForeignKey(to=Bill, verbose_name="Связный счёт")
is_open = models.BooleanField(default=True, verbose_name="Открывает ли платёж курс")
date = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
date = models.DateTimeField(auto_now_add=True)
def get_comment(self):
return '''Вам выставлен счёт,''' if \
self.comment == "" else self.comment
def send_link(self):
msg = EmailMessage(
'Вам выставлен новый счёт',
"""%s для оплаты перейдите по ссылке
%s/api/v1/finance/payment/%s/""" % (self.get_comment(), settings.DOMAIN, self.yandex_pay.id),
to=[self.yandex_pay.cps_email],
bcc=[self.bill.opener.email],
reply_to=[self.bill.opener.email],
)
msg.send()
def __str__(self):
return '%s:%s %s' % (self.id, self.get_status_display(), self.bill.user)
@ -72,15 +84,10 @@ class Invoice(models.Model):
verbose_name_plural = 'Платежи'
class InstallmentPlan(models.Model):
bill = models.OneToOneField(to=Bill, verbose_name="Связный счёт")
date = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
invoice_amount = models.IntegerField(verbose_name="Количество платежей")
price = models.IntegerField(verbose_name="Цена одного платежа")
def __str__(self):
return '%s' % self.bill.user.email
class InvoiceRebilling(Invoice):
rebilling_on = models.BooleanField(verbose_name='Повторять платеж', default=False, editable=False)
pay_count = models.SmallIntegerField(verbose_name='Всего платежей', editable=False)
class Meta:
verbose_name = 'Рассрочка'
verbose_name_plural = 'Рассрочки'
verbose_name = 'Повторный платёж'
verbose_name_plural = 'Повторные платежи'

@ -16,17 +16,6 @@ def invoice_signal(instance, **kwargs):
course = Course.objects.get(token=instance.bill.course_token)
if instance.yandex_pay and instance.method == 'Y' and instance.status == 'P':
msg = EmailMessage(
'Вам выставлен новый счёт',
"""%s для оплаты перейдите по ссылке
%s/api/v1/finance/payment/%s/""" % (instance.get_comment(), settings.DOMAIN, instance.yandex_pay.id),
to=[instance.yandex_pay.cps_email],
bcc=[instance.bill.opener.email],
reply_to=[instance.bill.opener.email],
)
msg.send()
if instance.status == 'F':
if instance.is_open:
try:

@ -0,0 +1,86 @@
import json
from datetime import datetime, timedelta
import logging
import os
import requests
from django_celery_beat.models import CrontabSchedule, PeriodicTask
from yandex_money.models import Payment
from finance.models import Invoice
from lms import celery_app
from django.conf import settings
logger_yandex = logging.getLogger('yandex_money')
def setup_periodic_billing(order_number):
# TODO: настроить периодичность и срок окончания
# 12:00 первого числа каждого месяца
schedule, _ = CrontabSchedule.objects.get_or_create(
minute='0',
hour='12',
day_of_week='*',
day_of_month='1',
month_of_year='*'
)
PeriodicTask.objects.create(
crontab=schedule,
name='Periodic billing (order_number={})'.format(order_number),
task='finance.tasks.periodic_billing',
kwargs=json.dumps({
'order_number': order_number
}),
expires=datetime.utcnow() + timedelta(days=180) # в течение полугода
)
@celery_app.task
def periodic_billing(order_number):
try:
sample = Invoice.objects.get(yandex_pay__order_number=order_number)
except Invoice.DoesNotExist:
raise ValueError('Номер заказа {} не найден'.format(order_number))
bill = sample.bill
invoice = Invoice.objects.create(
status='P',
price=sample.price,
method=sample.method,
rebilling=True,
bill=bill
)
if invoice.method == 'Y':
user = bill.user
yandex_pay = Payment.objects.create(
invoice_id=sample.yandex_pay.invoice_id,
order_amount=invoice.price,
customer_number=user.id,
user=user,
cps_email=user.email
)
invoice.yandex_pay = yandex_pay
invoice.save()
repeat_card_payment(invoice)
def repeat_card_payment(invoice):
resp = requests.post(settings.YANDEX_MONEY_MWS_URL + 'repeatCardPayment',
data={
'clientOrderId': invoice.id, # уникальное возрастающее целое число
'invoiceId': invoice.yandex_pay.invoice_id,
'amount': invoice.price,
'orderNumber': invoice.yandex_pay.order_number
},
cert=(
os.path.join(settings.SSL_ROOT, 'skillbox.cer'),
os.path.join(settings.SSL_ROOT, 'skillbox.key')
),
verify=os.path.join(settings.SSL_ROOT, 'yamoney_chain.cer'))
logger_yandex.info(resp.text)

@ -19,8 +19,9 @@ from django.utils.html import strip_tags
from courses.models import Course
from courses.api import CourseParamsApi
from finance.models import Bill, Invoice
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
@ -98,6 +99,8 @@ class InvoiceDetailView(APIView):
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)
@ -116,21 +119,30 @@ class InvoiceDetailView(APIView):
if bill.check_validate(invoice_id) and is_open:
return Response("Уже есть платёж открывающий курс", status=400)
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(
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)
@ -160,6 +172,8 @@ class InvoiceDetailView(APIView):
)
invoice.yandex_pay = yandex_pay
invoice.send_link()
context = {
'user_email': invoice.bill.user.email,
'opener_full_name': invoice.bill.opener.get_full_name(),
@ -247,6 +261,10 @@ class YandexPay(APIView):
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,
@ -254,6 +272,7 @@ class YandexPay(APIView):
'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,
})
@ -399,6 +418,7 @@ class YandexAvisoView(APIView):
pay.shop_amount = data['shopSumAmount']
pay.status = Payment.STATUS.SUCCESS
pay.invoice_id = data['invoiceId']
now = timezone.now()
pay.performed_datetime = now.isoformat()
pay.save()
@ -428,6 +448,9 @@ class YandexAvisoView(APIView):
msg.attach_alternative(html_content, "text/html")
msg.send()
if pay.invoice.rebilling_on:
setup_periodic_billing(pay.order_number)
return HttpResponse(xml_res, content_type='application/xml')

@ -47,6 +47,7 @@ YANDEX_MONEY_SHOP_ID = '157133'
YANDEX_MONEY_SHOP_PASSWORD = 'nu5Xefise'
YANDEX_MONEY_FAIL_URL = '%s/api/v1/finance/yandex/fail/' % DOMAIN
YANDEX_MONEY_SUCCESS_URL = '%s/' % DOMAIN
YANDEX_MONEY_MWS_URL = 'https://penelope.yamoney.ru/webservice/mws/api/'
# информировать о случаях, когда модуль вернул Яндекс.Кассе ошибку
YANDEX_MONEY_MAIL_ADMINS_ON_PAYMENT_ERROR = True
# Application definition
@ -180,6 +181,8 @@ STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATIC_URL = '/static/'
SSL_ROOT = os.path.join(BASE_DIR, 'ssl')
RAVEN_CONFIG = {
'dsn': 'http://1a09557dbd144e52af4b14bea569c114:fbb5dfaa39e64f02a1b4cc7ac665d7d7@sentry.skillbox.ru/7'
}

Loading…
Cancel
Save