Source code for fsspyipay.pyipay

# -*- coding: utf-8 -*-
import time
import xml.etree.ElementTree as ET
from urllib import parse
from iso4217 import Currency

from .util import Action
from .util import decrypt_payload as decrypt
from .util import encrypt_payload as encrypt
from .util import read_resource_file, sanitize, validate_gw_url
from .util import check_if_set, LANG_LOOKUP

from .exceptions import InvalidGatewayResponse


[docs]class Gateway: """A class that represents a payment gateway for hosted transactions, using the resource kit. The first step is to initialize the gateway by providing it the configuration received from the bank. This configuration consists of the following: - A resource file (called resource.cgn), which is an encrypted file that contains connection and credentials for the gateway. - A keystore file (called keystore.bin), which is a Java keystore that contains the encrption key which is used to decrypt the resource file. - The alias, which is a string. This is the friendly name of your terminal as setup by the bank. It it used to extract the correct configuration from the resource file. In addition to the above three values, you must also pass in the amount that you want to charge the customer for this transaction. :param str keystore: The file system path to the keystore file (normally called keystore.bin) :param str resource: The file system path to the resource file (normally called resource.cgn) :param str alias: The terminal alias that is to be used. :param float amount: The amount that needs to be charged. 3 decimal places by default for KWD. The following can be optionally specified, but are set to reasonable defaults: :param str lang: The language which will be used for the hosted pages to the end user. Values are 'en' (for English), and 'ar' for Arabic. The default is 'en' :param str currency: The ISO currency code. Defaults to 'KWD'. If you change this, make sure the terminal supports the currency. :param str tracking_id: This is a unique id for each transaction that is required by the gateway. If not supplied, the current timestamp is used. Once you have created an instance of the Gateway, you can specify further configuration. There are two pieces of information that are critical, the response url and the error url. These urls cannot have any query strings or fragments; and must be publicly accessible as this is where the gateway will return the results of the transactions. Finally, you request the redirect url from the module; which is where you should redirect the customer to start the payment process. The example provides details on how this all works: :Example: >>> from fsspyipay import Gateway >>> resource_file = r'/home/me/resource.cgn' >>> keystore_file = r'/home/me/keystore.bin' >>> gw = Gateway(keystore_file, resource_file, 'alias', 4.546) >>> gw.response_url = 'https://example.com/response' >>> gw.error_url = 'https://example.com/error' >>> url = gw.generate_purchase_request() """ def __init__( self, keystore, resource, alias, amount, lang="en", currency='KWD', tracking_id=None, ): self.lang = LANG_LOOKUP.get(lang, 'USA') self.currency = Currency(currency).number self.amount = amount # Configure the terminal settings terminal_configuration = read_resource_file(resource, keystore, alias) root = ET.fromstring(terminal_configuration) terminal = {c.tag: c.text for c in root} self.password = terminal["password"] self.key = terminal["resourceKey"] self.portal_id = terminal["id"] self._pgw_url = terminal["webaddress"] if tracking_id is None: now = int(time.time()) self.tracking_id = f"{now}" # Setting these as private, since we will manipulate the values # and sanitize them before setting the final value self._udf1 = self._udf2 = self._udf3 = self._udf4 = self._udf5 = "" self._response_url = None self._error_url = None
[docs] def get_udf1(self): return self._udf1
[docs] def set_udf1(self, s): self._udf1 = sanitize(s)
[docs] def get_udf2(self): return self._udf2
[docs] def set_udf2(self, s): self._udf2 = sanitize(s)
[docs] def get_udf3(self): return self._udf3
[docs] def set_udf3(self, s): self._udf3 = sanitize(s)
[docs] def get_udf4(self): return self._udf4
[docs] def set_udf4(self, s): self._udf4 = sanitize(s)
[docs] def get_udf5(self): return self._udf5
[docs] def set_udf5(self, s): self._udf5 = sanitize(s)
[docs] def set_response_url(self, s): self._response_url = validate_gw_url(s)
[docs] def set_error_url(self, s): self._error_url = validate_gw_url(s)
[docs] def get_response_url(self): return self._response_url
[docs] def get_error_url(self): return self._error_url
udf1 = property( get_udf1, set_udf1, None, "Set and retrieve UDF1, the values are sanitized." ) udf2 = property( get_udf2, set_udf2, None, "Set and retrieve UDF2, the values are sanitized." ) udf3 = property( get_udf3, set_udf3, None, "Set and retrieve UDF3, the values are sanitized." ) udf4 = property( get_udf4, set_udf4, None, "Set and retrieve UDF4, the values are sanitized." ) udf5 = property( get_udf5, set_udf5, None, "Set and retrieve UDF5, the values are sanitized." ) response_url = property( get_response_url, set_response_url, None, "Set and retrieve the response URL" ) error_url = property( get_error_url, set_error_url, None, "Set and retrieve the error URL" ) def _build_param_dict(self, action): params = [] if action == 1: params.extend( [ ("action", action), ("id", self.portal_id), ("password", self.password), ("langid", self.lang), ("currencycode", self.currency), ("trackid", self.tracking_id), ("amt", self.amount), ("udf1", self.udf1), ("udf2", self.udf2), ("udf3", self.udf3), ("udf4", self.udf4), ("udf5", self.udf5), ("responseURL", self.response_url), ("errorURL", self.error_url), ] ) return dict(params) def _build_payload(self, action): params = self._build_param_dict(action.value) # filter out empty values, and set `safe` to avoid # encoding URLs, which is not supported by the platform. d = parse.urlencode({k: v for k, v in params.items() if v}, safe=":/") payload = encrypt(d, self.key) return payload.decode("utf-8") def _get_redirect_url(self, action): payload = self._build_payload(action.value) pgw_url = self._pgw_url gw_url = f"{pgw_url}/PaymentHTTP.htm?param=paymentInit&trandata={payload}" gw_url = f"{gw_url}&tranportalId={self.portal_id}&responseURL={self.response_url}&errorURL={self.error_url}" return gw_url def _parse_response(self, raw_response): results = parse.parse_qs(raw_response) try: encrypted_payload = results.pop("trandata") except KeyError: raise InvalidGatewayResponse("Invalid response from the gateway, missing encrypted data") decrypted_results = parse.parse_qs(decrypt(encrypted_payload[0], self.key)) return results, decrypted_results
[docs] def get_result(self, response): common, decrypted = self._parse_response(response) d = {k: v[0] for k, v in common.items()} for k, v in decrypted.items(): if k not in d: d[k] = v[0] return d
[docs] @check_if_set(['response_url', 'error_url']) def generate_purchase_request(self): redirect_to = self._get_redirect_url(Action.PURCHASE) return redirect_to