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)
|
||||
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
|
||||
|
||||
|
|
|
|||
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
|
||||
|
||||
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='+')
|
||||
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
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">
|
||||
<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
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