Skip to content

Payment Gateways

The internet_payment_gateways adapter provides integration with Iranian Shaparak payment gateways. Supports both Parsian (SOAP/WSDL) and Saman (REST/JSON) protocols.

Parsian Shaparak

Parsian Shaparak adapter using the SOAP protocol with WSDL service definitions.

Ports

Abstract port interface defining the Parsian Shaparak adapter contract.

Port interfaces for Parsian IPG operations.

archipy.adapters.internet_payment_gateways.ir.parsian.ports.ParsianShaparakPaymentPort

Port interface for Parsian Shaparak payment gateway.

Defines the contract for Parsian IPG adapters implementing payment operations (token request, confirmation, and reversal).

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/ports.py
class ParsianShaparakPaymentPort:
    """Port interface for Parsian Shaparak payment gateway.

    Defines the contract for Parsian IPG adapters implementing payment
    operations (token request, confirmation, and reversal).
    """

    @abstractmethod
    def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
        """Step 1: Request payment token.

        Args:
            request: Payment request data.

        Returns:
            PaymentResponseDTO: Response containing token, status, and message.

        Raises:
            UnavailableError: If a SOAP fault occurs during the request.
            InternalError: If an unexpected error occurs during the request.
        """
        raise NotImplementedError

    @abstractmethod
    def confirm_payment(self, request: ConfirmRequestDTO) -> ConfirmResponseDTO:
        """Step 3: Confirm transaction.

        Args:
            request: Confirm request data.

        Returns:
            ConfirmResponseDTO: Response containing status, RRN, card number, and token.

        Raises:
            UnavailableError: If a SOAP fault occurs during the request.
            InternalError: If an unexpected error occurs during the request.
        """
        raise NotImplementedError

    @abstractmethod
    def confirm_payment_with_amount(self, request: ConfirmWithAmountRequestDTO) -> ConfirmWithAmountResponseDTO:
        """Confirm transaction with amount and order verification.

        Args:
            request: Confirm with amount request data.

        Returns:
            ConfirmWithAmountResponseDTO: Response containing status, RRN, card number, and token.

        Raises:
            UnavailableError: If a SOAP fault occurs during the request.
            InternalError: If an unexpected error occurs during the request.
        """
        raise NotImplementedError

    @abstractmethod
    def reverse_payment(self, request: ReverseRequestDTO) -> ReverseResponseDTO:
        """Reverse a transaction.

        Args:
            request: Reverse request data.

        Returns:
            ReverseResponseDTO: Response containing status, message, and token.

        Raises:
            UnavailableError: If a SOAP fault occurs during the request.
            InternalError: If an unexpected error occurs during the request.
        """
        raise NotImplementedError

archipy.adapters.internet_payment_gateways.ir.parsian.ports.ParsianShaparakPaymentPort.initiate_payment abstractmethod

initiate_payment(
    request: PaymentRequestDTO,
) -> PaymentResponseDTO

Step 1: Request payment token.

Parameters:

Name Type Description Default
request PaymentRequestDTO

Payment request data.

required

Returns:

Name Type Description
PaymentResponseDTO PaymentResponseDTO

Response containing token, status, and message.

Raises:

Type Description
UnavailableError

If a SOAP fault occurs during the request.

InternalError

If an unexpected error occurs during the request.

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/ports.py
@abstractmethod
def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
    """Step 1: Request payment token.

    Args:
        request: Payment request data.

    Returns:
        PaymentResponseDTO: Response containing token, status, and message.

    Raises:
        UnavailableError: If a SOAP fault occurs during the request.
        InternalError: If an unexpected error occurs during the request.
    """
    raise NotImplementedError

archipy.adapters.internet_payment_gateways.ir.parsian.ports.ParsianShaparakPaymentPort.confirm_payment abstractmethod

confirm_payment(
    request: ConfirmRequestDTO,
) -> ConfirmResponseDTO

Step 3: Confirm transaction.

Parameters:

Name Type Description Default
request ConfirmRequestDTO

Confirm request data.

required

Returns:

Name Type Description
ConfirmResponseDTO ConfirmResponseDTO

Response containing status, RRN, card number, and token.

Raises:

Type Description
UnavailableError

If a SOAP fault occurs during the request.

InternalError

If an unexpected error occurs during the request.

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/ports.py
@abstractmethod
def confirm_payment(self, request: ConfirmRequestDTO) -> ConfirmResponseDTO:
    """Step 3: Confirm transaction.

    Args:
        request: Confirm request data.

    Returns:
        ConfirmResponseDTO: Response containing status, RRN, card number, and token.

    Raises:
        UnavailableError: If a SOAP fault occurs during the request.
        InternalError: If an unexpected error occurs during the request.
    """
    raise NotImplementedError

archipy.adapters.internet_payment_gateways.ir.parsian.ports.ParsianShaparakPaymentPort.confirm_payment_with_amount abstractmethod

confirm_payment_with_amount(
    request: ConfirmWithAmountRequestDTO,
) -> ConfirmWithAmountResponseDTO

Confirm transaction with amount and order verification.

Parameters:

Name Type Description Default
request ConfirmWithAmountRequestDTO

Confirm with amount request data.

required

Returns:

Name Type Description
ConfirmWithAmountResponseDTO ConfirmWithAmountResponseDTO

Response containing status, RRN, card number, and token.

Raises:

Type Description
UnavailableError

If a SOAP fault occurs during the request.

InternalError

If an unexpected error occurs during the request.

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/ports.py
@abstractmethod
def confirm_payment_with_amount(self, request: ConfirmWithAmountRequestDTO) -> ConfirmWithAmountResponseDTO:
    """Confirm transaction with amount and order verification.

    Args:
        request: Confirm with amount request data.

    Returns:
        ConfirmWithAmountResponseDTO: Response containing status, RRN, card number, and token.

    Raises:
        UnavailableError: If a SOAP fault occurs during the request.
        InternalError: If an unexpected error occurs during the request.
    """
    raise NotImplementedError

archipy.adapters.internet_payment_gateways.ir.parsian.ports.ParsianShaparakPaymentPort.reverse_payment abstractmethod

reverse_payment(
    request: ReverseRequestDTO,
) -> ReverseResponseDTO

Reverse a transaction.

Parameters:

Name Type Description Default
request ReverseRequestDTO

Reverse request data.

required

Returns:

Name Type Description
ReverseResponseDTO ReverseResponseDTO

Response containing status, message, and token.

Raises:

Type Description
UnavailableError

If a SOAP fault occurs during the request.

InternalError

If an unexpected error occurs during the request.

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/ports.py
@abstractmethod
def reverse_payment(self, request: ReverseRequestDTO) -> ReverseResponseDTO:
    """Reverse a transaction.

    Args:
        request: Reverse request data.

    Returns:
        ReverseResponseDTO: Response containing status, message, and token.

    Raises:
        UnavailableError: If a SOAP fault occurs during the request.
        InternalError: If an unexpected error occurs during the request.
    """
    raise NotImplementedError

archipy.adapters.internet_payment_gateways.ir.parsian.ports.AsyncParsianShaparakPaymentPort

Async port interface for Parsian Shaparak payment gateway.

Defines the contract for async Parsian IPG adapters implementing payment operations (token request, confirmation, and reversal).

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/ports.py
class AsyncParsianShaparakPaymentPort:
    """Async port interface for Parsian Shaparak payment gateway.

    Defines the contract for async Parsian IPG adapters implementing payment
    operations (token request, confirmation, and reversal).
    """

    @abstractmethod
    async def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
        """Step 1: Request payment token (async).

        Args:
            request: Payment request data.

        Returns:
            PaymentResponseDTO: Response containing token, status, and message.

        Raises:
            UnavailableError: If a SOAP fault occurs during the request.
            InternalError: If an unexpected error occurs during the request.
        """
        raise NotImplementedError

    @abstractmethod
    async def confirm_payment(self, request: ConfirmRequestDTO) -> ConfirmResponseDTO:
        """Step 3: Confirm transaction (async).

        Args:
            request: Confirm request data.

        Returns:
            ConfirmResponseDTO: Response containing status, RRN, card number, and token.

        Raises:
            UnavailableError: If a SOAP fault occurs during the request.
            InternalError: If an unexpected error occurs during the request.
        """
        raise NotImplementedError

    @abstractmethod
    async def confirm_payment_with_amount(self, request: ConfirmWithAmountRequestDTO) -> ConfirmWithAmountResponseDTO:
        """Confirm transaction with amount and order verification (async).

        Args:
            request: Confirm with amount request data.

        Returns:
            ConfirmWithAmountResponseDTO: Response containing status, RRN, card number, and token.

        Raises:
            UnavailableError: If a SOAP fault occurs during the request.
            InternalError: If an unexpected error occurs during the request.
        """
        raise NotImplementedError

    @abstractmethod
    async def reverse_payment(self, request: ReverseRequestDTO) -> ReverseResponseDTO:
        """Reverse a transaction (async).

        Args:
            request: Reverse request data.

        Returns:
            ReverseResponseDTO: Response containing status, message, and token.

        Raises:
            UnavailableError: If a SOAP fault occurs during the request.
            InternalError: If an unexpected error occurs during the request.
        """
        raise NotImplementedError

archipy.adapters.internet_payment_gateways.ir.parsian.ports.AsyncParsianShaparakPaymentPort.initiate_payment abstractmethod async

initiate_payment(
    request: PaymentRequestDTO,
) -> PaymentResponseDTO

Step 1: Request payment token (async).

Parameters:

Name Type Description Default
request PaymentRequestDTO

Payment request data.

required

Returns:

Name Type Description
PaymentResponseDTO PaymentResponseDTO

Response containing token, status, and message.

Raises:

Type Description
UnavailableError

If a SOAP fault occurs during the request.

InternalError

If an unexpected error occurs during the request.

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/ports.py
@abstractmethod
async def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
    """Step 1: Request payment token (async).

    Args:
        request: Payment request data.

    Returns:
        PaymentResponseDTO: Response containing token, status, and message.

    Raises:
        UnavailableError: If a SOAP fault occurs during the request.
        InternalError: If an unexpected error occurs during the request.
    """
    raise NotImplementedError

archipy.adapters.internet_payment_gateways.ir.parsian.ports.AsyncParsianShaparakPaymentPort.confirm_payment abstractmethod async

confirm_payment(
    request: ConfirmRequestDTO,
) -> ConfirmResponseDTO

Step 3: Confirm transaction (async).

Parameters:

Name Type Description Default
request ConfirmRequestDTO

Confirm request data.

required

Returns:

Name Type Description
ConfirmResponseDTO ConfirmResponseDTO

Response containing status, RRN, card number, and token.

Raises:

Type Description
UnavailableError

If a SOAP fault occurs during the request.

InternalError

If an unexpected error occurs during the request.

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/ports.py
@abstractmethod
async def confirm_payment(self, request: ConfirmRequestDTO) -> ConfirmResponseDTO:
    """Step 3: Confirm transaction (async).

    Args:
        request: Confirm request data.

    Returns:
        ConfirmResponseDTO: Response containing status, RRN, card number, and token.

    Raises:
        UnavailableError: If a SOAP fault occurs during the request.
        InternalError: If an unexpected error occurs during the request.
    """
    raise NotImplementedError

archipy.adapters.internet_payment_gateways.ir.parsian.ports.AsyncParsianShaparakPaymentPort.confirm_payment_with_amount abstractmethod async

confirm_payment_with_amount(
    request: ConfirmWithAmountRequestDTO,
) -> ConfirmWithAmountResponseDTO

Confirm transaction with amount and order verification (async).

Parameters:

Name Type Description Default
request ConfirmWithAmountRequestDTO

Confirm with amount request data.

required

Returns:

Name Type Description
ConfirmWithAmountResponseDTO ConfirmWithAmountResponseDTO

