From bb319780b6972897ea23972e70da15e7fdf4f403 Mon Sep 17 00:00:00 2001 From: SDE Date: Sat, 20 Apr 2024 12:15:55 +0300 Subject: [PATCH] 0.12.36 pays and subscribes --- BaseModels/base_models.py | 2 +- .../pay_systems/DVL_Group_kaz/api/funcs.py | 96 ++++++++++++++++-- BillingApp/__init__.py | 0 BillingApp/admin.py | 37 +++++++ BillingApp/apps.py | 6 ++ BillingApp/funcs.py | 88 ++++++++++++++++ BillingApp/migrations/__init__.py | 0 BillingApp/models.py | 51 ++++++++++ BillingApp/tests.py | 3 + BillingApp/views.py | 3 + GeneralApp/views.py | 7 +- SubscribesApp/funcs.py | 40 +++++++- SubscribesApp/js_views.py | 18 +++- SubscribesApp/models.py | 13 ++- TWB/settings.py | 1 + dvldigitalprojects.p12 | Bin 0 -> 5429 bytes requirements.pip | 1 + 17 files changed, 347 insertions(+), 19 deletions(-) create mode 100644 BillingApp/__init__.py create mode 100644 BillingApp/admin.py create mode 100644 BillingApp/apps.py create mode 100644 BillingApp/funcs.py create mode 100644 BillingApp/migrations/__init__.py create mode 100644 BillingApp/models.py create mode 100644 BillingApp/tests.py create mode 100644 BillingApp/views.py create mode 100644 dvldigitalprojects.p12 diff --git a/BaseModels/base_models.py b/BaseModels/base_models.py index 21b5a4a..adc86f7 100644 --- a/BaseModels/base_models.py +++ b/BaseModels/base_models.py @@ -18,7 +18,7 @@ from django.contrib.contenttypes.fields import GenericRelation # add_introspection_rules([], ["^tinymce\.models\.HTMLField"]) class BaseModel(models.Model): - name = models.TextField(verbose_name=_('Название'), + name = models.TextField(verbose_name=_("Название"), help_text=_('Название'), null=True, blank=True) name_plural = models.TextField(verbose_name=_('Название (множественное число)'), null=True, blank=True) diff --git a/BaseModels/pay_systems/DVL_Group_kaz/api/funcs.py b/BaseModels/pay_systems/DVL_Group_kaz/api/funcs.py index 9bbe71e..ced9986 100644 --- a/BaseModels/pay_systems/DVL_Group_kaz/api/funcs.py +++ b/BaseModels/pay_systems/DVL_Group_kaz/api/funcs.py @@ -1,20 +1,100 @@ +import json + import requests +from requests_pkcs12 import get,post +pkcs12_filename = 'dvldigitalprojects.p12' +pkcs12_password = 'QNlhRStcY7mB' + + +def get_domain_url(): + return 'https://sandboxapi.paymtech.kz/' + +def get_kwargs_for_request(): + return { + 'headers': { + 'content-type': 'application/json', + }, + 'auth': ('dvldigitalprojects', 'aPqSRVZhxFjjSqbB'), + 'pkcs12_filename': pkcs12_filename, + 'pkcs12_password': pkcs12_password + } def ping(): - req_str = f'https://developerhub.alfabank.by:8273/partner/1.0.1/public/nationalRates{code_str}{date_str}' + url = f'{get_domain_url()}ping' data = {} - headers = { - 'content-type': 'application/json' - } try: - msg = f'GET {req_str}' + msg = f'GET {url}' print(msg) - res = requests.get(req_str, data=data, headers=headers) + res = get( + url, + **get_kwargs_for_request() + ) + msg = f'answer received = {str(res)}' print(msg) except Exception as e: - msg = f'Exception GET {req_str} = {str(e)} ({str(res)})' + msg = f'Exception GET {url} = {str(e)} ({str(res)})' print(msg) - res = None \ No newline at end of file + res = None + return False + + return True + + +def get_order_status(bank_order_id): + + url = f'{get_domain_url()}orders/{str(bank_order_id)}' + + res = None + + data = { + 'expand': [ + 'card', 'client', 'location', 'custom_fields', + 'issuer', 'secure3d', 'operations', 'cashflow' + ] + } + + try: + msg = f'GET {url}' + print(msg) + res = get( + url, + data=json.dumps(data), + **get_kwargs_for_request() + ) + + msg = f'create_order answer received = {str(res)}' + print(msg) + except Exception as e: + msg = f'Exception create_order GET {url} = {str(e)} ({str(res)})' + print(msg) + res = None + + return res + + +def create_order(data): + + url = f'{get_domain_url()}orders/create' + + res = None + + try: + msg = f'POST {url}' + print(msg) + res = post( + url, + data=json.dumps(data), + **get_kwargs_for_request() + ) + + msg = f'create_order answer received = {str(res)}' + print(msg) + except Exception as e: + msg = f'Exception create_order POST {url} = {str(e)} ({str(res)})' + print(msg) + res = None + + return res \ No newline at end of file diff --git a/BillingApp/__init__.py b/BillingApp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/BillingApp/admin.py b/BillingApp/admin.py new file mode 100644 index 0000000..0027da2 --- /dev/null +++ b/BillingApp/admin.py @@ -0,0 +1,37 @@ +from sets.admin import * +from .models import * +from django.contrib import admin + +class Admin_SubscribeOrder(Admin_BaseModel): + + fieldsets = ( + (None, { + 'classes': ['wide'], + 'fields': ( + ('user', 'subscribe', 'subscribe_for_user'), + ('enable', 'order'), + ('sum', 'currency'), + ('status', 'last_operation_status'), + ('bank_order_id', 'pay_page'), + ) + }), + ) + + list_display = [ + 'id', 'enable', + 'user', 'subscribe', 'subscribe_for_user', + 'sum', 'currency', + 'status', 'last_operation_status', + 'order', 'modifiedDT', 'createDT' + ] + + list_display_links = ['id', 'user', 'subscribe'] + list_editable = ['enable'] + + readonly_fields = ['subscribe_for_user', 'sum', 'currency', 'modifiedDT', 'createDT'] + + list_filter = ['enable', 'status', 'modifiedDT', 'createDT'] + search_fields = ['id', 'last_operation_status', 'status'] + # filter_horizontal = ['options'] + +admin.site.register(SubscribeOrder, Admin_SubscribeOrder) \ No newline at end of file diff --git a/BillingApp/apps.py b/BillingApp/apps.py new file mode 100644 index 0000000..a42d49d --- /dev/null +++ b/BillingApp/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class BillingappConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'BillingApp' diff --git a/BillingApp/funcs.py b/BillingApp/funcs.py new file mode 100644 index 0000000..6da202a --- /dev/null +++ b/BillingApp/funcs.py @@ -0,0 +1,88 @@ +from datetime import datetime + +from .models import * +import json + + +def get_order_status(order): + from BaseModels.pay_systems.DVL_Group_kaz.api.funcs import get_order_status + res_status = None + + try: + res_data = get_order_status(order.bank_order_id) + + res = json.loads(res_data.text) + order.json_data['status'] = res + res = res['orders'][0] + + order.status = res['status'] + + + # if res['amount'] == res['amount_charged'] and res['status'] == 'charged': + order.save() + return order.status + + + except Exception as e: + msg = f'Exception get_order_status = {str(e)}' + if order: + msg = f'Exception get_order_status (data = {str(order.id)}) = {str(e)}' + print(msg) + + return None + +def get_orders_for_user(user): + + orders = SubscribeOrder.objects.filter( + enable=True, + user=user, + subscribe_for_user=None + ).order_by('-modifiedDT') + return orders + + +def create_subscribe_order(data): + order = None + + try: + + order = SubscribeOrder.objects.create(**data) + + from GeneralApp.funcs_options import get_options_by_opt_types, get_mail_send_options + sets = get_options_by_opt_types(['domain', 'project_name'], only_vals=True) + + from BaseModels.pay_systems.DVL_Group_kaz.api.funcs import create_order + data = { + 'currency': data['currency'], + 'amount': data['sum'], + 'description': f'Заказ {order.id} на подписку ' + f'{data["subscribe"].name} ' + f'для пользователя {data["user"].username}', + 'options': { + 'auto_charge': 1, + 'return_url': f'{sets["domain"]}/profile/page/my_subscribe/' + } + } + + res_data = create_order(data) + + order.pay_page = res_data.headers.get('location') + + res = json.loads(res_data.text) + order.json_data['create_order'] = res + res = res['orders'][0] + order.modifiedDT = datetime.strptime(res['updated'], '%Y-%m-%d %H:%M:%S') + order.status = res['status'] + order.bank_order_id = res['id'] + if 'segment' in res: + order.segment = res['segment'] + if 'merchant_order_id' in res: + order.merchant_order_id = res['merchant_order_id'] + order.save() + + + except Exception as e: + msg = f'Exception create_subscribe_order (data = {str(data)}) = {str(e)}' + print(msg) + + return order \ No newline at end of file diff --git a/BillingApp/migrations/__init__.py b/BillingApp/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/BillingApp/models.py b/BillingApp/models.py new file mode 100644 index 0000000..1c2e8fb --- /dev/null +++ b/BillingApp/models.py @@ -0,0 +1,51 @@ +from django.db import models +from BaseModels.base_models import BaseModel +from SubscribesApp.models import Subscribe, SubscribeForUser +from AuthApp.models import User +from django.utils.translation import gettext as _ + +class SubscribeOrder(BaseModel): + + subscribe = models.ForeignKey( + Subscribe, verbose_name=_('Подписка'), + on_delete=models.SET_NULL, blank=True, null=True, + related_name='subscribe_orders_for_subscribe' + ) + + user = models.ForeignKey( + User, verbose_name=_('Пользователь'), + on_delete=models.SET_NULL, blank=True, null=True, + related_name='subscribe_orders_for_user' + ) + + subscribe_for_user = models.OneToOneField( + SubscribeForUser, verbose_name=_('Подписка пользователя'), + on_delete=models.SET_NULL, blank=True, null=True, + related_name='subscribe_orders_for_user_subscribe' + ) + + sum = models.PositiveSmallIntegerField(verbose_name=_('Сумма'), default=0) + currency = models.CharField(verbose_name=_('Валюта'), max_length=3, default='USD') + segment = models.CharField(verbose_name=_('ID Сегмента'), null=True, default=None) + merchant_order_id = models.CharField(verbose_name=_('merchant_order_id'), null=True, default=None) + bank_order_id = models.CharField(verbose_name=_('ID заказа в банке'), null=True, default=None) + + status = models.CharField(verbose_name=_('Статус заказа в банке'), null=True, default=None) + last_operation_status = models.CharField(verbose_name=_('Статус последней операции'), null=True, default=None) + + pay_page = models.URLField(verbose_name=_('Ссылка на страницу оплаты'), null=True, blank=True, default=None) + + class Meta: + verbose_name = _('Заказ на подписку') + verbose_name_plural = _('Заказы на подписки') + + def __str__(self): + res = 'Заказ' + if self.subscribe: + res += f' на подписку {self.subscribe.name}' + if self.user: + res += f' для {self.user.username}' + + if not res: + res += f' {str(self.id)}' + return res \ No newline at end of file diff --git a/BillingApp/tests.py b/BillingApp/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/BillingApp/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/BillingApp/views.py b/BillingApp/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/BillingApp/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/GeneralApp/views.py b/GeneralApp/views.py index dae385f..c0f2015 100644 --- a/GeneralApp/views.py +++ b/GeneralApp/views.py @@ -24,8 +24,11 @@ def test_code(request): # apps = SocialApp.objects.all() # apps.delete() - from PushMessages.views import send_push - send_push(request.user, 'test_title', 'test_content') + # from PushMessages.views import send_push + # send_push(request.user, 'test_title', 'test_content') + + from BaseModels.pay_systems.DVL_Group_kaz.api.funcs import create_order + create_order() # from RoutesApp.search_matches import search_matches # search_matches() diff --git a/SubscribesApp/funcs.py b/SubscribesApp/funcs.py index 20e0aac..aa542c8 100644 --- a/SubscribesApp/funcs.py +++ b/SubscribesApp/funcs.py @@ -1,6 +1,7 @@ from .models import * from django.template.loader import render_to_string from django.utils.translation import get_language, activate +from datetime import datetime, timedelta def get_cur_user_subscribe(user): @@ -13,7 +14,7 @@ def get_cur_user_subscribe(user): return user_subscribe -def get_subsribes_w_options(): +def get_subscribes_w_options(): all_options = SubscribeOption.objects.filter(enable=True) subscribes = Subscribe.objects.filter(enable=True) for subscribe in subscribes: @@ -23,6 +24,28 @@ def get_subsribes_w_options(): return subscribes, all_options +def check_n_enable_subscribe_by_order(order): + + subscribes_for_user = SubscribeForUser.objects.filter(user=order.user) + + if order and order.enable and order.status == 'charged': + kwargs = { + 'user': order.user, + 'subscribe': order.subscribe, + 'last_paid_DT': datetime.now(), + 'paid_period_from_DT': datetime.now(), + 'paid_period_to_DT': datetime.now() + timedelta(hours=order.subscribe.period), + 'receive_finish_subscribe_msg': True, + } + subscribe_for_user = SubscribeForUser.objects.create(**kwargs) + order.subscribe_for_user = subscribe_for_user + order.save() + + subscribes_for_user = [subscribe_for_user] + + return subscribes_for_user + + def get_profile_subscribe_page_content_html(request): try: @@ -32,9 +55,20 @@ def get_profile_subscribe_page_content_html(request): # data = json.loads(request.body) # all_options = SubscribeOption.objects.filter(enable=True) - subscribes, all_options = get_subsribes_w_options() + subscribes, all_options = get_subscribes_w_options() + + subscribe_for_user = None + + if request.user and request.user.is_authenticated: + from BillingApp.funcs import get_orders_for_user, get_order_status + orders = get_orders_for_user(request.user) + for order in orders: + res = get_order_status(order) + subscribe_for_user = check_n_enable_subscribe_by_order(order) + + if not subscribe_for_user: + subscribe_for_user = SubscribeForUser.objects.filter(user=request.user) - subscribe_for_user = SubscribeForUser.objects.filter(user=request.user) if not subscribe_for_user: tpl_name = 'blocks/profile/b_subscribe_variants.html' else: diff --git a/SubscribesApp/js_views.py b/SubscribesApp/js_views.py index be6ee84..b89c6b3 100644 --- a/SubscribesApp/js_views.py +++ b/SubscribesApp/js_views.py @@ -3,7 +3,7 @@ from django.shortcuts import render from uuid import uuid1 from .models import * from django.contrib import auth -from django.http import HttpResponse, Http404, JsonResponse +from django.http import HttpResponse, Http404, JsonResponse, HttpResponseRedirect from django.template import loader, RequestContext from django.contrib.auth.decorators import login_required from BaseModels.mailSender import techSendMail @@ -17,6 +17,7 @@ from datetime import datetime, time, timedelta from channels.layers import get_channel_layer from asgiref.sync import async_to_sync from GeneralApp.funcs import get_and_set_lang +from django.shortcuts import redirect @login_required()#login_url='/profile/login/') @@ -33,6 +34,18 @@ def subscribe_now_ajax(request): subscribe = Subscribe.objects.get(id=data['subscribe_id']) + kwargs_for_order = { + 'user': request.user, + 'subscribe': subscribe, + 'currency': 'USD', + 'sum': subscribe.price, + } + + from BillingApp.funcs import create_subscribe_order + order = create_subscribe_order(kwargs_for_order) + if order: + return JsonResponse({'redirect': order.pay_page}) + kwargs = { 'user': request.user, 'subscribe': subscribe, @@ -41,13 +54,10 @@ def subscribe_now_ajax(request): 'paid_period_to_DT': datetime.now() + timedelta(hours=subscribe.period), 'receive_finish_subscribe_msg': True, } - subscribe_for_user = SubscribeForUser.objects.filter(user=request.user) if subscribe_for_user: subscribe_for_user.update(**kwargs) subscribe_for_user = subscribe_for_user[0] - else: - subscribe_for_user = SubscribeForUser.objects.create(**kwargs) if not subscribe_for_user: tpl_name = 'blocks/profile/b_subscribe_variants.html' diff --git a/SubscribesApp/models.py b/SubscribesApp/models.py index 60a822f..6b0ffd4 100644 --- a/SubscribesApp/models.py +++ b/SubscribesApp/models.py @@ -49,4 +49,15 @@ class SubscribeForUser(BaseModel): class Meta: verbose_name = _('Пользовательская подписка') - verbose_name_plural = _('Пользовательские подписки') \ No newline at end of file + verbose_name_plural = _('Пользовательские подписки') + + def __str__(self): + res = 'Подписка' + if self.subscribe: + res += f' {self.subscribe.name}' + if self.user: + res += f' для {self.user.username}' + + if not res: + res += f' {str(self.id)}' + return res \ No newline at end of file diff --git a/TWB/settings.py b/TWB/settings.py index 5cfa9f2..dbe2f84 100644 --- a/TWB/settings.py +++ b/TWB/settings.py @@ -118,6 +118,7 @@ INSTALLED_APPS = [ 'ArticlesApp', 'SubscribesApp', 'PushMessages', + 'BillingApp', ] MIDDLEWARE = [ diff --git a/dvldigitalprojects.p12 b/dvldigitalprojects.p12 new file mode 100644 index 0000000000000000000000000000000000000000..47964d71a425b5c1932d52965439a367ee7727c2 GIT binary patch literal 5429 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 literal 0 HcmV?d00001 diff --git a/requirements.pip b/requirements.pip index 80e15ca..3bbd488 100644 --- a/requirements.pip +++ b/requirements.pip @@ -13,5 +13,6 @@ django-colorfield django-webpush==0.3.5 django-allauth==0.60.0 pytz==2024.1 +requests-pkcs12==1.24 #django-tz-detect==0.4.0