diff --git a/finance/admin.py b/finance/admin.py index 04b13c6..8143c83 100755 --- a/finance/admin.py +++ b/finance/admin.py @@ -3,5 +3,10 @@ from django.contrib import admin from finance.models import Bill, Invoice + +class InvoiceAdmin(admin.ModelAdmin): + list_display = ('__str__', 'rebilling_on', 'rebilling') + + admin.site.register(Bill) -admin.site.register(Invoice) \ No newline at end of file +admin.site.register(Invoice, InvoiceAdmin) diff --git a/finance/migrations/0004_auto_20180321_1653.py b/finance/migrations/0004_auto_20180321_1653.py new file mode 100644 index 0000000..b00a881 --- /dev/null +++ b/finance/migrations/0004_auto_20180321_1653.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2018-03-21 16:53 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('finance', '0003_auto_20180315_1358'), + ] + + operations = [ + 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='Повторять платеж'), + ), + ] diff --git a/finance/models.py b/finance/models.py index b94e527..956caa7 100755 --- a/finance/models.py +++ b/finance/models.py @@ -58,11 +58,9 @@ 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="Дата создания") - - def get_comment(self): - return '''Вам выставлен счёт,''' if \ - self.comment == "" else self.comment + date = models.DateTimeField(auto_now_add=True) + rebilling_on = models.BooleanField(verbose_name='Повторять платеж', default=False, editable=False) + rebilling = models.BooleanField(verbose_name='Повторный платеж', default=False, editable=False) def __str__(self): return '%s:%s %s' % (self.id, self.get_status_display(), self.bill.user) diff --git a/finance/signals.py b/finance/signals.py index ea0f3cf..9d62f16 100644 --- a/finance/signals.py +++ b/finance/signals.py @@ -16,7 +16,7 @@ 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': + if instance.yandex_pay and instance.method == 'Y' and instance.status == 'P' and not instance.rebilling: msg = EmailMessage( 'Вам выставлен новый счёт', """%s для оплаты перейдите по ссылке @@ -27,7 +27,7 @@ def invoice_signal(instance, **kwargs): ) msg.send() - if instance.status == 'F': + if instance.status == 'F' and not instance.rebilling: if instance.is_open: try: Progress.objects.get( diff --git a/finance/tasks.py b/finance/tasks.py new file mode 100644 index 0000000..a28b3ae --- /dev/null +++ b/finance/tasks.py @@ -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) diff --git a/finance/views.py b/finance/views.py index 9c3b5df..87d9a66 100644 --- a/finance/views.py +++ b/finance/views.py @@ -21,6 +21,7 @@ from courses.models import Course from courses.api import CourseParamsApi from finance.models import Bill, Invoice 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 @@ -254,6 +255,7 @@ class YandexPay(APIView): 'customerNumber': pay.customer_number, 'orderNumber': pay.order_number, 'cps_email': pay.cps_email, + 'rebillingOn': pay.invoice.rebilling_on, 'shopSuccessURL': settings.YANDEX_MONEY_SUCCESS_URL, 'shopFailURL': settings.YANDEX_MONEY_FAIL_URL, }) @@ -399,6 +401,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 +431,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') diff --git a/lms/settings.py b/lms/settings.py index e453ca3..9bc3f60 100644 --- a/lms/settings.py +++ b/lms/settings.py @@ -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' }