Response containing status, RRN, card number, and token.

Raises:

Type Description
UnavailableError

If a SOAP fault occurs during the request.

InternalError

If an unexpected error occurs during the request.

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/ports.py
@abstractmethod
async def confirm_payment_with_amount(self, request: ConfirmWithAmountRequestDTO) -> ConfirmWithAmountResponseDTO:
    """Confirm transaction with amount and order verification (async).

    Args:
        request: Confirm with amount request data.

    Returns:
        ConfirmWithAmountResponseDTO: Response containing status, RRN, card number, and token.

    Raises:
        UnavailableError: If a SOAP fault occurs during the request.
        InternalError: If an unexpected error occurs during the request.
    """
    raise NotImplementedError

archipy.adapters.internet_payment_gateways.ir.parsian.ports.AsyncParsianShaparakPaymentPort.reverse_payment abstractmethod async

reverse_payment(
    request: ReverseRequestDTO,
) -> ReverseResponseDTO

Reverse a transaction (async).

Parameters:

Name Type Description Default
request ReverseRequestDTO

Reverse request data.

required

Returns:

Name Type Description
ReverseResponseDTO ReverseResponseDTO

Response containing status, message, and token.

Raises:

Type Description
UnavailableError

If a SOAP fault occurs during the request.

InternalError

If an unexpected error occurs during the request.

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/ports.py
@abstractmethod
async def reverse_payment(self, request: ReverseRequestDTO) -> ReverseResponseDTO:
    """Reverse a transaction (async).

    Args:
        request: Reverse request data.

    Returns:
        ReverseResponseDTO: Response containing status, message, and token.

    Raises:
        UnavailableError: If a SOAP fault occurs during the request.
        InternalError: If an unexpected error occurs during the request.
    """
    raise NotImplementedError

options: show_root_toc_entry: false heading_level: 3

Adapters

Parsian IPG adapter implementations.

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.logger module-attribute

logger = logging.getLogger(__name__)

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.ParsianShaparakPaymentAdapter

Bases: ParsianShaparakPaymentPort

Adapter for interacting with Parsian Shaparak payment gateway services.

Provides methods for initiating payments, confirming transactions, and reversing payments through the Parsian Shaparak payment gateway SOAP services. Supports proxy configuration for environments where direct connections are not possible.

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/adapters.py
class ParsianShaparakPaymentAdapter(ParsianShaparakPaymentPort):
    """Adapter for interacting with Parsian Shaparak payment gateway services.

    Provides methods for initiating payments, confirming transactions, and reversing
    payments through the Parsian Shaparak payment gateway SOAP services. Supports
    proxy configuration for environments where direct connections are not possible.
    """

    def __init__(self, config: ParsianShaparakConfig | None = None) -> None:
        """Initialize the adapter with Parsian Shaparak configuration.

        Args:
            config (ParsianShaparakConfig | None): Configuration for Parsian Shaparak services.
                If None, uses global config. Includes optional proxy configuration via
                the PROXIES field.

        Raises:
            InvalidArgumentError: If LOGIN_ACCOUNT is not a valid string.
        """
        configs = config or BaseConfig.global_config().PARSIAN_SHAPARAK
        if not configs.LOGIN_ACCOUNT or not isinstance(configs.LOGIN_ACCOUNT, str):
            raise InvalidArgumentError(argument_name="LOGIN_ACCOUNT")

        self.login_account = configs.LOGIN_ACCOUNT
        session = requests.Session()
        if configs.PROXIES:
            session.proxies = configs.PROXIES
        sync_transport = Transport(session=session, operation_timeout=configs.TIMEOUT)

        self.sale_client = zeep.Client(wsdl=configs.PAYMENT_WSDL_URL, transport=sync_transport)
        self.confirm_client = zeep.Client(wsdl=configs.CONFIRM_WSDL_URL, transport=sync_transport)
        self.reversal_client = zeep.Client(wsdl=configs.REVERSAL_WSDL_URL, transport=sync_transport)

    def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
        """Step 1: Request payment token.

        Args:
            request: Payment request data.

        Returns:
            PaymentResponseDTO: Response containing token, status, and message.

        Raises:
            UnavailableError: If a SOAP fault occurs during the request.
            InternalError: If an unexpected error occurs during the request.
        """
        try:
            request_data = {
                "LoginAccount": self.login_account,
                "Amount": request.amount,
                "OrderId": request.order_id,
                "CallBackUrl": str(request.callback_url),
                "AdditionalData": request.additional_data,
                "Originator": request.originator,
            }

            logger.debug("Initiating payment: %s", request_data)
            response = self.sale_client.service.SalePaymentRequest(requestData=request_data)
            result = PaymentResponseDTO(
                token=response.Token,
                status=response.Status,
                message=response.Message,
            )
            logger.debug("Payment response: %s", result)
        except Fault as exception:
            raise UnavailableError(resource_type="Parsian Shaparak Sale Service") from exception
        except Exception as exception:
            raise InternalError() from exception
        else:
            return result

    def confirm_payment(self, request: ConfirmRequestDTO) -> ConfirmResponseDTO:
        """Step 3: Confirm transaction.

        Args:
            request: Confirm request data.

        Returns:
            ConfirmResponseDTO: Response containing status, RRN, card number, and token.

        Raises:
            UnavailableError: If a SOAP fault occurs during the request.
            InternalError: If an unexpected error occurs during the request.
        """
        try:
            request_data = {"LoginAccount": self.login_account, "Token": request.token}

            logger.debug("Confirming payment: %s", request_data)
            response = self.confirm_client.service.ConfirmPayment(requestData=request_data)
            result = ConfirmResponseDTO(
                status=response.Status,
                rrn=response.RRN,
                card_number_masked=response.CardNumberMasked,
                token=response.Token,
            )
            logger.debug("Confirm response: %s", result)
        except Fault as exception:
            raise UnavailableError(resource_type="Parsian Shaparak Confirm Service") from exception
        except Exception as exception:
            raise InternalError() from exception
        else:
            return result

    def confirm_payment_with_amount(self, request: ConfirmWithAmountRequestDTO) -> ConfirmWithAmountResponseDTO:
        """Confirm transaction with amount and order verification.

        Args:
            request: Confirm with amount request data.

        Returns:
            ConfirmWithAmountResponseDTO: Response containing status, RRN, card number, and token.

        Raises:
            UnavailableError: If a SOAP fault occurs during the request.
            InternalError: If an unexpected error occurs during the request.
        """
        try:
            request_data = {
                "LoginAccount": self.login_account,
                "Token": request.token,
                "OrderId": request.order_id,
                "Amount": request.amount,
            }

            logger.debug("Confirming payment with amount: %s", request_data)
            response = self.confirm_client.service.ConfirmPaymentWithAmount(requestData=request_data)
            result = ConfirmWithAmountResponseDTO(
                status=response.Status,
                rrn=response.RRN,
                card_number_masked=response.CardNumberMasked,
                token=response.Token,
            )
            logger.debug("Confirm with amount response: %s", result)
        except Fault as exception:
            raise UnavailableError(resource_type="Parsian Shaparak Confirm Service") from exception
        except Exception as exception:
            raise InternalError() from exception
        else:
            return result

    def reverse_payment(self, request: ReverseRequestDTO) -> ReverseResponseDTO:
        """Reverse a transaction.

        Args:
            request: Reverse request data.

        Returns:
            ReverseResponseDTO: Response containing status, message, and token.

        Raises:
            UnavailableError: If a SOAP fault occurs during the request.
            InternalError: If an unexpected error occurs during the request.
        """
        try:
            request_data = {"LoginAccount": self.login_account, "Token": request.token}

            logger.debug("Reversing payment: %s", request_data)
            response = self.reversal_client.service.ReversalRequest(requestData=request_data)
            result = ReverseResponseDTO(
                status=response.Status,
                message=response.Message,
                token=response.Token,
            )
            logger.debug("Reversal response: %s", result)
        except Fault as exception:
            raise UnavailableError(resource_type="Parsian Shaparak Reversal Service") from exception
        except Exception as exception:
            raise InternalError() from exception
        else:
            return result

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.ParsianShaparakPaymentAdapter.login_account instance-attribute

login_account = configs.LOGIN_ACCOUNT

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.ParsianShaparakPaymentAdapter.sale_client instance-attribute

sale_client = zeep.Client(
    wsdl=configs.PAYMENT_WSDL_URL, transport=sync_transport
)

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.ParsianShaparakPaymentAdapter.confirm_client instance-attribute

confirm_client = zeep.Client(
    wsdl=configs.CONFIRM_WSDL_URL, transport=sync_transport
)

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.ParsianShaparakPaymentAdapter.reversal_client instance-attribute

reversal_client = zeep.Client(
    wsdl=configs.REVERSAL_WSDL_URL, transport=sync_transport
)

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.ParsianShaparakPaymentAdapter.initiate_payment

initiate_payment(
    request: PaymentRequestDTO,
) -> PaymentResponseDTO

Step 1: Request payment token.

Parameters:

Name Type Description Default
request PaymentRequestDTO

Payment request data.

required

Returns:

Name Type Description
PaymentResponseDTO PaymentResponseDTO

Response containing token, status, and message.

Raises:

Type Description
UnavailableError

If a SOAP fault occurs during the request.

InternalError

If an unexpected error occurs during the request.

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/adapters.py
def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
    """Step 1: Request payment token.

    Args:
        request: Payment request data.

    Returns:
        PaymentResponseDTO: Response containing token, status, and message.

    Raises:
        UnavailableError: If a SOAP fault occurs during the request.
        InternalError: If an unexpected error occurs during the request.
    """
    try:
        request_data = {
            "LoginAccount": self.login_account,
            "Amount": request.amount,
            "OrderId": request.order_id,
            "CallBackUrl": str(request.callback_url),
            "AdditionalData": request.additional_data,
            "Originator": request.originator,
        }

        logger.debug("Initiating payment: %s", request_data)
        response = self.sale_client.service.SalePaymentRequest(requestData=request_data)
        result = PaymentResponseDTO(
            token=response.Token,
            status=response.Status,
            message=response.Message,
        )
        logger.debug("Payment response: %s", result)
    except Fault as exception:
        raise UnavailableError(resource_type="Parsian Shaparak Sale Service") from exception
    except Exception as exception:
        raise InternalError() from exception
    else:
        return result

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.ParsianShaparakPaymentAdapter.confirm_payment

confirm_payment(
    request: ConfirmRequestDTO,
) -> ConfirmResponseDTO

Step 3: Confirm transaction.

Parameters:

Name Type Description Default
request ConfirmRequestDTO

Confirm request data.

required

Returns:

Name Type Description
ConfirmResponseDTO ConfirmResponseDTO

Response containing status, RRN, card number, and token.

Raises:

Type Description
UnavailableError

If a SOAP fault occurs during the request.

InternalError

If an unexpected error occurs during the request.

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/adapters.py
def confirm_payment(self, request: ConfirmRequestDTO) -> ConfirmResponseDTO:
    """Step 3: Confirm transaction.

    Args:
        request: Confirm request data.

    Returns:
        ConfirmResponseDTO: Response containing status, RRN, card number, and token.

    Raises:
        UnavailableError: If a SOAP fault occurs during the request.
        InternalError: If an unexpected error occurs during the request.
    """
    try:
        request_data = {"LoginAccount": self.login_account, "Token": request.token}

        logger.debug("Confirming payment: %s", request_data)
        response = self.confirm_client.service.ConfirmPayment(requestData=request_data)
        result = ConfirmResponseDTO(
            status=response.Status,
            rrn=response.RRN,
            card_number_masked=response.CardNumberMasked,
            token=response.Token,
        )
        logger.debug("Confirm response: %s", result)
    except Fault as exception:
        raise UnavailableError(resource_type="Parsian Shaparak Confirm Service") from exception
    except Exception as exception:
        raise InternalError() from exception
    else:
        return result

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.ParsianShaparakPaymentAdapter.confirm_payment_with_amount

