basic sep
This commit is contained in:
parent
6037330c11
commit
d684bb7083
9 changed files with 252 additions and 6 deletions
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
33
apps/pg/migrations/0006_sep.py
Normal file
33
apps/pg/migrations/0006_sep.py
Normal 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,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -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='+')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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
BIN
static/image/sep.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
|
|
@ -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
86
utils/sep.py
Normal 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()
|
||||||
Loading…
Add table
Reference in a new issue