basic sep

This commit is contained in:
Sayyid Hamid Mahdavi 2026-03-09 14:53:31 +03:30
parent 6037330c11
commit d684bb7083
9 changed files with 252 additions and 6 deletions

View file

@ -21,6 +21,11 @@ class BasePGModel(models.Model):
created_at = models.DateTimeField(auto_now_add=True, db_index=True) created_at = models.DateTimeField(auto_now_add=True, db_index=True)
updated_at = models.DateTimeField(auto_now=True, db_index=True) updated_at = models.DateTimeField(auto_now=True, db_index=True)
redirect_method = 'GET'
def get_pg_page_url(self):
pass
def make_request(self): def make_request(self):
pass pass

View file

@ -0,0 +1,33 @@
# Generated by Django 5.1.4 on 2026-03-09 08:09
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pg', '0005_fake'),
]
operations = [
migrations.CreateModel(
name='SEP',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
('details', models.JSONField(blank=True, default=dict, null=True)),
('sep_token', models.CharField(blank=True, db_index=True, max_length=255, null=True)),
('sep_order_id', models.IntegerField(blank=True, db_index=True, null=True)),
('sep_originator', models.CharField(blank=True, max_length=255, null=True)),
('sep_status', models.IntegerField(blank=True, choices=[(1, 'created'), (2, 'ipg pending'), (3, 'ipg success'), (11, 'wallet submit pending'), (12, 'wallet submit success'), (18, 'failed'), (19, 'expected failure'), (20, 'success')], db_index=True, null=True)),
('sep_status_code', models.IntegerField(blank=True, null=True)),
('sep_status_message', models.CharField(blank=True, max_length=255, null=True)),
('payment_request', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='sep', to='pg.paymentrequest')),
],
options={
'abstract': False,
},
),
]

View file

@ -16,12 +16,15 @@ from utils.wallet_client import deposit_to_user_wallet_submit, withdraw_from_use
deposit_to_user_wallet_verify deposit_to_user_wallet_verify
from utils.pna import PNAClient from utils.pna import PNAClient
from utils.sep import SEPClient
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
class GatewayChoise(TextChoices): class GatewayChoise(TextChoices):
FAKE = "fake", _("fake") FAKE = "fake", _("fake")
PNA = 'pna', _('pna') PNA = 'pna', _('pna')
SEP = 'sep', _('sep')
# TODO: use generic relation # TODO: use generic relation
@ -47,6 +50,9 @@ class PaymentRequest(models.Model):
if self.gateway == GatewayChoise.PNA: if self.gateway == GatewayChoise.PNA:
return PNA.objects.get(payment_request=self) return PNA.objects.get(payment_request=self)
if self.gateway == GatewayChoise.SEP:
return SEP.objects.get(payment_request=self)
if self.gateway == GatewayChoise.FAKE: if self.gateway == GatewayChoise.FAKE:
return Fake.objects.get(payment_request=self) return Fake.objects.get(payment_request=self)
@ -60,8 +66,13 @@ class PaymentRequest(models.Model):
if 'pna' in data: if 'pna' in data:
self.gateway = GatewayChoise.PNA self.gateway = GatewayChoise.PNA
pna = PNA.objects.create(payment_request=self) pg = PNA.objects.create(payment_request=self)
pna.payment_request = self pg.payment_request = self
elif 'sep' in data:
self.gateway = GatewayChoise.SEP
pg = SEP.objects.create(payment_request=self)
pg.payment_request = self
else: else:
raise Exception('Unknown gateway') raise Exception('Unknown gateway')
@ -224,6 +235,103 @@ class PNA(BasePGModel):
raise NotImplementedError() raise NotImplementedError()
class SEP(BasePGModel):
redirect_method = 'GET'
payment_request = models.OneToOneField(PaymentRequest, on_delete=models.PROTECT, related_name='sep')
details = models.JSONField(null=True, blank=True, default=dict)
sep_token = models.CharField(null=True, blank=True, max_length=255, db_index=True)
sep_ref_number = models.CharField(null=True, blank=True, max_length=255, db_index=True)
# sep_originator = models.CharField(null=True, blank=True, max_length=255)
sep_status = models.IntegerField(choices=StateChoices.choices, null=True, blank=True, db_index=True)
sep_status_code = models.IntegerField(null=True, blank=True)
sep_status_message = models.CharField(null=True, blank=True, max_length=255)
client = SEPClient()
def make_request(self):
self.payment_request.change_state(StateChoices.CREATED, StateChoices.IPG_PENDING, 'can not change state')
amount = self.payment_request.amount
description = 'from addwin ipg'
callback_url = urllib.parse.urljoin(
settings.IPG_BASE_PUBLIC_URL,
reverse("pg:payment-confirm-view", kwargs={"uuid": self.payment_request.uuid}))
order_id = self.id
phone_number = self.payment_request.phone_number
try:
response = self.client.request_token(amount, callback_url, order_id, phone_number)
self.sep_status_code = response['status']
self.sep_status_message = response.get('title')
if response['status'] == 1:
self.sep_token = response['token']
else:
self.details['get_token_response'] = response
self.payment_request.change_state(self.payment_request.state, StateChoices.FAILED,
'error in sep gateway')
except Exception as e:
self.payment_request.change_state(self.payment_request.state, StateChoices.FAILED, 'error in sep gateway')
self.save()
def get_pg_page_url(self):
if self.sep_token:
return self.client.pg_base_url.format(token=self.sep_token)
def confirm_request(self, request=None):
if self.payment_request.state not in [StateChoices.IPG_PENDING]:
messages.error(request, "وضعیت تراکنش در حالت مناسبی نیست.")
return
print(dict(request.POST))
post_dict = dict(request.POST)
self.details['redirect_post_data'] = post_dict
# 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:
ref_number = post_dict.get('ref_number')[0]
except Exception as e:
print(e)
ref_number = None
if post_dict.get('Status') in [2, '2', [2], ['2']]:
try:
self.sep_ref_number = ref_number
self.save()
response = self.client.request_confirm(ref_number)
# 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': ['NDI2MDY3ODI0MTg1OA=='], 'OrderId': ['47'], 'status': ['-138']}>
self.details['verify_response'] = response
self.save()
if response['ResultCode'] == 0:
self.sep_status_status = StateChoices.SUCCESS.value
self.payment_request.change_state(StateChoices.IPG_PENDING, StateChoices.IPG_SUCCESS,
'error in change state')
self.save()
self.payment_request.charge_wallet_balance()
messages.success(request, 'شارژ انجام شد')
except Exception as e:
print(e)
messages.error(request, str(e))
else:
print(request.POST)
messages.error(request, "نتیجه تراکنش از سمت درگاه دریافت نشد.")
def reverse_request(self):
raise NotImplementedError()
class Fake(BasePGModel): class Fake(BasePGModel):
payment_request = models.OneToOneField(PaymentRequest, on_delete=models.PROTECT, related_name='+') payment_request = models.OneToOneField(PaymentRequest, on_delete=models.PROTECT, related_name='+')