confirm_payment_with_amount(
    request: ConfirmWithAmountRequestDTO,
) -> ConfirmWithAmountResponseDTO

Confirm transaction with amount and order verification.

Parameters:

Name Type Description Default
request ConfirmWithAmountRequestDTO

Confirm with amount request data.

required

Returns:

Name Type Description
ConfirmWithAmountResponseDTO ConfirmWithAmountResponseDTO

Response containing status, RRN, card number, and token.

Raises:

Type Description
UnavailableError

If a SOAP fault occurs during the request.

InternalError

If an unexpected error occurs during the request.

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/adapters.py
def confirm_payment_with_amount(self, request: ConfirmWithAmountRequestDTO) -> ConfirmWithAmountResponseDTO:
    """Confirm transaction with amount and order verification.

    Args:
        request: Confirm with amount request data.

    Returns:
        ConfirmWithAmountResponseDTO: Response containing status, RRN, card number, and token.

    Raises:
        UnavailableError: If a SOAP fault occurs during the request.
        InternalError: If an unexpected error occurs during the request.
    """
    try:
        request_data = {
            "LoginAccount": self.login_account,
            "Token": request.token,
            "OrderId": request.order_id,
            "Amount": request.amount,
        }

        logger.debug("Confirming payment with amount: %s", request_data)
        response = self.confirm_client.service.ConfirmPaymentWithAmount(requestData=request_data)
        result = ConfirmWithAmountResponseDTO(
            status=response.Status,
            rrn=response.RRN,
            card_number_masked=response.CardNumberMasked,
            token=response.Token,
        )
        logger.debug("Confirm with amount response: %s", result)
    except Fault as exception:
        raise UnavailableError(resource_type="Parsian Shaparak Confirm Service") from exception
    except Exception as exception:
        raise InternalError() from exception
    else:
        return result

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.ParsianShaparakPaymentAdapter.reverse_payment

reverse_payment(
    request: ReverseRequestDTO,
) -> ReverseResponseDTO

Reverse a transaction.

Parameters:

Name Type Description Default
request ReverseRequestDTO

Reverse request data.

required

Returns:

Name Type Description
ReverseResponseDTO ReverseResponseDTO

Response containing status, message, and token.

Raises:

Type Description
UnavailableError

If a SOAP fault occurs during the request.

InternalError

If an unexpected error occurs during the request.

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/adapters.py
def reverse_payment(self, request: ReverseRequestDTO) -> ReverseResponseDTO:
    """Reverse a transaction.

    Args:
        request: Reverse request data.

    Returns:
        ReverseResponseDTO: Response containing status, message, and token.

    Raises:
        UnavailableError: If a SOAP fault occurs during the request.
        InternalError: If an unexpected error occurs during the request.
    """
    try:
        request_data = {"LoginAccount": self.login_account, "Token": request.token}

        logger.debug("Reversing payment: %s", request_data)
        response = self.reversal_client.service.ReversalRequest(requestData=request_data)
        result = ReverseResponseDTO(
            status=response.Status,
            message=response.Message,
            token=response.Token,
        )
        logger.debug("Reversal response: %s", result)
    except Fault as exception:
        raise UnavailableError(resource_type="Parsian Shaparak Reversal Service") from exception
    except Exception as exception:
        raise InternalError() from exception
    else:
        return result

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.AsyncParsianShaparakPaymentAdapter

Bases: AsyncParsianShaparakPaymentPort

Async adapter for interacting with Parsian Shaparak payment gateway services.

Provides async methods for initiating payments, confirming transactions, and reversing payments through the Parsian Shaparak payment gateway SOAP services. Supports proxy configuration for environments where direct connections are not possible.

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/adapters.py
class AsyncParsianShaparakPaymentAdapter(AsyncParsianShaparakPaymentPort):
    """Async adapter for interacting with Parsian Shaparak payment gateway services.

    Provides async methods for initiating payments, confirming transactions, and
    reversing payments through the Parsian Shaparak payment gateway SOAP services.
    Supports proxy configuration for environments where direct connections are
    not possible.
    """

    def __init__(self, config: ParsianShaparakConfig | None = None) -> None:
        """Initialize the async adapter with Parsian Shaparak configuration.

        Args:
            config (ParsianShaparakConfig | None): Configuration for Parsian Shaparak services.
                If None, uses global config. Includes optional proxy configuration via
                the PROXIES field.

        Raises:
            InvalidArgumentError: If LOGIN_ACCOUNT is not a valid string.
        """
        configs = config or BaseConfig.global_config().PARSIAN_SHAPARAK
        if not configs.LOGIN_ACCOUNT or not isinstance(configs.LOGIN_ACCOUNT, str):
            raise InvalidArgumentError(argument_name="LOGIN_ACCOUNT")

        self.login_account = configs.LOGIN_ACCOUNT
        proxy = self._get_proxy(configs.PROXIES)
        # The proxy is also handed to the transport: zeep loads the WSDL with its own client, which would
        # otherwise bypass it.
        async_transport = AsyncTransport(
            client=httpx2.AsyncClient(proxy=proxy, timeout=configs.TIMEOUT),
            proxy=proxy,
            operation_timeout=configs.TIMEOUT,
        )

        self.sale_client = AsyncClient(wsdl=configs.PAYMENT_WSDL_URL, transport=async_transport)
        self.confirm_client = AsyncClient(wsdl=configs.CONFIRM_WSDL_URL, transport=async_transport)
        self.reversal_client = AsyncClient(wsdl=configs.REVERSAL_WSDL_URL, transport=async_transport)

    @staticmethod
    def _get_proxy(proxies: dict[str, str] | None) -> str | None:
        if proxies is None:
            return None
        return proxies.get("https") or proxies.get("http") or proxies.get("socks5") or proxies.get("socks5h")

    async def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
        """Step 1: Request payment token (async).

        Args:
            request: Payment request data.

        Returns:
            PaymentResponseDTO: Response containing token, status, and message.

        Raises:
            UnavailableError: If a SOAP fault occurs during the request.
            InternalError: If an unexpected error occurs during the request.
        """
        try:
            request_data = {
                "LoginAccount": self.login_account,
                "Amount": request.amount,
                "OrderId": request.order_id,
                "CallBackUrl": str(request.callback_url),
                "AdditionalData": request.additional_data,
                "Originator": request.originator,
            }

            logger.debug("Initiating payment: %s", request_data)
            response = await self.sale_client.service.SalePaymentRequest(requestData=request_data)
            result = PaymentResponseDTO(
                token=response.Token,
                status=response.Status,
                message=response.Message,
            )
            logger.debug("Payment response: %s", result)
        except Fault as exception:
            raise UnavailableError(resource_type="Parsian Shaparak Sale Service") from exception
        except Exception as exception:
            raise InternalError() from exception
        else:
            return result

    async def confirm_payment(self, request: ConfirmRequestDTO) -> ConfirmResponseDTO:
        """Step 3: Confirm transaction (async).

        Args:
            request: Confirm request data.

        Returns:
            ConfirmResponseDTO: Response containing status, RRN, card number, and token.

        Raises:
            UnavailableError: If a SOAP fault occurs during the request.
            InternalError: If an unexpected error occurs during the request.
        """
        try:
            request_data = {"LoginAccount": self.login_account, "Token": request.token}

            logger.debug("Confirming payment: %s", request_data)
            response = await self.confirm_client.service.ConfirmPayment(requestData=request_data)
            result = ConfirmResponseDTO(
                status=response.Status,
                rrn=response.RRN,
                card_number_masked=response.CardNumberMasked,
                token=response.Token,
            )
            logger.debug("Confirm response: %s", result)
        except Fault as exception:
            raise UnavailableError(resource_type="Parsian Shaparak Confirm Service") from exception
        except Exception as exception:
            raise InternalError() from exception
        else:
            return result

    async def confirm_payment_with_amount(self, request: ConfirmWithAmountRequestDTO) -> ConfirmWithAmountResponseDTO:
        """Confirm transaction with amount and order verification (async).

        Args:
            request: Confirm with amount request data.

        Returns:
            ConfirmWithAmountResponseDTO: Response containing status, RRN, card number, and token.

        Raises:
            UnavailableError: If a SOAP fault occurs during the request.
            InternalError: If an unexpected error occurs during the request.
        """
        try:
            request_data = {
                "LoginAccount": self.login_account,
                "Token": request.token,
                "OrderId": request.order_id,
                "Amount": request.amount,
            }

            logger.debug("Confirming payment with amount: %s", request_data)
            response = await self.confirm_client.service.ConfirmPaymentWithAmount(requestData=request_data)
            result = ConfirmWithAmountResponseDTO(
                status=response.Status,
                rrn=response.RRN,
                card_number_masked=response.CardNumberMasked,
                token=response.Token,
            )
            logger.debug("Confirm with amount response: %s", result)
        except Fault as exception:
            raise UnavailableError(resource_type="Parsian Shaparak Confirm Service") from exception
        except Exception as exception:
            raise InternalError() from exception
        else:
            return result

    async def reverse_payment(self, request: ReverseRequestDTO) -> ReverseResponseDTO:
        """Reverse a transaction (async).

        Args:
            request: Reverse request data.

        Returns:
            ReverseResponseDTO: Response containing status, message, and token.

        Raises:
            UnavailableError: If a SOAP fault occurs during the request.
            InternalError: If an unexpected error occurs during the request.
        """
        try:
            request_data = {"LoginAccount": self.login_account, "Token": request.token}

            logger.debug("Reversing payment: %s", request_data)
            response = await self.reversal_client.service.ReversalRequest(requestData=request_data)
            result = ReverseResponseDTO(
                status=response.Status,
                message=response.Message,
                token=response.Token,
            )
            logger.debug("Reversal response: %s", result)
        except Fault as exception:
            raise UnavailableError(resource_type="Parsian Shaparak Reversal Service") from exception
        except Exception as exception:
            raise InternalError() from exception
        else:
            return result

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.AsyncParsianShaparakPaymentAdapter.login_account instance-attribute

login_account = configs.LOGIN_ACCOUNT

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.AsyncParsianShaparakPaymentAdapter.sale_client instance-attribute

sale_client = AsyncClient(
    wsdl=configs.PAYMENT_WSDL_URL, transport=async_transport
)

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.AsyncParsianShaparakPaymentAdapter.confirm_client instance-attribute

confirm_client = AsyncClient(
    wsdl=configs.CONFIRM_WSDL_URL, transport=async_transport
)

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.AsyncParsianShaparakPaymentAdapter.reversal_client instance-attribute

reversal_client = AsyncClient(
    wsdl=configs.REVERSAL_WSDL_URL,
    transport=async_transport,
)

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.AsyncParsianShaparakPaymentAdapter.initiate_payment async

initiate_payment(
    request: PaymentRequestDTO,
) -> PaymentResponseDTO

Step 1: Request payment token (async).

Parameters:

Name Type Description Default
request PaymentRequestDTO

Payment request data.

required

Returns:

Name Type Description
PaymentResponseDTO PaymentResponseDTO

Response containing token, status, and message.

Raises:

Type Description
UnavailableError

If a SOAP fault occurs during the request.

InternalError

If an unexpected error occurs during the request.

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/adapters.py
async def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
    """Step 1: Request payment token (async).

    Args:
        request: Payment request data.

    Returns:
        PaymentResponseDTO: Response containing token, status, and message.

    Raises:
        UnavailableError: If a SOAP fault occurs during the request.
        InternalError: If an unexpected error occurs during the request.
    """
    try:
        request_data = {
            "LoginAccount": self.login_account,
            "Amount": request.amount,
            "OrderId": request.order_id,
            "CallBackUrl": str(request.callback_url),
            "AdditionalData": request.additional_data,
            "Originator": request.originator,
        }

        logger.debug("Initiating payment: %s", request_data)
        response = await self.sale_client.service.SalePaymentRequest(requestData=request_data)
        result = PaymentResponseDTO(
            token=response.Token,
            status=response.Status,
            message=response.Message,
        )
        logger.debug("Payment response: %s", result)
    except Fault as exception:
        raise UnavailableError(resource_type="Parsian Shaparak Sale Service") from exception
    except Exception as exception:
        raise InternalError() from exception
    else:
        return result

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.AsyncParsianShaparakPaymentAdapter.confirm_payment async

confirm_payment(
    request: ConfirmRequestDTO,
) -> ConfirmResponseDTO

Step 3: Confirm transaction (async).

Parameters:

Name Type Description Default
request ConfirmRequestDTO

Confirm request data.

required

Returns:

Name Type Description
ConfirmResponseDTO ConfirmResponseDTO

Response containing status, RRN, card number, and token.

Raises:

Type Description
UnavailableError

If a SOAP fault occurs during the request.

InternalError

If an unexpected error occurs during the request.

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/adapters.py
async def confirm_payment(self, request: ConfirmRequestDTO) -> ConfirmResponseDTO:
    """Step 3: Confirm transaction (async).

    Args:
        request: Confirm request data.

    Returns:
        ConfirmResponseDTO: Response containing status, RRN, card number, and token.

    Raises:
        UnavailableError: If a SOAP fault occurs during the request.
        InternalError: If an unexpected error occurs during the request.
    """
    try:
        request_data = {"LoginAccount": self.login_account, "Token": request.token}

        logger.debug("Confirming payment: %s", request_data)
        response = await self.confirm_client.service.ConfirmPayment(requestData=request_data)
        result = ConfirmResponseDTO(
            status=response.Status,
            rrn=response.RRN,
            card_number_masked=response.CardNumberMasked,
            token=response.Token,
        )
        logger.debug("Confirm response: %s", result)
    except Fault as exception:
        raise UnavailableError(resource_type="Parsian Shaparak Confirm Service") from exception
    except Exception as exception:
        raise InternalError() from exception
    else:
        return result

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.AsyncParsianShaparakPaymentAdapter.confirm_payment_with_amount async

confirm_payment_with_amount(
    request: ConfirmWithAmountRequestDTO,
) -> ConfirmWithAmountResponseDTO

Confirm transaction with amount and order verification (async).

Parameters:

Name Type Description Default
request ConfirmWithAmountRequestDTO

Confirm with amount request data.

required

Returns:

Name Type Description
ConfirmWithAmountResponseDTO ConfirmWithAmountResponseDTO

Response containing status, RRN, card number, and token.

Raises:

Type Description
UnavailableError

If a SOAP fault occurs during the request.

InternalError

If an unexpected error occurs during the request.

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/adapters.py
async def confirm_payment_with_amount(self, request: ConfirmWithAmountRequestDTO) -> ConfirmWithAmountResponseDTO:
    """Confirm transaction with amount and order verification (async).

    Args:
        request: Confirm with amount request data.

    Returns:
        ConfirmWithAmountResponseDTO: Response containing status, RRN, card number, and token.

    Raises:
        UnavailableError: If a SOAP fault occurs during the request.
        InternalError: If an unexpected error occurs during the request.
    """
    try:
        request_data = {
            "LoginAccount": self.login_account,
            "Token": request.token,
            "OrderId": request.order_id,
            "Amount": request.amount,
        }

        logger.debug("Confirming payment with amount: %s", request_data)
        response = await self.confirm_client.service.ConfirmPaymentWithAmount(requestData=request_data)
        result = ConfirmWithAmountResponseDTO(
            status=response.Status,
            rrn=response.RRN,
            card_number_masked=response.CardNumberMasked,
            token=response.Token,
        )
        logger.debug("Confirm with amount response: %s", result)
    except Fault as exception:
        raise UnavailableError(resource_type="Parsian Shaparak Confirm Service") from exception
    except Exception as exception:
        raise InternalError() from exception
    else:
        return result

archipy.adapters.internet_payment_gateways.ir.parsian.adapters.AsyncParsianShaparakPaymentAdapter.reverse_payment async

reverse_payment(
    request: ReverseRequestDTO,
) -> ReverseResponseDTO

Reverse a transaction (async).

Parameters:

Name Type Description Default
request ReverseRequestDTO

Reverse request data.

required

Returns:

Name Type Description
ReverseResponseDTO ReverseResponseDTO

Response containing status, message, and token.

Raises:

Type Description
UnavailableError

If a SOAP fault occurs during the request.

InternalError

If an unexpected error occurs during the request.

Source code in packages/parsian-ipg/archipy/adapters/internet_payment_gateways/ir/parsian/adapters.py
async def reverse_payment(self, request: ReverseRequestDTO) -> ReverseResponseDTO:
    """Reverse a transaction (async).

    Args:
        request: Reverse request data.

    Returns:
        ReverseResponseDTO: Response containing status, message, and token.

    Raises:
        UnavailableError: If a SOAP fault occurs during the request.
        InternalError: If an unexpected error occurs during the request.
    """
    try:
        request_data = {"LoginAccount": self.login_account, "Token": request.token}

        logger.debug("Reversing payment: %s", request_data)
        response = await self.reversal_client.service.ReversalRequest(requestData=request_data)
        result = ReverseResponseDTO(
            status=response.Status,
            message=response.Message,
            token=response.Token,
        )
        logger.debug("Reversal response: %s", result)
    except Fault as exception:
        raise UnavailableError(resource_type="Parsian Shaparak Reversal Service") from exception
    except Exception as exception:
        raise InternalError() from exception
    else:
        return result

options: show_root_toc_entry: false heading_level: 3

Saman Shaparak

Saman Shaparak adapters using the REST/JSON protocol. Includes four variants:

  • SamanShaparakPaymentAdapter — Classic SEP adapter, redirects to fixed payment URL
  • SamanNeoPgShaparakPaymentAdapter — Neo-PG adapter, receives dynamic payment URL from X-IPG-Url response header
  • AsyncSamanShaparakPaymentAdapter — Async version of classic SEP adapter
  • AsyncSamanNeoPgShaparakPaymentAdapter — Async version of Neo-PG adapter

Ports

Abstract port interfaces defining the Saman Shaparak adapter contract.

Port interfaces for Saman IPG operations.

archipy.adapters.internet_payment_gateways.ir.saman.ports.SamanShaparakPaymentPort

Port interface for Saman Shaparak payment gateway.

Defines the contract for Saman IPG adapters implementing payment operations (token request, verification, and reversal).

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/ports.py
class SamanShaparakPaymentPort:
    """Port interface for Saman Shaparak payment gateway.

    Defines the contract for Saman IPG adapters implementing payment
    operations (token request, verification, and reversal).
    """

    @abstractmethod
    def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
        """Step 1: Request payment token."""
        raise NotImplementedError

    @abstractmethod
    def verify_payment(self, request: VerifyRequestDTO) -> VerifyResponseDTO:
        """Step 3: Verify transaction."""
        raise NotImplementedError

    @abstractmethod
    def reverse_payment(self, request: ReverseRequestDTO) -> ReverseResponseDTO:
        """Reverse a transaction."""
        raise NotImplementedError

archipy.adapters.internet_payment_gateways.ir.saman.ports.SamanShaparakPaymentPort.initiate_payment abstractmethod

initiate_payment(
    request: PaymentRequestDTO,
) -> PaymentResponseDTO

Step 1: Request payment token.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/ports.py
@abstractmethod
def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
    """Step 1: Request payment token."""
    raise NotImplementedError

archipy.adapters.internet_payment_gateways.ir.saman.ports.SamanShaparakPaymentPort.verify_payment abstractmethod

verify_payment(
    request: VerifyRequestDTO,
) -> VerifyResponseDTO

Step 3: Verify transaction.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/ports.py
@abstractmethod
def verify_payment(self, request: VerifyRequestDTO) -> VerifyResponseDTO:
    """Step 3: Verify transaction."""
    raise NotImplementedError

archipy.adapters.internet_payment_gateways.ir.saman.ports.SamanShaparakPaymentPort.reverse_payment abstractmethod

reverse_payment(
    request: ReverseRequestDTO,
) -> ReverseResponseDTO

Reverse a transaction.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/ports.py
@abstractmethod
def reverse_payment(self, request: ReverseRequestDTO) -> ReverseResponseDTO:
    """Reverse a transaction."""
    raise NotImplementedError

archipy.adapters.internet_payment_gateways.ir.saman.ports.AsyncSamanShaparakPaymentPort

Async port interface for Saman Shaparak payment gateway.

Defines the contract for async Saman IPG adapters implementing payment operations (token request, verification, and reversal).

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/ports.py
class AsyncSamanShaparakPaymentPort:
    """Async port interface for Saman Shaparak payment gateway.

    Defines the contract for async Saman IPG adapters implementing payment
    operations (token request, verification, and reversal).
    """

    @abstractmethod
    async def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
        """Step 1: Request payment token (async)."""
        raise NotImplementedError

    @abstractmethod
    async def verify_payment(self, request: VerifyRequestDTO) -> VerifyResponseDTO:
        """Step 3: Verify transaction (async)."""
        raise NotImplementedError

    @abstractmethod
    async def reverse_payment(self, request: ReverseRequestDTO) -> ReverseResponseDTO:
        """Reverse a transaction (async)."""
        raise NotImplementedError

archipy.adapters.internet_payment_gateways.ir.saman.ports.AsyncSamanShaparakPaymentPort.initiate_payment abstractmethod async

initiate_payment(
    request: PaymentRequestDTO,
) -> PaymentResponseDTO

Step 1: Request payment token (async).

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/ports.py
@abstractmethod
async def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
    """Step 1: Request payment token (async)."""
    raise NotImplementedError

archipy.adapters.internet_payment_gateways.ir.saman.ports.AsyncSamanShaparakPaymentPort.verify_payment abstractmethod async

verify_payment(
    request: VerifyRequestDTO,
) -> VerifyResponseDTO

Step 3: Verify transaction (async).

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/ports.py
@abstractmethod
async def verify_payment(self, request: VerifyRequestDTO) -> VerifyResponseDTO:
    """Step 3: Verify transaction (async)."""
    raise NotImplementedError

archipy.adapters.internet_payment_gateways.ir.saman.ports.AsyncSamanShaparakPaymentPort.reverse_payment abstractmethod async

reverse_payment(
    request: ReverseRequestDTO,
) -> ReverseResponseDTO

Reverse a transaction (async).

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/ports.py
@abstractmethod
async def reverse_payment(self, request: ReverseRequestDTO) -> ReverseResponseDTO:
    """Reverse a transaction (async)."""
    raise NotImplementedError

options: show_root_toc_entry: false heading_level: 3

Adapters

Saman IPG adapter implementations.

archipy.adapters.internet_payment_gateways.ir.saman.adapters.logger module-attribute

logger = logging.getLogger(__name__)

archipy.adapters.internet_payment_gateways.ir.saman.adapters.SamanShaparakPaymentAdapter

Bases: SamanShaparakPaymentPort

Saman Shaparak (SEP) Classic Adapter - Full v3/.5 Protocol.

Implements the SamanShaparakPaymentPort interface using httpx for HTTP operations. Provides methods for initiating payments, verifying transactions, and reversing payments.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/adapters.py
class SamanShaparakPaymentAdapter(SamanShaparakPaymentPort):
    """Saman Shaparak (SEP) Classic Adapter - Full v3/.5 Protocol.

    Implements the SamanShaparakPaymentPort interface using httpx for HTTP operations.
    Provides methods for initiating payments, verifying transactions, and reversing payments.
    """

    def __init__(self, config: SamanShaparakConfig | None = None) -> None:
        """Initialize SamanShaparakPaymentAdapter.

        Args:
            config: Optional Saman Shaparak config; defaults to the global config.

        Raises:
            InvalidArgumentError: If TERMINAL_ID is not configured.
        """
        configs = config or BaseConfig.global_config().SAMAN_SHAPARAK

        if not configs.TERMINAL_ID:
            raise InvalidArgumentError(argument_name="TERMINAL_ID")

        self.terminal_id = configs.TERMINAL_ID
        self.payment_url = str(configs.PAYMENT_URL)
        self.verify_url = str(configs.VERIFY_URL)
        self.reverse_url = str(configs.REVERSE_URL)

        proxy = self._get_proxy(configs.PROXIES)
        self.client = httpx2.Client(proxy=proxy, timeout=30)

        logger.info("SamanShaparakPaymentAdapter initialized with terminal %s", self.terminal_id)

    @staticmethod
    def _get_proxy(proxies: dict[str, str] | None) -> str | None:
        if proxies is None:
            return None
        return proxies.get("https") or proxies.get("http") or proxies.get("socks5") or proxies.get("socks5h")

    def _get_terminal_number(self) -> int:
        """Convert the configured terminal ID to the numeric form required by verify/reverse.

        Returns:
            int: Numeric terminal number.

        Raises:
            InvalidArgumentError: If TERMINAL_ID is not numeric.
        """
        try:
            return int(self.terminal_id)
        except ValueError as e:
            raise InvalidArgumentError(argument_name="TERMINAL_ID") from e

    def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
        """Step 1: Request payment token.

        Args:
            request: Payment request data.

        Returns:
            PaymentResponseDTO: Response containing token, status, etc.

        Raises:
            UnavailableError: If the payment service is unavailable or returns HTTP 5xx.
            InternalError: If an unexpected error occurs.
        """
        try:
            payload = {
                "action": "token",
                "TerminalId": self.terminal_id,
                "Amount": request.amount,
                "ResNum": request.res_num,
                "RedirectUrl": str(request.redirect_url),
                "CellNumber": request.cell_number,
                "Wage": request.wage,
                "TokenExpiryInMin": request.token_expiry_in_min,
                "HashedCardNumber": request.hashed_card_number,
            }
            payload = {k: v for k, v in payload.items() if v is not None}

            logger.debug("Saman initiate payment payload: %s", payload)
            resp = self.client.post(self.payment_url, json=payload)
            resp.raise_for_status()
            data = resp.json()

            return PaymentResponseDTO(
                status=data.get("status"),
                token=data.get("token"),
                error_code=data.get("errorCode"),
                error_desc=data.get("errorDesc"),
            )
        except httpx2.RequestError as e:
            raise UnavailableError(resource_type="Saman Token Service") from e
        except httpx2.HTTPStatusError as e:
            if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
                raise UnavailableError(resource_type="Saman Token Service") from e
            raise InternalError() from e
        except Exception as e:
            raise InternalError() from e

    def verify_payment(self, request: VerifyRequestDTO) -> VerifyResponseDTO:
        """Step 3: Verify transaction (strongly recommended).

        Args:
            request: Verify request with reference number.

        Returns:
            VerifyResponseDTO: Response containing transaction details.

        Raises:
            InvalidArgumentError: If TERMINAL_ID is not numeric.
            UnavailableError: If the verify service is unavailable or returns HTTP 5xx.
            InternalError: If an unexpected error occurs.
        """
        terminal_number = self._get_terminal_number()
        try:
            payload = {
                "RefNum": request.reference_number,
                "TerminalNumber": terminal_number,
            }

            resp = self.client.post(self.verify_url, json=payload)
            resp.raise_for_status()
            data = resp.json()

            transaction_detail = data.get("TransactionDetail") or {}
            return VerifyResponseDTO(
                success=data.get("Success", False),
                result_code=data.get("ResultCode", -1),
                result_description=data.get("ResultDescription", "Unknown"),
                rrn=transaction_detail.get("RRN"),
                reference_number=transaction_detail.get("RefNum"),
                masked_pan=transaction_detail.get("MaskedPan"),
                hashed_pan=transaction_detail.get("HashedPan"),
                original_amount=transaction_detail.get("OrginalAmount"),
                affective_amount=transaction_detail.get("AffectiveAmount"),
                trace_no=transaction_detail.get("StraceNo"),
            )
        except httpx2.RequestError as e:
            raise UnavailableError(resource_type="Saman Verify Service") from e
        except httpx2.HTTPStatusError as e:
            if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
                raise UnavailableError(resource_type="Saman Verify Service") from e
            raise InternalError() from e
        except Exception as e:
            raise InternalError() from e

    def reverse_payment(self, request: ReverseRequestDTO) -> ReverseResponseDTO:
        """Reverse a transaction.

        Args:
            request: Reverse request with reference number.

        Returns:
            ReverseResponseDTO: Response containing reversal result.

        Raises:
            InvalidArgumentError: If TERMINAL_ID is not numeric.
            UnavailableError: If the reverse service is unavailable or returns HTTP 5xx.
            InternalError: If an unexpected error occurs.
        """
        terminal_number = self._get_terminal_number()
        try:
            payload = {
                "RefNum": request.reference_number,
                "TerminalNumber": terminal_number,
            }
            resp = self.client.post(self.reverse_url, json=payload)
            resp.raise_for_status()
            data = resp.json()

            return ReverseResponseDTO(
                success=data.get("Success", False),
                result_code=data.get("ResultCode", -1),
                result_description=data.get("ResultDescription", "Unknown"),
            )
        except httpx2.RequestError as e:
            raise UnavailableError(resource_type="Saman Reverse Service") from e
        except httpx2.HTTPStatusError as e:
            if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
                raise UnavailableError(resource_type="Saman Reverse Service") from e
            raise InternalError() from e
        except Exception as e:
            raise InternalError() from e

archipy.adapters.internet_payment_gateways.ir.saman.adapters.SamanShaparakPaymentAdapter.terminal_id instance-attribute

terminal_id = configs.TERMINAL_ID

archipy.adapters.internet_payment_gateways.ir.saman.adapters.SamanShaparakPaymentAdapter.payment_url instance-attribute

payment_url = str(configs.PAYMENT_URL)

archipy.adapters.internet_payment_gateways.ir.saman.adapters.SamanShaparakPaymentAdapter.verify_url instance-attribute

verify_url = str(configs.VERIFY_URL)

archipy.adapters.internet_payment_gateways.ir.saman.adapters.SamanShaparakPaymentAdapter.reverse_url instance-attribute

reverse_url = str(configs.REVERSE_URL)

archipy.adapters.internet_payment_gateways.ir.saman.adapters.SamanShaparakPaymentAdapter.client instance-attribute

client = httpx2.Client(proxy=proxy, timeout=30)

archipy.adapters.internet_payment_gateways.ir.saman.adapters.SamanShaparakPaymentAdapter.initiate_payment

initiate_payment(
    request: PaymentRequestDTO,
) -> PaymentResponseDTO

Step 1: Request payment token.

Parameters:

Name Type Description Default
request PaymentRequestDTO

Payment request data.

required

Returns:

Name Type Description
PaymentResponseDTO PaymentResponseDTO

Response containing token, status, etc.

Raises:

Type Description
UnavailableError

If the payment service is unavailable or returns HTTP 5xx.

InternalError

If an unexpected error occurs.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/adapters.py
def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
    """Step 1: Request payment token.

    Args:
        request: Payment request data.

    Returns:
        PaymentResponseDTO: Response containing token, status, etc.

    Raises:
        UnavailableError: If the payment service is unavailable or returns HTTP 5xx.
        InternalError: If an unexpected error occurs.
    """
    try:
        payload = {
            "action": "token",
            "TerminalId": self.terminal_id,
            "Amount": request.amount,
            "ResNum": request.res_num,
            "RedirectUrl": str(request.redirect_url),
            "CellNumber": request.cell_number,
            "Wage": request.wage,
            "TokenExpiryInMin": request.token_expiry_in_min,
            "HashedCardNumber": request.hashed_card_number,
        }
        payload = {k: v for k, v in payload.items() if v is not None}

        logger.debug("Saman initiate payment payload: %s", payload)
        resp = self.client.post(self.payment_url, json=payload)
        resp.raise_for_status()
        data = resp.json()

        return PaymentResponseDTO(
            status=data.get("status"),
            token=data.get("token"),
            error_code=data.get("errorCode"),
            error_desc=data.get("errorDesc"),
        )
    except httpx2.RequestError as e:
        raise UnavailableError(resource_type="Saman Token Service") from e
    except httpx2.HTTPStatusError as e:
        if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
            raise UnavailableError(resource_type="Saman Token Service") from e
        raise InternalError() from e
    except Exception as e:
        raise InternalError() from e

archipy.adapters.internet_payment_gateways.ir.saman.adapters.SamanShaparakPaymentAdapter.verify_payment

verify_payment(
    request: VerifyRequestDTO,
) -> VerifyResponseDTO

Step 3: Verify transaction (strongly recommended).

Parameters:

Name Type Description Default
request VerifyRequestDTO

Verify request with reference number.

required

Returns:

Name Type Description
VerifyResponseDTO VerifyResponseDTO

Response containing transaction details.

Raises:

Type Description
InvalidArgumentError

If TERMINAL_ID is not numeric.

UnavailableError

If the verify service is unavailable or returns HTTP 5xx.

InternalError

If an unexpected error occurs.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/adapters.py
def verify_payment(self, request: VerifyRequestDTO) -> VerifyResponseDTO:
    """Step 3: Verify transaction (strongly recommended).

    Args:
        request: Verify request with reference number.

    Returns:
        VerifyResponseDTO: Response containing transaction details.

    Raises:
        InvalidArgumentError: If TERMINAL_ID is not numeric.
        UnavailableError: If the verify service is unavailable or returns HTTP 5xx.
        InternalError: If an unexpected error occurs.
    """
    terminal_number = self._get_terminal_number()
    try:
        payload = {
            "RefNum": request.reference_number,
            "TerminalNumber": terminal_number,
        }

        resp = self.client.post(self.verify_url, json=payload)
        resp.raise_for_status()
        data = resp.json()

        transaction_detail = data.get("TransactionDetail") or {}
        return VerifyResponseDTO(
            success=data.get("Success", False),
            result_code=data.get("ResultCode", -1),
            result_description=data.get("ResultDescription", "Unknown"),
            rrn=transaction_detail.get("RRN"),
            reference_number=transaction_detail.get("RefNum"),
            masked_pan=transaction_detail.get("MaskedPan"),
            hashed_pan=transaction_detail.get("HashedPan"),
            original_amount=transaction_detail.get("OrginalAmount"),
            affective_amount=transaction_detail.get("AffectiveAmount"),
            trace_no=transaction_detail.get("StraceNo"),
        )
    except httpx2.RequestError as e:
        raise UnavailableError(resource_type="Saman Verify Service") from e
    except httpx2.HTTPStatusError as e:
        if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
            raise UnavailableError(resource_type="Saman Verify Service") from e
        raise InternalError() from e
    except Exception as e:
        raise InternalError() from e

archipy.adapters.internet_payment_gateways.ir.saman.adapters.SamanShaparakPaymentAdapter.reverse_payment

reverse_payment(
    request: ReverseRequestDTO,
) -> ReverseResponseDTO

Reverse a transaction.

Parameters:

Name Type Description Default
request ReverseRequestDTO

Reverse request with reference number.

required

Returns:

Name Type Description
ReverseResponseDTO ReverseResponseDTO

Response containing reversal result.

