diff --git a/backend/azbankgateways/__init__.py b/backend/azbankgateways/__init__.py index 9af9f2f..ebd55fc 100644 --- a/backend/azbankgateways/__init__.py +++ b/backend/azbankgateways/__init__.py @@ -1,2 +1,7 @@ -__version__ = "v2.0.5" -default_app_config = "azbankgateways.apps.AZIranianBankGatewaysConfig" +import django + + +__version__ = "1.0.0" + +if django.VERSION < (3, 2): + default_app_config = "azbankgateways.apps.AZIranianBankGatewaysConfig" diff --git a/backend/azbankgateways/admin.py b/backend/azbankgateways/admin.py index fe26c29..7144e20 100644 --- a/backend/azbankgateways/admin.py +++ b/backend/azbankgateways/admin.py @@ -1,9 +1,9 @@ from django.contrib import admin -from utils.admin import ModelAdmin + from .models import Bank -class BankAdmin(ModelAdmin): +class BankAdmin(admin.ModelAdmin): fields = [ "pk", "status", @@ -17,7 +17,6 @@ class BankAdmin(ModelAdmin): "bank_choose_identifier", "created_at", "update_at", - 'order' ] list_display = [ "pk", @@ -32,7 +31,6 @@ class BankAdmin(ModelAdmin): "bank_choose_identifier", "created_at", "update_at", - 'order' ] list_filter = [ "status", @@ -66,7 +64,6 @@ class BankAdmin(ModelAdmin): "extra_information", "created_at", "update_at", - 'order', ] diff --git a/backend/azbankgateways/bankfactories_interface.py b/backend/azbankgateways/bankfactories_interface.py new file mode 100644 index 0000000..c87431b --- /dev/null +++ b/backend/azbankgateways/bankfactories_interface.py @@ -0,0 +1,60 @@ +from django.http import request +from azbankgateways.banks import BaseBank +from azbankgateways.models import BankType +from azbankgateways.bankfactories import BankFactory as BaseBankFactory + + +class BankFactory(BaseBankFactory): + def create( + self, + request: request, + amount: int, + callback_url : str, + mobile_number: str = None, + bank_type: BankType = None, + identifier: str = "1", + ) -> BaseBank: + bank = super().create(bank_type, identifier) + + bank = self.set_payment_info( + bank=bank, + request=request, + amount=amount, + callback_url=callback_url, + mobile_number=mobile_number, + ) + return bank + + def auto_create( + self, + request: request, + amount: int, + callback_url : str, + mobile_number: str = None, + identifier: str = "1", + ) -> BaseBank: + + bank = super().auto_create(identifier, amount) + + bank = self.set_payment_info( + bank=bank, + request=request, + amount=amount, + callback_url=callback_url, + mobile_number=mobile_number, + ) + return bank + + def set_payment_info( + self, + bank: BaseBank, + request: request, + amount: int, + callback_url : str, + mobile_number: str = None, + ): + bank.set_request(request=request) + bank.set_amount(amount=amount) + bank.set_client_callback_url(callback_url=callback_url) + bank.set_mobile_number(mobile_number=mobile_number) + return bank \ No newline at end of file diff --git a/backend/azbankgateways/banks/__init__.py b/backend/azbankgateways/banks/__init__.py index fdc85ce..0df8870 100644 --- a/backend/azbankgateways/banks/__init__.py +++ b/backend/azbankgateways/banks/__init__.py @@ -1,8 +1,31 @@ -from .bahamta import Bahamta # noqa -from .banks import BaseBank # noqa -from .bmi import BMI # noqa -from .idpay import IDPay # noqa -from .mellat import Mellat # noqa -from .sep import SEP # noqa -from .zarinpal import Zarinpal # noqa -from .zibal import Zibal # noqa +""" +This package exposes bank gateway classes. + +NOTE: +`from .banks import BaseBank` **must appear first** to avoid circular-import +issues. Other classes depend on `BaseBank`, so importing it earlier prevents +initialization-order problems. +""" + +# isort: off +from .banks import BaseBank +from .asanpardakht import AsanPardakht +from .bahamta import Bahamta +from .bmi import BMI +from .mellat import Mellat +from .sep import SEP +from .zarinpal import Zarinpal +from .zibal import Zibal + +# isort: on + +__all__ = [ + "BaseBank", + "AsanPardakht", + "Bahamta", + "BMI", + "Mellat", + "SEP", + "Zarinpal", + "Zibal", +] diff --git a/backend/azbankgateways/banks/asanpardakht.py b/backend/azbankgateways/banks/asanpardakht.py new file mode 100755 index 0000000..7be52f5 --- /dev/null +++ b/backend/azbankgateways/banks/asanpardakht.py @@ -0,0 +1,175 @@ +import json +import logging + +import requests + +from azbankgateways.banks import BaseBank +from azbankgateways.exceptions import ( + AZBankGatewaysException, + BankGatewayConnectionError, + BankGatewayRejectPayment, + SettingDoesNotExist, +) +from azbankgateways.models import BankType, CurrencyEnum, PaymentStatus + + +class AsanPardakht(BaseBank): + _merchant_configuration_id = None + _username = None + _password = None + + def __init__(self, **kwargs): + super(AsanPardakht, self).__init__(**kwargs) + + self.set_gateway_currency(CurrencyEnum.IRR) + self._token_api_url = "https://ipgrest.asanpardakht.ir/v1/Token" + self._payment_url = "https://asan.shaparak.ir" + self._verify_api_url = "https://ipgrest.asanpardakht.ir/v1/Verify" + self._local_date_api_url = "https://ipgrest.asanpardakht.ir/v1/Time" + self._transaction_result_api_url = "https://ipgrest.asanpardakht.ir/v1/TranResult" + self._settlement_api_url = "https://ipgrest.asanpardakht.ir/v1/Settlement" + + def get_bank_type(self): + return BankType.ASANPARDAKHT + + def set_default_settings(self): + required_settings = ["MERCHANT_CONFIGURATION_ID", "USERNAME", "PASSWORD"] + for item in required_settings: + if item not in self.default_setting_kwargs: + raise SettingDoesNotExist(f"{item} is not set in settings.") + + setattr(self, f"_{item.lower()}", self.default_setting_kwargs[item]) + + def get_pay_data(self): + data = { + "serviceTypeId": 1, # Service type code. For making a purchase, send code 1. + "merchantConfigurationId": self._merchant_configuration_id, + "localInvoiceId": self.get_tracking_code(), + "amountInRials": self.get_gateway_amount(), + "localDate": self._get_local_date(), + "callbackURL": self._get_gateway_callback_url() + f'&localInvoiceId={self.get_tracking_code()}', + "paymentId": self.get_tracking_code(), + **self.get_custom_data(), + } + return data + + def prepare_pay(self): + super(AsanPardakht, self).prepare_pay() + + def pay(self): + super(AsanPardakht, self).pay() + data = self.get_pay_data() + token = self._send_request(self._token_api_url, data) + if token: + self._set_reference_number(token) + else: + status_text = "Failed to retrieve token from Asan Pardakht" + self._set_transaction_status_text(status_text) + logging.critical(status_text) + raise BankGatewayRejectPayment(self.get_transaction_status_text()) + + def _get_gateway_payment_url_parameter(self): + return self._payment_url + + def _get_gateway_payment_method_parameter(self): + return "POST" + + def _get_gateway_payment_parameter(self): + params = { + "RefId": self.get_reference_number(), + } + return params + + def prepare_verify_from_gateway(self): + super(AsanPardakht, self).prepare_verify_from_gateway() + request = self.get_request() + tracking_code = request.GET.get("localInvoiceId") + self._set_tracking_code(tracking_code) + self._set_bank_record() + self._check_transaction_data() + + def verify_from_gateway(self, request): + super(AsanPardakht, self).verify_from_gateway(request) + + def get_verify_data(self): + data = { + "merchantConfigurationId": self._merchant_configuration_id, + "payGateTranId": self._get_pay_gate_tran_id(), + } + return data + + def prepare_verify(self, tracking_code): + super(AsanPardakht, self).prepare_verify(tracking_code) + + def verify(self, transaction_code): + super(AsanPardakht, self).verify(transaction_code) + data = self.get_verify_data() + self._send_request(self._verify_api_url, data, is_json=False) + self._set_payment_status(PaymentStatus.COMPLETE) + self._settle_transaction() + + def _send_request(self, api_url, data, method='POST', is_json=True): + headers = { + "usr": self._username, + "pwd": self._password, + } + try: + response = requests.request( + method, api_url, json=data, headers=headers, timeout=self.get_timeout() + ) + response.raise_for_status() + except requests.Timeout: + logging.exception(f"Asan Pardakht gateway timeout: {data}") + raise BankGatewayConnectionError() + except requests.ConnectionError: + logging.exception(f"Asan Pardakht gateway connection error: {data}") + raise BankGatewayConnectionError() + except requests.HTTPError as e: + logging.exception(f"HTTP error occurred: {e}") + raise BankGatewayConnectionError() + if is_json: + return response.json() + return response.text + + def _get_local_date(self): + return self._send_request(self._local_date_api_url, {}, method='GET') + + def _get_transaction_data(self): + data = { + 'merchantConfigurationId': self._merchant_configuration_id, + 'localInvoiceId': self.get_tracking_code(), + } + return self._send_request(self._transaction_result_api_url, data, method='GET') + + def _check_transaction_data(self): + transaction_data = self._get_transaction_data() + is_valid = ( + transaction_data + and self._bank.reference_number == transaction_data.get('refID') + and transaction_data.get('amount') is not None + and int(self._bank.amount) == transaction_data.get('amount') + ) + if not is_valid: + error_message = ( + "Transaction data validation failed. The reference number or the amount " + "received from the gateway does not match the internal bank record." + ) + raise AZBankGatewaysException(error_message) + self._set_pay_gate_tran_id(transaction_data) + + def _settle_transaction(self): + try: + data = { + "merchantConfigurationId": self._merchant_configuration_id, + "payGateTranId": self._get_pay_gate_tran_id(), + } + self._send_request(self._settlement_api_url, data=data, is_json=False) + except Exception: + logging.debug("AsanPardakht gateway did not settle the payment") + + def _set_pay_gate_tran_id(self, transaction_data): + self._bank.extra_information = json.dumps({'payGateTranID': transaction_data.get('payGateTranID')}) + self._bank.save(update_fields={'extra_information'}) + + def _get_pay_gate_tran_id(self): + return json.loads(self._bank.extra_information)['payGateTranID'] diff --git a/backend/azbankgateways/banks/bahamta.py b/backend/azbankgateways/banks/bahamta.py index 20e46e7..5d173f9 100644 --- a/backend/azbankgateways/banks/bahamta.py +++ b/backend/azbankgateways/banks/bahamta.py @@ -58,6 +58,7 @@ class Bahamta(BaseBank): "payer_mobile": self.get_mobile_number(), "callback_url": self._get_gateway_callback_url(), } + data.update(self.get_custom_data()) return data def prepare_pay(self): @@ -70,7 +71,9 @@ class Bahamta(BaseBank): if response_json["ok"]: # در این سیستم رفرنس برای ذخیره سازی بر نمی گردد! token = self.get_tracking_code() - self._payment_url, self._params = split_to_dict_querystring(response_json["result"]["payment_url"]) + self._payment_url, self._params = split_to_dict_querystring( + response_json["result"]["payment_url"] + ) self._set_reference_number(token) else: logging.critical("Bahamta gateway reject payment") @@ -82,7 +85,7 @@ class Bahamta(BaseBank): def prepare_verify_from_gateway(self): super(Bahamta, self).prepare_verify_from_gateway() - token = self.get_request().GET.get("reference", None) + token = self.get_request().GET.get("reference") self._set_reference_number(token) self._set_bank_record() @@ -109,7 +112,7 @@ class Bahamta(BaseBank): super(Bahamta, self).verify(transaction_code) data = self.get_verify_data() response_json = self._send_data(self._verify_api_url, data) - if response_json.get("ok", False) and response_json.get("result", {}).get("state", None) == "paid": + if response_json.get("ok", False) and response_json.get("result", {}).get("state") == "paid": self._set_payment_status(PaymentStatus.COMPLETE) extra_information = json.dumps(response_json.get("result", {})) self._bank.extra_information = extra_information @@ -121,7 +124,7 @@ class Bahamta(BaseBank): def _send_data(self, api, data): try: url = append_querystring(api, data) - response = requests.get(url, timeout=5) + response = requests.get(url, timeout=self.get_timeout()) except requests.Timeout: logging.exception("Bahamta time out gateway {}".format(data)) raise BankGatewayConnectionError() diff --git a/backend/azbankgateways/banks/banks.py b/backend/azbankgateways/banks/banks.py index e1956a5..ca58782 100644 --- a/backend/azbankgateways/banks/banks.py +++ b/backend/azbankgateways/banks/banks.py @@ -4,11 +4,14 @@ import uuid from urllib import parse import six +from django.conf import settings as django_settings from django.db.models import Q from django.shortcuts import redirect from django.urls import reverse from django.utils import timezone +from azbankgateways.utils import append_querystring, build_full_url + from .. import default_settings as settings from ..exceptions import ( AmountDoesNotSupport, @@ -18,7 +21,6 @@ from ..exceptions import ( SafeSettingsEnabled, ) from ..models import Bank, CurrencyEnum, PaymentStatus -from ..utils import append_querystring # TODO: handle and expire record after 15 minutes @@ -41,8 +43,12 @@ class BaseBank: def __init__(self, identifier: str, **kwargs): self.identifier = identifier self.default_setting_kwargs = kwargs + self._custom_data: dict = {} self.set_default_settings() + def _is_strict_origin_policy_enabled(self): + return django_settings.SECURE_REFERRER_POLICY == 'strict-origin-when-cross-origin' + @abc.abstractmethod def set_default_settings(self): """default setting, like fetch merchant code, terminal id and etc""" @@ -162,6 +168,13 @@ class BaseBank: def get_mobile_number(self): return self._mobile_number + def set_custom_data(self, data: dict): + """تنظیم قابلیت های سفارشی برای درگاه""" + self._custom_data = data + + def get_custom_data(self): + return self._custom_data + def set_client_callback_url(self, callback_url): """ذخیره کال بک از طریق نرم افزار برای بازگردانی کاربر پس از بازگشت درگاه بانک به پکیج و سپس از پکیج به نرم افزار.""" @@ -187,7 +200,10 @@ class BaseBank: def _set_bank_record(self): try: self._bank = Bank.objects.get( - Q(Q(reference_number=self.get_reference_number()) | Q(tracking_code=self.get_tracking_code())), + Q( + Q(reference_number=self.get_reference_number()) + | Q(tracking_code=self.get_tracking_code()) + ), Q(bank_type=self.get_bank_type()), ) logging.debug("Set reference find bank object.") @@ -216,7 +232,10 @@ class BaseBank: return self._transaction_status_text def _set_payment_status(self, payment_status): - if payment_status == PaymentStatus.RETURN_FROM_BANK and self._bank.status != PaymentStatus.REDIRECT_TO_BANK: + if ( + payment_status == PaymentStatus.RETURN_FROM_BANK + and self._bank.status != PaymentStatus.REDIRECT_TO_BANK + ): logging.debug( "Payment status is not status suitable.", extra={"status": self._bank.status}, @@ -247,6 +266,10 @@ class BaseBank: def get_currency(self): return self._currency + @staticmethod + def get_timeout(): + return settings.BANK_TIMEOUT + def get_gateway_amount(self): return self._gateway_amount @@ -357,8 +380,8 @@ class BaseBank: return redirect_url def _get_gateway_callback_url(self): - url = reverse(settings.CALLBACK_NAMESPACE) if self.get_request(): + url = reverse(settings.CALLBACK_NAMESPACE) url_parts = list(parse.urlparse(url)) if not (url_parts[0] and url_parts[1]): url = self.get_request().build_absolute_uri(url) @@ -366,5 +389,6 @@ class BaseBank: query.update({"bank_type": self.get_bank_type()}) query.update({"identifier": self.identifier}) url = append_querystring(url, query) - + else: + url = build_full_url(settings.CALLBACK_NAMESPACE) return url diff --git a/backend/azbankgateways/banks/bmi.py b/backend/azbankgateways/banks/bmi.py index 7ec4373..6d6f4f6 100644 --- a/backend/azbankgateways/banks/bmi.py +++ b/backend/azbankgateways/banks/bmi.py @@ -22,6 +22,12 @@ class BMI(BaseBank): def __init__(self, **kwargs): super(BMI, self).__init__(**kwargs) + if not self._is_strict_origin_policy_enabled(): + raise SettingDoesNotExist( + "SECURE_REFERRER_POLICY is not set to 'strict-origin-when-cross-origin'" + " in django setting, it's mandatory for BMI gateway" + ) + self.set_gateway_currency(CurrencyEnum.IRR) self._token_api_url = "https://sadad.shaparak.ir/vpg/api/v0/Request/PaymentRequest" self._payment_url = "https://sadad.shaparak.ir/VPG/Purchase" @@ -54,6 +60,7 @@ class BMI(BaseBank): "OrderId": self.get_tracking_code(), "AdditionalData": "oi:%s-ou:%s" % (self.get_tracking_code(), self.get_mobile_number()), } + data.update(self.get_custom_data()) return data def prepare_pay(self): @@ -63,7 +70,7 @@ class BMI(BaseBank): super(BMI, self).pay() data = self.get_pay_data() response_json = self._send_data(self._token_api_url, data) - if response_json["ResCode"] == "0": + if str(response_json["ResCode"]) == "0": token = response_json["Token"] self._set_reference_number(token) else: @@ -99,10 +106,11 @@ class BMI(BaseBank): super(BMI, self).verify(transaction_code) data = self.get_verify_data() response_json = self._send_data(self._verify_api_url, data) - if response_json["ResCode"] == "0": + if str(response_json["ResCode"]) == "0": self._set_payment_status(PaymentStatus.COMPLETE) extra_information = ( - f"RetrivalRefNo={response_json['RetrivalRefNo']},SystemTraceNo={response_json['SystemTraceNo']}" + f"RetrivalRefNo={response_json['RetrivalRefNo']}" + ",SystemTraceNo={response_json['SystemTraceNo']}" ) self._bank.extra_information = extra_information self._bank.save() @@ -113,10 +121,13 @@ class BMI(BaseBank): def prepare_verify_from_gateway(self): super(BMI, self).prepare_verify_from_gateway() request = self.get_request() - for method in ["POST", "GET", "data", "PUT"]: - token = getattr(request, method, {}).get("token", None) - if token: + method_data = getattr(request, "POST", {}) + token = None + for key, value in method_data.items(): + if key.lower() == "token": + token = value break + if not token: raise BankGatewayStateInvalid self._set_reference_number(token) @@ -142,7 +153,7 @@ class BMI(BaseBank): def _send_data(self, api, data): try: - response = requests.post(api, json=data, timeout=5) + response = requests.post(api, json=data, timeout=self.get_timeout()) except requests.Timeout: logging.exception("BMI time out gateway {}".format(data)) raise BankGatewayConnectionError() diff --git a/backend/azbankgateways/banks/idpay.py b/backend/azbankgateways/banks/idpay.py deleted file mode 100644 index 0388066..0000000 --- a/backend/azbankgateways/banks/idpay.py +++ /dev/null @@ -1,141 +0,0 @@ -import json -import logging - -import requests - -from azbankgateways.banks import BaseBank -from azbankgateways.exceptions import BankGatewayConnectionError, SettingDoesNotExist -from azbankgateways.exceptions.exceptions import BankGatewayRejectPayment -from azbankgateways.models import BankType, CurrencyEnum, PaymentStatus -from azbankgateways.utils import get_json, split_to_dict_querystring - - -class IDPay(BaseBank): - _merchant_code = None - _method = None - _x_sandbox = None - _payment_url = None - _params = {} - - def __init__(self, **kwargs): - super(IDPay, self).__init__(**kwargs) - self.set_gateway_currency(CurrencyEnum.IRR) - self._token_api_url = "https://api.idpay.ir/v1.1/payment" - self._verify_api_url = "https://api.idpay.ir/v1.1/payment/verify" - - def get_bank_type(self): - return BankType.IDPAY - - def set_default_settings(self): - for item in ["MERCHANT_CODE", "METHOD", "X_SANDBOX"]: - if item not in self.default_setting_kwargs: - raise SettingDoesNotExist() - setattr(self, f"_{item.lower()}", self.default_setting_kwargs[item]) - - self._x_sandbox = str(self._x_sandbox) - - """ - gateway - """ - - def _get_gateway_payment_url_parameter(self): - return self._payment_url - - def _get_gateway_payment_parameter(self): - params = {} - params.update(self._params) - return params - - def _get_gateway_payment_method_parameter(self): - return "GET" - - """ - pay - """ - - def get_pay_data(self): - data = { - "order_id": self.get_tracking_code(), - "amount": self.get_gateway_amount(), - "phone": self.get_mobile_number(), - "callback": self._get_gateway_callback_url(), - } - return data - - def prepare_pay(self): - super(IDPay, self).prepare_pay() - - def pay(self): - super(IDPay, self).pay() - data = self.get_pay_data() - response_json = self._send_data(self._token_api_url, data) - if "id" in response_json and "link" in response_json and response_json["link"] and response_json["id"]: - token = response_json["id"] - self._payment_url, self._params = split_to_dict_querystring(response_json["link"]) - self._set_reference_number(token) - else: - logging.critical("IDPay gateway reject payment") - raise BankGatewayRejectPayment(self.get_transaction_status_text()) - - """ - verify gateway - """ - - def prepare_verify_from_gateway(self): - super(IDPay, self).prepare_verify_from_gateway() - for method in ["GET", "POST", "data"]: - token = getattr(self.get_request(), method).get("id", None) - if token: - self._set_reference_number(token) - self._set_bank_record() - break - - def verify_from_gateway(self, request): - super(IDPay, self).verify_from_gateway(request) - - """ - verify - """ - - def get_verify_data(self): - super(IDPay, self).get_verify_data() - data = { - "id": self.get_reference_number(), - "order_id": self.get_tracking_code(), - } - return data - - def prepare_verify(self, tracking_code): - super(IDPay, self).prepare_verify(tracking_code) - - def verify(self, transaction_code): - super(IDPay, self).verify(transaction_code) - data = self.get_verify_data() - response_json = self._send_data(self._verify_api_url, data, timeout=10) - if response_json.get("verify", {}).get("date", None): - self._set_payment_status(PaymentStatus.COMPLETE) - extra_information = json.dumps(response_json) - self._bank.extra_information = extra_information - self._bank.save() - else: - self._set_payment_status(PaymentStatus.CANCEL_BY_USER) - logging.debug("IDPay gateway unapprove payment") - - def _send_data(self, api, data, timeout=5): - headers = { - "X-API-KEY": self._merchant_code, - "X-SANDBOX": self._x_sandbox, - } - try: - response = requests.post(api, headers=headers, json=data, timeout=timeout) - except requests.Timeout: - logging.exception("IDPay time out gateway {}".format(data)) - raise BankGatewayConnectionError() - except requests.ConnectionError: - logging.exception("IDPay time out gateway {}".format(data)) - raise BankGatewayConnectionError() - - response_json = get_json(response) - if "error_message" in response_json: - self._set_transaction_status_text(response_json["error_message"]) - return response_json diff --git a/backend/azbankgateways/banks/irandargah.py b/backend/azbankgateways/banks/irandargah.py new file mode 100644 index 0000000..22ed0bc --- /dev/null +++ b/backend/azbankgateways/banks/irandargah.py @@ -0,0 +1,115 @@ +import logging + +import requests + +from azbankgateways.banks import BaseBank +from azbankgateways.exceptions import SettingDoesNotExist +from azbankgateways.exceptions.exceptions import ( + BankGatewayConnectionError, + BankGatewayRejectPayment, +) +from azbankgateways.models import BankType, CurrencyEnum, PaymentStatus +from azbankgateways.utils import get_json + + +class IranDargah(BaseBank): + _merchant_code = None + _sandbox = False + + def __init__(self, **kwargs): + kwargs.setdefault("SANDBOX", 0) + super().__init__(**kwargs) + self.set_gateway_currency(CurrencyEnum.IRR) + self._sandbox = kwargs.get("SANDBOX", 0) == 1 + + base_url = "https://dargaah.ir" + if self._sandbox: + base_url += "/sandbox" + + self._payment_url = f"{base_url}/payment" + self._startpay_url = f"{base_url}/ird/startpay/" + self._verify_url = f"{base_url}/verification" + + def get_bank_type(self): + return BankType.IRANDARGAH + + def set_default_settings(self): + for item in ["MERCHANT_CODE"]: + if item not in self.default_setting_kwargs: + raise SettingDoesNotExist(f"{item} not in settings") + setattr(self, f"_{item.lower()}", self.default_setting_kwargs[item]) + + def _get_gateway_payment_url_parameter(self): + return f"{self._startpay_url}{self.get_reference_number()}" + + def _get_gateway_payment_parameter(self): + return {} + + def _get_gateway_payment_method_parameter(self): + return "GET" + + def get_pay_data(self): + return { + "merchantID": self._merchant_code, + "amount": self.get_gateway_amount(), + "callbackURL": self._get_gateway_callback_url(), + "orderId": str(self.get_tracking_code()), + **self.get_custom_data(), + } + + def prepare_pay(self): + super().prepare_pay() + + def pay(self): + super().pay() + data = self.get_pay_data() + result = self._send_data(api=self._payment_url, data=data) + if result["status"] == 200: + self._set_reference_number(result["authority"]) + else: + logging.critical("IranDargah reject payment: %s", result.get("message")) + raise BankGatewayRejectPayment(self.get_transaction_status_text()) + + def prepare_verify_from_gateway(self): + super().prepare_verify_from_gateway() + authority = self.get_request().POST.get("authority") + self._set_reference_number(authority) + self._set_bank_record() + + def verify_from_gateway(self, request): + super().verify_from_gateway(request) + + def get_verify_data(self): + return { + "merchantID": self._merchant_code, + "authority": self.get_reference_number(), + "amount": self.get_gateway_amount(), + "orderId": str(self.get_tracking_code()), + } + + def prepare_verify(self, tracking_code): + super().prepare_verify(tracking_code) + + def verify(self, transaction_code): + super().verify(transaction_code) + data = self.get_verify_data() + result = self._send_data(api=self._verify_url, data=data) + + if result.get("status") in [100, 101]: + self._set_payment_status(PaymentStatus.COMPLETE) + else: + self._set_payment_status(PaymentStatus.CANCEL_BY_USER) + logging.debug("IranDargah verify failed: %s", result.get("message")) + + def _send_data(self, api, data): + try: + response = requests.post(api, json=data, timeout=self.get_timeout()) + response.raise_for_status() + except requests.RequestException as e: + logging.exception("IranDargah connection error: %s", e) + raise BankGatewayConnectionError() + + result = get_json(response) + msg = result.get("message", "no message") + self._set_transaction_status_text(msg) + return result diff --git a/backend/azbankgateways/banks/mellat.py b/backend/azbankgateways/banks/mellat.py index cda82a3..433df69 100644 --- a/backend/azbankgateways/banks/mellat.py +++ b/backend/azbankgateways/banks/mellat.py @@ -68,6 +68,7 @@ class Mellat(BaseBank): "callBackUrl": self._get_gateway_callback_url(), "payerId": 0, } + data.update(self.get_custom_data()) return data def prepare_pay(self): @@ -163,9 +164,7 @@ class Mellat(BaseBank): status_text = "Payment ID is incorrect" elif response == "414": status_text = "The organization issuing the bill is invalid" - elif response == "415": - status_text = "The working session has ended" - elif response == "416": + elif response in ["415", "416"]: status_text = "The working session has ended" elif response == "417": status_text = "Payer ID is invalid" @@ -187,12 +186,12 @@ class Mellat(BaseBank): def prepare_verify_from_gateway(self): super(Mellat, self).prepare_verify_from_gateway() post = self.get_request().POST - token = post.get("RefId", None) + token = post.get("RefId") if not token: return self._set_reference_number(token) self._set_bank_record() - self._bank.extra_information = dumps(dict(zip(post.keys(), post.values()))) + self._bank.extra_information = dumps(dict(post.items())) self._bank.save() def verify_from_gateway(self, request): @@ -250,7 +249,7 @@ class Mellat(BaseBank): @staticmethod def _get_client(): - transport = Transport(timeout=5, operation_timeout=5) + transport = Transport(timeout=Mellat.get_timeout(), operation_timeout=Mellat.get_timeout()) client = Client("https://bpm.shaparak.ir/pgwchannel/services/pgw?wsdl", transport=transport) return client diff --git a/backend/azbankgateways/banks/payV1.py b/backend/azbankgateways/banks/payV1.py deleted file mode 100644 index 2dce312..0000000 --- a/backend/azbankgateways/banks/payV1.py +++ /dev/null @@ -1,152 +0,0 @@ -import json -import logging - -import requests - -from azbankgateways.banks import BaseBank -from azbankgateways.default_settings import TRACKING_CODE_QUERY_PARAM -from azbankgateways.exceptions import BankGatewayConnectionError, SettingDoesNotExist -from azbankgateways.exceptions.exceptions import ( - BankGatewayRejectPayment, - BankGatewayStateInvalid, -) -from azbankgateways.models import BankType, CurrencyEnum, PaymentStatus - - -class PayV1(BaseBank): - _merchant_code = None - _x_sandbox = None - - def __init__(self, **kwargs): - super(PayV1, self).__init__(**kwargs) - self.set_gateway_currency(CurrencyEnum.IRR) - self._token_api_url = "https://pay.ir/pg/send" - self._payment_url = "https://pay.ir/pg/{}" - self._verify_api_url = "https://pay.ir/pg/verify" - - def get_bank_type(self): - return BankType.PAYV1 - - def set_default_settings(self): - for item in ["MERCHANT_CODE", "X_SANDBOX"]: - if item not in self.default_setting_kwargs: - raise SettingDoesNotExist() - setattr(self, f"_{item.lower()}", self.default_setting_kwargs[item]) - - self._merchant_code = self._merchant_code if not self._x_sandbox else "test" - - """ - gateway - """ - - def _get_gateway_payment_url_parameter(self): - return self._payment_url.format(self._reference_number) - - def _get_gateway_payment_parameter(self): - return {} - - def _get_gateway_payment_method_parameter(self): - return "GET" - - """ - pay - """ - - def get_pay_data(self): - data = { - "api": self._merchant_code, - "amount": self.get_gateway_amount(), - "redirect": self._get_gateway_callback_url(), - "mobile": self.get_mobile_number(), - "factorNumber": self.get_tracking_code(), - } - return data - - def prepare_pay(self): - super(PayV1, self).prepare_pay() - - def pay(self): - super(PayV1, self).pay() - data = self.get_pay_data() - response = self._send_data(self._token_api_url, data) - response_json = response.json() - if response.status_code == 200 and int(response_json["status"]) == 1: - token = response_json["token"] - self._set_reference_number(token) - else: - logging.critical( - "PayV1 gateway reject payment with error code {0} and status code {1}".format( - response_json["errorCode"], response.status_code - ) - ) - raise BankGatewayRejectPayment(self.get_transaction_status_text()) - - """ - verify gateway - """ - - def prepare_verify_from_gateway(self): - super(PayV1, self).prepare_verify_from_gateway() - for method in ["GET", "POST", "data"]: - token = getattr(self.get_request(), method).get(TRACKING_CODE_QUERY_PARAM, None) - if token: - self._set_reference_number(token) - self._set_bank_record() - break - else: - raise BankGatewayStateInvalid - - def verify_from_gateway(self, request): - super(PayV1, self).verify_from_gateway(request) - - """ - verify - """ - - def get_verify_data(self): - super(PayV1, self).get_verify_data() - data = { - "api": self._merchant_code(), - "token": self.get_reference_number(), - } - return data - - def prepare_verify(self, tracking_code): - super(PayV1, self).prepare_verify(tracking_code) - - def verify(self, tracking_code): - super(PayV1, self).verify(tracking_code) - - data = self.get_verify_data() - response = self._send_data(self._verify_api_url, data, timeout=10) - response_json = response.json() - status = PaymentStatus.COMPLETE - if int(response_json["status"]) != 1: - if int(response_json["errorCode"]) == -5: - status = PaymentStatus.ERROR - elif int(response_json["errorCode"]) == -9: - status = PaymentStatus.EXPIRE_VERIFY_PAYMENT - elif int(response_json["errorCode"]) == -15: - status = PaymentStatus.CANCEL_BY_USER - elif int(response_json["errorCode"]) == -27: - status = PaymentStatus.RETURN_FROM_BANK - else: - status = PaymentStatus.ERROR - - self._set_payment_status(status) - extra_information = json.dumps(response_json) - self._bank.extra_information = extra_information - self._bank.save() - - def _send_data(self, url, data, timeout=5) -> requests.post: - try: - logging.debug("Sending POST request to {} with data {}".format(url, data)) - response = requests.post(url, json=data, timeout=timeout) - except requests.Timeout: - logging.exception("PayV1 time out gateway {}".format(data)) - raise BankGatewayConnectionError() - except requests.ConnectionError: - logging.exception("PayV1 time out gateway {}".format(data)) - raise BankGatewayConnectionError() - - return response diff --git a/backend/azbankgateways/banks/sep.py b/backend/azbankgateways/banks/sep.py index 7b5d9fd..928c1f2 100644 --- a/backend/azbankgateways/banks/sep.py +++ b/backend/azbankgateways/banks/sep.py @@ -1,7 +1,6 @@ import logging import requests -from zeep import Client, Transport from azbankgateways.banks import BaseBank from azbankgateways.exceptions import BankGatewayConnectionError, SettingDoesNotExist @@ -16,10 +15,16 @@ class SEP(BaseBank): def __init__(self, **kwargs): super(SEP, self).__init__(**kwargs) + if not self._is_strict_origin_policy_enabled(): + raise SettingDoesNotExist( + "SECURE_REFERRER_POLICY is not set to 'strict-origin-when-cross-origin' in django setting," + " it's mandatory for Saman gateway" + ) + self.set_gateway_currency(CurrencyEnum.IRR) - self._token_api_url = "https://sep.shaparak.ir/MobilePG/MobilePayment" + self._token_api_url = "https://sep.shaparak.ir/onlinepg/onlinepg" self._payment_url = "https://sep.shaparak.ir/OnlinePG/OnlinePG" - self._verify_api_url = "https://verify.sep.ir/Payments/ReferencePayment.asmx?WSDL" + self._verify_api_url = "https://sep.shaparak.ir/verifyTxnRandomSessionkey/ipg/VerifyTransaction" def get_bank_type(self): return BankType.SEP @@ -32,14 +37,14 @@ class SEP(BaseBank): def get_pay_data(self): data = { - "Action": "Token", + "action": "Token", "Amount": self.get_gateway_amount(), - "Wage": 0, "TerminalId": self._merchant_code, "ResNum": self.get_tracking_code(), "RedirectURL": self._get_gateway_callback_url(), "CellNumber": self.get_mobile_number(), } + data.update(self.get_custom_data()) return data def prepare_pay(self): @@ -80,15 +85,15 @@ class SEP(BaseBank): def prepare_verify_from_gateway(self): super(SEP, self).prepare_verify_from_gateway() request = self.get_request() - tracking_code = request.GET.get("ResNum", None) - token = request.GET.get("Token", None) + tracking_code = request.GET.get("ResNum") + token = request.GET.get("Token") self._set_tracking_code(tracking_code) self._set_bank_record() - ref_num = request.GET.get("RefNum", None) + ref_num = request.GET.get("RefNum") if request.GET.get("State", "NOK") == "OK" and ref_num: self._set_reference_number(ref_num) self._bank.reference_number = ref_num - extra_information = f"TRACENO={request.GET.get('TRACENO', None)}, RefNum={ref_num}, Token={token}" + extra_information = f"TRACENO={request.GET.get('TRACENO')}, RefNum={ref_num}, Token={token}" self._bank.extra_information = extra_information self._bank.save() @@ -101,8 +106,7 @@ class SEP(BaseBank): def get_verify_data(self): super(SEP, self).get_verify_data() - data = self.get_reference_number(), self._merchant_code - return data + return {"RefNum": self.get_reference_number(), "TerminalNumber": self._merchant_code} def prepare_verify(self, tracking_code): super(SEP, self).prepare_verify(tracking_code) @@ -110,9 +114,8 @@ class SEP(BaseBank): def verify(self, transaction_code): super(SEP, self).verify(transaction_code) data = self.get_verify_data() - client = self._get_client(self._verify_api_url) - result = client.service.verifyTransaction(*data) - if result == self.get_gateway_amount(): + result = self._send_data(api=self._verify_api_url, data=data) + if result.get('ResultCode') == 0: self._set_payment_status(PaymentStatus.COMPLETE) else: self._set_payment_status(PaymentStatus.CANCEL_BY_USER) @@ -120,7 +123,7 @@ class SEP(BaseBank): def _send_data(self, api, data): try: - response = requests.post(api, json=data, timeout=5) + response = requests.post(api, json=data, timeout=self.get_timeout()) except requests.Timeout: logging.exception("SEP time out gateway {}".format(data)) raise BankGatewayConnectionError() @@ -131,16 +134,3 @@ class SEP(BaseBank): response_json = get_json(response) self._set_transaction_status_text(response_json.get("errorDesc")) return response_json - - @staticmethod - def _get_client(url): - headers = { - "Accept": "*/*", - "Accept-Encoding": "gzip, deflate, br", - "Connection": "keep-alive", - "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0", - } - transport = Transport(timeout=5, operation_timeout=5) - transport.session.headers = headers - client = Client(url, transport=transport) - return client diff --git a/backend/azbankgateways/banks/zarinpal.py b/backend/azbankgateways/banks/zarinpal.py index 4684f09..8ef3557 100644 --- a/backend/azbankgateways/banks/zarinpal.py +++ b/backend/azbankgateways/banks/zarinpal.py @@ -1,11 +1,15 @@ import logging -from zeep import Client, Transport +import requests from azbankgateways.banks import BaseBank from azbankgateways.exceptions import SettingDoesNotExist -from azbankgateways.exceptions.exceptions import BankGatewayRejectPayment +from azbankgateways.exceptions.exceptions import ( + BankGatewayConnectionError, + BankGatewayRejectPayment, +) from azbankgateways.models import BankType, CurrencyEnum, PaymentStatus +from azbankgateways.utils import get_json class Zarinpal(BaseBank): @@ -16,8 +20,12 @@ class Zarinpal(BaseBank): kwargs.setdefault("SANDBOX", 0) super(Zarinpal, self).__init__(**kwargs) self.set_gateway_currency(CurrencyEnum.IRT) - self._payment_url = "https://www.zarinpal.com/pg/StartPay/{}/ZarinGate" - self._sandbox_url = "https://sandbox.zarinpal.com/pg/StartPay/{}/ZarinGate" + self._payment_type = 'payment' + if self._sandbox: + self._payment_type = 'sandbox' + self._payment_url = f"https://{self._payment_type}.zarinpal.com/pg/v4/payment/request.json" + self._startpay_url = f"https://{self._payment_type}.zarinpal.com/pg/StartPay/" + self._verify_url = f"https://{self._payment_type}.zarinpal.com/pg/v4/payment/verify.json" def get_bank_type(self): return BankType.ZARINPAL @@ -37,9 +45,7 @@ class Zarinpal(BaseBank): return 1000 def _get_gateway_payment_url_parameter(self): - if self._sandbox: - return self._sandbox_url.format(self.get_reference_number()) - return self._payment_url.format(self.get_reference_number()) + return self._startpay_url + "{}".format(self.get_reference_number()) def _get_gateway_payment_parameter(self): return {} @@ -54,14 +60,19 @@ class Zarinpal(BaseBank): def get_pay_data(self): description = "خرید با شماره پیگیری - {}".format(self.get_tracking_code()) - return { - "Description": description, - "MerchantID": self._merchant_code, - "Amount": self.get_gateway_amount(), - "Email": None, - "Mobile": self.get_mobile_number(), - "CallbackURL": self._get_gateway_callback_url(), + data = { + "description": description, + "merchant_id": self._merchant_code, + "amount": self.get_gateway_amount(), + "currency": self.get_gateway_currency(), + "metadata": {}, + "callback_url": self._get_gateway_callback_url(), } + mobile_number = self.get_mobile_number() + if mobile_number: + data["metadata"].update({"mobile": mobile_number}) + data.update(self.get_custom_data()) + return data def prepare_pay(self): super(Zarinpal, self).prepare_pay() @@ -69,10 +80,9 @@ class Zarinpal(BaseBank): def pay(self): super(Zarinpal, self).pay() data = self.get_pay_data() - client = self._get_client() - result = client.service.PaymentRequest(**data) - if result.Status == 100: - token = result.Authority + result = self._send_data(api=self._payment_url, data=data) + if result['data']: + token = result['data']['authority'] self._set_reference_number(token) else: logging.critical("Zarinpal gateway reject payment") @@ -84,7 +94,7 @@ class Zarinpal(BaseBank): def prepare_verify_from_gateway(self): super(Zarinpal, self).prepare_verify_from_gateway() - token = self.get_request().GET.get("Authority", None) + token = self.get_request().GET.get("Authority") self._set_reference_number(token) self._set_bank_record() @@ -98,9 +108,9 @@ class Zarinpal(BaseBank): def get_verify_data(self): super(Zarinpal, self).get_verify_data() return { - "MerchantID": self._merchant_code, - "Authority": self.get_reference_number(), - "Amount": self.get_gateway_amount(), + "merchant_id": self._merchant_code, + "authority": self.get_reference_number(), + "amount": self.get_gateway_amount(), } def prepare_verify(self, tracking_code): @@ -109,27 +119,26 @@ class Zarinpal(BaseBank): def verify(self, transaction_code): super(Zarinpal, self).verify(transaction_code) data = self.get_verify_data() - client = self._get_client(timeout=10) - try: - result = client.service.PaymentVerification(**data) - if result.Status in [100, 101]: - self._set_payment_status(PaymentStatus.COMPLETE) - else: - self._set_payment_status(PaymentStatus.CANCEL_BY_USER) - logging.debug("Zarinpal gateway unapprove payment") - except: + result = self._send_data(api=self._verify_url, data=data) + if result['data'] and result['data']['code'] in [100, 101]: + self._set_payment_status(PaymentStatus.COMPLETE) + else: self._set_payment_status(PaymentStatus.CANCEL_BY_USER) logging.debug("Zarinpal gateway unapprove payment") - def _get_client(self, timeout=5): - transport = Transport(timeout=timeout, operation_timeout=timeout) - if self._sandbox: - return Client( - "https://sandbox.zarinpal.com/pg/services/WebGate/wsdl", - transport=transport, - ) + def _send_data(self, api, data): + try: + response = requests.post(api, json=data, timeout=self.get_timeout()) + except requests.Timeout: + logging.exception("ZARINPAL time out gateway {}".format(data)) + raise BankGatewayConnectionError() + except requests.ConnectionError: + logging.exception("ZARINPAL time out gateway {}".format(data)) + raise BankGatewayConnectionError() - return Client( - "https://www.zarinpal.com/pg/services/WebGate/wsdl", - transport=transport, - ) + response_json = get_json(response) + if response_json['data']: + self._set_transaction_status_text(response_json['data']['message']) + else: + self._set_transaction_status_text(response_json['errors']['message']) + return response_json diff --git a/backend/azbankgateways/banks/zibal.py b/backend/azbankgateways/banks/zibal.py index fb41d85..58bb638 100644 --- a/backend/azbankgateways/banks/zibal.py +++ b/backend/azbankgateways/banks/zibal.py @@ -55,6 +55,7 @@ class Zibal(BaseBank): "orderId": self.get_tracking_code(), "mobile": self.get_mobile_number(), } + data.update(self.get_custom_data()) return data def prepare_pay(self): @@ -77,7 +78,7 @@ class Zibal(BaseBank): def prepare_verify_from_gateway(self): super(Zibal, self).prepare_verify_from_gateway() - token = self.get_request().GET.get("trackId", None) + token = self.get_request().GET.get("trackId") self._set_reference_number(token) self._set_bank_record() @@ -114,7 +115,7 @@ class Zibal(BaseBank): def _send_data(self, api, data): try: - response = requests.post(api, json=data, timeout=5) + response = requests.post(api, json=data, timeout=self.get_timeout()) except requests.Timeout: logging.exception("Zibal time out gateway {}".format(data)) raise BankGatewayConnectionError() diff --git a/backend/azbankgateways/default_settings.py b/backend/azbankgateways/default_settings.py old mode 100644 new mode 100755 index f0436dd..8a40980 --- a/backend/azbankgateways/default_settings.py +++ b/backend/azbankgateways/default_settings.py @@ -12,17 +12,18 @@ BANK_CLASS = getattr( "BMI": "azbankgateways.banks.BMI", "SEP": "azbankgateways.banks.SEP", "ZARINPAL": "azbankgateways.banks.Zarinpal", - "IDPAY": "azbankgateways.banks.IDPay", "ZIBAL": "azbankgateways.banks.Zibal", "BAHAMTA": "azbankgateways.banks.Bahamta", "MELLAT": "azbankgateways.banks.Mellat", - "PAYV1": "azbankgateways.banks.PayV1", + "IRANDARGAH": "azbankgateways.banks.irandargah.IranDargah", + "ASANPARDAKHT": "azbankgateways.banks.asanpardakht.AsanPardakht", }, ) _AZ_IRANIAN_BANK_GATEWAYS = getattr(settings, "AZ_IRANIAN_BANK_GATEWAYS", {}) BANK_PRIORITIES = _AZ_IRANIAN_BANK_GATEWAYS.get("BANK_PRIORITIES", []) BANK_GATEWAYS = _AZ_IRANIAN_BANK_GATEWAYS.get("GATEWAYS", {}) BANK_DEFAULT = _AZ_IRANIAN_BANK_GATEWAYS.get("DEFAULT", "BMI") +BANK_TIMEOUT = _AZ_IRANIAN_BANK_GATEWAYS.get("BANK_TIMEOUT", 5) SETTING_VALUE_READER_CLASS = _AZ_IRANIAN_BANK_GATEWAYS.get( "SETTING_VALUE_READER_CLASS", "azbankgateways.readers.DefaultReader" ) diff --git a/backend/azbankgateways/exceptions/__init__.py b/backend/azbankgateways/exceptions/__init__.py index 8899825..aec06ee 100644 --- a/backend/azbankgateways/exceptions/__init__.py +++ b/backend/azbankgateways/exceptions/__init__.py @@ -2,10 +2,11 @@ from .exceptions import ( # noqa AmountDoesNotSupport, AZBankGatewaysException, BankGatewayConnectionError, + BankGatewayRejectPayment, BankGatewayStateInvalid, BankGatewayTokenExpired, BankGatewayUnclear, CurrencyDoesNotSupport, - SettingDoesNotExist, SafeSettingsEnabled, + SettingDoesNotExist, ) diff --git a/backend/azbankgateways/locale/en/LC_MESSAGES/django.po b/backend/azbankgateways/locale/en/LC_MESSAGES/django.po index 99c4e11..0e8ff0e 100644 --- a/backend/azbankgateways/locale/en/LC_MESSAGES/django.po +++ b/backend/azbankgateways/locale/en/LC_MESSAGES/django.po @@ -82,10 +82,6 @@ msgstr "" msgid "Zarinpal" msgstr "" -#: models/enum.py:10 -msgid "IDPay" -msgstr "" - #: models/enum.py:11 msgid "Zibal" msgstr "" diff --git a/backend/azbankgateways/locale/fa/LC_MESSAGES/django.po b/backend/azbankgateways/locale/fa/LC_MESSAGES/django.po index f095897..f979374 100644 --- a/backend/azbankgateways/locale/fa/LC_MESSAGES/django.po +++ b/backend/azbankgateways/locale/fa/LC_MESSAGES/django.po @@ -90,10 +90,6 @@ msgstr "بانک سامان" msgid "Zarinpal" msgstr "زرین پال" -#: models/enum.py:10 -msgid "IDPay" -msgstr "آی دی پی" - #: models/enum.py:11 msgid "Zibal" msgstr "زیبال" diff --git a/backend/azbankgateways/migrations/0009_alter_bank_bank_type.py b/backend/azbankgateways/migrations/0009_alter_bank_bank_type.py new file mode 100644 index 0000000..767407c --- /dev/null +++ b/backend/azbankgateways/migrations/0009_alter_bank_bank_type.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2 on 2026-05-22 16:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('azbankgateways', '0008_alter_bank_order'), + ] + + operations = [ + migrations.AlterField( + model_name='bank', + name='bank_type', + field=models.CharField(choices=[('BMI', 'BMI'), ('SEP', 'SEP'), ('ZARINPAL', 'Zarinpal'), ('ZIBAL', 'Zibal'), ('BAHAMTA', 'Bahamta'), ('MELLAT', 'Mellat'), ('IRANDARGAH', 'IranDargah'), ('ASANPARDAKHT', 'AsanPardakht')], max_length=50, verbose_name='Bank'), + ), + ] diff --git a/backend/azbankgateways/models/enum.py b/backend/azbankgateways/models/enum.py old mode 100644 new mode 100755 index ce0f1e5..0f5b1f6 --- a/backend/azbankgateways/models/enum.py +++ b/backend/azbankgateways/models/enum.py @@ -6,11 +6,11 @@ class BankType(models.TextChoices): BMI = "BMI", _("BMI") SEP = "SEP", _("SEP") ZARINPAL = "ZARINPAL", _("Zarinpal") - IDPAY = "IDPAY", _("IDPay") ZIBAL = "ZIBAL", _("Zibal") BAHAMTA = "BAHAMTA", _("Bahamta") MELLAT = "MELLAT", _("Mellat") - PAYV1 = "PAYV1", _("PayV1") + IRANDARGAH = "IRANDARGAH", _("IranDargah") + ASANPARDAKHT = "ASANPARDAKHT", _("AsanPardakht") class CurrencyEnum(models.TextChoices): diff --git a/backend/azbankgateways/utils.py b/backend/azbankgateways/utils.py index 7f4bf20..41cc73b 100644 --- a/backend/azbankgateways/utils.py +++ b/backend/azbankgateways/utils.py @@ -1,6 +1,9 @@ import json from urllib import parse +from django.conf import settings +from django.urls import reverse + from azbankgateways.types import DictQuerystring @@ -33,3 +36,28 @@ def split_to_dict_querystring(url: str) -> DictQuerystring: url_parts[5] = "" return parse.urlunparse(url_parts), query + + +def build_full_url(viewname: str, *args, **kwargs): + """ + Build a full absolute URL including domain if Sites framework is available. + Falls back to relative path if no site is configured. + """ + # Generate the path part + path = reverse(viewname, args=args, kwargs=kwargs) + + # Try to use django.contrib.sites if installed + if "django.contrib.sites" in settings.INSTALLED_APPS: + try: + from django.contrib.sites.models import Site + + site = Site.objects.get_current() + if site and site.domain: + protocol = getattr(settings, "DEFAULT_PROTOCOL", "https") + return f"{protocol}://{site.domain}{path}" + except Exception: + # Any issue with Sites, just return relative path + pass + + # Fallback: return only relative path + return path