Compare commits
No commits in common. "f5ce799a38a9270b835723e31df6e7e66602d9a4" and "da7d25d29eb64a88d8ee9a9e6c9e85cafe01c277" have entirely different histories.
f5ce799a38
...
da7d25d29e
12 changed files with 140 additions and 442 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -3,5 +3,3 @@
|
||||||
media
|
media
|
||||||
/clients/
|
/clients/
|
||||||
/delme.py
|
/delme.py
|
||||||
/log/accounts.log
|
|
||||||
/log/errors.log
|
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,6 @@ class BasePGModel(models.Model):
|
||||||
|
|
||||||
redirect_method = 'GET'
|
redirect_method = 'GET'
|
||||||
|
|
||||||
@property
|
|
||||||
def label(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def image(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def get_pg_page_url(self):
|
def get_pg_page_url(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -43,17 +35,6 @@ class BasePGModel(models.Model):
|
||||||
def reverse_request(self):
|
def reverse_request(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
|
||||||
def jajali_datetime(self):
|
|
||||||
def to_jalali(value):
|
|
||||||
if value:
|
|
||||||
import jdatetime
|
|
||||||
return jdatetime.datetime.fromgregorian(date=value, locale='fa_IR').strftime("%Y/%m/%d-%H:%M:%S")
|
|
||||||
else:
|
|
||||||
return value
|
|
||||||
|
|
||||||
return to_jalali(self.created_at)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,24 +12,16 @@ from django.shortcuts import redirect, get_object_or_404
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .constans import StateChoices
|
from .constans import StateChoices
|
||||||
from .forms import ApplicationGatewayPolicyForm
|
from .models import PaymentRequest, PNA, Fake
|
||||||
from .models import PaymentRequest, PNA, Fake, ApplicationGatewayPolicy, SEP
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PNAAdmin(admin.ModelAdmin):
|
class PNAAdmin(admin.ModelAdmin):
|
||||||
list_display = ['pna_status_code', 'pna_status_message', 'jajali_datetime', 'payment_request']
|
list_display = ['pna_status_code', 'pna_status_message', 'jajali_datetime', 'payment_request']
|
||||||
list_filter = ['pna_status']
|
list_filter = ['pna_status']
|
||||||
search_fields = ['pna_status_code', 'pna_status_message']
|
search_fields = ['pna_status_code', 'pna_status_message']
|
||||||
|
|
||||||
|
|
||||||
class SEPAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['sep_status_code', 'sep_status_message', 'jajali_datetime', 'payment_request']
|
|
||||||
list_filter = ['sep_status']
|
|
||||||
search_fields = ['sep_status_code', 'sep_status_message']
|
|
||||||
|
|
||||||
|
|
||||||
@admin.action(description="export exel")
|
@admin.action(description="export exel")
|
||||||
def export_exel(modeladmin, request, queryset):
|
def export_exel(modeladmin, request, queryset):
|
||||||
output = io.BytesIO()
|
output = io.BytesIO()
|
||||||
|
|
@ -62,7 +54,6 @@ def export_exel(modeladmin, request, queryset):
|
||||||
# return the response
|
# return the response
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@admin.action(description="mark as registered")
|
@admin.action(description="mark as registered")
|
||||||
def mark_as_registered(model_admin, request, queryset):
|
def mark_as_registered(model_admin, request, queryset):
|
||||||
for obj in queryset:
|
for obj in queryset:
|
||||||
|
|
@ -71,12 +62,12 @@ def mark_as_registered(model_admin, request, queryset):
|
||||||
|
|
||||||
|
|
||||||
class PaymentRequestAdmin(admin.ModelAdmin):
|
class PaymentRequestAdmin(admin.ModelAdmin):
|
||||||
list_display = ['state', 'check_success_again', 'uuid', 'splited_amount', 'user_id', 'jajali_datetime',
|
list_display = ['state', 'check_success_again', 'uuid', 'splited_amount', 'user_id', 'jajali_datetime', 'is_registered', 'created_at', 'application', 'gateway']
|
||||||
'is_registered', 'created_at', 'application', 'gateway']
|
search_fields = ['application__uuid','application__client_id', 'amount', 'user__uuid']
|
||||||
search_fields = ['application__uuid', 'application__client_id', 'amount', 'user__uuid']
|
|
||||||
list_filter = ['application', 'state', 'gateway']
|
list_filter = ['application', 'state', 'gateway']
|
||||||
actions = [export_exel, mark_as_registered]
|
actions = [export_exel, mark_as_registered]
|
||||||
|
|
||||||
|
|
||||||
def changelist_view(self, request, extra_context=None):
|
def changelist_view(self, request, extra_context=None):
|
||||||
extra_context = extra_context or {}
|
extra_context = extra_context or {}
|
||||||
|
|
||||||
|
|
@ -89,8 +80,7 @@ class PaymentRequestAdmin(admin.ModelAdmin):
|
||||||
return super().changelist_view(request, extra_context=extra_context)
|
return super().changelist_view(request, extra_context=extra_context)
|
||||||
|
|
||||||
def check_success_again(self, obj):
|
def check_success_again(self, obj):
|
||||||
if obj.state in [StateChoices.IPG_SUCCESS, StateChoices.WALLET_SUBMIT_PENDING,
|
if obj.state in [StateChoices.IPG_SUCCESS, StateChoices.WALLET_SUBMIT_PENDING, StateChoices.WALLET_SUBMIT_SUCCESS]:
|
||||||
StateChoices.WALLET_SUBMIT_SUCCESS]:
|
|
||||||
return format_html(
|
return format_html(
|
||||||
'<a class="button" href="{}">بررسی دوباره موفقیت</a>',
|
'<a class="button" href="{}">بررسی دوباره موفقیت</a>',
|
||||||
f'{obj.uuid}/check_success_again/'
|
f'{obj.uuid}/check_success_again/'
|
||||||
|
|
@ -100,11 +90,9 @@ class PaymentRequestAdmin(admin.ModelAdmin):
|
||||||
def get_urls(self):
|
def get_urls(self):
|
||||||
urls = super().get_urls()
|
urls = super().get_urls()
|
||||||
custom_urls = [
|
custom_urls = [
|
||||||
path('<uuid:payment_request_uuid>/check_success_again/',
|
path('<uuid:payment_request_uuid>/check_success_again/', self.admin_site.admin_view(self.check_success_again_view))
|
||||||
self.admin_site.admin_view(self.check_success_again_view))
|
|
||||||
]
|
]
|
||||||
return custom_urls + urls
|
return custom_urls + urls
|
||||||
|
|
||||||
def check_success_again_view(self, request, payment_request_uuid):
|
def check_success_again_view(self, request, payment_request_uuid):
|
||||||
logger.info(f"[Admin panel] Check success again for payment_request_uuid={payment_request_uuid}")
|
logger.info(f"[Admin panel] Check success again for payment_request_uuid={payment_request_uuid}")
|
||||||
payment_request: PaymentRequest = get_object_or_404(PaymentRequest, uuid=payment_request_uuid)
|
payment_request: PaymentRequest = get_object_or_404(PaymentRequest, uuid=payment_request_uuid)
|
||||||
|
|
@ -122,15 +110,8 @@ class PaymentRequestAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
class FakeAdmin(admin.ModelAdmin):
|
class FakeAdmin(admin.ModelAdmin):
|
||||||
list_display = ['created_at']
|
list_display = [ 'created_at']
|
||||||
|
|
||||||
|
|
||||||
class ApplicationGatewayPolicyAdmin(admin.ModelAdmin):
|
|
||||||
form = ApplicationGatewayPolicyForm
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(PaymentRequest, PaymentRequestAdmin)
|
admin.site.register(PaymentRequest, PaymentRequestAdmin)
|
||||||
admin.site.register(PNA, PNAAdmin)
|
admin.site.register(PNA, PNAAdmin)
|
||||||
admin.site.register(SEP, SEPAdmin)
|
|
||||||
admin.site.register(Fake, FakeAdmin)
|
admin.site.register(Fake, FakeAdmin)
|
||||||
admin.site.register(ApplicationGatewayPolicy, ApplicationGatewayPolicyAdmin)
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from .models import PaymentRequest, GatewayChoise
|
from .models import PaymentRequest
|
||||||
|
|
||||||
|
|
||||||
class PaymentRequestForm(forms.ModelForm):
|
class PaymentRequestForm(forms.ModelForm):
|
||||||
|
|
@ -8,44 +8,3 @@ class PaymentRequestForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PaymentRequest
|
model = PaymentRequest
|
||||||
fields = []
|
fields = []
|
||||||
|
|
||||||
|
|
||||||
class DelimitedListField(forms.MultipleChoiceField):
|
|
||||||
"""
|
|
||||||
Django form field that allows for the use of list widgets with a text field
|
|
||||||
containing a delimited list.
|
|
||||||
"""
|
|
||||||
delimiter = ','
|
|
||||||
|
|
||||||
def __init__(self, delimiter=None, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.delimiter = delimiter or self.delimiter
|
|
||||||
|
|
||||||
def prepare_value(self, value):
|
|
||||||
# If the value is already a list or tuple, just use it as-is
|
|
||||||
if isinstance(value, (list, tuple)): return value
|
|
||||||
# Otherwise, prepare the value by splitting on the delimiter, trimming
|
|
||||||
# leading and trailing whitespace and excluding empty values
|
|
||||||
return [p.strip() for p in value.split(self.delimiter) if p.strip()]
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
# Let the parent clean the value first, then join the result using the
|
|
||||||
# specified delimiter
|
|
||||||
return self.delimiter.join(super().clean(value))
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationGatewayPolicyForm(forms.ModelForm):
|
|
||||||
"""
|
|
||||||
Form for creating or updating a restricted application.
|
|
||||||
"""
|
|
||||||
# allowed_scope is a space-delimited list, but we want to present
|
|
||||||
# a selection of valid scopes with checkboxes
|
|
||||||
preferred_gateways = DelimitedListField(
|
|
||||||
label='preferred gateways',
|
|
||||||
# The choices and initial values are callables, because the scopes might
|
|
||||||
# not be available at import type, e.g. if coming from the database
|
|
||||||
choices=GatewayChoise.choices,
|
|
||||||
initial=GatewayChoise.choices,
|
|
||||||
delimiter=' ',
|
|
||||||
widget=forms.CheckboxSelectMultiple
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
# Generated by Django 5.1.4 on 2026-04-22 19:48
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('pg', '0009_remove_sep_sep_order_id_remove_sep_sep_originator_and_more'),
|
|
||||||
migrations.swappable_dependency(settings.OAUTH2_PROVIDER_APPLICATION_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='paymentrequest',
|
|
||||||
name='gateway',
|
|
||||||
field=models.CharField(blank=True, choices=[('fake', 'fake ipg'), ('pna', 'pna ipg'), ('sep', 'sep ipg')], max_length=255),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ApplicationGatewayPolicy',
|
|
||||||
fields=[
|
|
||||||
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
|
||||||
('preferred_gateways', models.CharField(blank=True, default='', max_length=100, null=True)),
|
|
||||||
('policy', models.CharField(choices=[('random', 'random'), ('selectable', 'selectable')], default='selectable', max_length=20)),
|
|
||||||
('application', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL, unique=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -22,22 +22,18 @@ from utils.pna import PNAClient
|
||||||
from utils.sep import SEPClient
|
from utils.sep import SEPClient
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class GatewayChoise(TextChoices):
|
class GatewayChoise(TextChoices):
|
||||||
# FAKE = "fake", _("fake ipg")
|
FAKE = "fake", _("fake")
|
||||||
PNA = 'pna', _('pna ipg')
|
PNA = 'pna', _('pna')
|
||||||
SEP = 'sep', _('sep ipg')
|
SEP = 'sep', _('sep')
|
||||||
|
|
||||||
|
|
||||||
def get_gateway_class(gateway_label):
|
class GatewayPolicy(BasePGModel):
|
||||||
return {
|
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
||||||
# "fake": Fake,
|
related_name='+', null=True, blank=True, unique=True)
|
||||||
"pna": PNA,
|
preference_gateway = models.JSONField(default=list, blank=True)
|
||||||
"sep": SEP,
|
|
||||||
}[gateway_label]
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: use generic relation
|
# TODO: use generic relation
|
||||||
|
|
@ -48,7 +44,7 @@ class PaymentRequest(models.Model):
|
||||||
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
||||||
related_name='+', null=True, blank=True)
|
related_name='+', null=True, blank=True)
|
||||||
|
|
||||||
gateway = models.CharField(max_length=255, choices=GatewayChoise.choices, blank=True)
|
gateway = models.CharField(max_length=255, choices=GatewayChoise.choices)
|
||||||
|
|
||||||
state = models.IntegerField(choices=StateChoices.choices, default=StateChoices.CREATED)
|
state = models.IntegerField(choices=StateChoices.choices, default=StateChoices.CREATED)
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
||||||
|
|
@ -87,41 +83,44 @@ class PaymentRequest(models.Model):
|
||||||
self.commission = calculate_pna_commission(self.amount)
|
self.commission = calculate_pna_commission(self.amount)
|
||||||
self.save(update_fields=['commission'])
|
self.save(update_fields=['commission'])
|
||||||
|
|
||||||
def get_policy(self):
|
|
||||||
application_gateway, created = ApplicationGatewayPolicy.objects.get_or_create(application=self.application)
|
|
||||||
return application_gateway
|
|
||||||
|
|
||||||
def get_preferred_gateways(self):
|
|
||||||
application_policy = self.get_policy()
|
|
||||||
return application_policy.get_preferred_gateways()
|
|
||||||
|
|
||||||
def get_preferred_gateway_classes(self):
|
|
||||||
preferred_gateways = self.get_preferred_gateways()
|
|
||||||
return [get_gateway_class(gateway_label) for gateway_label in preferred_gateways]
|
|
||||||
|
|
||||||
|
|
||||||
def get_gateway_tools(self):
|
def get_gateway_tools(self):
|
||||||
gateway_class = get_gateway_class(self.gateway)
|
if self.gateway == GatewayChoise.PNA:
|
||||||
return gateway_class.objects.get(payment_request=self)
|
return PNA.objects.get(payment_request=self)
|
||||||
|
|
||||||
def set_gateway(self, gateway_label):
|
if self.gateway == GatewayChoise.SEP:
|
||||||
preferred_gateways = self.get_preferred_gateways()
|
return SEP.objects.get(payment_request=self)
|
||||||
if gateway_label in preferred_gateways:
|
|
||||||
self.gateway = gateway_label
|
if self.gateway == GatewayChoise.FAKE:
|
||||||
gateway_class = get_gateway_class(gateway_label)
|
return Fake.objects.get(payment_request=self)
|
||||||
pg = gateway_class.objects.create(payment_request=self)
|
|
||||||
|
def set_gateway(self, data):
|
||||||
|
|
||||||
|
# if 'fake' in data:
|
||||||
|
# self.gateway = GatewayChoise.FAKE
|
||||||
|
# fake = Fake.objects.create(payment_request=self)
|
||||||
|
# fake.payment_request = self
|
||||||
|
#
|
||||||
|
|
||||||
|
if 'pna' in data:
|
||||||
|
self.gateway = GatewayChoise.PNA
|
||||||
|
pg = PNA.objects.create(payment_request=self)
|
||||||
pg.payment_request = self
|
pg.payment_request = self
|
||||||
|
|
||||||
self.save()
|
elif 'sep' in data:
|
||||||
|
self.gateway = GatewayChoise.SEP
|
||||||
|
pg = SEP.objects.create(payment_request=self)
|
||||||
|
pg.payment_request = self
|
||||||
|
|
||||||
if not self.gateway:
|
else:
|
||||||
raise Exception('No gateway')
|
raise Exception('Unknown gateway')
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def reference_number(self):
|
def reference_number(self):
|
||||||
gateway_tools = self.get_gateway_tools()
|
gateway_tools = self.get_gateway_tools()
|
||||||
if gateway_tools:
|
if gateway_tools:
|
||||||
return f'{self.gateway}{gateway_tools.pk}'
|
return f'{self.gateway.value}{gateway_tools.pk}'
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return '00000000'
|
return '00000000'
|
||||||
|
|
@ -182,8 +181,7 @@ class PaymentRequest(models.Model):
|
||||||
raise Exception('Failed to get response uuid from wallet service')
|
raise Exception('Failed to get response uuid from wallet service')
|
||||||
|
|
||||||
if verify_response.state != 5:
|
if verify_response.state != 5:
|
||||||
logger.error(
|
logger.error(f"[Deposit Verify] Wallet verification failed | payment_id={self.pk}, response={verify_response}")
|
||||||
f"[Deposit Verify] Wallet verification failed | payment_id={self.pk}, response={verify_response}")
|
|
||||||
# to_pay_failed = PaymentRequest.objects.filter(
|
# to_pay_failed = PaymentRequest.objects.filter(
|
||||||
# pk=self.pk,
|
# pk=self.pk,
|
||||||
# state=StateChoices.WALLET_SUBMIT_SUCCESS.value
|
# state=StateChoices.WALLET_SUBMIT_SUCCESS.value
|
||||||
|
|
@ -227,8 +225,6 @@ class PaymentRequest(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class PNA(BasePGModel):
|
class PNA(BasePGModel):
|
||||||
label = GatewayChoise.PNA.value
|
|
||||||
image = f'image/{GatewayChoise.PNA.value}.png'
|
|
||||||
payment_request = models.OneToOneField(PaymentRequest, on_delete=models.PROTECT, related_name='pna')
|
payment_request = models.OneToOneField(PaymentRequest, on_delete=models.PROTECT, related_name='pna')
|
||||||
|
|
||||||
details = models.JSONField(null=True, blank=True, default=dict)
|
details = models.JSONField(null=True, blank=True, default=dict)
|
||||||
|
|
@ -242,6 +238,17 @@ class PNA(BasePGModel):
|
||||||
|
|
||||||
client = PNAClient()
|
client = PNAClient()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def jajali_datetime(self):
|
||||||
|
def to_jalali(value):
|
||||||
|
if value:
|
||||||
|
return jdatetime.datetime.fromgregorian(date=value, locale='fa_IR').strftime("%Y/%m/%d-%H:%M:%S")
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
|
||||||
|
return to_jalali(self.created_at)
|
||||||
|
|
||||||
|
|
||||||
def make_request(self):
|
def make_request(self):
|
||||||
self.payment_request.change_state(StateChoices.CREATED, StateChoices.IPG_PENDING, 'can not change state')
|
self.payment_request.change_state(StateChoices.CREATED, StateChoices.IPG_PENDING, 'can not change state')
|
||||||
amount = self.payment_request.amount + self.payment_request.commission
|
amount = self.payment_request.amount + self.payment_request.commission
|
||||||
|
|
@ -254,7 +261,6 @@ class PNA(BasePGModel):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = self.client.request_normal_sale(amount, description, callback_url, order_id, phone_number)
|
response = self.client.request_normal_sale(amount, description, callback_url, order_id, phone_number)
|
||||||
self.details['make_request'] = response
|
|
||||||
self.pna_status_code = response['status']
|
self.pna_status_code = response['status']
|
||||||
self.pna_status_message = response.get('title')
|
self.pna_status_message = response.get('title')
|
||||||
|
|
||||||
|
|
@ -265,8 +271,7 @@ class PNA(BasePGModel):
|
||||||
self.payment_request.change_state(self.payment_request.state, StateChoices.FAILED,
|
self.payment_request.change_state(self.payment_request.state, StateChoices.FAILED,
|
||||||
'error in pardakht novin gateway')
|
'error in pardakht novin gateway')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.payment_request.change_state(self.payment_request.state, StateChoices.FAILED,
|
self.payment_request.change_state(self.payment_request.state, StateChoices.FAILED, 'error in pardakht novin gateway')
|
||||||
'error in pardakht novin gateway')
|
|
||||||
|
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
|
@ -286,13 +291,12 @@ class PNA(BasePGModel):
|
||||||
|
|
||||||
pna_token = self.pna_token
|
pna_token = self.pna_token
|
||||||
response = self.client.request_normal_sale_confirm(pna_token)
|
response = self.client.request_normal_sale_confirm(pna_token)
|
||||||
self.details['confirm_request'] = response
|
|
||||||
self.save()
|
|
||||||
# response = {'status': 0, 'cardNumberMasked': '504172******9210', 'rrn': 511629210215, 'appToken': 4101434578524, 'token': 'NDEwMTQzNDU3ODUyNA=='}
|
# response = {'status': 0, 'cardNumberMasked': '504172******9210', 'rrn': 511629210215, 'appToken': 4101434578524, 'token': 'NDEwMTQzNDU3ODUyNA=='}
|
||||||
|
|
||||||
# post = <QueryDict: {'Token': ['NDEwMTI0Nzc0MDMzMA=='], 'OrderId': ['7'], 'TerminalNo': ['31127029'], 'RRN': ['511629156251'], 'status': ['0'], 'TspToken': ['00000000-0000-0000-0000-000000000000'], 'HashCardNumber': ['6008617E6BAB76997499446356F5C8B60288445650DD551FDF32007BCF2B14AF'], 'Amount': ['21,000'], 'SwAmount': [''], 'STraceNo': ['123232']}>
|
# post = <QueryDict: {'Token': ['NDEwMTI0Nzc0MDMzMA=='], 'OrderId': ['7'], 'TerminalNo': ['31127029'], 'RRN': ['511629156251'], 'status': ['0'], 'TspToken': ['00000000-0000-0000-0000-000000000000'], 'HashCardNumber': ['6008617E6BAB76997499446356F5C8B60288445650DD551FDF32007BCF2B14AF'], 'Amount': ['21,000'], 'SwAmount': [''], 'STraceNo': ['123232']}>
|
||||||
# post = <QueryDict: {'Token': ['NDI2MDY3ODI0MTg1OA=='], 'OrderId': ['47'], 'status': ['-138']}>
|
# post = <QueryDict: {'Token': ['NDI2MDY3ODI0MTg1OA=='], 'OrderId': ['47'], 'status': ['-138']}>
|
||||||
|
|
||||||
|
self.details = response
|
||||||
if response['status'] == 0:
|
if response['status'] == 0:
|
||||||
self.pna_status = StateChoices.SUCCESS.value
|
self.pna_status = StateChoices.SUCCESS.value
|
||||||
self.payment_request.change_state(StateChoices.IPG_PENDING, StateChoices.IPG_SUCCESS,
|
self.payment_request.change_state(StateChoices.IPG_PENDING, StateChoices.IPG_SUCCESS,
|
||||||
|
|
@ -318,9 +322,6 @@ class PNA(BasePGModel):
|
||||||
|
|
||||||
|
|
||||||
class SEP(BasePGModel):
|
class SEP(BasePGModel):
|
||||||
label = GatewayChoise.SEP.value
|
|
||||||
image = f'image/{GatewayChoise.SEP.value}.png'
|
|
||||||
|
|
||||||
redirect_method = 'GET'
|
redirect_method = 'GET'
|
||||||
payment_request = models.OneToOneField(PaymentRequest, on_delete=models.PROTECT, related_name='sep')
|
payment_request = models.OneToOneField(PaymentRequest, on_delete=models.PROTECT, related_name='sep')
|
||||||
|
|
||||||
|
|
@ -347,7 +348,7 @@ class SEP(BasePGModel):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = self.client.request_token(amount, callback_url, order_id, phone_number)
|
response = self.client.request_token(amount, callback_url, order_id, phone_number)
|
||||||
self.details['make_request'] = response
|
self.details['get_token_response'] = response
|
||||||
|
|
||||||
self.sep_status_code = response['status']
|
self.sep_status_code = response['status']
|
||||||
self.sep_status_message = response.get('title')
|
self.sep_status_message = response.get('title')
|
||||||
|
|
@ -374,8 +375,7 @@ class SEP(BasePGModel):
|
||||||
|
|
||||||
print(dict(request.POST))
|
print(dict(request.POST))
|
||||||
post_dict = dict(request.POST)
|
post_dict = dict(request.POST)
|
||||||
self.details['post_dict'] = post_dict
|
self.details['redirect_post_data'] = post_dict
|
||||||
self.save()
|
|
||||||
# post = {'MID': ['0'], 'TerminalId': ['15336754'], 'RefNum': ['wSLVxHt51y3BjY9tkFXieaQAxHg6cmGp2X7B506RZs'], 'ResNum': ['10'], 'State': ['OK'], 'TraceNo': ['393594'], 'Amount': ['20000'], 'AffectiveAmount': ['20000'], 'Wage': [''], 'Rrn': ['224413541909'], 'SecurePan': ['504172******9210'], 'Status': ['2'], 'Token': ['dd830117c88a4e8284230430ccf6c553'], 'HashedCardNumber': ['6008617e6bab76997499446356f5c8b60288445650dd551fdf32007bcf2b14af']}
|
# post = {'MID': ['0'], 'TerminalId': ['15336754'], 'RefNum': ['wSLVxHt51y3BjY9tkFXieaQAxHg6cmGp2X7B506RZs'], 'ResNum': ['10'], 'State': ['OK'], 'TraceNo': ['393594'], 'Amount': ['20000'], 'AffectiveAmount': ['20000'], 'Wage': [''], 'Rrn': ['224413541909'], 'SecurePan': ['504172******9210'], 'Status': ['2'], 'Token': ['dd830117c88a4e8284230430ccf6c553'], 'HashedCardNumber': ['6008617e6bab76997499446356f5c8b60288445650dd551fdf32007bcf2b14af']}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -421,9 +421,6 @@ class SEP(BasePGModel):
|
||||||
|
|
||||||
|
|
||||||
class Fake(BasePGModel):
|
class Fake(BasePGModel):
|
||||||
# label = GatewayChoise.FAKE.value
|
|
||||||
# image = f'image/{GatewayChoise.FAKE.value}.png'
|
|
||||||
|
|
||||||
payment_request = models.OneToOneField(PaymentRequest, on_delete=models.PROTECT, related_name='+')
|
payment_request = models.OneToOneField(PaymentRequest, on_delete=models.PROTECT, related_name='+')
|
||||||
|
|
||||||
def make_request(self):
|
def make_request(self):
|
||||||
|
|
@ -441,17 +438,3 @@ class Fake(BasePGModel):
|
||||||
|
|
||||||
def reverse_request(self):
|
def reverse_request(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
class ApplicationGatewayPolicy(BaseModel):
|
|
||||||
class PolicyChoise(TextChoices):
|
|
||||||
RANDOM = "random", _("random")
|
|
||||||
SELECTABLE = 'selectable', _('selectable')
|
|
||||||
|
|
||||||
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
|
||||||
related_name='+', null=True, blank=True, unique=True)
|
|
||||||
preferred_gateways = models.CharField(max_length=100, null=True, blank=True, default='')
|
|
||||||
policy = models.CharField(max_length=20, choices=PolicyChoise.choices, default=PolicyChoise.SELECTABLE)
|
|
||||||
|
|
||||||
def get_preferred_gateways(self):
|
|
||||||
return [p.strip() for p in self.preferred_gateways.split() if p.strip()]
|
|
||||||
|
|
|
||||||
105
apps/pg/views.py
105
apps/pg/views.py
|
|
@ -14,7 +14,7 @@ from django.shortcuts import render
|
||||||
from utils.commons import calculate_pna_commission
|
from utils.commons import calculate_pna_commission
|
||||||
from .constans import StateChoices
|
from .constans import StateChoices
|
||||||
from .forms import PaymentRequestForm
|
from .forms import PaymentRequestForm
|
||||||
from .models import PaymentRequest, get_gateway_class
|
from .models import PaymentRequest
|
||||||
from .serializers import PaymentRequestSerializer, CommissionSerializer
|
from .serializers import PaymentRequestSerializer, CommissionSerializer
|
||||||
from ..core.models import BasePGModel
|
from ..core.models import BasePGModel
|
||||||
from ..gooyal_oauth2.utils import get_application
|
from ..gooyal_oauth2.utils import get_application
|
||||||
|
|
@ -74,58 +74,16 @@ class PaymentRequestView(UpdateView):
|
||||||
slug_field = 'uuid'
|
slug_field = 'uuid'
|
||||||
slug_url_kwarg = 'uuid'
|
slug_url_kwarg = 'uuid'
|
||||||
|
|
||||||
|
# TODO: remove it in future
|
||||||
def get(self, request, *args, **kwargs):
|
# def get(self, request, *args, **kwargs):
|
||||||
charge_request: PaymentRequest = self.get_object()
|
# charge_request: PaymentRequest = self.get_object()
|
||||||
# TODO: check more states
|
# if charge_request.state == StateChoices.FAILED:
|
||||||
# TODO: prevent double
|
# return render(request, 'pg/payment_failure.html', {
|
||||||
if charge_request.state == StateChoices.FAILED:
|
# 'object': charge_request
|
||||||
return render(request, 'pg/payment_failure.html', {
|
# })
|
||||||
'object': charge_request
|
# redirect_url = tool.get_pg_page_url()
|
||||||
})
|
# return HttpResponseRedirect(redirect_url)
|
||||||
|
#
|
||||||
# # gateway was setted in api call
|
|
||||||
# # TODO: double check
|
|
||||||
# if charge_request.gateway and not charge_request.commission:
|
|
||||||
# charge_request.save_commission()
|
|
||||||
# gateway_tool = charge_request.get_gateway_tools()
|
|
||||||
# gateway_tool.make_request()
|
|
||||||
# return HttpResponseRedirect(gateway_tool.redirect_url)
|
|
||||||
|
|
||||||
|
|
||||||
application_policy = charge_request.get_policy()
|
|
||||||
preferred_gateways = application_policy.get_preferred_gateways()
|
|
||||||
if preferred_gateways:
|
|
||||||
if application_policy.policy == application_policy.PolicyChoise.RANDOM:
|
|
||||||
# TODO: move to model
|
|
||||||
import random
|
|
||||||
gateway = random.choices(preferred_gateways)
|
|
||||||
charge_request.set_gateway(gateway)
|
|
||||||
charge_request.save_commission()
|
|
||||||
gateway_tool = charge_request.get_gateway_tools()
|
|
||||||
gateway_tool.make_request()
|
|
||||||
redirect_url = gateway_tool.get_pg_page_url()
|
|
||||||
if redirect_url:
|
|
||||||
return HttpResponseRedirect(redirect_url)
|
|
||||||
|
|
||||||
elif len(preferred_gateways) == 1:
|
|
||||||
gateway = preferred_gateways[0]
|
|
||||||
charge_request.set_gateway(gateway)
|
|
||||||
charge_request.save_commission()
|
|
||||||
gateway_tool = charge_request.get_gateway_tools()
|
|
||||||
gateway_tool.make_request()
|
|
||||||
redirect_url = gateway_tool.get_pg_page_url()
|
|
||||||
if redirect_url:
|
|
||||||
return HttpResponseRedirect(redirect_url)
|
|
||||||
|
|
||||||
else:
|
|
||||||
return super().get(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
return render(request, 'pg/payment_failure.html', {
|
|
||||||
'object': charge_request
|
|
||||||
})
|
|
||||||
|
|
||||||
# # TODO: remove it in future
|
# # TODO: remove it in future
|
||||||
# def post(self, request, *args, **kwargs):
|
# def post(self, request, *args, **kwargs):
|
||||||
# charge_request: PaymentRequest = self.get_object()
|
# charge_request: PaymentRequest = self.get_object()
|
||||||
|
|
@ -146,35 +104,30 @@ class PaymentRequestView(UpdateView):
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
self.object = form.save()
|
self.object = form.save()
|
||||||
charge_request: PaymentRequest = self.get_object()
|
charge_request: PaymentRequest = self.get_object()
|
||||||
application_policy = charge_request.get_policy()
|
|
||||||
preferred_gateways = application_policy.get_preferred_gateways()
|
|
||||||
|
|
||||||
if preferred_gateways:
|
charge_request.set_gateway(form.data)
|
||||||
for gateway_label in preferred_gateways:
|
charge_request.save_commission()
|
||||||
if gateway_label in form.data:
|
|
||||||
charge_request.set_gateway(gateway_label)
|
|
||||||
charge_request.save_commission()
|
|
||||||
|
|
||||||
tool:BasePGModel = charge_request.get_gateway_tools()
|
tool:BasePGModel = charge_request.get_gateway_tools()
|
||||||
tool.make_request()
|
|
||||||
|
|
||||||
redirect_url = tool.get_pg_page_url()
|
tool.make_request()
|
||||||
|
|
||||||
if redirect_url:
|
redirect_url = tool.get_pg_page_url()
|
||||||
if tool.redirect_method == 'GET':
|
|
||||||
return HttpResponseRedirect(redirect_url)
|
if redirect_url:
|
||||||
elif tool.redirect_method == 'POST':
|
if tool.redirect_method == 'GET':
|
||||||
print(redirect_url)
|
return HttpResponseRedirect(redirect_url)
|
||||||
return
|
elif tool.redirect_method == 'POST':
|
||||||
else:
|
print(redirect_url)
|
||||||
pass
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
else:
|
||||||
messages.error(self.request, 'can not redirect to payment gateway')
|
messages.error(self.request, 'can not redirect to payment gateway')
|
||||||
messages.info(self.request, 'در پردازش صفحه خطایی وجود دارد.')
|
messages.info(self.request, 'در پردازش صفحه خطایی وجود دارد.')
|
||||||
# TODO: redirect to error page
|
# TODO: redirect to error page
|
||||||
return HttpResponseRedirect(reverse('pg:payment-confirm-view', kwargs={'uuid': charge_request.uuid}))
|
return HttpResponseRedirect(reverse('pg:payment-confirm-view', kwargs={'uuid': charge_request.uuid}))
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(csrf_exempt, name='dispatch')
|
@method_decorator(csrf_exempt, name='dispatch')
|
||||||
|
|
|
||||||
|
|
@ -1,142 +0,0 @@
|
||||||
import os
|
|
||||||
|
|
||||||
def get_settings(key):
|
|
||||||
from django.conf import settings
|
|
||||||
return getattr(settings, 'BASE_DIR')
|
|
||||||
|
|
||||||
|
|
||||||
def get_logging_setting():
|
|
||||||
BASE_DIR = get_settings('BASE_DIR')
|
|
||||||
|
|
||||||
LOG_DIR = BASE_DIR / 'log'
|
|
||||||
if not os.path.exists(LOG_DIR):
|
|
||||||
os.makedirs(LOG_DIR)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'version': 1,
|
|
||||||
'disable_existing_loggers': False,
|
|
||||||
'formatters': {
|
|
||||||
'verbose': {
|
|
||||||
'format': '{asctime} [{levelname}] {name} {module} {process:d} {thread:d} {message}',
|
|
||||||
'style': '{',
|
|
||||||
},
|
|
||||||
'standard': {
|
|
||||||
'format': '{asctime} [{levelname}] {name}: {message}',
|
|
||||||
'style': '{',
|
|
||||||
},
|
|
||||||
'simple': {
|
|
||||||
'format': '{levelname} {message}',
|
|
||||||
'style': '{',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'filters': {
|
|
||||||
'require_debug_true': {
|
|
||||||
'()': 'django.utils.log.RequireDebugTrue',
|
|
||||||
},
|
|
||||||
'require_debug_false': {
|
|
||||||
'()': 'django.utils.log.RequireDebugFalse',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'handlers': {
|
|
||||||
# هندلر برای نوشتن در فایل
|
|
||||||
'file': {
|
|
||||||
'level': 'INFO',
|
|
||||||
'class': 'logging.handlers.TimedRotatingFileHandler',
|
|
||||||
'filename': os.path.join(LOG_DIR, 'accounts.log'),
|
|
||||||
# 'maxBytes': 1024 * 1024 * 10, # 10 MB
|
|
||||||
'when': 'midnight',
|
|
||||||
'interval': 5,
|
|
||||||
'backupCount': 30,
|
|
||||||
'formatter': 'verbose',
|
|
||||||
'encoding': 'utf-8',
|
|
||||||
},
|
|
||||||
# هندلر برای خطاها در فایل جداگانه
|
|
||||||
'error_file': {
|
|
||||||
'level': 'ERROR',
|
|
||||||
'class': 'logging.handlers.TimedRotatingFileHandler',
|
|
||||||
'filename': os.path.join(LOG_DIR, 'errors.log'),
|
|
||||||
# 'maxBytes': 1024 * 1024 * 10,
|
|
||||||
'when': 'midnight',
|
|
||||||
'interval': 5,
|
|
||||||
'backupCount': 30,
|
|
||||||
'formatter': 'verbose',
|
|
||||||
'encoding': 'utf-8',
|
|
||||||
},
|
|
||||||
# هندلر برای کنسول (فقط در حالت DEBUG)
|
|
||||||
'console': {
|
|
||||||
'level': 'DEBUG',
|
|
||||||
'filters': ['require_debug_true'],
|
|
||||||
'class': 'logging.StreamHandler',
|
|
||||||
'formatter': 'simple',
|
|
||||||
},
|
|
||||||
# هندلر برای کنسول همیشه فعال (حتی در production)
|
|
||||||
'console_always': {
|
|
||||||
'level': 'INFO',
|
|
||||||
'class': 'logging.StreamHandler',
|
|
||||||
'formatter': 'standard',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'loggers': {
|
|
||||||
# لاگر ریشه
|
|
||||||
'': { # empty string = root logger
|
|
||||||
'handlers': ['console', 'file', 'error_file'],
|
|
||||||
'level': 'INFO',
|
|
||||||
'propagate': True,
|
|
||||||
},
|
|
||||||
# لاگر اختصاصی برای Django
|
|
||||||
'django': {
|
|
||||||
'handlers': ['console', 'file'],
|
|
||||||
'level': 'INFO',
|
|
||||||
'propagate': False,
|
|
||||||
},
|
|
||||||
# لاگر اختصاصی برای درخواستها
|
|
||||||
'django.request': {
|
|
||||||
'handlers': ['file', 'error_file', 'console'],
|
|
||||||
'level': 'ERROR',
|
|
||||||
'propagate': False,
|
|
||||||
},
|
|
||||||
# # لاگر اختصاصی برای برنامه خودتان
|
|
||||||
# 'root': {
|
|
||||||
# 'handlers': ['console', 'file', 'error_file'],
|
|
||||||
# 'level': 'DEBUG',
|
|
||||||
# 'propagate': False,
|
|
||||||
# },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
# LOKI_BASE_PUBLIC_URL = config('LOKI_BASE_PUBLIC_URL', default=None, cast=str)
|
|
||||||
# {
|
|
||||||
# 'version': 1,
|
|
||||||
# 'disable_existing_loggers': False,
|
|
||||||
# 'formatters': {
|
|
||||||
# 'loki': {
|
|
||||||
# 'class': 'utils.logs.LokiFormatter', # required
|
|
||||||
# },
|
|
||||||
# },
|
|
||||||
#
|
|
||||||
# 'handlers': {
|
|
||||||
# 'loki': {
|
|
||||||
# 'level': 'DEBUG', # Log level. Required
|
|
||||||
# 'class': 'utils.logs.LokiHandler', # Required
|
|
||||||
# 'formatter': 'loki', # Loki formatter. Required
|
|
||||||
# 'timeout': 2, # Post request timeout, default is 0.5. Optional
|
|
||||||
# 'url': f'{LOKI_BASE_PUBLIC_URL}/loki/api/v1/push', # Loki url. Defaults to localhost. Optional.
|
|
||||||
# # 'auth': ("user", "password"), # Basic auth to authenticate with loki. Default is None (i.e. no auth). Optional
|
|
||||||
# 'tags': {"app": "accounts"}, # Tags / Labels to attach to the log. Optional, but strongly encoraged to use.
|
|
||||||
# 'mode': 'thread',
|
|
||||||
# # Push mode. Can be 'sync' or 'thread'. Sync is blocking, thread is non-blocking. Defaults to sync. Optional.
|
|
||||||
# },
|
|
||||||
# 'console': {
|
|
||||||
# 'level': 'DEBUG',
|
|
||||||
# 'class': 'logging.StreamHandler',
|
|
||||||
# # 'formatter': 'verbose',
|
|
||||||
# },
|
|
||||||
# },
|
|
||||||
# 'loggers': {
|
|
||||||
# '': {
|
|
||||||
# 'handlers': ['console', 'loki'],
|
|
||||||
# 'level': 'INFO',
|
|
||||||
# 'propagate': True,
|
|
||||||
# },
|
|
||||||
# },
|
|
||||||
# }
|
|
||||||
|
|
@ -257,8 +257,40 @@ CACHES = {
|
||||||
LOKI_BASE_PUBLIC_URL = config('LOKI_BASE_PUBLIC_URL', default=None, cast=str)
|
LOKI_BASE_PUBLIC_URL = config('LOKI_BASE_PUBLIC_URL', default=None, cast=str)
|
||||||
|
|
||||||
|
|
||||||
from main.other_settings.logging import get_logging_setting
|
LOGGING = {
|
||||||
LOGGING = get_logging_setting()
|
'version': 1,
|
||||||
|
'disable_existing_loggers': False,
|
||||||
|
'formatters': {
|
||||||
|
'loki': {
|
||||||
|
'class': 'utils.logs.LokiFormatter', # required
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'handlers': {
|
||||||
|
'loki': {
|
||||||
|
'level': 'DEBUG', # Log level. Required
|
||||||
|
'class': 'utils.logs.LokiHandler', # Required
|
||||||
|
'formatter': 'loki', # Loki formatter. Required
|
||||||
|
'timeout': 2, # Post request timeout, default is 0.5. Optional
|
||||||
|
'url': f'{LOKI_BASE_PUBLIC_URL}/loki/api/v1/push', # Loki url. Defaults to localhost. Optional.
|
||||||
|
# 'auth': ("user", "password"), # Basic auth to authenticate with loki. Default is None (i.e. no auth). Optional
|
||||||
|
'tags': {"app": "ipg"}, # Tags / Labels to attach to the log. Optional, but strongly encoraged to use.
|
||||||
|
'mode': 'thread', # Push mode. Can be 'sync' or 'thread'. Sync is blocking, thread is non-blocking. Defaults to sync. Optional.
|
||||||
|
},
|
||||||
|
'console': {
|
||||||
|
'level': 'DEBUG',
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
# 'formatter': 'verbose',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'loggers': {
|
||||||
|
'': {
|
||||||
|
'handlers': ['console', 'loki'],
|
||||||
|
'level': 'INFO',
|
||||||
|
'propagate': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
PNA_CORPORATION_PIN = config('PNA_CORPORATION_PIN')
|
PNA_CORPORATION_PIN = config('PNA_CORPORATION_PIN')
|
||||||
WALLET_BASE_PUBLIC_URL = config('WALLET_BASE_PUBLIC_URL')
|
WALLET_BASE_PUBLIC_URL = config('WALLET_BASE_PUBLIC_URL')
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,17 @@
|
||||||
<h6 class="h6">پرداخت با:</h6>
|
<h6 class="h6">پرداخت با:</h6>
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
{% for gateway_tool in object.get_preferred_gateway_classes %}
|
{# <div class="">#}
|
||||||
<button type="submit" class="btn mx-auto center-block" name="{{ gateway_tool.label }}">
|
<button type="submit" class="btn mx-auto center-block" name="pna">
|
||||||
<img class="img-thumbnail" width="128" height="128" src="{% static gateway_tool.image %}"/>
|
<img class="img-thumbnail" width="128" height="128" src="{% static "image/pna.png" %}"/>
|
||||||
</button>
|
</button>
|
||||||
{% endfor %}
|
<button type="submit" class="btn mx-auto center-block" name="sep">
|
||||||
|
<img class="img-thumbnail" width="128" height="128" src="{% static "image/sep.png" %}"/>
|
||||||
|
</button>
|
||||||
|
{# <button type="submit" class="btn mx-auto center-block" name="fake">#}
|
||||||
|
{# <img class="img-thumbnail" width="128" height="128" src="{% static "image/fake.png" %}"/>#}
|
||||||
|
{# </button>#}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils import timezone
|
|
||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
from rest_framework.views import exception_handler as drf_exception_handler
|
from rest_framework.views import exception_handler as drf_exception_handler
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def exception_handler(exc, context):
|
def exception_handler(exc, context):
|
||||||
logger.exception(exc)
|
logger.exception(exc)
|
||||||
# پاسخ پیشفرض DRF را دریافت میکنیم
|
# پاسخ پیشفرض DRF را دریافت میکنیم
|
||||||
|
|
@ -17,8 +13,8 @@ def exception_handler(exc, context):
|
||||||
response_data = {
|
response_data = {
|
||||||
'success': False,
|
'success': False,
|
||||||
'status_code': response.status_code,
|
'status_code': response.status_code,
|
||||||
'status_message': str(getattr(exc, 'default_detail', exc)),
|
'message': str(exc.default_detail),
|
||||||
'details': getattr(exc, 'detail', None),
|
'details': exc.detail,
|
||||||
}
|
}
|
||||||
|
|
||||||
if isinstance(exc, APIException):
|
if isinstance(exc, APIException):
|
||||||
|
|
@ -46,12 +42,8 @@ def exception_handler(exc, context):
|
||||||
# message = error
|
# message = error
|
||||||
#
|
#
|
||||||
# response_data['message'] = message
|
# response_data['message'] = message
|
||||||
if isinstance(exc.detail, dict):
|
response_data['details'] = exc.detail if isinstance(exc.detail, dict) else {}
|
||||||
response_data['details'] = exc.detail
|
|
||||||
else:
|
|
||||||
response_data['details'] = {"message": exc.detail}
|
|
||||||
response_data['details']['error'] = error
|
response_data['details']['error'] = error
|
||||||
response_data['details']['timestamp'] = timezone.now().isoformat()
|
|
||||||
|
|
||||||
response.data = response_data
|
response.data = response_data
|
||||||
|
|
||||||
|
|
@ -61,7 +53,6 @@ def exception_handler(exc, context):
|
||||||
# در فایل middleware.py
|
# در فایل middleware.py
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
|
|
||||||
|
|
||||||
class ErrorMiddleware:
|
class ErrorMiddleware:
|
||||||
def __init__(self, get_response):
|
def __init__(self, get_response):
|
||||||
self.get_response = get_response
|
self.get_response = get_response
|
||||||
|
|
@ -76,13 +67,13 @@ class ErrorMiddleware:
|
||||||
{
|
{
|
||||||
"success": False,
|
"success": False,
|
||||||
"status_code": 500,
|
"status_code": 500,
|
||||||
"status_message": "Internal server error",
|
'error_code': 500,
|
||||||
"details": str(exception) if settings.DEBUG else None,
|
"message": "Internal server error",
|
||||||
|
"details": str(exception) # فقط در حالت توسعه! در تولید بهتر است لاگ شود.
|
||||||
},
|
},
|
||||||
status=500
|
status=500
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
@ -93,16 +84,8 @@ class UnprocessableEntity(APIException):
|
||||||
default_detail = 'The request was well-formed but cannot be processed due to semantic errors.'
|
default_detail = 'The request was well-formed but cannot be processed due to semantic errors.'
|
||||||
default_code = 'unprocessable_entity'
|
default_code = 'unprocessable_entity'
|
||||||
|
|
||||||
|
|
||||||
class ServiceUnavailable(APIException):
|
class ServiceUnavailable(APIException):
|
||||||
status_code = status.HTTP_503_SERVICE_UNAVAILABLE
|
status_code = status.HTTP_503_SERVICE_UNAVAILABLE
|
||||||
default_ = _('SERVICE_UNAVAILABLE')
|
default_ = _('SERVICE_UNAVAILABLE')
|
||||||
default_code = 'service_unavailable'
|
default_code = 'service_unavailable'
|
||||||
default_detail = 'Service Unavailable'
|
default_detail = 'Service Unavailable'
|
||||||
|
|
||||||
|
|
||||||
class Conflict(APIException):
|
|
||||||
status_code = status.HTTP_409_CONFLICT
|
|
||||||
default_ = _('CONFLICT')
|
|
||||||
default_code = 'conflict'
|
|
||||||
default_detail = 'Conflict'
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue