text: import base64
  
  from typing import List, Tuple
  from datetime import datetime
  
  from py3rijndael import RijndaelCbc, Pkcs7Padding
  
  from zeep import Client, exceptions as zeep_exceptions
  
  from payit import (
      Gateway,
      Transaction,
      Redirection,
      GatewayNetworkError,
      TransactionError,
      TransactionAlreadyPaidError,
  )
  
  
  class AsanPardakhtGateway(Gateway):
      """
      Irankish
  
      TODO: transaction.pan not implemented
  
      Home: http://asanpardakht.ir/
      Documentation: http://asanpardakht.ir/ipgdownload/ipgdoc.pdf
      """
  
      __gateway_name__ = "asan_pardakht"
      __gateway_unit__ = "IRR"
      __config_params__ = [
          "key",
          "iv",
          "username",
          "password",
          "merchant_config_id",
          "callback_url",
          "proxies",
      ]
      _server_url = (
          "https://services.asanpardakht.net/paygate/merchantservices.asmx?WSDL"
      )
  
      def __init__(self, config):
          super().__init__(config)
          self.rijndael = RijndaelCbc(
              key=base64.b64decode(config["key"]),
              iv=base64.b64decode(config["iv"]),
              padding=Pkcs7Padding(32),
              block_size=32,
          )
  
      def _get_request_payload(
          self,
          transaction: Transaction,
          additional_data: str = "",
          accounts: List[Tuple[str, str]] = None,
      ):
          """
          Get request payload
          :param transaction:
          :param additional_data:
          :param accounts: More (Shaba) accounts, Maximum 7 accounts can use.
                 eg: [('shaba1', '100'), ('shaba2', '200'))]
          :return:
          """
          accounts = (
              [n for account in accounts for n in account] if accounts else []
          )
          return [
              "1",
              self.config["username"],
              self.config["password"],
              str(transaction.order_id),
              str(transaction.amount),
              datetime.now().strftime("%Y%m%d %I%M%S"),
              additional_data,  # Additional data maximum length is 100 characters
              self.config["callback_url"],
              "0",  # Payment id (optional)
          ] + accounts
  
      def _get_encrypted_request(self, transaction: Transaction):
          return base64.b64encode(
              self.rijndael.encrypt(
                  ",".join(self._get_request_payload(transaction)).encode()
              )
          )
  
      def _parse_callback_data(self, data: dict):
          data = self.rijndael.decrypt(
              base64.b64decode(data["ReturningParams"])
          ).decode()
          exploded_data = data.split(",")
          return dict(
              amount=exploded_data[0],  # Transaction amount
              sale_order_id=exploded_data[1],  # Transaction Order ID
              ref_id=exploded_data[2],  # Transaction ID
              res_code=exploded_data[3],  # Result Code
              res_message=exploded_data[4],  # Result Message
              pay_gate_trans_id=exploded_data[5],  # Transaction inquiry id
              rrn=exploded_data[6],  # Bank Reference ID
              last_four_digit_of_pan=exploded_data[
                  7
              ],  # Last four digits of payer card ID
          )
  
      @property
      def _encrypted_credentials(self):
          return base64.b64encode(
              self.rijndael.encrypt(
                  ",".join(
                      [self.config["username"], self.config["password"]]
                  ).encode()
              )
          )
  
      def get_redirection(self, transaction) -> Redirection:
          return Redirection(
              url="https://asan.shaparak.ir/",
              body_params=dict(RefId=transaction.id, RedirctToIPGFORM="true"),
              method="post",
          )
  
      def request_transaction(self, transaction: Transaction) -> Transaction:
          client = Client(self._server_url)
          if "proxies" in self.config:
              client.transport.session.proxies = self.config["proxies"]
          try:
              params = {
                  "merchantConfigurationID": self.config["merchant_config_id"],
                  "encryptedRequest": self._get_encrypted_request(transaction),
              }
  
              result = client.service.RequestOperation(**params)
              if not result:
                  raise TransactionError("AsanPardakht: invalid information.")
  
              if result[0:1] == "0":
                  exploded_result = str(result).split(
                      ","
                  )  # Should have two part [XX,YY]
                  transaction.id = exploded_result[1]
              else:
                  raise TransactionError(
                      "AsanPardakht: invalid information. code: %s" % str(result)
                  )
  
          except zeep_exceptions.Fault:
              raise TransactionError("AsanPardakht: invalid information.")
  
          except zeep_exceptions.Error:
              raise GatewayNetworkError
  
          return transaction
  
      def validate_transaction(self, data: dict) -> Transaction:
          parsed_data = self._parse_callback_data(data)
          transaction = Transaction()
          transaction.id = parsed_data["ref_id"]
          transaction.meta = parsed_data
          if int(parsed_data["res_code"]) == 0:
              transaction.validate_status = True
          return transaction
  
      def verify_transaction(self, transaction: Transaction, data):
          try:
              parsed_data = self._parse_callback_data(data)
              params = {
                  "merchantConfigurationID": int(
                      self.config["merchant_config_id"]
                  ),
                  "encryptedCredentials": self._encrypted_credentials,
                  "payGateTranID": parsed_data["pay_gate_trans_id"],
              }
              client = Client(self._server_url)
              if "proxies" in self.config:
                  client.transport.session.proxies = self.config["proxies"]
  
              verify_result = int(client.service.RequestVerification(**params))
              if verify_result == 505:
                  raise TransactionAlreadyPaidError(
                      "AsanPardakht: transaction already verified"
                  )
              elif verify_result != 500:
                  raise TransactionError(
                      "AsanPardakht: invalid transaction, %s" % verify_result
                  )
  
              reconcile_result = int(
                  client.service.RequestReconciliation(**params)
              )
              if reconcile_result != 600:
                  raise TransactionError(
                      "AsanPardakht: invalid transaction settlement, %s"
                      % reconcile_result
                  )
  
          except zeep_exceptions.Error:
              raise GatewayNetworkError
  
          return transaction
  