new templates

This commit is contained in:
Sayyid Hamid Mahdavi 2025-08-09 12:29:50 +03:30
parent 2ccc838b11
commit b410078649
8 changed files with 821 additions and 6 deletions

View file

@ -91,6 +91,15 @@ class PaymentRequest(models.Model):
self.save()
@property
def reference_number(self):
pna = PNA.objects.filter(payment_request=self).first()
if pna:
return pna.pk
else:
return '00000000'
# TODO: complete and use this
def change_state(self, from_state, to_state, error_message=None, **data):
if self.state == from_state:
@ -222,7 +231,7 @@ class PNA(BasePGModel):
def confirm_request(self, request=None):
if self.payment_request.state not in [StateChoices.IPG_PENDING]:
messages.error(request, "وضعیت تراکنش در حالت مناسبی نیست.")
return
return False, "وضعیت تراکنش در حالت مناسبی نیست."
print(dict(request.POST))
post_dict = dict(request.POST)
@ -246,13 +255,16 @@ class PNA(BasePGModel):
self.payment_request.charge_wallet_balance()
messages.success(request, 'شارژ انجام شد')
return True, 'شارژ انجام شد'
except Exception as e:
print(e)
messages.error(request, str(e))
return False, "خطایی رخ داده است."
else:
print(request.POST)
messages.error(request, "نتیجه تراکنش از سمت درگاه دریافت نشد.")
return False, "نتیجه تراکنش از سمت درگاه دریافت نشد."
def reverse_request(self):
raise NotImplementedError()

View file

@ -83,7 +83,8 @@ class PaymentRequestView(UpdateView):
@method_decorator(csrf_exempt, name='dispatch')
class PaymentConfirmView(UpdateView):
template_name = 'pg/payment-confirm.html'
template_name = 'pg/payment_failure.html'
# template_name = 'pg/payment_success.html'
form_class = PaymentRequestForm
# pk_url_kwarg = 'uuid'
slug_field = 'uuid'
@ -118,8 +119,11 @@ class PaymentConfirmView(UpdateView):
return response
tool = self.object.get_gateway_tools()
tool.confirm_request(request)
state, message = tool.confirm_request(request)
if state:
self.template_name = 'pg/payment_success.html'
context = self.get_context_data(object=self.object)
context['message'] = message
return self.render_to_response(context)

View file

@ -244,6 +244,6 @@ CACHES = {
}
}
PNA_CORPORATION_PIN = config('PNA_CORPORATION_PIN')
WALLET_BASE_PUBLIC_URL = config('WALLET_BASE_PUBLIC_URL')
IPG_BASE_PUBLIC_URL = config('IPG_BASE_PUBLIC_URL')
PNA_CORPORATION_PIN = config('PNA_CORPORATION_PIN')
WALLET_BASE_PUBLIC_URL = config('WALLET_BASE_PUBLIC_URL')
IPG_BASE_PUBLIC_URL = config('IPG_BASE_PUBLIC_URL')

View file

@ -0,0 +1,34 @@
.cursorily{ cursor: pointer;}
.hov:hover{ color: #000;}
.ico-size{font-size: 16px;}
.ico-size-month{font-size: 26px!important; line-height: 26px!important;}
.ico-size-large{ font-size: 40px!important; line-height: 30px;}
.dtp_main{ border: solid 1px #eee; border-radius: 3px; background-color: #fff; padding: 8px 0 8px 8px;}
.dtp_main span, .dtp_main i{ display: inline-block; padding-right: 8px;}
.dtp_modal-win{position: fixed;left: 0; top: 0; width: 100%; height: 100%;
z-index: 999; background-color: #eeeeee; opacity: 0.6;}
.dtp_modal-content{ background-color: #fff; border-radius: 10px; width: 624px;
position: absolute; z-index: 1000; top: 100px; left: 100px; font-size: 16px;font-weight: normal;}
.dtp_modal-content-no-time{ background-color: #fff; border-radius: 10px; width: 312px;
position: absolute; z-index: 1000; top: 100px; left: 100px; font-size: 16px;font-weight: normal;}
.dtp_modal-title{ border-bottom: solid 3px #54646b; padding: 16px 36px; margin-bottom: 16px; font-size: 22px; }
.dtp_modal-cell-date{ width: 312px; float: right; margin-bottom: 22px; margin-top: 6px;}
.dtp_modal-cell-time{width: 311px; float: left; direction: ltr; border-right: solid 1px #000;}
.dtp_modal-months{ color: #7d7d7d; text-align: center; font-size: 20px; padding: 0 20px;}
.dtp_modal-months span{ display: inline-block; padding: 10px 20px; width: 182px;}
.dtp_modal-calendar{ width: 266px; margin-left: 22px; }
.dtp_modal-calendar-cell{ width: 38px; padding: 7px 0; display: inline-block; text-align: center;}
.dtp_modal-colored{ color: #54646b; }
.dtp_modal-grey{ color: #7d7d7d; }
.dtp_modal-cell-selected{ background-color: #54646b; border-radius: 48%; transition: background-color 1s ease-out;}
.dtp_modal-time-block{ height: 212px; width: 310px; }
.dpt_modal-button{ background-color: #54646b; color: #fff; font-size: 24px; padding: 8px 40px; float: left;
text-align: center; display: inline-block; margin-left: 22px; cursor: pointer; border: solid 1px #fff;
border-radius: 3px; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);}
.dtp_modal-time-line{ text-align: center; color: #7d7d7d; font-size: 20px; padding-top: 15px; }
.dtp_modal-time-mechanic{ padding-top: 16px;}
.dtp_modal-append{ color: #7d7d7d; padding-left: 108px; font-weight: normal; }
.dtp_modal-midle{ display: inline-block; width: 40px; }
.dtp_modal-midle-dig{display: inline-block; width: 16px; text-align: center; }
.dtp_modal-digits{ font-size: 50px; padding-left: 96px;}
.dtp_modal-digit{ }

396
static/js/datetimepicker.js Normal file
View file

@ -0,0 +1,396 @@
(function ($) {
'use strict';
$.fn.dateTimePicker = function (options) {
var settings = $.extend({
selectData: "now",
dateFormat: "YYYY-MM-DD HH:mm",
showTime: true,
locale: 'en',
positionShift: { top: 20, left: 0},
title: "Select Date and Time",
buttonTitle: "Select",
allowBackwards: false
}, options);
moment.locale(settings.locale);
var elem = this;
var limitation = {"hour": 23, "minute": 59};
var mousedown = false;
var timeout = 800;
var selectDate = settings.selectData == "now" ? moment() : moment(settings.selectData, settings.dateFormat);
if (selectDate < moment() && (settings.allowBackwards == false)) {
selectDate = moment();
}
var startDate = copyDate(moment());
var lastSelected = copyDate(selectDate);
return this.each(function () {
if (lastSelected != selectDate) {
selectDate = copyDate(lastSelected);
}
elem.addClass("dtp_main");
updateMainElemGlobal();
// elem.text(selectDate.format(settings.dateFormat));
function updateMainElemGlobal() {
var arrF = settings.dateFormat.split(' ');
if (settings.showTime && arrF.length != 2) {
arrF.length = 2;
arrF[0] = 'DD/MM/YY';
arrF[1] = 'HH:mm';
}
var $s = $('<span>');
$s.text(lastSelected.format(arrF[0]));
elem.empty();
elem.append($s);
$s = $('<i>');
$s.addClass('fa fa-calendar ico-size');
elem.append($s);
if (settings.showTime) {
$s = $('<span>');
$s.text(lastSelected.format(arrF[1]));
elem.append($s);
$s = $('<i>');
$s.addClass('fa fa-clock-o ico-size');
elem.append($s);
}
}
elem.on('click', function () {
var $win = $('<div>');
$win.addClass("dtp_modal-win");
var $body = $('body');
$body.append($win);
var $content = createContent();
$body.append($content);
var offset = elem.offset();
$content.css({top: (offset.top + settings.positionShift.top) + "px", left: (offset.left + settings.positionShift.left) + "px"});
feelDates(selectDate);
$win.on('click', function () {
$content.remove();
$win.remove();
})
if (settings.showTime) {
attachChangeTime();
var $fieldTime = $('#field-time');
var $hour = $fieldTime.find('#d-hh');
var $minute = $fieldTime.find('#d-mm');
}
function feelDates(selectM) {
var $fDate = $content.find('#field-data');
$fDate.empty();
$fDate.append(createMonthPanel(selectM));
$fDate.append(createCalendar(selectM));
}
function createCalendar(selectedMonth) {
var $c = $('<div>');
$c.addClass('dtp_modal-calendar');
for (var i = 0; i < 7; i++) {
var $e = $('<div>');
$e.addClass('dtp_modal-calendar-cell dtp_modal-colored');
$e.text(moment().weekday(i).format('ddd'));
$c.append($e);
}
var m = copyDate(selectedMonth);
m.date(1);
// console.log(m.format('DD--MM--YYYY'));
// console.log(selectData.format('DD--MM--YYYY'));
// console.log(m.weekday());
var flagStart = totalMonths(selectedMonth) === totalMonths(startDate);
var flagSelect = totalMonths(lastSelected) === totalMonths(selectedMonth);
var cerDay = parseInt(selectedMonth.format('D'));
var dayNow = parseInt(startDate.format('D'));
for (var i = 0; i < 6; i++) {
for (var j = 0; j < 7; j++) {
var $b = $('<div>');
$b.html('&nbsp;');
$b.addClass('dtp_modal-calendar-cell');
if (m.month() == selectedMonth.month() && m.weekday() == j) {
var day = parseInt(m.format('D'));
$b.text(day);
if (flagStart && day < dayNow) {
if (settings.allowBackwards == false) {
$b.addClass('dtp_modal-grey');
} else {
$b.addClass('cursorily');
$b.bind('click', changeDate);
}
}
else if (flagSelect && day == cerDay) {
$b.addClass('dtp_modal-cell-selected');
}
else {
$b.addClass('cursorily');
$b.bind('click', changeDate);
}
m.add(1, 'days');
}
$c.append($b);
}
}
return $c;
}
function changeDate() {
var $div = $(this);
selectDate.date($div.text());
lastSelected = copyDate(selectDate);
updateDate();
var $fDate = $content.find('#field-data');
var old = $fDate.find('.dtp_modal-cell-selected');
old.removeClass('dtp_modal-cell-selected');
old.addClass('cursorily');
$div.addClass('dtp_modal-cell-selected');
$div.removeClass('cursorily');
old.bind('click', changeDate);
$div.unbind('click');
// console.log(selectDate.format('DD-MM-YYYY'));
}
function createMonthPanel(selectMonth) {
var $d = $('<div>');
$d.addClass('dtp_modal-months');
var $s = $('<i></i>');
$s.addClass('fa fa-angle-left cursorily ico-size-month hov');
//$s.attr('data-fa-mask', 'fas fa-circle');
$s.bind('click', prevMonth);
$d.append($s);
$s = $('<span>');
$s.text(selectMonth.format("MMMM YYYY"));
$d.append($s);
$s = $('<i></i>');
$s.addClass('fa fa-angle-right cursorily ico-size-month hov');
$s.bind('click', nextMonth);
$d.append($s);
return $d;
}
function close() {
if (settings.showTime) {
lastSelected.hour(parseInt($hour.text()));
lastSelected.minute(parseInt($minute.text()));
selectDate.hour(parseInt($hour.text()));
selectDate.minute(parseInt($minute.text()));
}
updateDate();
$content.remove();
$win.remove();
}
function nextMonth() {
selectDate.add(1, 'month');
feelDates(selectDate);
}
function prevMonth() {
if (totalMonths(selectDate) > totalMonths(startDate) || settings.allowBackwards == true) {
selectDate.add(-1, 'month');
feelDates(selectDate);
}
}
function attachChangeTime() {
var $angles = $($content).find('i[id^="angle-"]');
// $angles.bind('click', changeTime);
$angles.bind('mouseup', function () {
mousedown = false;
timeout = 800;
});
$angles.bind('mousedown', function () {
mousedown = true;
changeTime(this);
});
}
function changeTime(el) {
var $el = this || el;
$el = $($el);
///angle-up-hour angle-up-minute angle-down-hour angle-down-minute
var arr = $el.attr('id').split('-');
var increment = 1;
if (arr[1] == 'down') {
increment = -1;
}
appendIncrement(arr[2], increment);
setTimeout(function () {
autoIncrement($el);
}, timeout);
}
function autoIncrement(el) {
if (mousedown) {
if (timeout > 200) {
timeout -= 200;
}
changeTime(el);
}
}
function appendIncrement(typeDigits, increment) {
var $i = typeDigits == "hour" ? $hour : $minute;
var val = parseInt($i.text()) + increment;
if (val < 0) {
val = limitation[typeDigits];
}
else if (val > limitation[typeDigits]) {
val = 0;
}
$i.text(formatDigits(val));
}
function formatDigits(val) {
if (val < 10) {
return '0' + val;
}
return val;
}
function createTimer() {
var $div = $('<div>');
$div.addClass('dtp_modal-time-mechanic');
var $panel = $('<div>');
$panel.addClass('dtp_modal-append');
var $i = $('<i>');
$i.attr('id', 'angle-up-hour');
$i.addClass('fa fa-angle-up ico-size-large cursorily hov');
$panel.append($i);
var $m = $('<span>');
$m.addClass('dtp_modal-midle');
$panel.append($m);
$i = $('<i>');
$i.attr('id', 'angle-up-minute');
$i.addClass('fa fa-angle-up ico-size-large cursorily hov');
$panel.append($i);
$div.append($panel);
$panel = $('<div>');
$panel.addClass('dtp_modal-digits');
var $d = $('<span>');
$d.addClass('dtp_modal-digit');
$d.attr('id', 'd-hh');
$d.text(lastSelected.format('HH'));
$panel.append($d);
$m = $('<span>');
$m.addClass('dtp_modal-midle-dig');
$m.html(':');
$panel.append($m);
$d = $('<span>');
$d.addClass('dtp_modal-digit');
$d.attr('id', 'd-mm');
$d.text(lastSelected.format('mm'));
$panel.append($d);
$div.append($panel);
$panel = $('<div>');
$panel.addClass('dtp_modal-append');
$i = $('<i>');
$i.attr('id', 'angle-down-hour');
$i.addClass('fa fa-angle-down ico-size-large cursorily hov');
$panel.append($i);
$m = $('<span>');
$m.addClass('dtp_modal-midle');
$panel.append($m);
$i = $('<i>');
$i.attr('id', 'angle-down-minute');
$i.addClass('fa fa-angle-down ico-size-large cursorily hov');
$panel.append($i);
$div.append($panel);
return $div;
}
function createContent() {
var $c = $('<div>');
if (settings.showTime) {
$c.addClass("dtp_modal-content");
}
else {
$c.addClass("dtp_modal-content-no-time");
}
var $el = $('<div>');
$el.addClass("dtp_modal-title");
$el.text(settings.title);
$c.append($el);
$el = $('<div>');
$el.addClass('dtp_modal-cell-date');
$el.attr('id', 'field-data');
$c.append($el);
if (settings.showTime) {
$el = $('<div>');
$el.addClass('dtp_modal-cell-time');
var $a = $('<div>');
$a.addClass('dtp_modal-time-block');
$a.attr('id', 'field-time');
$el.append($a);
var $line = $('<div>');
$line.attr('id', 'time-line');
$line.addClass('dtp_modal-time-line');
$line.text(lastSelected.format(settings.dateFormat));
$a.append($line);
$a.append(createTimer());
var $but = $('<div>');
$but.addClass('dpt_modal-button');
$but.text(settings.buttonTitle);
$but.bind('click', close);
$el.append($but);
$c.append($el);
}
return $c;
}
function updateDate() {
if (settings.showTime) {
$('#time-line').text(lastSelected.format(settings.dateFormat));
}
updateMainElem();
elem.next().val(selectDate.format(settings.dateFormat));
if (!settings.showTime) {
$content.remove();
$win.remove();
}
}
function updateMainElem() {
var arrF = settings.dateFormat.split(' ');
if (settings.showTime && arrF.length != 2) {
arrF.length = 2;
arrF[0] = 'DD/MM/YY';
arrF[1] = 'HH:mm';
}
var $s = $('<span>');
$s.text(lastSelected.format(arrF[0]));
elem.empty();
elem.append($s);
$s = $('<i>');
$s.addClass('fa fa-calendar ico-size');
elem.append($s);
if (settings.showTime) {
$s = $('<span>');
$s.text(lastSelected.format(arrF[1]));
elem.append($s);
$s = $('<i>');
$s.addClass('fa fa-clock-o ico-size');
elem.append($s);
}
}
});
});
};
function copyDate(d) {
return moment(d.toDate());
}
function totalMonths(m) {
var r = m.format('YYYY') * 12 + parseInt(m.format('MM'));
return r;
}
}(jQuery));
// fa-caret-down

View file

@ -0,0 +1,15 @@
<!-- templates/admin/input_filter.html -->
{% load i18n %}
<h3>{% blocktrans with filter_title=title %} By {{ filter_title }} {% endblocktrans %}</h3>
<ul>
<li>
<form method="GET" action="">
<input
type="text"
value="{{ spec.value|default_if_none:'' }}"
name="{{ spec.parameter_name }}"/>
</form>
</li>
</ul>

View file

@ -0,0 +1,179 @@
{% load static %}
{% load crispy_forms_tags %}
{% load jalali_tags %}
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Payment Unsuccessful</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;700&display=swap" rel="stylesheet">
<style>
body {
margin: 0;
font-family: 'Vazirmatn', sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.mobile-frame {
width: 390px;
height: 844px;
background-color: #ffffff;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
padding: 30px;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.screen-content {
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.message-section {
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
}
.error-icon {
width: 40px;
height: 40px;
background-color: #e53935; /* Red for error */
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 24px;
font-weight: bold;
}
.error-text {
color: #e53935;
font-size: 1.1rem;
font-weight: bold;
margin-top: 15px;
margin-bottom: 25px;
}
.divider {
width: 100%;
height: 1px;
background-color: #eeeeee;
border: none;
margin-bottom: 25px;
}
.details-text {
color: #757575;
font-size: 0.9rem;
line-height: 1.6;
}
.details-text .code {
color: #424242;
font-weight: 600;
}
.placeholder-link {
color: #bdbdbd;
font-size: 0.9rem;
text-decoration: underline;
margin-top: 40px;
}
.button-container {
display: flex;
flex-direction: column;
gap: 15px;
width: 100%;
}
.btn {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
width: 100%;
padding: 14px;
border-radius: 20px;
font-size: 1rem;
font-weight: bold;
font-family: 'Vazirmatn', sans-serif;
cursor: pointer;
transition: all 0.2s ease-in-out;
border: 1px solid transparent;
}
.btn-retry {
background-color: #ffffff;
border: 2px solid #6931c9;
color: #6931c9;
}
.btn-retry:hover {
background-color: #f9f7fd;
}
.btn-primary {
background-color: #6931c9;
color: #ffffff;
}
.btn-primary:hover {
background-color: #7745ce;
}
</style>
</head>
<body>
<div class="mobile-frame">
<div class="screen-content">
<div class="message-section">
<div class="error-icon">!</div>
{% if message %}
<p class="error-text">{{ message }}</p>
{% else %}
<p class="error-text">پرداخت شما ناموفق بود</p>
{% endif %}
<hr class="divider">
<div class="details-text">
<p>کد پیگیری تراکنش شما: <span class="code">{{ object.reference_number }}</span></p>
<p>در صورت کسر از موجودی، مبلغ پرداختی طی یک تا 72 ساعت آینده به حساب شما بازگردانده خواهد شد.</p>
</div>
<a href="https://addwin.ir" class="placeholder-link">addwin</a>
</div>
<div class="button-container">
<form action="{{ request.get_full_path }}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary">بازگشت به صفحه اصلی</button>
</form>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,175 @@
{% load static %}
{% load crispy_forms_tags %}
{% load jalali_tags %}
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Payment Successful</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;700&display=swap" rel="stylesheet">
<style>
body {
margin: 0;
font-family: 'Vazirmatn', sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.mobile-frame {
width: 390px;
height: 844px;
background-color: #ffffff;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
padding: 30px;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.screen-content {
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
}
.message-section {
width: 100%;
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
}
.success-icon {
width: 50px;
height: 50px;
background-color: #4CAF50;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 28px;
font-weight: bold;
}
.title {
color: #333;
font-size: 1.2rem;
font-weight: bold;
margin-top: 20px;
margin-bottom: 8px;
}
.subtitle {
color: #757575;
font-size: 0.9rem;
margin-bottom: 25px;
}
.divider {
width: 100%;
height: 1px;
background-color: #eeeeee;
border: none;
margin-bottom: 25px;
}
.amount-row {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 1rem;
}
.amount-label {
color: #757575;
}
.amount-value {
color: #4CAF50;
font-weight: bold;
}
.placeholder-link {
color: #bdbdbd;
font-size: 0.9rem;
text-decoration: underline;
margin-top: 100px;
}
.button-container {
width: 100%;
display: flex;
flex-direction: column;
}
.btn {
width: 100%;
padding: 15px;
border: none;
border-radius: 20px;
font-size: 1.1rem;
font-weight: bold;
font-family: 'Vazirmatn', sans-serif;
cursor: pointer;
transition: all 0.2s ease-in-out;
}
.btn-primary {
background-color: #6931c9;
color: #ffffff;
}
.btn-primary:hover {
background-color: #7745ce;
}
</style>
</head>
<body>
<div class="mobile-frame">
<div class="screen-content">
<div class="message-section">
<div class="success-icon">&#10003;</div>
<p class="title">پرداخت موفق</p>
<p class="subtitle">پرداخت با موفقیت انجام شد.</p>
<hr class="divider">
<div class="amount-row">
<span class="amount-label">مقدار پرداخت شده:</span>
<span class="amount-value">{{ object.amount }} تومان</span>
</div>
<a href="https://addwin.ir" class="placeholder-link">addwin</a>
</div>
<div class="button-container">
<form action="{{ request.get_full_path }}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary">بازگشت به صفحه اصلی</button>
</form>
</div>
</div>
</div>
</body>
</html>