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 # coding=utf-8
from django.contrib import admin 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(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 # coding=utf-8
from django.conf import settings from django.conf import settings
from django.core.mail import EmailMessage
from django.db import models from django.db import models
from yandex_money.models import Payment from yandex_money.models import Payment
@ -58,12 +59,23 @@ class Invoice(models.Model):
blank=True, editable=False) blank=True, editable=False)
bill = models.ForeignKey(to=Bill, verbose_name="Связный счёт") bill = models.ForeignKey(to=Bill, verbose_name="Связный счёт")
is_open = models.BooleanField(default=True, 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): def get_comment(self):
return '''Вам выставлен счёт,''' if \ return '''Вам выставлен счёт,''' if \
self.comment == "" else self.comment 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): def __str__(self):
return '%s:%s %s' % (self.id, self.get_status_display(), self.bill.user) return '%s:%s %s' % (self.id, self.get_status_display(), self.bill.user)
@ -72,15 +84,10 @@ class Invoice(models.Model):
verbose_name_plural = 'Платежи' verbose_name_plural = 'Платежи'
class InstallmentPlan(models.Model): class InvoiceRebilling(Invoice):
bill = models.OneToOneField(to=Bill, verbose_name="Связный счёт") rebilling_on = models.BooleanField(verbose_name='Повторять платеж', default=False, editable=False)
date = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания") pay_count = models.SmallIntegerField(verbose_name='Всего платежей', editable=False)
invoice_amount = models.IntegerField(verbose_name="Количество платежей")
price = models.IntegerField(verbose_name="Цена одного платежа")
def __str__(self):
return '%s' % self.bill.user.email
class Meta: class Meta:
verbose_name = 'Рассрочка' verbose_name = 'Повторный платёж'
verbose_name_plural = 'Рассрочки' verbose_name_plural = 'Повторные платежи'

@ -16,17 +16,6 @@ def invoice_signal(instance, **kwargs):
course = Course.objects.get(token=instance.bill.course_token) 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.status == 'F':
if instance.is_open: if instance.is_open:
try: 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.models import Course
from courses.api import CourseParamsApi 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.serializers import BillSerializer, InvoiceSerializer
from finance.tasks import setup_periodic_billing
from lms.global_decorators import transaction_decorator from lms.global_decorators import transaction_decorator
from lms.tools import get_real_name from lms.tools import get_real_name
from django.utils import timezone from django.utils import timezone
@ -98,6 +99,8 @@ class InvoiceDetailView(APIView):
price = request.JSON.get('price', None) price = request.JSON.get('price', None)
comment = request.JSON.get('comment', None) comment = request.JSON.get('comment', None)
real_price = request.JSON.get('real_price', 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: if bill_id is None:
return Response("Не передан id счёта", status=400) return Response("Не передан id счёта", status=400)
@ -116,21 +119,30 @@ class InvoiceDetailView(APIView):
if bill.check_validate(invoice_id) and is_open: if bill.check_validate(invoice_id) and is_open:
return Response("Уже есть платёж открывающий курс", status=400) return Response("Уже есть платёж открывающий курс", status=400)
try: if rebilling_on:
invoice = Invoice.objects.get(id=invoice_id) invoice = InvoiceRebilling.objects.create(
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, bill=bill,
method=method, method=method,
status=status, status=status,
is_open=is_open, 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": if invoice.status == "F":
return Response(InvoiceSerializer(invoice).data, status=200) return Response(InvoiceSerializer(invoice).data, status=200)
@ -160,6 +172,8 @@ class InvoiceDetailView(APIView):
) )
invoice.yandex_pay = yandex_pay invoice.yandex_pay = yandex_pay
invoice.send_link()
context = { context = {
'user_email': invoice.bill.user.email, 'user_email': invoice.bill.user.email,
'opener_full_name': invoice.bill.opener.get_full_name(), 'opener_full_name': invoice.bill.opener.get_full_name(),
@ -247,6 +261,10 @@ class YandexPay(APIView):
def get(request, pk): def get(request, pk):
try: try:
pay = Payment.objects.get(id=pk) 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={ r = requests.post('https://money.yandex.ru/eshop.xml', data={
'shopId': pay.shop_id, 'shopId': pay.shop_id,
'scid': pay.scid, 'scid': pay.scid,
@ -254,6 +272,7 @@ class YandexPay(APIView):
'customerNumber': pay.customer_number, 'customerNumber': pay.customer_number,
'orderNumber': pay.order_number, 'orderNumber': pay.order_number,
'cps_email': pay.cps_email, 'cps_email': pay.cps_email,
'rebillingOn': False if inv is None else inv.rebilling_on,
'shopSuccessURL': settings.YANDEX_MONEY_SUCCESS_URL, 'shopSuccessURL': settings.YANDEX_MONEY_SUCCESS_URL,
'shopFailURL': settings.YANDEX_MONEY_FAIL_URL, 'shopFailURL': settings.YANDEX_MONEY_FAIL_URL,
}) })
@ -399,6 +418,7 @@ class YandexAvisoView(APIView):
pay.shop_amount = data['shopSumAmount'] pay.shop_amount = data['shopSumAmount']
pay.status = Payment.STATUS.SUCCESS pay.status = Payment.STATUS.SUCCESS
pay.invoice_id = data['invoiceId']
now = timezone.now() now = timezone.now()
pay.performed_datetime = now.isoformat() pay.performed_datetime = now.isoformat()
pay.save() pay.save()
@ -428,6 +448,9 @@ class YandexAvisoView(APIView):
msg.attach_alternative(html_content, "text/html") msg.attach_alternative(html_content, "text/html")
msg.send() msg.send()
if pay.invoice.rebilling_on:
setup_periodic_billing(pay.order_number)
return HttpResponse(xml_res, content_type='application/xml') return HttpResponse(xml_res, content_type='application/xml')

@ -47,6 +47,7 @@ YANDEX_MONEY_SHOP_ID = '157133'
YANDEX_MONEY_SHOP_PASSWORD = 'nu5Xefise' YANDEX_MONEY_SHOP_PASSWORD = 'nu5Xefise'
YANDEX_MONEY_FAIL_URL = '%s/api/v1/finance/yandex/fail/' % DOMAIN YANDEX_MONEY_FAIL_URL = '%s/api/v1/finance/yandex/fail/' % DOMAIN
YANDEX_MONEY_SUCCESS_URL = '%s/' % 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 YANDEX_MONEY_MAIL_ADMINS_ON_PAYMENT_ERROR = True
# Application definition # Application definition
@ -180,6 +181,8 @@ STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATIC_URL = '/static/' STATIC_URL = '/static/'
SSL_ROOT = os.path.join(BASE_DIR, 'ssl')
RAVEN_CONFIG = { RAVEN_CONFIG = {
'dsn': 'http://1a09557dbd144e52af4b14bea569c114:fbb5dfaa39e64f02a1b4cc7ac665d7d7@sentry.skillbox.ru/7' 'dsn': 'http://1a09557dbd144e52af4b14bea569c114:fbb5dfaa39e64f02a1b4cc7ac665d7d7@sentry.skillbox.ru/7'
} }

Loading…
Cancel
Save