update bank gateway

This commit is contained in:
Parsa Nazer
2026-05-22 20:11:17 +03:30
parent e56df858fd
commit 6ed95784a3
22 changed files with 574 additions and 415 deletions
+6 -1
View File
@@ -1,2 +1,7 @@
__version__ = "v2.0.5"
import django
__version__ = "1.0.0"
if django.VERSION < (3, 2):
default_app_config = "azbankgateways.apps.AZIranianBankGatewaysConfig"
+2 -5
View File
@@ -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',
]
@@ -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
+31 -8
View File
@@ -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",
]
+175
View File
@@ -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']
+7 -4
View File
@@ -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()
+29 -5
View File
@@ -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
+18 -7
View File
@@ -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()
-141
View File
@@ -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
+115
View File
@@ -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
+5 -6
View File
@@ -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
-152
View File
@@ -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
+18 -28
View File
@@ -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
+49 -40
View File
@@ -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]:
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")
except:
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
+3 -2
View File
@@ -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()
+3 -2
View File
@@ -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"
)
@@ -2,10 +2,11 @@ from .exceptions import ( # noqa
AmountDoesNotSupport,
AZBankGatewaysException,
BankGatewayConnectionError,
BankGatewayRejectPayment,
BankGatewayStateInvalid,
BankGatewayTokenExpired,
BankGatewayUnclear,
CurrencyDoesNotSupport,
SettingDoesNotExist,
SafeSettingsEnabled,
SettingDoesNotExist,
)
@@ -82,10 +82,6 @@ msgstr ""
msgid "Zarinpal"
msgstr ""
#: models/enum.py:10
msgid "IDPay"
msgstr ""
#: models/enum.py:11
msgid "Zibal"
msgstr ""
@@ -90,10 +90,6 @@ msgstr "بانک سامان"
msgid "Zarinpal"
msgstr "زرین پال"
#: models/enum.py:10
msgid "IDPay"
msgstr "آی دی پی"
#: models/enum.py:11
msgid "Zibal"
msgstr "زیبال"
@@ -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'),
),
]
+2 -2
View File
@@ -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):
+28
View File
@@ -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