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)
updated_at = models.DateTimeField(auto_now=True, db_index=True)
redirect_method = 'GET'
def get_pg_page_url(self):
pass
def make_request(self):
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
from utils.pna import PNAClient
from utils.sep import SEPClient
from django.utils.translation import gettext_lazy as _
class GatewayChoise(TextChoices):
FAKE = "fake", _("fake")
PNA = 'pna', _('pna')
SEP = 'sep', _('sep')
# TODO: use generic relation
@ -47,6 +50,9 @@ class PaymentRequest(models.Model):
if self.gateway == GatewayChoise.PNA:
return PNA.objects.get(payment_request=self)
if self.gateway == GatewayChoise.SEP:
return SEP.objects.get(payment_request=self)
if self.gateway == GatewayChoise.FAKE:
return Fake.objects.get(payment_request=self)
@ -60,8 +66,13 @@ class PaymentRequest(models.Model):
if 'pna' in data:
self.gateway = GatewayChoise.PNA
pna = PNA.objects.create(payment_request=self)
pna.payment_request = self
pg = PNA.objects.create(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:
raise Exception('Unknown gateway')
@ -224,6 +235,103 @@ class PNA(BasePGModel):
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):
payment_request = models.OneToOneField(PaymentRequest, on_delete=models.PROTECT, related_name='+')

View file

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

View file

@ -247,3 +247,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')
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">
<img class="img-thumbnail" width="128" height="128" src="{% static "image/pna.png" %}"/>
</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">#}
{# <img class="img-thumbnail" width="128" height="128" src="{% static "image/fake.png" %}"/>#}
{# </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()