diff --git a/apps/core/models.py b/apps/core/models.py index 4afbf6f..6461db4 100644 --- a/apps/core/models.py +++ b/apps/core/models.py @@ -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 diff --git a/apps/pg/migrations/0006_sep.py b/apps/pg/migrations/0006_sep.py new file mode 100644 index 0000000..f40e17d --- /dev/null +++ b/apps/pg/migrations/0006_sep.py @@ -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, + }, + ), + ] diff --git a/apps/pg/models.py b/apps/pg/models.py index 040e488..4d7faa3 100644 --- a/apps/pg/models.py +++ b/apps/pg/models.py @@ -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 = + # post = + + 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='+') diff --git a/apps/pg/urls.py b/apps/pg/urls.py index 4d3320d..febc622 100644 --- a/apps/pg/urls.py +++ b/apps/pg/urls.py @@ -6,7 +6,7 @@ app_name = "pg" urlpatterns = [ path('page/charge//', PaymentRequestView.as_view(), name='payment-request-view'), - path('page/pna_callback//', PaymentConfirmView.as_view(), name='payment-confirm-view'), + path('page/callback//', 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/', ApplicationPaymentRequestCreateApiView.as_view(), name='application-user-charge-request-create-api-view'), ] diff --git a/apps/pg/views.py b/apps/pg/views.py index 4816098..5135177 100644 --- a/apps/pg/views.py +++ b/apps/pg/views.py @@ -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: - return HttpResponseRedirect(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: diff --git a/main/settings.py b/main/settings.py index fa0e768..5632a26 100644 --- a/main/settings.py +++ b/main/settings.py @@ -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') \ No newline at end of file diff --git a/static/image/sep.png b/static/image/sep.png new file mode 100644 index 0000000..c158959 Binary files /dev/null and b/static/image/sep.png differ diff --git a/templates/pg/payment-request.html b/templates/pg/payment-request.html index dc71e4e..27bfda7 100644 --- a/templates/pg/payment-request.html +++ b/templates/pg/payment-request.html @@ -26,6 +26,9 @@ + {# #} diff --git a/utils/sep.py b/utils/sep.py new file mode 100644 index 0000000..e101e81 --- /dev/null +++ b/utils/sep.py @@ -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()