Raises:

Type Description
InvalidArgumentError

If TERMINAL_ID is not numeric.

UnavailableError

If the reverse service is unavailable or returns HTTP 5xx.

InternalError

If an unexpected error occurs.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/adapters.py
def reverse_payment(self, request: ReverseRequestDTO) -> ReverseResponseDTO:
    """Reverse a transaction.

    Args:
        request: Reverse request with reference number.

    Returns:
        ReverseResponseDTO: Response containing reversal result.

    Raises:
        InvalidArgumentError: If TERMINAL_ID is not numeric.
        UnavailableError: If the reverse service is unavailable or returns HTTP 5xx.
        InternalError: If an unexpected error occurs.
    """
    terminal_number = self._get_terminal_number()
    try:
        payload = {
            "RefNum": request.reference_number,
            "TerminalNumber": terminal_number,
        }
        resp = self.client.post(self.reverse_url, json=payload)
        resp.raise_for_status()
        data = resp.json()

        return ReverseResponseDTO(
            success=data.get("Success", False),
            result_code=data.get("ResultCode", -1),
            result_description=data.get("ResultDescription", "Unknown"),
        )
    except httpx2.RequestError as e:
        raise UnavailableError(resource_type="Saman Reverse Service") from e
    except httpx2.HTTPStatusError as e:
        if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
            raise UnavailableError(resource_type="Saman Reverse Service") from e
        raise InternalError() from e
    except Exception as e:
        raise InternalError() from e

archipy.adapters.internet_payment_gateways.ir.saman.adapters.SamanNeoPgShaparakPaymentAdapter

Bases: SamanShaparakPaymentAdapter

Saman Neo-PG (New Generation) Adapter - Uses dynamic X-IPG-Url.

Extends SamanShaparakPaymentAdapter to capture the dynamic payment page URL from the X-IPG-Url header in the token response.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/adapters.py
class SamanNeoPgShaparakPaymentAdapter(SamanShaparakPaymentAdapter):
    """Saman Neo-PG (New Generation) Adapter - Uses dynamic X-IPG-Url.

    Extends SamanShaparakPaymentAdapter to capture the dynamic payment page URL
    from the X-IPG-Url header in the token response.
    """

    def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
        """Override to capture dynamic neo-pg URL from header.

        Args:
            request: Payment request data.

        Returns:
            PaymentResponseDTO: Response containing token, status, and IPG URL.

        Raises:
            UnavailableError: If the payment service is unavailable or returns HTTP 5xx.
            InternalError: If an unexpected error occurs.
        """
        try:
            payload = {
                "action": "token",
                "TerminalId": self.terminal_id,
                "Amount": request.amount,
                "ResNum": request.res_num,
                "RedirectUrl": str(request.redirect_url),
                "CellNumber": request.cell_number,
                "Wage": request.wage,
                "TokenExpiryInMin": request.token_expiry_in_min,
                "HashedCardNumber": request.hashed_card_number,
            }
            payload = {k: v for k, v in payload.items() if v is not None}

            resp = self.client.post(self.payment_url, json=payload)
            resp.raise_for_status()
            data = resp.json()

            ipg_url = resp.headers.get("X-IPG-Url")

            return PaymentResponseDTO(
                status=data.get("status"),
                token=data.get("token"),
                error_code=data.get("errorCode"),
                error_desc=data.get("errorDesc"),
                ipg_url=ipg_url,
            )
        except httpx2.RequestError as e:
            raise UnavailableError(resource_type="Saman Neo-PG Token Service") from e
        except httpx2.HTTPStatusError as e:
            if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
                raise UnavailableError(resource_type="Saman Neo-PG Token Service") from e
            raise InternalError() from e
        except Exception as e:
            raise InternalError() from e

archipy.adapters.internet_payment_gateways.ir.saman.adapters.SamanNeoPgShaparakPaymentAdapter.terminal_id instance-attribute

terminal_id = configs.TERMINAL_ID

archipy.adapters.internet_payment_gateways.ir.saman.adapters.SamanNeoPgShaparakPaymentAdapter.payment_url instance-attribute

payment_url = str(configs.PAYMENT_URL)

archipy.adapters.internet_payment_gateways.ir.saman.adapters.SamanNeoPgShaparakPaymentAdapter.verify_url instance-attribute

verify_url = str(configs.VERIFY_URL)

archipy.adapters.internet_payment_gateways.ir.saman.adapters.SamanNeoPgShaparakPaymentAdapter.reverse_url instance-attribute

reverse_url = str(configs.REVERSE_URL)

archipy.adapters.internet_payment_gateways.ir.saman.adapters.SamanNeoPgShaparakPaymentAdapter.client instance-attribute

client = httpx2.Client(proxy=proxy, timeout=30)

archipy.adapters.internet_payment_gateways.ir.saman.adapters.SamanNeoPgShaparakPaymentAdapter.initiate_payment

initiate_payment(
    request: PaymentRequestDTO,
) -> PaymentResponseDTO

Override to capture dynamic neo-pg URL from header.

Parameters:

Name Type Description Default
request PaymentRequestDTO

Payment request data.

required

Returns:

Name Type Description
PaymentResponseDTO PaymentResponseDTO

Response containing token, status, and IPG URL.

Raises:

Type Description
UnavailableError

If the payment service is unavailable or returns HTTP 5xx.

InternalError

If an unexpected error occurs.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/adapters.py
def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
    """Override to capture dynamic neo-pg URL from header.

    Args:
        request: Payment request data.

    Returns:
        PaymentResponseDTO: Response containing token, status, and IPG URL.

    Raises:
        UnavailableError: If the payment service is unavailable or returns HTTP 5xx.
        InternalError: If an unexpected error occurs.
    """
    try:
        payload = {
            "action": "token",
            "TerminalId": self.terminal_id,
            "Amount": request.amount,
            "ResNum": request.res_num,
            "RedirectUrl": str(request.redirect_url),
            "CellNumber": request.cell_number,
            "Wage": request.wage,
            "TokenExpiryInMin": request.token_expiry_in_min,
            "HashedCardNumber": request.hashed_card_number,
        }
        payload = {k: v for k, v in payload.items() if v is not None}

        resp = self.client.post(self.payment_url, json=payload)
        resp.raise_for_status()
        data = resp.json()

        ipg_url = resp.headers.get("X-IPG-Url")

        return PaymentResponseDTO(
            status=data.get("status"),
            token=data.get("token"),
            error_code=data.get("errorCode"),
            error_desc=data.get("errorDesc"),
            ipg_url=ipg_url,
        )
    except httpx2.RequestError as e:
        raise UnavailableError(resource_type="Saman Neo-PG Token Service") from e
    except httpx2.HTTPStatusError as e:
        if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
            raise UnavailableError(resource_type="Saman Neo-PG Token Service") from e
        raise InternalError() from e
    except Exception as e:
        raise InternalError() from e

archipy.adapters.internet_payment_gateways.ir.saman.adapters.SamanNeoPgShaparakPaymentAdapter.verify_payment

verify_payment(
    request: VerifyRequestDTO,
) -> VerifyResponseDTO

Step 3: Verify transaction (strongly recommended).

Parameters:

Name Type Description Default
request VerifyRequestDTO

Verify request with reference number.

required

Returns:

Name Type Description
VerifyResponseDTO VerifyResponseDTO

Response containing transaction details.

Raises:

Type Description
InvalidArgumentError

If TERMINAL_ID is not numeric.

UnavailableError

If the verify service is unavailable or returns HTTP 5xx.

InternalError

If an unexpected error occurs.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/adapters.py
def verify_payment(self, request: VerifyRequestDTO) -> VerifyResponseDTO:
    """Step 3: Verify transaction (strongly recommended).

    Args:
        request: Verify request with reference number.

    Returns:
        VerifyResponseDTO: Response containing transaction details.

    Raises:
        InvalidArgumentError: If TERMINAL_ID is not numeric.
        UnavailableError: If the verify service is unavailable or returns HTTP 5xx.
        InternalError: If an unexpected error occurs.
    """
    terminal_number = self._get_terminal_number()
    try:
        payload = {
            "RefNum": request.reference_number,
            "TerminalNumber": terminal_number,
        }

        resp = self.client.post(self.verify_url, json=payload)
        resp.raise_for_status()
        data = resp.json()

        transaction_detail = data.get("TransactionDetail") or {}
        return VerifyResponseDTO(
            success=data.get("Success", False),
            result_code=data.get("ResultCode", -1),
            result_description=data.get("ResultDescription", "Unknown"),
            rrn=transaction_detail.get("RRN"),
            reference_number=transaction_detail.get("RefNum"),
            masked_pan=transaction_detail.get("MaskedPan"),
            hashed_pan=transaction_detail.get("HashedPan"),
            original_amount=transaction_detail.get("OrginalAmount"),
            affective_amount=transaction_detail.get("AffectiveAmount"),
            trace_no=transaction_detail.get("StraceNo"),
        )
    except httpx2.RequestError as e:
        raise UnavailableError(resource_type="Saman Verify Service") from e
    except httpx2.HTTPStatusError as e:
        if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
            raise UnavailableError(resource_type="Saman Verify Service") from e
        raise InternalError() from e
    except Exception as e:
        raise InternalError() from e

archipy.adapters.internet_payment_gateways.ir.saman.adapters.SamanNeoPgShaparakPaymentAdapter.reverse_payment

reverse_payment(
    request: ReverseRequestDTO,
) -> ReverseResponseDTO

Reverse a transaction.

Parameters:

Name Type Description Default
request ReverseRequestDTO

Reverse request with reference number.

required

Returns:

Name Type Description
ReverseResponseDTO ReverseResponseDTO

Response containing reversal result.

Raises:

Type Description
InvalidArgumentError

If TERMINAL_ID is not numeric.

UnavailableError

If the reverse service is unavailable or returns HTTP 5xx.

InternalError

If an unexpected error occurs.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/adapters.py
def reverse_payment(self, request: ReverseRequestDTO) -> ReverseResponseDTO:
    """Reverse a transaction.

    Args:
        request: Reverse request with reference number.

    Returns:
        ReverseResponseDTO: Response containing reversal result.

    Raises:
        InvalidArgumentError: If TERMINAL_ID is not numeric.
        UnavailableError: If the reverse service is unavailable or returns HTTP 5xx.
        InternalError: If an unexpected error occurs.
    """
    terminal_number = self._get_terminal_number()
    try:
        payload = {
            "RefNum": request.reference_number,
            "TerminalNumber": terminal_number,
        }
        resp = self.client.post(self.reverse_url, json=payload)
        resp.raise_for_status()
        data = resp.json()

        return ReverseResponseDTO(
            success=data.get("Success", False),
            result_code=data.get("ResultCode", -1),
            result_description=data.get("ResultDescription", "Unknown"),
        )
    except httpx2.RequestError as e:
        raise UnavailableError(resource_type="Saman Reverse Service") from e
    except httpx2.HTTPStatusError as e:
        if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
            raise UnavailableError(resource_type="Saman Reverse Service") from e
        raise InternalError() from e
    except Exception as e:
        raise InternalError() from e

archipy.adapters.internet_payment_gateways.ir.saman.adapters.AsyncSamanShaparakPaymentAdapter

Bases: AsyncSamanShaparakPaymentPort

Async Saman Shaparak (SEP) Classic Adapter - Full v3/.5 Protocol.