View file

@ -6,7 +6,7 @@ app_name = "pg"
urlpatterns = [ urlpatterns = [
path('page/charge/<uuid>/', PaymentRequestView.as_view(), name='payment-request-view'), path('page/charge/<uuid>/', PaymentRequestView.as_view(), name='payment-request-view'),
path('page/pna_callback/<uuid>/', PaymentConfirmView.as_view(), name='payment-confirm-view'), path('page/callback/<uuid>/', PaymentConfirmView.as_view(), name='payment-confirm-view'),
path('api/charge_create/', PaymentRequestCreateApiView.as_view(), name='charge-request-create-api-view'), path('api/charge_create/', PaymentRequestCreateApiView.as_view(), name='charge-request-create-api-view'),
path('api/application/charge_create/<uuid>', ApplicationPaymentRequestCreateApiView.as_view(), name='application-user-charge-request-create-api-view'), path('api/application/charge_create/<uuid>', ApplicationPaymentRequestCreateApiView.as_view(), name='application-user-charge-request-create-api-view'),
] ]

View file

@ -14,6 +14,7 @@ from .forms import PaymentRequestForm
from .models import PaymentRequest, GatewayChoise from .models import PaymentRequest, GatewayChoise
from .serializers import PaymentRequestSerializer, ApplicationPaymentRequestSerializer from .serializers import PaymentRequestSerializer, ApplicationPaymentRequestSerializer
from .constans import StateChoices from .constans import StateChoices
from ..core.models import BasePGModel
from ..gooyal_oauth2.utils import get_application from ..gooyal_oauth2.utils import get_application
from ..users.models import User from ..users.models import User
@ -66,14 +67,21 @@ class PaymentRequestView(UpdateView):
charge_request.set_gateway(form.data) charge_request.set_gateway(form.data)
tool = charge_request.get_gateway_tools() tool:BasePGModel = charge_request.get_gateway_tools()
tool.make_request() tool.make_request()
redirect_url = tool.get_pg_page_url() redirect_url = tool.get_pg_page_url()
if redirect_url: if redirect_url:
return HttpResponseRedirect(redirect_url) if tool.redirect_method == 'GET':
return HttpResponseRedirect(redirect_url)
elif tool.redirect_method == 'POST':
print(redirect_url)
else:
pass
else: 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, 'در پردازش صفحه خطایی وجود دارد.')
@ -94,7 +102,6 @@ class PaymentConfirmView(UpdateView):
# return Charge.objects.filter(state__in=[StateChoices.CREATED.value, StateChoices.SUCCESS.value]) # return Charge.objects.filter(state__in=[StateChoices.CREATED.value, StateChoices.SUCCESS.value])
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
print('get') print('get')
print(args) print(args)
print(kwargs) print(kwargs)
@ -110,6 +117,7 @@ class PaymentConfirmView(UpdateView):
print(request.POST) print(request.POST)
print(args) print(args)
print(kwargs) print(kwargs)
self.object = self.get_object() self.object = self.get_object()
if 'redirect-to-app' in request.POST: if 'redirect-to-app' in request.POST:

