diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index ed567a4..efffb0e 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.10', '3.11', '3.12'] + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] steps: - uses: actions/checkout@v1 diff --git a/README.md b/README.md index 0cbae50..ce7da9d 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,29 @@ new_payment.command_data_set().add_payment_method_data_source(gateway.DATA_SOURC new_payment.command_data_set().add_payment_method_data_token('initial gateway-transaction-id') ``` +### Using alternative payment methods + +To use an alternative payment method (like Google Pay), send a received token AS-IS or data from a decrypted token. + +```python +# set a corresponding flag that indicates a token provider +payment.command_data_set().add_payment_method_type(gateway.PAYMENT_METHOD_TYPE_GOOGLE_PAY) + +# option 1: send received token AS-IS +payment.payment_method_set().add_token('') + +# option 2: send data from decrypted token +payment.payment_method_set().add_pan_number('4111111111111111') +payment.payment_method_set().add_pan_expiry_date('12/30') +payment.payment_method_set().add_pan_cardholder_name('John Doe') # if available +payment.payment_method_set().add_external_token_cryptogram('') # if available +payment.payment_method_set().add_external_token_eci('') # if available +payment.payment_method_set().add_external_token_trans_status('') # available for Click to Pay +payment.payment_method_set().add_external_token_ds_trans_id('') # available for Click to Pay +payment.payment_method_set().add_external_token_acs_trans_id('') # available for Click to Pay +payment.payment_method_set().add_external_token_cardholder_authenticated(decryptedToken.paymentMethodDetails.assuranceDetails.cardHolderAuthenticated) # for Google Pay +``` + ### Callback validation ```python @@ -221,4 +244,4 @@ Please review code style guideline and try to keep in accordance with it [CodeStyle](https://github.com/TransactPRO/gw3-python-client/blob/master/CODESTYLE.md) ### License -This library is licensed under the MIT License - see the `LICENSE` file for details. \ No newline at end of file +This library is licensed under the MIT License - see the `LICENSE` file for details. diff --git a/gateway/__init__.py b/gateway/__init__.py index 06663fc..036f4f5 100644 --- a/gateway/__init__.py +++ b/gateway/__init__.py @@ -52,3 +52,8 @@ DATA_SOURCE_USE_MERCHANT_SAVED_CARDHOLDER_INITIATED = 4 DATA_SOURCE_USE_GATEWAY_SAVED_MERCHANT_INITIATED = 5 DATA_SOURCE_USE_MERCHANT_SAVED_MERCHANT_INITIATED = 6 + +PAYMENT_METHOD_TYPE_CARD = 'cc' +PAYMENT_METHOD_TYPE_GOOGLE_PAY = 'google_pay' +PAYMENT_METHOD_TYPE_APPLE_PAY = 'apple_pay' +PAYMENT_METHOD_TYPE_CLICK2PAY = 'click2pay' diff --git a/gateway/builders/command_data_builder.py b/gateway/builders/command_data_builder.py index 2b5af8a..e9f0540 100644 --- a/gateway/builders/command_data_builder.py +++ b/gateway/builders/command_data_builder.py @@ -135,3 +135,22 @@ def add_payment_method_data_token(self, token=None): new_key=self.__COMMAND_DATA_KEY, new_dict={self.__data_sets.COMMAND_DATA_PAYMENT_METHOD_DATA_TOKEN: token} ) + + def add_payment_method_type(self, type=None): + """ + Add payment method type + + Args: + type (str): payment method type + cc: card data + google_pay: Google Pay token + apple_pay: Apple Pay token + click2pay: Click to Pay token + """ + + self.__data_structure_util.add_to_dict( + source_dict=self.__command_data_set, + working_dict=self.__command_data_nested_structure, + new_key=self.__COMMAND_DATA_KEY, + new_dict={self.__data_sets.COMMAND_DATA_PAYMENT_METHOD_TYPE: type} + ) diff --git a/gateway/builders/payment_data_builder.py b/gateway/builders/payment_data_builder.py index ff2c149..55d2c6d 100644 --- a/gateway/builders/payment_data_builder.py +++ b/gateway/builders/payment_data_builder.py @@ -34,6 +34,8 @@ class PaymentDataBuilder(object): __PAYMENT_METHOD_DATA_KEY = 'payment-method-data' # Nested layer of external 3-D Secure data set __EXTERNAL_MPI_DATA_KEY = 'external-mpi-data' + # Nested layer of decrypted token's data set + __EXTERNAL_TOKEN_DATA_KEY = 'external-token-data' def __init__(self, __client_transaction_data_set, __client_mandatory_fields): self.__payment_data_structure = { @@ -44,6 +46,10 @@ def __init__(self, __client_transaction_data_set, __client_mandatory_fields): self.__EXTERNAL_MPI_DATA_KEY: None } + self.__external_token_data_structure = { + self.__EXTERNAL_TOKEN_DATA_KEY: None + } + self.__data_structure_util = DataStructuresUtils self.__data_sets = RequestParameters self.__data_types = RequestParametersTypes @@ -54,6 +60,10 @@ def __setup_external_mpi_data(self): if self.__EXTERNAL_MPI_DATA_KEY not in self.__payment_data_structure: self.__payment_data_structure[self.__EXTERNAL_MPI_DATA_KEY] = self.__external_mpi_data_structure + def __setup_external_token_data(self): + if self.__EXTERNAL_TOKEN_DATA_KEY not in self.__payment_data_structure: + self.__payment_data_structure[self.__EXTERNAL_TOKEN_DATA_KEY] = self.__external_token_data_structure + def add_pan_number(self, pan_number=None): """ Add credit card number @@ -115,6 +125,21 @@ def add_pan_cardholder_name(self, first_last_name=None): new_dict={self.__data_sets.PAYMENT_METHOD_DATA_CARDHOLDER_NAME: first_last_name} ) + def add_token(self, token=None): + """ + Add token + + Args: + token (str): Token AS-IS + """ + + self.__data_structure_util.add_to_dict( + source_dict=self.__payment_data_set, + working_dict=self.__payment_data_structure, + new_key=self.__PAYMENT_METHOD_DATA_KEY, + new_dict={self.__data_sets.PAYMENT_METHOD_DATA_TOKEN: token} + ) + def add_external_mpi_protocol_version(self, protocol_version=None): """ Add 3-D Secure protocolVersion @@ -194,3 +219,99 @@ def add_external_mpi_trans_status(self, trans_status=None): new_key=self.__EXTERNAL_MPI_DATA_KEY, new_dict={self.__data_sets.PAYMENT_METHOD_DATA_EXTERNAL_MPI_TRANS_STATUS: trans_status} ) + + def add_external_token_cryptogram(self, cryptogram=None): + """ + Add cryptogram from decrypted token's data + + Args: + cryptogram (str): token cryptogram (TAVV etc.) + """ + + self.__setup_external_token_data() + self.__data_structure_util.add_to_dict( + source_dict=self.__payment_data_structure[self.__PAYMENT_METHOD_DATA_KEY], + working_dict=self.__external_token_data_structure, + new_key=self.__EXTERNAL_TOKEN_DATA_KEY, + new_dict={self.__data_sets.PAYMENT_METHOD_DATA_EXTERNAL_TOKEN_CRYPTOGRAM: cryptogram} + ) + + def add_external_token_eci(self, eci=None): + """ + Add ECI from decrypted token's data + + Args: + eci (str): token ECI + """ + + self.__setup_external_token_data() + self.__data_structure_util.add_to_dict( + source_dict=self.__payment_data_structure[self.__PAYMENT_METHOD_DATA_KEY], + working_dict=self.__external_token_data_structure, + new_key=self.__EXTERNAL_TOKEN_DATA_KEY, + new_dict={self.__data_sets.PAYMENT_METHOD_DATA_EXTERNAL_TOKEN_ECI: eci} + ) + + def add_external_token_trans_status(self, trans_status=None): + """ + Add transStatus from decrypted token's data + + Args: + trans_status (str): token transStatus + """ + + self.__setup_external_token_data() + self.__data_structure_util.add_to_dict( + source_dict=self.__payment_data_structure[self.__PAYMENT_METHOD_DATA_KEY], + working_dict=self.__external_token_data_structure, + new_key=self.__EXTERNAL_TOKEN_DATA_KEY, + new_dict={self.__data_sets.PAYMENT_METHOD_DATA_EXTERNAL_TOKEN_TRANS_STATUS: trans_status} + ) + + def add_external_token_ds_trans_id(self, ds_trans_id=None): + """ + Add dsTransId from decrypted token's data + + Args: + ds_trans_id (str): token dsTransId + """ + + self.__setup_external_token_data() + self.__data_structure_util.add_to_dict( + source_dict=self.__payment_data_structure[self.__PAYMENT_METHOD_DATA_KEY], + working_dict=self.__external_token_data_structure, + new_key=self.__EXTERNAL_TOKEN_DATA_KEY, + new_dict={self.__data_sets.PAYMENT_METHOD_DATA_EXTERNAL_TOKEN_DS_TRANS_ID: ds_trans_id} + ) + + def add_external_token_acs_trans_id(self, acs_trans_id=None): + """ + Add acsTransId from decrypted token's data + + Args: + acs_trans_id (str): token acsTransId + """ + + self.__setup_external_token_data() + self.__data_structure_util.add_to_dict( + source_dict=self.__payment_data_structure[self.__PAYMENT_METHOD_DATA_KEY], + working_dict=self.__external_token_data_structure, + new_key=self.__EXTERNAL_TOKEN_DATA_KEY, + new_dict={self.__data_sets.PAYMENT_METHOD_DATA_EXTERNAL_TOKEN_ACS_TRANS_ID: acs_trans_id} + ) + + def add_external_token_cardholder_authenticated(self, cardholder_authenticated=None): + """ + Add cardHolderAuthenticated from decrypted Google Pay token's data + + Args: + cardholder_authenticated (bool): value of paymentMethodDetails.assuranceDetails.cardHolderAuthenticated from Google Pay token + """ + + self.__setup_external_token_data() + self.__data_structure_util.add_to_dict( + source_dict=self.__payment_data_structure[self.__PAYMENT_METHOD_DATA_KEY], + working_dict=self.__external_token_data_structure, + new_key=self.__EXTERNAL_TOKEN_DATA_KEY, + new_dict={self.__data_sets.PAYMENT_METHOD_DATA_EXTERNAL_TOKEN_AUTHENTICATED: cardholder_authenticated} + ) diff --git a/gateway/data_sets/request_parameters.py b/gateway/data_sets/request_parameters.py index ca63864..e8950a4 100644 --- a/gateway/data_sets/request_parameters.py +++ b/gateway/data_sets/request_parameters.py @@ -40,6 +40,7 @@ class RequestParameters: COMMAND_DATA_CARDS_VERIFICATION = 'card-verification' COMMAND_DATA_PAYMENT_METHOD_DATA_SOURCE = 'payment-method-data-source' COMMAND_DATA_PAYMENT_METHOD_DATA_TOKEN = 'payment-method-data-token' + COMMAND_DATA_PAYMENT_METHOD_TYPE = 'payment-method-type' # Customer data sets GENERAL_DATA_CUSTOMER_DATA_EMAIL = 'email' @@ -81,11 +82,18 @@ class RequestParameters: PAYMENT_METHOD_DATA_EXPIRE = 'exp-mm-yy' PAYMENT_METHOD_DATA_CVV = 'cvv' PAYMENT_METHOD_DATA_CARDHOLDER_NAME = 'cardholder-name' + PAYMENT_METHOD_DATA_TOKEN = 'token' PAYMENT_METHOD_DATA_EXTERNAL_MPI_PROTOCOL = 'protocolVersion' PAYMENT_METHOD_DATA_EXTERNAL_MPI_DS_TRANS_ID = 'dsTransID' PAYMENT_METHOD_DATA_EXTERNAL_MPI_XID = 'xid' PAYMENT_METHOD_DATA_EXTERNAL_MPI_CAVV = 'cavv' PAYMENT_METHOD_DATA_EXTERNAL_MPI_TRANS_STATUS = 'transStatus' + PAYMENT_METHOD_DATA_EXTERNAL_TOKEN_CRYPTOGRAM = 'cryptogram' + PAYMENT_METHOD_DATA_EXTERNAL_TOKEN_ECI = 'eci' + PAYMENT_METHOD_DATA_EXTERNAL_TOKEN_TRANS_STATUS = 'transStatus' + PAYMENT_METHOD_DATA_EXTERNAL_TOKEN_DS_TRANS_ID = 'dsTransID' + PAYMENT_METHOD_DATA_EXTERNAL_TOKEN_ACS_TRANS_ID = 'acsTransID' + PAYMENT_METHOD_DATA_EXTERNAL_TOKEN_AUTHENTICATED = 'cardHolderAuthenticated' # Money data sets MONEY_DATA_AMOUNT = 'amount' @@ -133,6 +141,7 @@ class RequestParametersTypes(RequestParameters): COMMAND_DATA_CARDS_VERIFICATION = int COMMAND_DATA_PAYMENT_METHOD_DATA_SOURCE = int COMMAND_DATA_PAYMENT_METHOD_DATA_TOKEN = str + COMMAND_DATA_PAYMENT_METHOD_TYPE = str # Customer data sets GENERAL_DATA_CUSTOMER_DATA_EMAIL = str @@ -174,11 +183,18 @@ class RequestParametersTypes(RequestParameters): PAYMENT_METHOD_DATA_EXPIRE = str PAYMENT_METHOD_DATA_CVV = str PAYMENT_METHOD_DATA_CARDHOLDER_NAME = str + PAYMENT_METHOD_DATA_TOKEN = str PAYMENT_METHOD_DATA_EXTERNAL_MPI_PROTOCOL = str PAYMENT_METHOD_DATA_EXTERNAL_MPI_DS_TRANS_ID = str PAYMENT_METHOD_DATA_EXTERNAL_MPI_XID = str PAYMENT_METHOD_DATA_EXTERNAL_MPI_CAVV = str PAYMENT_METHOD_DATA_EXTERNAL_MPI_TRANS_STATUS = str + PAYMENT_METHOD_DATA_EXTERNAL_TOKEN_CRYPTOGRAM = str + PAYMENT_METHOD_DATA_EXTERNAL_TOKEN_ECI = str + PAYMENT_METHOD_DATA_EXTERNAL_TOKEN_TRANS_STATUS = str + PAYMENT_METHOD_DATA_EXTERNAL_TOKEN_DS_TRANS_ID = str + PAYMENT_METHOD_DATA_EXTERNAL_TOKEN_ACS_TRANS_ID = str + PAYMENT_METHOD_DATA_EXTERNAL_TOKEN_AUTHENTICATED = bool # Money data sets MONEY_DATA_AMOUNT = int diff --git a/gateway/transport/transport.py b/gateway/transport/transport.py index 91477ab..a969042 100644 --- a/gateway/transport/transport.py +++ b/gateway/transport/transport.py @@ -75,7 +75,7 @@ def new_http_client(cli_name='requests', *args, **kwargs): class HttpTransport(object): def __init__(self, verify_ssl=True, proxy=None, timeout=60): - if verify_ssl is bool: + if isinstance(verify_ssl, bool): self.verify_ssl = verify_ssl else: self.verify_ssl = True diff --git a/setup.py b/setup.py index 8f31d27..966f3a2 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ try: import pypandoc - LONG_DESCRIPTION = pypandoc.convert('README.md', 'rst') + LONG_DESCRIPTION = pypandoc.convert_file('README.md', 'rst') except (IOError, ImportError, OSError, RuntimeError): LONG_DESCRIPTION = '' @@ -21,12 +21,11 @@ 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Topic :: Software Development :: Libraries :: Python Modules' ] @@ -36,7 +35,7 @@ setuptools.setup( name='transactpro-gw3-client', - version='1.7.8', + version='1.7.9', description='Transact PRO Gateway3 implementation in Python.', long_description=LONG_DESCRIPTION, long_description_content_type="text/markdown", @@ -48,5 +47,5 @@ license='MIT', classifiers=CLASSIFIERS, keywords='GW3 gateway3 integration gateway TransactPRO python python3', - python_requires='>=3.6', + python_requires='>=3.9', ) diff --git a/tests/builders/test_command_data_builder.py b/tests/builders/test_command_data_builder.py index 78d1c07..d87b05c 100644 --- a/tests/builders/test_command_data_builder.py +++ b/tests/builders/test_command_data_builder.py @@ -47,6 +47,7 @@ def test_mandatory_and_data_fields(self): new.add_card_verification_mode(321) new.add_payment_method_data_source(456) new.add_payment_method_data_token('mega-token') + new.add_payment_method_type('google_pay') from gateway.data_sets.request_parameters import (RequestParameters, RequestParametersTypes) valid_fields_types = { RequestParameters.COMMAND_DATA_GATEWAY_TRANSACTION_ID: @@ -60,7 +61,8 @@ def test_mandatory_and_data_fields(self): 'form-id': '#Bravo345', 'card-verification': 321, 'payment-method-data-source': 456, - 'payment-method-data-token': 'mega-token' + 'payment-method-data-token': 'mega-token', + 'payment-method-type': 'google_pay' } } self.assertDictEqual(valid_data_structure, self.DATA) diff --git a/tests/builders/test_payment_data_builder.py b/tests/builders/test_payment_data_builder.py index 387b430..b3318a8 100644 --- a/tests/builders/test_payment_data_builder.py +++ b/tests/builders/test_payment_data_builder.py @@ -67,6 +67,7 @@ def test_additional_data_fields(self): new.add_pan_cvv_code(cvv_number='442') new.add_pan_expiry_date(mm_yy='12/30') new.add_pan_number(pan_number='4222222222222') + new.add_token(token='qwerty') new.add_external_mpi_protocol_version('2.2.0') new.add_external_mpi_ds_trans_id('26221368-1c3d-4f3c-ba34-2efb76644c320') @@ -74,6 +75,13 @@ def test_additional_data_fields(self): new.add_external_mpi_cavv('kBMI/uGZvlKCygBkcQIlLJeBTPLG') new.add_external_mpi_trans_status('Y') + new.add_external_token_cryptogram('AAMI/uGZvlKCygBkcQIlLJeBTPLG') + new.add_external_token_eci('07') + new.add_external_token_trans_status('N') + new.add_external_token_ds_trans_id('33321368-1c3d-4f3c-ba34-2efb76644c320') + new.add_external_token_acs_trans_id('99921368-1c3d-4f3c-ba34-2efb76644c320') + new.add_external_token_cardholder_authenticated(True) + from gateway.data_sets.request_parameters import (RequestParameters, RequestParametersTypes) valid_fields_types = { RequestParameters.PAYMENT_METHOD_DATA_PAN: @@ -86,12 +94,21 @@ def test_additional_data_fields(self): 'pan': '4222222222222', 'cardholder-name': 'Jane Doe', 'exp-mm-yy': '12/30', + 'token': 'qwerty', 'external-mpi-data': { 'protocolVersion': '2.2.0', 'dsTransID': '26221368-1c3d-4f3c-ba34-2efb76644c320', 'xid': 'b+f8duAy8jNTQ0DB4U3mSmPyp8s=', 'cavv': 'kBMI/uGZvlKCygBkcQIlLJeBTPLG', 'transStatus': 'Y' + }, + 'external-token-data': { + 'cryptogram': 'AAMI/uGZvlKCygBkcQIlLJeBTPLG', + 'eci': '07', + 'cardHolderAuthenticated': True, + 'transStatus': 'N', + 'dsTransID': '33321368-1c3d-4f3c-ba34-2efb76644c320', + 'acsTransID': '99921368-1c3d-4f3c-ba34-2efb76644c320' } } }