Async implementation of SamanShaparakPaymentPort using httpx2.AsyncClient. Provides async methods for initiating payments, verifying transactions, and reversing payments.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/adapters.py
class AsyncSamanShaparakPaymentAdapter(AsyncSamanShaparakPaymentPort):
    """Async Saman Shaparak (SEP) Classic Adapter - Full v3/.5 Protocol.

    Async implementation of SamanShaparakPaymentPort using httpx2.AsyncClient.
    Provides async methods for initiating payments, verifying transactions,
    and reversing payments.
    """

    def __init__(self, config: SamanShaparakConfig | None = None) -> None:
        """Initialize AsyncSamanShaparakPaymentAdapter.

        Args:
            config: Optional Saman Shaparak config; defaults to the global config.

        Raises:
            InvalidArgumentError: If TERMINAL_ID is not configured.
        """
        configs = config or BaseConfig.global_config().SAMAN_SHAPARAK

        if not configs.TERMINAL_ID:
            raise InvalidArgumentError(argument_name="TERMINAL_ID")

        self.terminal_id = configs.TERMINAL_ID
        self.payment_url = str(configs.PAYMENT_URL)
        self.verify_url = str(configs.VERIFY_URL)
        self.reverse_url = str(configs.REVERSE_URL)

        proxy = self._get_proxy(configs.PROXIES)
        self.client = httpx2.AsyncClient(proxy=proxy, timeout=30)

        logger.info(
            "AsyncSamanShaparakPaymentAdapter initialized with terminal %s",
            self.terminal_id,
        )

    @staticmethod
    def _get_proxy(proxies: dict[str, str] | None) -> str | None:
        if proxies is None:
            return None
        return proxies.get("https") or proxies.get("http") or proxies.get("socks5") or proxies.get("socks5h")

    def _get_terminal_number(self) -> int:
        """Convert the configured terminal ID to the numeric form required by verify/reverse.

        Returns:
            int: Numeric terminal number.

        Raises:
            InvalidArgumentError: If TERMINAL_ID is not numeric.
        """
        try:
            return int(self.terminal_id)
        except ValueError as e:
            raise InvalidArgumentError(argument_name="TERMINAL_ID") from e

    async def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
        """Step 1: Request payment token (async).

        Args:
            request: Payment request data.

        Returns:
            PaymentResponseDTO: Response containing token, status, etc.

        Raises:
            UnavailableError: If the payment service is unavailable or returns HTTP 5xx.
            InternalError: If an unexpected error occurs.
        """
        try:
            payload = {
                "action": "token",
                "TerminalId": self.terminal_id,
                "Amount": request.amount,
                "ResNum": request.res_num,
                "RedirectUrl": str(request.redirect_url),
                "CellNumber": request.cell_number,
                "Wage": request.wage,
                "TokenExpiryInMin": request.token_expiry_in_min,
                "HashedCardNumber": request.hashed_card_number,
            }
            payload = {k: v for k, v in payload.items() if v is not None}

            logger.debug("Saman async initiate payment payload: %s", payload)
            resp = await self.client.post(self.payment_url, json=payload)
            resp.raise_for_status()
            data = resp.json()

            return PaymentResponseDTO(
                status=data.get("status"),
                token=data.get("token"),
                error_code=data.get("errorCode"),
                error_desc=data.get("errorDesc"),
            )
        except httpx2.RequestError as e:
            raise UnavailableError(resource_type="Saman Token Service") from e
        except httpx2.HTTPStatusError as e:
            if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
                raise UnavailableError(resource_type="Saman Token Service") from e
            raise InternalError() from e
        except Exception as e:
            raise InternalError() from e

    async def verify_payment(self, request: VerifyRequestDTO) -> VerifyResponseDTO:
        """Step 3: Verify transaction (async, strongly recommended).

        Args:
            request: Verify request with reference number.

        Returns:
            VerifyResponseDTO: Response containing transaction details.

        Raises:
            InvalidArgumentError: If TERMINAL_ID is not numeric.
            UnavailableError: If the verify service is unavailable or returns HTTP 5xx.
            InternalError: If an unexpected error occurs.
        """
        terminal_number = self._get_terminal_number()
        try:
            payload = {
                "RefNum": request.reference_number,
                "TerminalNumber": terminal_number,
            }

            resp = await self.client.post(self.verify_url, json=payload)
            resp.raise_for_status()
            data = resp.json()

            transaction_detail = data.get("TransactionDetail") or {}
            return VerifyResponseDTO(
                success=data.get("Success", False),
                result_code=data.get("ResultCode", -1),
                result_description=data.get("ResultDescription", "Unknown"),
                rrn=transaction_detail.get("RRN"),
                reference_number=transaction_detail.get("RefNum"),
                masked_pan=transaction_detail.get("MaskedPan"),
                hashed_pan=transaction_detail.get("HashedPan"),
                original_amount=transaction_detail.get("OrginalAmount"),
                affective_amount=transaction_detail.get("AffectiveAmount"),
                trace_no=transaction_detail.get("StraceNo"),
            )
        except httpx2.RequestError as e:
            raise UnavailableError(resource_type="Saman Verify Service") from e
        except httpx2.HTTPStatusError as e:
            if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
                raise UnavailableError(resource_type="Saman Verify Service") from e
            raise InternalError() from e
        except Exception as e:
            raise InternalError() from e

    async def reverse_payment(self, request: ReverseRequestDTO) -> ReverseResponseDTO:
        """Reverse a transaction (async).

        Args:
            request: Reverse request with reference number.

        Returns:
            ReverseResponseDTO: Response containing reversal result.

        Raises:
            InvalidArgumentError: If TERMINAL_ID is not numeric.
            UnavailableError: If the reverse service is unavailable or returns HTTP 5xx.
            InternalError: If an unexpected error occurs.
        """
        terminal_number = self._get_terminal_number()
        try:
            payload = {
                "RefNum": request.reference_number,
                "TerminalNumber": terminal_number,
            }
            resp = await self.client.post(self.reverse_url, json=payload)
            resp.raise_for_status()
            data = resp.json()

            return ReverseResponseDTO(
                success=data.get("Success", False),
                result_code=data.get("ResultCode", -1),
                result_description=data.get("ResultDescription", "Unknown"),
            )
        except httpx2.RequestError as e:
            raise UnavailableError(resource_type="Saman Reverse Service") from e
        except httpx2.HTTPStatusError as e:
            if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
                raise UnavailableError(resource_type="Saman Reverse Service") from e
            raise InternalError() from e
        except Exception as e:
            raise InternalError() from e

archipy.adapters.internet_payment_gateways.ir.saman.adapters.AsyncSamanShaparakPaymentAdapter.terminal_id instance-attribute

terminal_id = configs.TERMINAL_ID

archipy.adapters.internet_payment_gateways.ir.saman.adapters.AsyncSamanShaparakPaymentAdapter.payment_url instance-attribute

payment_url = str(configs.PAYMENT_URL)

archipy.adapters.internet_payment_gateways.ir.saman.adapters.AsyncSamanShaparakPaymentAdapter.verify_url instance-attribute

verify_url = str(configs.VERIFY_URL)

archipy.adapters.internet_payment_gateways.ir.saman.adapters.AsyncSamanShaparakPaymentAdapter.reverse_url instance-attribute

reverse_url = str(configs.REVERSE_URL)

archipy.adapters.internet_payment_gateways.ir.saman.adapters.AsyncSamanShaparakPaymentAdapter.client instance-attribute

client = httpx2.AsyncClient(proxy=proxy, timeout=30)

archipy.adapters.internet_payment_gateways.ir.saman.adapters.AsyncSamanShaparakPaymentAdapter.initiate_payment async

initiate_payment(
    request: PaymentRequestDTO,
) -> PaymentResponseDTO

Step 1: Request payment token (async).

Parameters:

Name Type Description Default
request PaymentRequestDTO

Payment request data.

required

Returns:

Name Type Description
PaymentResponseDTO PaymentResponseDTO

Response containing token, status, etc.

Raises:

Type Description
UnavailableError

If the payment service is unavailable or returns HTTP 5xx.

InternalError

If an unexpected error occurs.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/adapters.py
async def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
    """Step 1: Request payment token (async).

    Args:
        request: Payment request data.

    Returns:
        PaymentResponseDTO: Response containing token, status, etc.

    Raises:
        UnavailableError: If the payment service is unavailable or returns HTTP 5xx.
        InternalError: If an unexpected error occurs.
    """
    try:
        payload = {
            "action": "token",
            "TerminalId": self.terminal_id,
            "Amount": request.amount,
            "ResNum": request.res_num,
            "RedirectUrl": str(request.redirect_url),
            "CellNumber": request.cell_number,
            "Wage": request.wage,
            "TokenExpiryInMin": request.token_expiry_in_min,
            "HashedCardNumber": request.hashed_card_number,
        }
        payload = {k: v for k, v in payload.items() if v is not None}

        logger.debug("Saman async initiate payment payload: %s", payload)
        resp = await self.client.post(self.payment_url, json=payload)
        resp.raise_for_status()
        data = resp.json()

        return PaymentResponseDTO(
            status=data.get("status"),
            token=data.get("token"),
            error_code=data.get("errorCode"),
            error_desc=data.get("errorDesc"),
        )
    except httpx2.RequestError as e:
        raise UnavailableError(resource_type="Saman Token Service") from e
    except httpx2.HTTPStatusError as e:
        if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
            raise UnavailableError(resource_type="Saman Token Service") from e
        raise InternalError() from e
    except Exception as e:
        raise InternalError() from e

archipy.adapters.internet_payment_gateways.ir.saman.adapters.AsyncSamanShaparakPaymentAdapter.verify_payment async

verify_payment(
    request: VerifyRequestDTO,
) -> VerifyResponseDTO

Step 3: Verify transaction (async, strongly recommended).

Parameters:

Name Type Description Default
request VerifyRequestDTO

Verify request with reference number.

required

Returns:

Name Type Description
VerifyResponseDTO VerifyResponseDTO

Response containing transaction details.

Raises:

Type Description
InvalidArgumentError

If TERMINAL_ID is not numeric.

UnavailableError

If the verify service is unavailable or returns HTTP 5xx.

InternalError

If an unexpected error occurs.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/adapters.py
async def verify_payment(self, request: VerifyRequestDTO) -> VerifyResponseDTO:
    """Step 3: Verify transaction (async, strongly recommended).

    Args:
        request: Verify request with reference number.

    Returns:
        VerifyResponseDTO: Response containing transaction details.

    Raises:
        InvalidArgumentError: If TERMINAL_ID is not numeric.
        UnavailableError: If the verify service is unavailable or returns HTTP 5xx.
        InternalError: If an unexpected error occurs.
    """
    terminal_number = self._get_terminal_number()
    try:
        payload = {
            "RefNum": request.reference_number,
            "TerminalNumber": terminal_number,
        }

        resp = await self.client.post(self.verify_url, json=payload)
        resp.raise_for_status()
        data = resp.json()

        transaction_detail = data.get("TransactionDetail") or {}
        return VerifyResponseDTO(
            success=data.get("Success", False),
            result_code=data.get("ResultCode", -1),
            result_description=data.get("ResultDescription", "Unknown"),
            rrn=transaction_detail.get("RRN"),
            reference_number=transaction_detail.get("RefNum"),
            masked_pan=transaction_detail.get("MaskedPan"),
            hashed_pan=transaction_detail.get("HashedPan"),
            original_amount=transaction_detail.get("OrginalAmount"),
            affective_amount=transaction_detail.get("AffectiveAmount"),
            trace_no=transaction_detail.get("StraceNo"),
        )
    except httpx2.RequestError as e:
        raise UnavailableError(resource_type="Saman Verify Service") from e
    except httpx2.HTTPStatusError as e:
        if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
            raise UnavailableError(resource_type="Saman Verify Service") from e
        raise InternalError() from e
    except Exception as e:
        raise InternalError() from e

archipy.adapters.internet_payment_gateways.ir.saman.adapters.AsyncSamanShaparakPaymentAdapter.reverse_payment async

reverse_payment(
    request: ReverseRequestDTO,
) -> ReverseResponseDTO

Reverse a transaction (async).