View file

@ -247,3 +247,6 @@ CACHES = {
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')
IPG_BASE_PUBLIC_URL = config('IPG_BASE_PUBLIC_URL') IPG_BASE_PUBLIC_URL = config('IPG_BASE_PUBLIC_URL')
SEP_TERMINAL_ID = config('SEP_TERMINAL_ID')
SEP_TERMINAL_PASSWORD = config('SEP_TERMINAL_PASSWORD')

BIN
static/image/sep.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View file

@ -26,6 +26,9 @@
<button type="submit" class="btn mx-auto center-block" name="pna"> <button type="submit" class="btn mx-auto center-block" name="pna">
<img class="img-thumbnail" width="128" height="128" src="{% static "image/pna.png" %}"/> <img class="img-thumbnail" width="128" height="128" src="{% static "image/pna.png" %}"/>
</button> </button>
<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">#} {# <button type="submit" class="btn mx-auto center-block" name="fake">#}
{# <img class="img-thumbnail" width="128" height="128" src="{% static "image/fake.png" %}"/>#} {# <img class="img-thumbnail" width="128" height="128" src="{% static "image/fake.png" %}"/>#}
{# </button>#} {# </button>#}

86
utils/sep.py Normal file
View file

@ -0,0 +1,86 @@
import requests
from django.conf import settings
class SEPClient:
request_token_url = 'https://sep.shaparak.ir/onlinepg/onlinepg'
request_confirm_url = 'https://sep.shaparak.ir/verifyTxnRandomSessionkey/ipg/VerifyTransaction'
request_reverse_url = 'https://sep.shaparak.ir/verifyTxnRandomSessionkey/ipg/ReverseTransaction'
pg_base_url = 'https://sep.shaparak.ir/OnlinePG/SendToken?token={token}'
terminal_id = settings.SEP_TERMINAL_ID
terminal_password = settings.SEP_TERMINAL_PASSWORD
def request_token(self, amount, callback_url, order_id: int, phone_number=None):
data = {
"action": "token",
"TerminalId": self.terminal_id,
"Amount": amount,
"ResNum": order_id,
"RedirectUrl": callback_url,
"CellNumber": phone_number
}
print(data)
# success_token = {'status': 1, 'token': '38faf9c852664f3ea1ecf764f853a284'}
# failed_token = {'status': -1, 'errorCode': '5', 'errorDesc': 'پارامترهای ارسال شده نامعتبر است.; شماره ترمینال باید بزرگتر یا برابر با 1 باشد'}
response = requests.post(self.token_request_url, json=data)
print(response.json())
tmp = {}
for key, value in response.json().items():
tmp[key.lower()] = value
return tmp
def request_confirm(self, ref_number):
data = {
"RefNum": ref_number,
"TerminalNumber": self.terminal_id
}
print(data)
response_json_sample = {
"TransactionDetail": {
"RRN": "14226761817",
"RefNum": "50",
"MaskedPan": "621986****8080",
"HashedPan":
"b96a14400c3a59249e87c300ecc06e5920327e70220213b5bbb7d7b2410f7e0d",
"TerminalNumber": 2001,
"OrginalAmount": 1000,
"AffectiveAmount": 1000,
"StraceDate": "2019-09-16 18:11:06",
"StraceNo": "100428"
},
"ResultCode": 0,
"ResultDescription": "عملیات با موفقیت انجام شد",
"Success": True
}
response = requests.post(self.request_confirm_url, json=data)
print(response.json())
tmp = {}
for key, value in response.json().items():
tmp[key.lower()] = value
return tmp
def request_normal_sale_reverse(self, token):
data = {
"CorporationPin": self.corporation_pin,
"Token": token
}
response_json_sample = {
"Token": " MTIzNDU2Nzg5MTA=",
"Message": "عملیات موفق",
"Status": "0"
}
response = requests.post(self.normal_sale_confirm_url, json=data)
return response.json()