Parameters:

Name Type Description Default
request ReverseRequestDTO

Reverse request with reference number.

required

Returns:

Name Type Description
ReverseResponseDTO ReverseResponseDTO

Response containing reversal result.

Raises:

Type Description
InvalidArgumentError

If TERMINAL_ID is not numeric.

UnavailableError

If the reverse service is unavailable or returns HTTP 5xx.

InternalError

If an unexpected error occurs.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/adapters.py
async def reverse_payment(self, request: ReverseRequestDTO) -> ReverseResponseDTO:
    """Reverse a transaction (async).

    Args:
        request: Reverse request with reference number.

    Returns:
        ReverseResponseDTO: Response containing reversal result.

    Raises:
        InvalidArgumentError: If TERMINAL_ID is not numeric.
        UnavailableError: If the reverse service is unavailable or returns HTTP 5xx.
        InternalError: If an unexpected error occurs.
    """
    terminal_number = self._get_terminal_number()
    try:
        payload = {
            "RefNum": request.reference_number,
            "TerminalNumber": terminal_number,
        }
        resp = await self.client.post(self.reverse_url, json=payload)
        resp.raise_for_status()
        data = resp.json()

        return ReverseResponseDTO(
            success=data.get("Success", False),
            result_code=data.get("ResultCode", -1),
            result_description=data.get("ResultDescription", "Unknown"),
        )
    except httpx2.RequestError as e:
        raise UnavailableError(resource_type="Saman Reverse Service") from e
    except httpx2.HTTPStatusError as e:
        if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
            raise UnavailableError(resource_type="Saman Reverse Service") from e
        raise InternalError() from e
    except Exception as e:
        raise InternalError() from e

archipy.adapters.internet_payment_gateways.ir.saman.adapters.AsyncSamanNeoPgShaparakPaymentAdapter

Bases: AsyncSamanShaparakPaymentAdapter

Async Saman Neo-PG (New Generation) Adapter - Uses dynamic X-IPG-Url.

Async implementation extending AsyncSamanShaparakPaymentAdapter to capture the dynamic payment page URL from the X-IPG-Url header in the token response.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/adapters.py
class AsyncSamanNeoPgShaparakPaymentAdapter(AsyncSamanShaparakPaymentAdapter):
    """Async Saman Neo-PG (New Generation) Adapter - Uses dynamic X-IPG-Url.

    Async implementation extending AsyncSamanShaparakPaymentAdapter to capture
    the dynamic payment page URL from the X-IPG-Url header in the token response.
    """

    async def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
        """Override to capture dynamic neo-pg URL from header (async).

        Args:
            request: Payment request data.

        Returns:
            PaymentResponseDTO: Response containing token, status, and IPG URL.

        Raises:
            UnavailableError: If the payment service is unavailable or returns HTTP 5xx.
            InternalError: If an unexpected error occurs.
        """
        try:
            payload = {
                "action": "token",
                "TerminalId": self.terminal_id,
                "Amount": request.amount,
                "ResNum": request.res_num,
                "RedirectUrl": str(request.redirect_url),
                "CellNumber": request.cell_number,
                "Wage": request.wage,
                "TokenExpiryInMin": request.token_expiry_in_min,
                "HashedCardNumber": request.hashed_card_number,
            }
            payload = {k: v for k, v in payload.items() if v is not None}

            resp = await self.client.post(self.payment_url, json=payload)
            resp.raise_for_status()
            data = resp.json()

            ipg_url = resp.headers.get("X-IPG-Url")

            return PaymentResponseDTO(
                status=data.get("status"),
                token=data.get("token"),
                error_code=data.get("errorCode"),
                error_desc=data.get("errorDesc"),
                ipg_url=ipg_url,
            )
        except httpx2.RequestError as e:
            raise UnavailableError(resource_type="Saman Neo-PG Token Service") from e
        except httpx2.HTTPStatusError as e:
            if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
                raise UnavailableError(resource_type="Saman Neo-PG Token Service") from e
            raise InternalError() from e
        except Exception as e:
            raise InternalError() from e

archipy.adapters.internet_payment_gateways.ir.saman.adapters.AsyncSamanNeoPgShaparakPaymentAdapter.terminal_id instance-attribute

terminal_id = configs.TERMINAL_ID

archipy.adapters.internet_payment_gateways.ir.saman.adapters.AsyncSamanNeoPgShaparakPaymentAdapter.payment_url instance-attribute

payment_url = str(configs.PAYMENT_URL)

archipy.adapters.internet_payment_gateways.ir.saman.adapters.AsyncSamanNeoPgShaparakPaymentAdapter.verify_url instance-attribute

verify_url = str(configs.VERIFY_URL)

archipy.adapters.internet_payment_gateways.ir.saman.adapters.AsyncSamanNeoPgShaparakPaymentAdapter.reverse_url instance-attribute

reverse_url = str(configs.REVERSE_URL)

archipy.adapters.internet_payment_gateways.ir.saman.adapters.AsyncSamanNeoPgShaparakPaymentAdapter.client instance-attribute

client = httpx2.AsyncClient(proxy=proxy, timeout=30)

archipy.adapters.internet_payment_gateways.ir.saman.adapters.AsyncSamanNeoPgShaparakPaymentAdapter.initiate_payment async

initiate_payment(
    request: PaymentRequestDTO,
) -> PaymentResponseDTO

Override to capture dynamic neo-pg URL from header (async).

Parameters:

Name Type Description Default
request PaymentRequestDTO

Payment request data.

required

Returns:

Name Type Description
PaymentResponseDTO PaymentResponseDTO

Response containing token, status, and IPG URL.

Raises:

Type Description
UnavailableError

If the payment service is unavailable or returns HTTP 5xx.

InternalError

If an unexpected error occurs.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/adapters.py
async def initiate_payment(self, request: PaymentRequestDTO) -> PaymentResponseDTO:
    """Override to capture dynamic neo-pg URL from header (async).

    Args:
        request: Payment request data.

    Returns:
        PaymentResponseDTO: Response containing token, status, and IPG URL.

    Raises:
        UnavailableError: If the payment service is unavailable or returns HTTP 5xx.
        InternalError: If an unexpected error occurs.
    """
    try:
        payload = {
            "action": "token",
            "TerminalId": self.terminal_id,
            "Amount": request.amount,
            "ResNum": request.res_num,
            "RedirectUrl": str(request.redirect_url),
            "CellNumber": request.cell_number,
            "Wage": request.wage,
            "TokenExpiryInMin": request.token_expiry_in_min,
            "HashedCardNumber": request.hashed_card_number,
        }
        payload = {k: v for k, v in payload.items() if v is not None}

        resp = await self.client.post(self.payment_url, json=payload)
        resp.raise_for_status()
        data = resp.json()

        ipg_url = resp.headers.get("X-IPG-Url")

        return PaymentResponseDTO(
            status=data.get("status"),
            token=data.get("token"),
            error_code=data.get("errorCode"),
            error_desc=data.get("errorDesc"),
            ipg_url=ipg_url,
        )
    except httpx2.RequestError as e:
        raise UnavailableError(resource_type="Saman Neo-PG Token Service") from e
    except httpx2.HTTPStatusError as e:
        if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
            raise UnavailableError(resource_type="Saman Neo-PG Token Service") from e
        raise InternalError() from e
    except Exception as e:
        raise InternalError() from e

archipy.adapters.internet_payment_gateways.ir.saman.adapters.AsyncSamanNeoPgShaparakPaymentAdapter.verify_payment async

verify_payment(
    request: VerifyRequestDTO,
) -> VerifyResponseDTO

Step 3: Verify transaction (async, strongly recommended).

Parameters:

Name Type Description Default
request VerifyRequestDTO

Verify request with reference number.

required

Returns:

Name Type Description
VerifyResponseDTO VerifyResponseDTO

Response containing transaction details.

Raises:

Type Description
InvalidArgumentError

If TERMINAL_ID is not numeric.

UnavailableError

If the verify service is unavailable or returns HTTP 5xx.

InternalError

If an unexpected error occurs.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/adapters.py
async def verify_payment(self, request: VerifyRequestDTO) -> VerifyResponseDTO:
    """Step 3: Verify transaction (async, strongly recommended).

    Args:
        request: Verify request with reference number.

    Returns:
        VerifyResponseDTO: Response containing transaction details.

    Raises:
        InvalidArgumentError: If TERMINAL_ID is not numeric.
        UnavailableError: If the verify service is unavailable or returns HTTP 5xx.
        InternalError: If an unexpected error occurs.
    """
    terminal_number = self._get_terminal_number()
    try:
        payload = {
            "RefNum": request.reference_number,
            "TerminalNumber": terminal_number,
        }

        resp = await self.client.post(self.verify_url, json=payload)
        resp.raise_for_status()
        data = resp.json()

        transaction_detail = data.get("TransactionDetail") or {}
        return VerifyResponseDTO(
            success=data.get("Success", False),
            result_code=data.get("ResultCode", -1),
            result_description=data.get("ResultDescription", "Unknown"),
            rrn=transaction_detail.get("RRN"),
            reference_number=transaction_detail.get("RefNum"),
            masked_pan=transaction_detail.get("MaskedPan"),
            hashed_pan=transaction_detail.get("HashedPan"),
            original_amount=transaction_detail.get("OrginalAmount"),
            affective_amount=transaction_detail.get("AffectiveAmount"),
            trace_no=transaction_detail.get("StraceNo"),
        )
    except httpx2.RequestError as e:
        raise UnavailableError(resource_type="Saman Verify Service") from e
    except httpx2.HTTPStatusError as e:
        if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
            raise UnavailableError(resource_type="Saman Verify Service") from e
        raise InternalError() from e
    except Exception as e:
        raise InternalError() from e

archipy.adapters.internet_payment_gateways.ir.saman.adapters.AsyncSamanNeoPgShaparakPaymentAdapter.reverse_payment async

reverse_payment(
    request: ReverseRequestDTO,
) -> ReverseResponseDTO

Reverse a transaction (async).

Parameters:

Name Type Description Default
request ReverseRequestDTO

Reverse request with reference number.

required

Returns:

Name Type Description
ReverseResponseDTO ReverseResponseDTO

Response containing reversal result.

Raises:

Type Description
InvalidArgumentError

If TERMINAL_ID is not numeric.

UnavailableError

If the reverse service is unavailable or returns HTTP 5xx.

InternalError

If an unexpected error occurs.

Source code in packages/saman-ipg/archipy/adapters/internet_payment_gateways/ir/saman/adapters.py
async def reverse_payment(self, request: ReverseRequestDTO) -> ReverseResponseDTO:
    """Reverse a transaction (async).

    Args:
        request: Reverse request with reference number.

    Returns:
        ReverseResponseDTO: Response containing reversal result.

    Raises:
        InvalidArgumentError: If TERMINAL_ID is not numeric.
        UnavailableError: If the reverse service is unavailable or returns HTTP 5xx.
        InternalError: If an unexpected error occurs.
    """
    terminal_number = self._get_terminal_number()
    try:
        payload = {
            "RefNum": request.reference_number,
            "TerminalNumber": terminal_number,
        }
        resp = await self.client.post(self.reverse_url, json=payload)
        resp.raise_for_status()
        data = resp.json()

        return ReverseResponseDTO(
            success=data.get("Success", False),
            result_code=data.get("ResultCode", -1),
            result_description=data.get("ResultDescription", "Unknown"),
        )
    except httpx2.RequestError as e:
        raise UnavailableError(resource_type="Saman Reverse Service") from e
    except httpx2.HTTPStatusError as e:
        if e.response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
            raise UnavailableError(resource_type="Saman Reverse Service") from e
        raise InternalError() from e
    except Exception as e:
        raise InternalError() from e

options: show_root_toc_entry: false heading_level: 3