From 077ed73325d49e40d64c4f6a540627917c7771d4 Mon Sep 17 00:00:00 2001 From: Nikki Date: Wed, 31 May 2017 12:13:42 -0700 Subject: [PATCH 01/41] Add optional 'full_dehydrate' param to both User and Node class --- .gitignore | 2 ++ samples.md | 7 +++++++ synapse_pay_rest/api/nodes.py | 2 +- synapse_pay_rest/api/users.py | 2 +- synapse_pay_rest/http_client.py | 2 +- synapse_pay_rest/models/nodes/node.py | 5 +++-- synapse_pay_rest/models/users/user.py | 5 +++-- 7 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index fee3a42..8590d2e 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,5 @@ target/ *.html venv + +/test.py diff --git a/samples.md b/samples.md index ff58841..ce5f5ff 100644 --- a/samples.md +++ b/samples.md @@ -37,6 +37,9 @@ users = User.all(client, **options) ```python user = User.by_id(client, '57e97ab786c2737f4ccd4dc1') + +(optional, full_dehydrate='yes' returns extra info on user) +user = User.by_id(client, '57e97ab786c2737f4ccd4dc1', full_dehydrate='yes') ``` #### Create a User @@ -210,6 +213,10 @@ nodes = Node.all(user, **options) ```python node = Node.by_id(user, '57ec57be86c27345b3f8a159') + +(optional, full_dehydrate='yes' returns transaction data on node) +node = Node.by_id(user, '57ec57be86c27345b3f8a159', full_dehydrate='yes') + ``` #### Create ACH-US Node(s) via Bank Login diff --git a/synapse_pay_rest/api/nodes.py b/synapse_pay_rest/api/nodes.py index 97cbf1f..e2e84a9 100644 --- a/synapse_pay_rest/api/nodes.py +++ b/synapse_pay_rest/api/nodes.py @@ -45,7 +45,7 @@ def get(self, user_id, node_id=None, **params): Args: user_id (str): id of the user the node belongs to node_id (str): if specified the method returns a single node - **params: valid params are 'type', 'page', 'per_page' + **params: valid params are 'type', 'page', 'per_page', 'full_dehydrate' Returns: dict: response body (single or multiple node records) diff --git a/synapse_pay_rest/api/users.py b/synapse_pay_rest/api/users.py index aade7c0..34c1e6d 100644 --- a/synapse_pay_rest/api/users.py +++ b/synapse_pay_rest/api/users.py @@ -45,7 +45,7 @@ def get(self, user_id=None, **params): Args: user_id (str): if specified the method returns a single user - **params: valid params are 'query', 'page', 'per_page' + **params: valid params are 'query', 'page', 'per_page', 'full_dehydrate' Returns: dict: response body (single or multiple user records) diff --git a/synapse_pay_rest/http_client.py b/synapse_pay_rest/http_client.py index 6d5473c..67c90f2 100644 --- a/synapse_pay_rest/http_client.py +++ b/synapse_pay_rest/http_client.py @@ -49,7 +49,7 @@ def get_headers(self): def get(self, url, **params): """Send a GET request to the API.""" self.log_information(self.logging) - valid_params = ['query', 'page', 'per_page', 'type'] + valid_params = ['query', 'page', 'per_page', 'type', 'full_dehydrate'] parameters = {} for param in valid_params: if param in params: diff --git a/synapse_pay_rest/models/nodes/node.py b/synapse_pay_rest/models/nodes/node.py index 1259e83..11c9071 100644 --- a/synapse_pay_rest/models/nodes/node.py +++ b/synapse_pay_rest/models/nodes/node.py @@ -91,17 +91,18 @@ def multiple_from_response(cls, user, response): return nodes @classmethod - def by_id(cls, user=None, id=None): + def by_id(cls, user=None, id=None, full_dehydrate='no'): """Retrieve a node record by id and create a BaseNode instance from it. Args: user (User): the User that the node belongs to id (str): id of the node to retrieve + full_dehydrate(optional, str): if 'yes', returns transaction data on node Returns: BaseNode: a BaseNode instance corresponding to the record """ - response = user.client.nodes.get(user.id, id) + response = user.client.nodes.get(user.id, id, full_dehydrate=full_dehydrate) return cls.from_response(user, response) @classmethod diff --git a/synapse_pay_rest/models/users/user.py b/synapse_pay_rest/models/users/user.py index ffada35..0496586 100644 --- a/synapse_pay_rest/models/users/user.py +++ b/synapse_pay_rest/models/users/user.py @@ -94,17 +94,18 @@ def create(cls, client=None, email=None, phone_number=None, return cls.from_response(client, response) @classmethod - def by_id(cls, client=None, id=None): + def by_id(cls, client=None, id=None, full_dehydrate='no'): """Retrieve a user record by id and create a User instance from it. Args: client (Client): an instance of the API Client id (str): id of the user to retrieve + full_dehyrdate (optional, str): if 'yes' returns all info on user Returns: User: a User instance corresponding to the record """ - response = client.users.get(id) + response = client.users.get(id, full_dehydrate=full_dehydrate) return cls.from_response(client, response) @classmethod From 005529ab6d1e94d93067c6127ed53fdc9d0e40b9 Mon Sep 17 00:00:00 2001 From: Nikki Date: Wed, 31 May 2017 12:32:30 -0700 Subject: [PATCH 02/41] Add 'oauth_key' and 'expires_in' to User class --- synapse_pay_rest/models/users/user.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/synapse_pay_rest/models/users/user.py b/synapse_pay_rest/models/users/user.py index 0496586..9140870 100644 --- a/synapse_pay_rest/models/users/user.py +++ b/synapse_pay_rest/models/users/user.py @@ -130,7 +130,9 @@ def authenticate(self): Returns: User: self """ - self.client.users.refresh(self.id, self.payload_for_refresh()) + response = self.client.users.refresh(self.id, self.payload_for_refresh()) + self.oauth_key = response['oauth_key'] + self.expires_in = response['expires_in'] return self def payload_for_update(self, **kwargs): From e60d78e9784798eb4ecb45d872004d00fcf92e45 Mon Sep 17 00:00:00 2001 From: Nikki Date: Wed, 31 May 2017 15:02:06 -0700 Subject: [PATCH 03/41] some updates --- synapse_pay_rest/models/nodes/node.py | 5 ++++- synapse_pay_rest/models/users/base_document.py | 14 ++++++++++++++ synapse_pay_rest/models/users/document.py | 4 +++- synapse_pay_rest/models/users/user.py | 1 + 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/synapse_pay_rest/models/nodes/node.py b/synapse_pay_rest/models/nodes/node.py index 11c9071..4e79055 100644 --- a/synapse_pay_rest/models/nodes/node.py +++ b/synapse_pay_rest/models/nodes/node.py @@ -55,7 +55,10 @@ def from_response(cls, user, response): 'account_id': response['info'].get('account_id'), 'address': response['info'].get('address'), 'swift': response['info'].get('swift'), - 'ifsc': response['info'].get('ifsc') + 'ifsc': response['info'].get('ifsc'), + 'user_info': response['extra']['other'].get('info',None), + 'timeline': response.get('timeline',None), + 'transactions': response['extra']['other'].get('transactions',None) } # correspondent info (optional) diff --git a/synapse_pay_rest/models/users/base_document.py b/synapse_pay_rest/models/users/base_document.py index 661f45d..0449ac1 100644 --- a/synapse_pay_rest/models/users/base_document.py +++ b/synapse_pay_rest/models/users/base_document.py @@ -36,6 +36,20 @@ def from_response(cls, user, response): base_doc = cls(user=user, id=response['id'], name=response['name'], + email=response.get('email',None), + phone_number=response.get('phone_number',None), + ip=response.get('ip',None), + alias=response.get('alias',None), + entity_type=response.get('entity_type',None), + entity_scope=response.get('entity_scope',None), + birth_day=response.get('birth_day',None), + birth_month=response.get('birth_month',None), + birth_year=response.get('birth_year',None), + address_street=response.get('address_street',None), + address_city=response.get('address_city',None), + address_subdivision=response.get('address_subdivision',None), + address_postal_code=response.get('address_postal_code',None), + address_country_code=response.get('address_country_code',None), permission_scope=response['permission_scope'], physical_documents=physical_docs, social_documents=social_docs, diff --git a/synapse_pay_rest/models/users/document.py b/synapse_pay_rest/models/users/document.py index d3af4fc..8f2eae2 100644 --- a/synapse_pay_rest/models/users/document.py +++ b/synapse_pay_rest/models/users/document.py @@ -28,7 +28,9 @@ def from_response(cls, response): return cls(type=response['document_type'], id=response['id'], status=response['status'], - last_updated=response['last_updated']) + last_updated=response['last_updated'], + meta=response.get('meta',None), + document_value=response.get('document_value',None)) @classmethod def multiple_from_response(cls, response): diff --git a/synapse_pay_rest/models/users/user.py b/synapse_pay_rest/models/users/user.py index 9140870..cdbe70d 100644 --- a/synapse_pay_rest/models/users/user.py +++ b/synapse_pay_rest/models/users/user.py @@ -64,6 +64,7 @@ def payload_for_create(email, phone_number, legal_name, **kwargs): 'cip_tag': kwargs.get('cip_tag') } } + if 'password' in kwargs: payload['logins'][0]['password'] = kwargs['password'] return payload From 3bcef06830f78460b03d99e4e676b5b557a6a16d Mon Sep 17 00:00:00 2001 From: Nikki Date: Wed, 31 May 2017 16:27:04 -0700 Subject: [PATCH 04/41] Add 'build_base_doc' method for User class to add base_docs on User.create --- synapse_pay_rest/models/users/user.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/synapse_pay_rest/models/users/user.py b/synapse_pay_rest/models/users/user.py index cdbe70d..e8f81a0 100644 --- a/synapse_pay_rest/models/users/user.py +++ b/synapse_pay_rest/models/users/user.py @@ -64,11 +64,34 @@ def payload_for_create(email, phone_number, legal_name, **kwargs): 'cip_tag': kwargs.get('cip_tag') } } - + if 'base_doc' in kwargs: + payload['documents'] = [kwargs['base_doc']] if 'password' in kwargs: payload['logins'][0]['password'] = kwargs['password'] return payload + @staticmethod + def build_base_doc(**kwargs): + """Build the API 'create user with base docs' payload from property values.""" + payload = { + 'email': kwargs.get('email'), + 'phone_number': kwargs.get('phone_number'), + 'ip': kwargs.get('ip'), + 'name': kwargs.get('name'), + 'alias': kwargs.get('alias'), + 'entity_type': kwargs.get('entity_type'), + 'entity_scope': kwargs.get('entity_scope'), + 'birth_day': kwargs.get('birth_day'), + 'birth_month': kwargs.get('birth_month'), + 'birth_year': kwargs.get('birth_year'), + 'address_street': kwargs.get('address_street'), + 'address_city': kwargs.get('address_city'), + 'address_subdivision': kwargs.get('address_subdivision'), + 'address_postal_code': kwargs.get('address_postal_code'), + 'address_country_code': kwargs.get('address_country_code') + } + return payload + @classmethod def create(cls, client=None, email=None, phone_number=None, legal_name=None, **kwargs): From 6fe8101c3585fc45dd33c81fbce6f540bb1ffe0d Mon Sep 17 00:00:00 2001 From: Nikki Date: Wed, 31 May 2017 17:57:14 -0700 Subject: [PATCH 05/41] Add base_doc on user create test --- synapse_pay_rest/tests/fixtures/user.py | 18 ++++++++++++++++++ synapse_pay_rest/tests/models/user_tests.py | 8 ++++++++ 2 files changed, 26 insertions(+) diff --git a/synapse_pay_rest/tests/fixtures/user.py b/synapse_pay_rest/tests/fixtures/user.py index c87c410..cf21612 100644 --- a/synapse_pay_rest/tests/fixtures/user.py +++ b/synapse_pay_rest/tests/fixtures/user.py @@ -41,3 +41,21 @@ 'cip_tag': 1, 'password': 'password' } + +user_base_doc = { + 'email': 'scoobie@doo.com', + 'phone_number': '707-555-5555', + 'ip': '127.0.0.1', + 'name': 'Doctor BaseDoc', + 'alias': 'Basey', + 'entity_type': 'F', + 'entity_scope': 'Arts & Entertainment', + 'birth_day': 28, + 'birth_month': 2, + 'birth_year': 1990, + 'address_street': '42 Base Blvd', + 'address_city': 'San Francisco', + 'address_subdivision': 'CA', + 'address_postal_code': '94114', + 'address_country_code': 'US' +} diff --git a/synapse_pay_rest/tests/models/user_tests.py b/synapse_pay_rest/tests/models/user_tests.py index 2cfe899..3cba8c5 100644 --- a/synapse_pay_rest/tests/models/user_tests.py +++ b/synapse_pay_rest/tests/models/user_tests.py @@ -22,6 +22,14 @@ def test_create(self): for prop in properties: self.assertIsNotNone(getattr(user, prop)) + def test_create_with_add_base_doc(self): + base_doc = User.build_base_doc(**user_base_doc) + user_create_args['base_doc'] = base_doc + user = User.create(self.client, **user_create_args) + self.assertEqual(self.client, user.client) + print(user.base_documents) + self.assertEqual(len(user.base_documents), 1) + def test_by_id(self): user_id = User.create(self.client, **user_create_args).id user = User.by_id(self.client, user_id) From cf33d833726e9e416f96e8e49174628531e0b4c7 Mon Sep 17 00:00:00 2001 From: Nikki Galusha Date: Wed, 31 May 2017 18:10:58 -0700 Subject: [PATCH 06/41] Update setup.py Update version --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 94c59e0..47bac6d 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='3.0.3', + version='3.1.0', description='SynapsePay Rest Native Python Library', @@ -25,7 +25,7 @@ url='https://github.com/synapsepay/SynapsePayRest-Python', # Author details - author='SynapsePay LLC', + author='Synapse Financial Technologies Inc.', author_email='help@synapsepay.com', # Choose your license From 2ac4c81b0492fe3a33491eaad942e2d548e1b8e8 Mon Sep 17 00:00:00 2001 From: Nikki Date: Thu, 1 Jun 2017 16:17:47 -0700 Subject: [PATCH 07/41] Add screening_results to full_dehydrate response and update samples.md --- samples.md | 83 ++++++++++++++++++- .../models/users/base_document.py | 7 +- 2 files changed, 86 insertions(+), 4 deletions(-) diff --git a/samples.md b/samples.md index ce5f5ff..6bf35fa 100644 --- a/samples.md +++ b/samples.md @@ -38,8 +38,89 @@ users = User.all(client, **options) ```python user = User.by_id(client, '57e97ab786c2737f4ccd4dc1') -(optional, full_dehydrate='yes' returns extra info on user) +optional: user = User.by_id(client, '57e97ab786c2737f4ccd4dc1', full_dehydrate='yes') + +Example of user response with full_dehydrate: +**Please note: if full_dehydrate='no', some fields will return as 'None' + +user.base_documents[0] = + +{ + 'user': "(id=592f1dfa8384540026e39a95)", + 'id': '189d2fc37c1ee5694aa62f302bcd7c0efaae2c0229f45bfc8bb3470f6f7ab92a', + 'name': 'Charlie Brown', + 'email': 'test@test.com', + 'phone_number': '111-111-1111', + 'ip': '::1', + 'alias': 'Woof Woof', + 'entity_type': 'M', + 'entity_scope': 'Arts & Entertainment', + 'birth_day': 2, + 'birth_month': 5, + 'birth_year': 1989, + 'address_street': '170 St Germain Ave', + 'address_city': 'SF', + 'address_subdivision': 'CA', + 'address_postal_code': '94114', + 'address_country_code': 'US', + 'screening_results': { + '561': 'NO_MATCH', + 'aucl': 'NO_MATCH', + 'concern_location': 'NO_MATCH', + 'dpl': 'NO_MATCH', + 'dtc': 'NO_MATCH', + 'el': 'NO_MATCH', + 'eucl': 'NO_MATCH', + 'fatf_non_cooperative_jurisdiction': 'NO_MATCH', + 'fbi_bank_robbers': 'NO_MATCH', + 'fbi_counter_intelligence': 'NO_MATCH', + 'fbi_crimes_against_children': 'NO_MATCH', + 'fbi_criminal_enterprise_investigations': 'NO_MATCH', + 'fbi_cyber': 'NO_MATCH', + 'fbi_domestic_terrorism': 'NO_MATCH', + 'fbi_human_trafficking': 'NO_MATCH', + 'fbi_murders': 'NO_MATCH', + 'fbi_violent_crimes': 'NO_MATCH', + 'fbi_wanted_terrorists': 'NO_MATCH', + 'fbi_white_collar': 'NO_MATCH', + 'fincen_red_list': 'NO_MATCH', + 'fse': 'NO_MATCH', + 'fto_sanctions': 'NO_MATCH', + 'futures_sanctions': 'NO_MATCH', + 'hkma_sanctions': 'NO_MATCH', + 'hm_treasury_sanctions': 'NO_MATCH', + 'isn': 'NO_MATCH', + 'mas_sanctions': 'NO_MATCH', + 'monitored_location': 'NO_MATCH', + 'ns-isa': 'NO_MATCH', + 'ofac_561_list': 'NO_MATCH', + 'ofac_eo13645': 'NO_MATCH', + 'ofac_fse': 'NO_MATCH', + 'ofac_fse_ir': 'NO_MATCH', + 'ofac_fse_sy': 'NO_MATCH', + 'ofac_isa': 'NO_MATCH', + 'ofac_ns_isa': 'NO_MATCH', + 'ofac_plc': 'NO_MATCH', + 'ofac_sdn': 'NO_MATCH', + 'ofac_ssi': 'NO_MATCH', + 'ofac_syria': 'NO_MATCH', + 'ofac_ukraine_eo13662': 'NO_MATCH', + 'osfi': 'NO_MATCH', + 'pep': 'NO_MATCH', + 'plc': 'NO_MATCH', + 'primary_concern': 'NO_MATCH', + 'sdn': 'NO_MATCH', + 'ssi': 'NO_MATCH', + 'tel_sanctions': 'NO_MATCH', + 'ukcl': 'NO_MATCH', + 'uvl': 'NO_MATCH' + }, + 'permission_scope': 'SEND|RECEIVE|1000|DAILY', + 'physical_documents': 1, + 'social_documents': 4, + 'virtual_documents': 1 +} ``` #### Create a User diff --git a/synapse_pay_rest/models/users/base_document.py b/synapse_pay_rest/models/users/base_document.py index 0449ac1..fa21883 100644 --- a/synapse_pay_rest/models/users/base_document.py +++ b/synapse_pay_rest/models/users/base_document.py @@ -42,14 +42,15 @@ def from_response(cls, user, response): alias=response.get('alias',None), entity_type=response.get('entity_type',None), entity_scope=response.get('entity_scope',None), - birth_day=response.get('birth_day',None), - birth_month=response.get('birth_month',None), - birth_year=response.get('birth_year',None), + birth_day=response.get('day',None), + birth_month=response.get('month',None), + birth_year=response.get('year',None), address_street=response.get('address_street',None), address_city=response.get('address_city',None), address_subdivision=response.get('address_subdivision',None), address_postal_code=response.get('address_postal_code',None), address_country_code=response.get('address_country_code',None), + screening_results=response.get('screening_results',None), permission_scope=response['permission_scope'], physical_documents=physical_docs, social_documents=social_docs, From dd18a854013a1b8de9fa3cf24c203dc7e5dcb918 Mon Sep 17 00:00:00 2001 From: Nikki Date: Thu, 1 Jun 2017 16:25:40 -0700 Subject: [PATCH 08/41] Update samples.md with full_dehydrate on node response --- samples.md | 371 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 370 insertions(+), 1 deletion(-) diff --git a/samples.md b/samples.md index 6bf35fa..bd03af0 100644 --- a/samples.md +++ b/samples.md @@ -295,9 +295,378 @@ nodes = Node.all(user, **options) ```python node = Node.by_id(user, '57ec57be86c27345b3f8a159') -(optional, full_dehydrate='yes' returns transaction data on node) +optional: node = Node.by_id(user, '57ec57be86c27345b3f8a159', full_dehydrate='yes') +Example of node response with full_dehydrate: +**Please note: if full_dehydrate='no', some fields will return as 'None' + +node = + +({ + 'user': "(id=592f1dfa8384540026e39a95)", + 'type': 'ACH-US', + 'id': '592f1e2d603964002f1b07f7', + 'is_active': True, + 'permission': 'LOCKED', + 'nickname': 'SynapsePay Test Checking Account - 8901', + 'name_on_account': ' ', + 'bank_long_name': 'CAPITAL ONE N.A.', + 'bank_name': 'CAPITAL ONE N.A.', + 'account_type': 'PERSONAL', + 'account_class': 'CHECKING', + 'account_number': '12345678901', + 'routing_number': '031176110', + 'account_id': None, + 'address': 'PO BOX 85139, RICHMOND, VA, US', + 'swift': None, + 'ifsc': None, + 'user_info': { + 'account_id': 'fd52bf51f0354335e634940139a006ef91d7e789c665857e9821656d18e7d012', + 'addresses': [ + { + 'city': 'San Francisco', + 'state': 'CA', + 'street': '5315 Castro St.', + 'zipcode': '94110' + } + ], + 'dob': '', + 'emails': [ + 'test@synapsepay.com' + ], + 'names': [ + 'Test User' + ], + 'phone_numbers': [ + '1652545112', + '1432656106', + '1888321821', + '6589100779' + ] + }, + 'timeline': [ + { + 'date': 1496260140541, + 'note': 'Node created.' + }, + { + 'date': 1496260142204, + 'note': 'Unable to send micro deposits as node allowed is not CREDIT.' + }, + { + 'date': 1496260420927, + 'note': "User locked. Thus node 'allowed' changed to LOCKED." + } + ], + 'transactions': [ + { + 'amount': 8.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 8693.0, + 'date': 1470715200.0, + 'debit': False, + 'description': 'CAPITAL ONE MOBILE PMT PPD:32078173097005', + 'pending': False + }, + { + 'amount': 249.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 827.0, + 'date': 1411358400.0, + 'debit': False, + 'description': 'WF Credit Card PPD:21164346914575', + 'pending': True + }, + { + 'amount': 61.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 4284.0, + 'date': 1460779200.0, + 'debit': False, + 'description': 'AMEX EPAYMENT PPD:22042046432370', + 'pending': True + }, + { + 'amount': 289.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 2039.0, + 'date': 1481432400.0, + 'debit': False, + 'description': 'BK OF AM CRD PPD:76067398573865', + 'pending': True + }, + { + 'amount': 489.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 1938.0, + 'date': 1397880000.0, + 'debit': True, + 'description': 'CAPITAL ONE MOBILE PMT PPD:12283074879351', + 'pending': True + }, + { + 'amount': 306.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 4175.0, + 'date': 1436587200.0, + 'debit': False, + 'description': 'BK OF AM CRD PPD:36590769006391', + 'pending': False + }, + { + 'amount': 162.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 2830.0, + 'date': 1480654800.0, + 'debit': True, + 'description': 'Payment to Chase card PPD:50214599701703', + 'pending': True + }, + { + 'amount': 40.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 1738.0, + 'date': 1425099600.0, + 'debit': False, + 'description': 'BK OF AM CRD PPD:51422971086988', + 'pending': False + }, + { + 'amount': 126.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 8386.0, + 'date': 1393131600.0, + 'debit': False, + 'description': 'CAPITAL ONE MOBILE PMT PPD:83488871898117', + 'pending': False + }, + { + 'amount': 18.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 5685.0, + 'date': 1439611200.0, + 'debit': True, + 'description': 'DISCOVER E-PAYMENT PPD:35631911243360', + 'pending': True + }, + { + 'amount': 67.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 4634.0, + 'date': 1416027600.0, + 'debit': True, + 'description': 'DISCOVER E-PAYMENT PPD:52756385725550', + 'pending': True + }, + { + 'amount': 228.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 1321.0, + 'date': 1404532800.0, + 'debit': False, + 'description': 'Payment to Chase card PPD:18727859364976', + 'pending': True + }, + { + 'amount': 107.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 1065.0, + 'date': 1414296000.0, + 'debit': True, + 'description': 'CAPITAL ONE MOBILE PMT PPD:26468111647708', + 'pending': True + }, + { + 'amount': 103.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 7907.0, + 'date': 1415336400.0, + 'debit': False, + 'description': 'DISCOVER E-PAYMENT PPD:82490497472711', + 'pending': True + }, + { + 'amount': 106.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 7574.0, + 'date': 1468814400.0, + 'debit': True, + 'description': 'DISCOVER E-PAYMENT PPD:38032120832376', + 'pending': False + }, + { + 'amount': 50.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 8876.0, + 'date': 1406433600.0, + 'debit': True, + 'description': 'Payment to Chase card PPD:35892567736339', + 'pending': True + }, + { + 'amount': 61.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 9015.0, + 'date': 1435291200.0, + 'debit': False, + 'description': 'CITI CARDS PPD:28749452237576', + 'pending': True + }, + { + 'amount': 445.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 9027.0, + 'date': 1447477200.0, + 'debit': True, + 'description': 'Payment to Chase card PPD:77434089827332', + 'pending': False + }, + { + 'amount': 343.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 1252.0, + 'date': 1428206400.0, + 'debit': True, + 'description': 'Payment to Chase card PPD:48935439759518', + 'pending': True + }, + { + 'amount': 50.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 5973.0, + 'date': 1462766400.0, + 'debit': True, + 'description': 'WF Credit Card PPD:91101427763846', + 'pending': False + }, + { + 'amount': 253.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 2814.0, + 'date': 1401854400.0, + 'debit': False, + 'description': 'CAPITAL ONE MOBILE PMT PPD:79516847131777', + 'pending': True + }, + { + 'amount': 275.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 9891.0, + 'date': 1423112400.0, + 'debit': False, + 'description': 'WF Credit Card PPD:69798304985993', + 'pending': True + }, + { + 'amount': 102.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 1775.0, + 'date': 1421211600.0, + 'debit': True, + 'description': 'DISCOVER E-PAYMENT PPD:67858400658712', + 'pending': True + }, + { + 'amount': 382.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 1585.0, + 'date': 1402632000.0, + 'debit': False, + 'description': 'BK OF AM CRD PPD:59371638589444', + 'pending': False + }, + { + 'amount': 486.0, + 'category': { + 'primary': '', + 'subcategory': '' + }, + 'current_balance': 3946.0, + 'date': 1404187200.0, + 'debit': True, + 'description': 'WF Credit Card PPD:97364551261150', + 'pending': False + } + ], + 'balance': '800.00', + 'currency': 'USD', + 'supp_id': '', + 'gateway_restricted': None +}) + ``` #### Create ACH-US Node(s) via Bank Login From 5a01ce5c4eef9975e9a6987becd7db95ea8cfdf8 Mon Sep 17 00:00:00 2001 From: Nikki Date: Thu, 1 Jun 2017 18:37:08 -0700 Subject: [PATCH 09/41] Add transaction analysis data to node on full_dehydrate --- synapse_pay_rest/models/nodes/node.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/synapse_pay_rest/models/nodes/node.py b/synapse_pay_rest/models/nodes/node.py index 4e79055..d3a02b5 100644 --- a/synapse_pay_rest/models/nodes/node.py +++ b/synapse_pay_rest/models/nodes/node.py @@ -58,7 +58,9 @@ def from_response(cls, user, response): 'ifsc': response['info'].get('ifsc'), 'user_info': response['extra']['other'].get('info',None), 'timeline': response.get('timeline',None), - 'transactions': response['extra']['other'].get('transactions',None) + 'transactions': response['extra']['other'].get('transactions',None), + 'billpay_info': response['extra']['other'].get('billpay_info',None), + 'transaction_analysis': response['extra']['other'].get('transaction_analysis',None) } # correspondent info (optional) From fe0e75306e362b4965a29100aee92dcd239c39ce Mon Sep 17 00:00:00 2001 From: Nikki Galusha Date: Thu, 1 Jun 2017 18:40:41 -0700 Subject: [PATCH 10/41] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 47bac6d..f4c550a 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='3.1.0', + version='3.1.1', description='SynapsePay Rest Native Python Library', From 7c5139e33a93db9bebfc1399f8cfe13070f71fa5 Mon Sep 17 00:00:00 2001 From: Nikki Galusha Date: Thu, 1 Jun 2017 18:41:27 -0700 Subject: [PATCH 11/41] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 94c59e0..02ce379 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='3.0.3', + version='3.1.1', description='SynapsePay Rest Native Python Library', From 1f489039b8e55e81d1ddafe311b30d314a03b0eb Mon Sep 17 00:00:00 2001 From: Nikki Galusha Date: Fri, 2 Jun 2017 14:45:38 -0700 Subject: [PATCH 12/41] Update samples.md to remove KBA --- samples.md | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/samples.md b/samples.md index bd03af0..ad34fb4 100644 --- a/samples.md +++ b/samples.md @@ -251,28 +251,6 @@ virtual_document = base_document.add_virtual_document(type='SSN', value='3333') base_document = virtual_document.base_document ``` -##### Answer KBA Questions for Virtual Document -If a Virtual Document is returned with status **SUBMITTED|MFA_PENDING**, you will need to have the user answer some questions: - -```python -# check for any virtual docs with SUBMITTED|MFA_PENDING status -pending_doc = [doc for doc in base_document.virtual_documents - if doc.status == 'SUBMITTED|MFA_PENDING'][0] - -for question in pending_doc.question_set: - print(question.question) - # => "Which one of the following zip codes is associated with you?" - print(question.answers) - # => {1=>"49230", 2=>"49209", 3=>"49268", 4=>"49532", 5=>"None Of The Above"} - question.choice = 1 # this should be based on user input - -# submit after finished answering all questions in question_set -pending_doc = pending_doc.submit_kba() - -# assign the variable to the updated base doc if needed -base_document = pending_doc.base_document -``` - ## Node Methods From be15d83a0eea3d4b50d3829246907e06b44c092a Mon Sep 17 00:00:00 2001 From: tynahuynh Date: Wed, 21 Feb 2018 12:34:06 -0800 Subject: [PATCH 13/41] added info.card_has and info.is_international --- synapse_pay_rest/__init__.py | 6 +- synapse_pay_rest/api/__init__.py | 3 + synapse_pay_rest/api/client.py | 17 ++ synapse_pay_rest/api/subnets.py | 76 ++++++++ synapse_pay_rest/api/subscriptions.py | 73 ++++++++ synapse_pay_rest/client.py | 11 +- synapse_pay_rest/models/__init__.py | 5 +- .../models/issue_public_keys/__init__.py | 7 + .../issue_public_keys/issue_public_key.py | 49 ++++++ synapse_pay_rest/models/nodes/__init__.py | 7 + synapse_pay_rest/models/nodes/base_node.py | 34 +++- .../models/nodes/check_us_node.py | 20 +++ .../models/nodes/clearing_us_node.py | 13 ++ .../models/nodes/deposit_us_node.py | 13 ++ .../models/nodes/ib_deposit_us_node.py | 13 ++ .../models/nodes/ib_subaccount_us_node.py | 13 ++ .../models/nodes/interchange_us_node.py | 16 ++ synapse_pay_rest/models/nodes/node.py | 31 +++- .../models/nodes/subaccount_us_node.py | 14 ++ synapse_pay_rest/models/subnets/__init__.py | 7 + synapse_pay_rest/models/subnets/subnet.py | 118 +++++++++++++ .../models/subscriptions/__init__.py | 7 + .../models/subscriptions/subscription.py | 164 ++++++++++++++++++ synapse_pay_rest/tests/api/__init__.py | 4 +- synapse_pay_rest/tests/api/subnets_tests.py | 50 ++++++ .../tests/api/subscriptions_tests.py | 30 ++++ synapse_pay_rest/tests/client_tests.py | 6 +- synapse_pay_rest/tests/fixtures/client.py | 4 +- .../tests/fixtures/issue_public_key.py | 3 + synapse_pay_rest/tests/fixtures/node.py | 1 + synapse_pay_rest/tests/fixtures/subnet.py | 7 + .../tests/fixtures/subscription.py | 15 ++ synapse_pay_rest/tests/models/__init__.py | 3 + .../tests/models/document_tests.py | 2 - .../tests/models/issue_public_key.py | 21 +++ synapse_pay_rest/tests/models/node_tests.py | 140 +++++++++++++++ synapse_pay_rest/tests/models/subnet_tests.py | 65 +++++++ .../tests/models/subscription_tests.py | 57 ++++++ 38 files changed, 1110 insertions(+), 15 deletions(-) create mode 100644 synapse_pay_rest/api/client.py create mode 100644 synapse_pay_rest/api/subnets.py create mode 100644 synapse_pay_rest/api/subscriptions.py create mode 100644 synapse_pay_rest/models/issue_public_keys/__init__.py create mode 100644 synapse_pay_rest/models/issue_public_keys/issue_public_key.py create mode 100644 synapse_pay_rest/models/nodes/check_us_node.py create mode 100644 synapse_pay_rest/models/nodes/clearing_us_node.py create mode 100644 synapse_pay_rest/models/nodes/deposit_us_node.py create mode 100644 synapse_pay_rest/models/nodes/ib_deposit_us_node.py create mode 100644 synapse_pay_rest/models/nodes/ib_subaccount_us_node.py create mode 100644 synapse_pay_rest/models/nodes/interchange_us_node.py create mode 100644 synapse_pay_rest/models/nodes/subaccount_us_node.py create mode 100644 synapse_pay_rest/models/subnets/__init__.py create mode 100644 synapse_pay_rest/models/subnets/subnet.py create mode 100644 synapse_pay_rest/models/subscriptions/__init__.py create mode 100644 synapse_pay_rest/models/subscriptions/subscription.py create mode 100644 synapse_pay_rest/tests/api/subnets_tests.py create mode 100644 synapse_pay_rest/tests/api/subscriptions_tests.py create mode 100644 synapse_pay_rest/tests/fixtures/issue_public_key.py create mode 100644 synapse_pay_rest/tests/fixtures/subnet.py create mode 100644 synapse_pay_rest/tests/fixtures/subscription.py create mode 100644 synapse_pay_rest/tests/models/issue_public_key.py create mode 100644 synapse_pay_rest/tests/models/subnet_tests.py create mode 100644 synapse_pay_rest/tests/models/subscription_tests.py diff --git a/synapse_pay_rest/__init__.py b/synapse_pay_rest/__init__.py index bcfa179..c96be13 100644 --- a/synapse_pay_rest/__init__.py +++ b/synapse_pay_rest/__init__.py @@ -1,10 +1,10 @@ """SynapsePay client library for the SynapsePay platform. This client library is designed to support the SynapsePay API for creating -users, linking nodes (accounts), and creating transactions between users. Read +users, linking nodes (accounts), creating transactions between users, and adding subnets. Read more at https://docs.synapsepay.com """ from .client import Client -from .api import Users, Nodes, Trans -from .models import User, Node, Transaction +from .api import Users, Nodes, Trans, Subnets, Subscriptions, ClientEndpoint +from .models import User, Node, Transaction, Subnet, Subscription, PublicKey diff --git a/synapse_pay_rest/api/__init__.py b/synapse_pay_rest/api/__init__.py index 1643c18..2da61d7 100644 --- a/synapse_pay_rest/api/__init__.py +++ b/synapse_pay_rest/api/__init__.py @@ -7,3 +7,6 @@ from .users import Users from .nodes import Nodes from .trans import Trans +from .subnets import Subnets +from .subscriptions import Subscriptions +from .client import ClientEndpoint diff --git a/synapse_pay_rest/api/client.py b/synapse_pay_rest/api/client.py new file mode 100644 index 0000000..4f9973b --- /dev/null +++ b/synapse_pay_rest/api/client.py @@ -0,0 +1,17 @@ +from synapse_pay_rest.http_client import HttpClient + + +class ClientEndpoint(): + """Abstraction of the /client endpoint. + + Used to make public key-related calls to the API. + https://docs.synapsepay.com/docs/issuing-public-key + """ + + def __init__(self, client): + self.client = client + + def issue_public_key(self, scope): + path = '/client?issue_public_key=YES&scope=' + scope + response = self.client.get(path) + return response \ No newline at end of file diff --git a/synapse_pay_rest/api/subnets.py b/synapse_pay_rest/api/subnets.py new file mode 100644 index 0000000..f77ab04 --- /dev/null +++ b/synapse_pay_rest/api/subnets.py @@ -0,0 +1,76 @@ +from synapse_pay_rest.http_client import HttpClient + + +class Subnets(): + """Abstraction of the /subnets endpoint. + + Used to make subnets-related calls to the API. + https://docs.synapsepay.com/docs/subnets + """ + + def __init__(self, client): + self.client = client + + def create_subnet_path(self, user_id, node_id, subnet_id=None): + """Construct the correct URL for the request.""" + path = '/users/{0}/nodes/{1}/subnets'.format(user_id, node_id) + if subnet_id: + return path + '/' + subnet_id + else: + return path + + def create(self, user_id, node_id, payload): + """Create a subnet record via POST request to the API. + + https://docs.synapsepay.com/docs/create-subnet + + Args: + user_id (str): id of the user to whom the node will belong + node_id (str): id of the node the subnet belongs to + payload (dict): See the docs for exact payload structure + + Returns: + dict: JSON data from response body (single node record) + """ + path = self.create_subnet_path(user_id, node_id) + response = self.client.post(path, payload) + return response + + def get(self, user_id, node_id, subnet_id=None, **params): + """Retrieve a single or multiple subnet records via GET request. + + https://docs.synapsepay.com/docs/subnets + https://docs.synapsepay.com/docs/subnet + + Args: + user_id (str): id of the user the node belongs to + node_id (str): id of the node the subnet belongs to + subnet_id (str): if specified the method returns a single subnet + **params: valid params are 'page', 'per_page' + + Returns: + dict: response body (single or multiple subnet records) + """ + path = self.create_subnet_path(user_id, node_id, subnet_id) + response = self.client.get(path, **params) + return response + + def update(self, user_id, node_id, subnet_id, payload): + """Updates a subnet record via PATCH request to the API. + + Used to edit subnet information (lock subnet). + + https://docs.synapsepay.com/docs/subnet-1 + + Args: + user_id (str): id of the user the node belongs to + node_id (str): id of the from node + subnet_id (str): id of the subnet to update + payload (dict): See the docs for exact payload structure + + Returns: + dict: response body (single subnet record) + """ + path = self.create_subnet_path(user_id, node_id, subnet_id) + response = self.client.patch(path, payload) + return response diff --git a/synapse_pay_rest/api/subscriptions.py b/synapse_pay_rest/api/subscriptions.py new file mode 100644 index 0000000..e0ad7e3 --- /dev/null +++ b/synapse_pay_rest/api/subscriptions.py @@ -0,0 +1,73 @@ +from synapse_pay_rest.http_client import HttpClient + + +class Subscriptions(): + """Abstraction of the /subscriptions endpoint. + + Used to make subscription-related calls to the API. It should only ever be + instantiated by the Client. + https://docs.synapsepay.com/docs/subscriptions + """ + + def __init__(self, client): + self.client = client + + def create_subscription_path(self, subscription_id=None): + """Construct the correct URL for the request.""" + path = '/subscriptions' + if subscription_id: + return path + '/' + subscription_id + else: + return path + + def create(self, payload): + """Create a subscription record via POST request to the API. + + https://docs.synapsepay.com/docs/create-subscription + + Args: + payload (dict): See the docs for exact payload structure + + Returns: + dict: response body (single subscriton record) + """ + path = self.create_subscription_path() + response = self.client.post(path, payload) + return response + + def get(self, subscription_id=None, **params): + """Retrieve a single or multiple subscriptions records via GET request to the API. + + https://docs.synapsepay.com/docs/subscriptions-1 + https://docs.synapsepay.com/docs/subscription + + Args: + subscription_id (str): if specified the method returns a single user + **params: valid params are 'query', 'page', 'per_page', 'full_dehydrate' + + Returns: + dict: response body (single or multiple subscription records) + """ + path = self.create_subscription_path(subscription_id) + response = self.client.get(path, **params) + return response + + def update(self, subscription_id, payload): + """Updates a subscription record via PATCH request to the API. + + Used to edit subscription information + https://docs.synapsepay.com/docs/update-subscription + + Args: + subscription_id (str): id of the subscription to update + payload (dict): See the docs for exact payload structure + + Returns: + dict: response body (single subscription record) + """ + path = self.create_subscription_path(subscription_id) + response = self.client.patch(path, payload) + return response + + + diff --git a/synapse_pay_rest/client.py b/synapse_pay_rest/client.py index dea298e..007a7cb 100644 --- a/synapse_pay_rest/client.py +++ b/synapse_pay_rest/client.py @@ -2,7 +2,9 @@ from .api.users import Users from .api.trans import Trans from .api.nodes import Nodes - +from .api.subnets import Subnets +from .api.subscriptions import Subscriptions +from .api.client import ClientEndpoint class Client(): """Handles configuration and requests to the SynapsePay API. @@ -23,7 +25,7 @@ def __init__(self, **kwargs): Todo: Allow logging to file """ - self.base_url = 'https://synapsepay.com/api/3' + self.base_url = 'https://api.synapsefi.com/v3.1' if kwargs.get('development_mode'): self.base_url = 'https://uat-api.synapsefi.com/v3.1' @@ -31,6 +33,9 @@ def __init__(self, **kwargs): self.users = Users(self.http_client) self.nodes = Nodes(self.http_client) self.trans = Trans(self.http_client) + self.subnets = Subnets(self.http_client) + self.subscriptions = Subscriptions(self.http_client) + self.client_endpoint = ClientEndpoint(self.http_client) def __repr__(self): - return '{0}(base_url={1})'.format(self.__class__, self.base_url) + return '{0}(base_url={1})'.format(self.__class__, self.base_url) \ No newline at end of file diff --git a/synapse_pay_rest/models/__init__.py b/synapse_pay_rest/models/__init__.py index bddae2c..ec30e1a 100644 --- a/synapse_pay_rest/models/__init__.py +++ b/synapse_pay_rest/models/__init__.py @@ -8,5 +8,8 @@ BaseDocument, Question from .nodes import AchUsNode, EftIndNode, EftNpNode, IouNode, ReserveUsNode,\ SynapseIndNode, SynapseNpNode, SynapseUsNode, WireIntNode,\ - WireUsNode, Node + WireUsNode, DepositUsNode, CheckUsNode, InterchangeUsNode, IbSubaccountUsNode, IbDepositUsNode, SubaccountUsNode, ClearingUsNode, Node from .transactions import Transaction +from .subnets import Subnet +from .subscriptions import Subscription +from .issue_public_keys import PublicKey diff --git a/synapse_pay_rest/models/issue_public_keys/__init__.py b/synapse_pay_rest/models/issue_public_keys/__init__.py new file mode 100644 index 0000000..5d62ef7 --- /dev/null +++ b/synapse_pay_rest/models/issue_public_keys/__init__.py @@ -0,0 +1,7 @@ +"""This module contains models for objects tied to the /client endpoint. + +Currently just the Issue Public Key class. +""" + + +from .issue_public_key import PublicKey diff --git a/synapse_pay_rest/models/issue_public_keys/issue_public_key.py b/synapse_pay_rest/models/issue_public_keys/issue_public_key.py new file mode 100644 index 0000000..d2881b2 --- /dev/null +++ b/synapse_pay_rest/models/issue_public_keys/issue_public_key.py @@ -0,0 +1,49 @@ +import copy +from synapse_pay_rest.client import Client + +class PublicKey(): + """Represents a Public Key record with methods for constructing Public key + instances. + + """ + + def __init__(self, **kwargs): + for arg, value in kwargs.items(): + setattr(self, arg, value) + + def __repr__(self): + clean_dict = self.__dict__.copy() + return '{0}({1})'.format(self.__class__, clean_dict) + + @classmethod + def from_response(cls, client, response): + """Construct a Public Key from a response dict.""" + return cls( + client = client, + json=response, + client_obj_id=response['public_key_obj']['client_obj_id'], + expires_at=response['public_key_obj']['expires_at'], + expires_in=response['public_key_obj']['expires_in'], + public_key=response['public_key_obj']['public_key'], + scope=response['public_key_obj']['scope'] + ) + + + @classmethod + def issue(cls,client=None, scope=None): + """Issues a public key record and create a Public Key instance from it. + + Args: + client (Client): an instance of the API Client + scope (string): scope of the subscription + + Returns: + Public Key: a PublicKey instance corresponding to the record + """ + if scope is None: + scope = "OAUTH|POST,USERS|POST,USERS|GET,USER|GET,USER|PATCH,SUBSCRIPTIONS|GET,SUBSCRIPTIONS|POST,SUBSCRIPTION|GET,SUBSCRIPTION|PATCH,CLIENT|REPORTS,CLIENT|CONTROLS" + + response = client.client_endpoint.issue_public_key(scope) + return cls.from_response(client, response) + + \ No newline at end of file diff --git a/synapse_pay_rest/models/nodes/__init__.py b/synapse_pay_rest/models/nodes/__init__.py index ad6a646..e322320 100644 --- a/synapse_pay_rest/models/nodes/__init__.py +++ b/synapse_pay_rest/models/nodes/__init__.py @@ -16,4 +16,11 @@ from .triumph_subaccount_us_node import TriumphSubaccountUsNode from .wire_int_node import WireIntNode from .wire_us_node import WireUsNode +from .deposit_us_node import DepositUsNode +from .check_us_node import CheckUsNode +from .interchange_us_node import InterchangeUsNode +from .ib_deposit_us_node import IbDepositUsNode +from .ib_subaccount_us_node import IbSubaccountUsNode +from .clearing_us_node import ClearingUsNode +from .subaccount_us_node import SubaccountUsNode from .node import Node diff --git a/synapse_pay_rest/models/nodes/base_node.py b/synapse_pay_rest/models/nodes/base_node.py index 83e79e2..27fa223 100644 --- a/synapse_pay_rest/models/nodes/base_node.py +++ b/synapse_pay_rest/models/nodes/base_node.py @@ -36,7 +36,13 @@ def from_response(cls, user, response): 'account_id': response['info'].get('account_id'), 'address': response['info'].get('address'), 'swift': response['info'].get('swift'), - 'ifsc': response['info'].get('ifsc') + 'ifsc': response['info'].get('ifsc'), + 'payee_name': response['info'].get('payee_name'), + 'document_id': response['info'].get('document_id'), + 'network': response['info'].get('network'), + 'card_type': response['info'].get('type'), + 'card_hash': response['info'].get('card_hash'), + 'is_international': response['info'].get('is_international') } if response['info'].get('correspondent_info'): @@ -62,6 +68,15 @@ def from_response(cls, user, response): args['supp_id'] = info.get('supp_id') args['gateway_restricted'] = info.get('gateway_restricted') + #check info(optional) + if response['info'].get('payee_address'): + info = response['info']['payee_address'] + args['address_street'] = info.get('address_street') + args['address_city'] = info.get('address_city') + args['address_subdivision'] = info.get('address_subdivision') + args['address_country_code'] = info.get('address_country_code') + args['address_postal_code'] = info.get('address_postal_code') + return cls(**args) @classmethod @@ -109,6 +124,14 @@ def payload_for_create(cls, type, **kwargs): payload['info']['bank_id'] = kwargs['username'] if 'password' in kwargs: payload['info']['bank_pw'] = kwargs['password'] + if 'payee_name' in kwargs: + payload['info']['payee_name'] = kwargs['payee_name'] + if 'card_number' in kwargs: + payload['info']['card_number'] = kwargs['card_number'] + if 'exp_date' in kwargs: + payload['info']['exp_date'] = kwargs['exp_date'] + if 'document_id' in kwargs: + payload['info']['document_id'] = kwargs['document_id'] balance_options = ['currency'] balance = {} @@ -125,6 +148,15 @@ def payload_for_create(cls, type, **kwargs): extra[option] = kwargs[option] if extra: payload['extra'] = extra + + payee_address_options = ['address_street', 'address_city', 'address_subdivision', + 'address_country_code', 'address_postal_code'] + payee_address = {} + for option in payee_address_options: + if option in kwargs: + payee_address[option] = kwargs[option] + if payee_address: + payload['info']['payee_address'] =payee_address return payload @classmethod diff --git a/synapse_pay_rest/models/nodes/check_us_node.py b/synapse_pay_rest/models/nodes/check_us_node.py new file mode 100644 index 0000000..91f6c49 --- /dev/null +++ b/synapse_pay_rest/models/nodes/check_us_node.py @@ -0,0 +1,20 @@ +from .base_node import BaseNode + + +class CheckUsNode(BaseNode): + """Represents a SYNAPSE-US node.""" + + @classmethod + def payload_for_create(cls, nickname, payee_name, address_street, address_city, + address_subdivision, address_country_code, address_postal_code, **kwargs): + """Build the API 'create node' payload specific to SYNAPSE-US.""" + payload = super().payload_for_create('CHECK-US', + nickname=nickname, + payee_name=payee_name, + address_street=address_street, + address_city=address_city, + address_subdivision=address_subdivision, + address_country_code=address_country_code, + address_postal_code=address_postal_code, + **kwargs) + return payload diff --git a/synapse_pay_rest/models/nodes/clearing_us_node.py b/synapse_pay_rest/models/nodes/clearing_us_node.py new file mode 100644 index 0000000..abfee21 --- /dev/null +++ b/synapse_pay_rest/models/nodes/clearing_us_node.py @@ -0,0 +1,13 @@ +from .base_node import BaseNode + + +class ClearingUsNode(BaseNode): + """Represents a SYNAPSE-US node.""" + + @classmethod + def payload_for_create(cls, nickname, **kwargs): + """Build the API 'create node' payload specific to CLEARING-US.""" + payload = super().payload_for_create('CLEARING-US', + nickname=nickname, + **kwargs) + return payload diff --git a/synapse_pay_rest/models/nodes/deposit_us_node.py b/synapse_pay_rest/models/nodes/deposit_us_node.py new file mode 100644 index 0000000..6332968 --- /dev/null +++ b/synapse_pay_rest/models/nodes/deposit_us_node.py @@ -0,0 +1,13 @@ +from .base_node import BaseNode + + +class DepositUsNode(BaseNode): + """Represents a SYNAPSE-US node.""" + + @classmethod + def payload_for_create(cls, nickname, **kwargs): + """Build the API 'create node' payload specific to SYNAPSE-US.""" + payload = super().payload_for_create('DEPOSIT-US', + nickname=nickname, + **kwargs) + return payload diff --git a/synapse_pay_rest/models/nodes/ib_deposit_us_node.py b/synapse_pay_rest/models/nodes/ib_deposit_us_node.py new file mode 100644 index 0000000..819df53 --- /dev/null +++ b/synapse_pay_rest/models/nodes/ib_deposit_us_node.py @@ -0,0 +1,13 @@ +from .base_node import BaseNode + + +class IbDepositUsNode(BaseNode): + """Represents a SYNAPSE-US node.""" + + @classmethod + def payload_for_create(cls, nickname, **kwargs): + """Build the API 'create node' payload specific to IB-DEPOSIT-US.""" + payload = super().payload_for_create('IB-DEPOSIT-US', + nickname=nickname, + **kwargs) + return payload diff --git a/synapse_pay_rest/models/nodes/ib_subaccount_us_node.py b/synapse_pay_rest/models/nodes/ib_subaccount_us_node.py new file mode 100644 index 0000000..bb98f64 --- /dev/null +++ b/synapse_pay_rest/models/nodes/ib_subaccount_us_node.py @@ -0,0 +1,13 @@ +from .base_node import BaseNode + + +class IbSubaccountUsNode(BaseNode): + """Represents a SYNAPSE-US node.""" + + @classmethod + def payload_for_create(cls, nickname, **kwargs): + """Build the API 'create node' payload specific to IB-SUBACCOUNT-US.""" + payload = super().payload_for_create('IB-SUBACCOUNT-US', + nickname=nickname, + **kwargs) + return payload diff --git a/synapse_pay_rest/models/nodes/interchange_us_node.py b/synapse_pay_rest/models/nodes/interchange_us_node.py new file mode 100644 index 0000000..974fe58 --- /dev/null +++ b/synapse_pay_rest/models/nodes/interchange_us_node.py @@ -0,0 +1,16 @@ +from .base_node import BaseNode + + +class InterchangeUsNode(BaseNode): + """Represents a INTERCHANGE-US node.""" + + @classmethod + def payload_for_create(cls, nickname, card_number, exp_date, document_id, **kwargs): + """Build the API 'create node' payload specific to SYNAPSE-US.""" + payload = super().payload_for_create('INTERCHANGE-US', + nickname=nickname, + card_number=card_number, + exp_date=exp_date, + document_id=document_id, + **kwargs) + return payload diff --git a/synapse_pay_rest/models/nodes/node.py b/synapse_pay_rest/models/nodes/node.py index d3a02b5..0a0563d 100644 --- a/synapse_pay_rest/models/nodes/node.py +++ b/synapse_pay_rest/models/nodes/node.py @@ -11,6 +11,13 @@ from .triumph_subaccount_us_node import TriumphSubaccountUsNode from .wire_int_node import WireIntNode from .wire_us_node import WireUsNode +from .deposit_us_node import DepositUsNode +from .check_us_node import CheckUsNode +from .interchange_us_node import InterchangeUsNode +from .ib_deposit_us_node import IbDepositUsNode +from .ib_subaccount_us_node import IbSubaccountUsNode +from .clearing_us_node import ClearingUsNode +from .subaccount_us_node import SubaccountUsNode class Node(): @@ -31,7 +38,14 @@ class Node(): 'SYNAPSE-US': SynapseUsNode, 'TRIUMPH-SUBACCOUNT-US': TriumphSubaccountUsNode, 'WIRE-INT': WireIntNode, - 'WIRE-US': WireUsNode + 'WIRE-US': WireUsNode, + 'DEPOSIT-US': DepositUsNode, + 'CHECK-US': CheckUsNode, + 'INTERCHANGE-US': InterchangeUsNode, + 'IB-DEPOSIT-US': IbDepositUsNode, + 'IB-SUBACCOUNT-US': IbSubaccountUsNode, + 'CLEARING-US': ClearingUsNode, + 'SUBACCOUNT-US': SubaccountUsNode } @classmethod @@ -60,7 +74,11 @@ def from_response(cls, user, response): 'timeline': response.get('timeline',None), 'transactions': response['extra']['other'].get('transactions',None), 'billpay_info': response['extra']['other'].get('billpay_info',None), - 'transaction_analysis': response['extra']['other'].get('transaction_analysis',None) + 'transaction_analysis': response['extra']['other'].get('transaction_analysis',None), + 'payee_name': response['info'].get('payee_name'), + 'document_id': response['info'].get('document_id'), + 'network': response['info'].get('network'), + 'card_type': response['info'].get('type'), } # correspondent info (optional) @@ -84,6 +102,15 @@ def from_response(cls, user, response): args['supp_id'] = info.get('supp_id') args['gateway_restricted'] = info.get('gateway_restricted') + #check info(optional) + if response['info'].get('payee_address'): + info = response['info']['payee_address'] + args['address_street'] = info.get('address_street') + args['address_city'] = info.get('address_city') + args['address_subdivision'] = info.get('address_subdivision') + args['address_country_code'] = info.get('address_country_code') + args['address_postal_code'] = info.get('address_postal_code') + klass = cls.NODE_TYPES_TO_CLASSES.get(response['type'], BaseNode) return klass(**args) diff --git a/synapse_pay_rest/models/nodes/subaccount_us_node.py b/synapse_pay_rest/models/nodes/subaccount_us_node.py new file mode 100644 index 0000000..f3c9bb1 --- /dev/null +++ b/synapse_pay_rest/models/nodes/subaccount_us_node.py @@ -0,0 +1,14 @@ +from .base_node import BaseNode + + +class SubaccountUsNode(BaseNode): + """Represents a SUBACCOUNT-US node.""" + + @classmethod + def payload_for_create(cls, nickname, **kwargs): + """Build the API 'create node' payload specific to SUBACCOUNT-US. + """ + payload = super().payload_for_create('SUBACCOUNT-US', + nickname=nickname, + **kwargs) + return payload diff --git a/synapse_pay_rest/models/subnets/__init__.py b/synapse_pay_rest/models/subnets/__init__.py new file mode 100644 index 0000000..8db2700 --- /dev/null +++ b/synapse_pay_rest/models/subnets/__init__.py @@ -0,0 +1,7 @@ +"""This module contains models for objects tied to the /subnets endpoint. + +Currently just the Subnet class. +""" + + +from .subnet import Subnet diff --git a/synapse_pay_rest/models/subnets/subnet.py b/synapse_pay_rest/models/subnets/subnet.py new file mode 100644 index 0000000..9b9b8ea --- /dev/null +++ b/synapse_pay_rest/models/subnets/subnet.py @@ -0,0 +1,118 @@ +class Subnet(): + """Represents a Subnet record with methods for constructing Subnet + instances. + + """ + + def __init__(self, **kwargs): + for arg, value in kwargs.items(): + setattr(self, arg, value) + + def __repr__(self): + node = '{0}(id={1})'.format(self.node.__class__, self.node.id) + clean_dict = self.__dict__.copy() + clean_dict['node'] = node + return '{0}({1})'.format(self.__class__, clean_dict) + + @classmethod + def from_response(cls, node, response): + """Construct a Subnet from a response dict.""" + return cls( + node=node, + json=response, + id=response['_id'], + account_num=response['account_num'], + allowed=response['allowed'], + client_id=response['client']['id'], + client_name=response['client']['name'], + nickname=response['nickname'], + node_id=response['node_id'], + routing_num_ach=response['routing_num']['ach'], + routing_num_wire=response['routing_num']['wire'], + user_id=response['user_id'] + ) + + @classmethod + def multiple_from_response(cls, node, response): + """Construct multiple Subnets from a response dict.""" + subnets = [cls.from_response(node, subnets_data) + for subnets_data in response ] + return subnets + + @staticmethod + def payload_for_create(nickname, **kwargs): + """Build the API 'create subnet' payload from property values.""" + payload = { + "nickname": nickname + } + return payload + + + @classmethod + def create(cls, node=None, nickname=None, **kwargs): + """Create a subnets record in API and corresponding Subnet instance. + + Args: + node (BaseNode): the node from which the subnet belongs + nickname (str): nickname of subnet + + Returns: + Subnet: a new Subnet instance + """ + payload = cls.payload_for_create(nickname, + **kwargs) + response = node.user.client.subnets.create(node.user.id, node.id, payload) + return cls.from_response(node, response) + + @classmethod + def by_id(cls, node=None, id=None): + """Retrieve a subnets record by id and create a Subnet instance from it. + + Args: + node (BaseNode): the node from which the subnet belongs + id (str): id of the subnet to retrieve + + Returns: + Subnet: a Subnet instance corresponding to the record + """ + response = node.user.client.subnets.get(node.user.id, node.id, id) + return cls.from_response(node, response) + + @classmethod + def all(cls, node=None, **kwargs): + """Retrieve all subnets records (limited by pagination) as Subnets. + + Args: + node (BaseNode): the node from which the subnet belongs + per_page (int, str): (opt) number of records to retrieve + page (int, str): (opt) page number to retrieve + + Returns: + list: containing 0 or more Subnet instances + """ + response = node.user.client.subnets.get(node.user.id, node.id, **kwargs) + return cls.multiple_from_response(node, response['subnets']) + + def lock(self): + """Lock subnet + + Args: + allowed (str): Denotes the subnet standing. Currently you can only set allowed to + + Returns: + Subnet: a new instance representing the same record updated + """ + payload = { + 'allowed': 'LOCKED' + } + response = self.node.user.client.subnets.update(self.node.user.id, + self.node.id, + self.id, + payload) + if 'subnets' in response: + # API v3.1.0 + return self.from_response(self.node, response['subnets']) + else: + # API v3.1.1 + return self.from_response(self.node, response) + diff --git a/synapse_pay_rest/models/subscriptions/__init__.py b/synapse_pay_rest/models/subscriptions/__init__.py new file mode 100644 index 0000000..5f8d7fd --- /dev/null +++ b/synapse_pay_rest/models/subscriptions/__init__.py @@ -0,0 +1,7 @@ +"""This module contains models for objects tied to the /subscriptions endpoint. + +Currently just the Subscription class. +""" + + +from .subscription import Subscription diff --git a/synapse_pay_rest/models/subscriptions/subscription.py b/synapse_pay_rest/models/subscriptions/subscription.py new file mode 100644 index 0000000..50c28f9 --- /dev/null +++ b/synapse_pay_rest/models/subscriptions/subscription.py @@ -0,0 +1,164 @@ +import copy +from synapse_pay_rest.client import Client + + +class Subscription(): + """Object representation of a subscription record. + + Contains various constructors (instances from existing API records) as well as methods for modifying subscription records + """ + + def __init__(self, **kwargs): + for arg, value in kwargs.items(): + setattr(self, arg, value) + + def __repr__(self): + clean_dict = self.__dict__.copy() + return '{0}({1})'.format(self.__class__, clean_dict) + + @classmethod + def from_response(cls, client, response): + """Construct a Subscription from a response dict.""" + return cls( + client=client, + json=response, + id=response['_id'], + client_id=response['client_id'], + is_active=response['is_active'], + scope=response['scope'], + url=response['url'] + ) + + + @classmethod + def multiple_from_response(cls, client, response): + """Construct multiple Subscriptions from a response dict.""" + subscriptions = [cls.from_response(copy.copy(client), subscription_data) + for subscription_data in response] + return subscriptions + + @staticmethod + def payload_for_create(scope, url, **kwargs): + """Build the API 'create subscription' payload from property values.""" + payload = { + "scope": scope, + "url": url + } + return payload + + @classmethod + def create(cls, client=None, scope=None, url=None, **kwargs): + """Create a subscription record in API and corresponding Subscription instance. + + Args: + client (Client): an instance of the API Client + scope (arr): scope of the subscription + url (str) webhook URL + + Returns: + Subscription: a new Subscription instance + """ + payload = cls.payload_for_create(scope, url, + **kwargs) + response = client.subscriptions.create(payload) + return cls.from_response(client, response) + + @classmethod + def by_id(cls, client=None, id=None): + """Retrieve a subscription record by id and create a Subscription instance from it. + + Args: + client (Client): an instance of the API Client + id (str): id of the subscription to retrieve + + Returns: + Subscription: a new Subscription instance + """ + response = client.subscriptions.get(id) + return cls.from_response(client, response) + + @classmethod + def all(cls, client=None, **kwargs): + """Retrieve all subscription records (limited by pagination) as Subscriptions. + + Args: + client (Client): an instance of the API Client + per_page (int, str): (opt) number of records to retrieve + page (int, str): (opt) page number to retrieve + query (str): (opt) substring to filter for in user names/emails + + Returns: + list: containing 0 or more Subscription instances + """ + response = client.subscriptions.get(**kwargs) + return cls.multiple_from_response(client, response['subscriptions']) + + + # def update(self, **kwargs): + # """Build the API 'update subscription' payload from property values.""" + # payload = {} + + # if 'is_active' in kwargs: + # payload['is_active'] = kwargs['is_active'] + # if 'url' in kwargs: + # payload['url'] = kwargs['url'] + # if 'scope' in kwargs: + # payload['scope'] = kwargs['scope'] + + # response = self.client.subscriptions.update(self.id, payload) + # return cls.from_response(client, response) + + + def payload_for_update(self, **kwargs): + payload = {} + + if 'is_active' in kwargs: + payload['is_active'] = kwargs['is_active'] + if 'url' in kwargs: + payload['url'] = kwargs['url'] + if 'scope' in kwargs: + payload['scope'] = kwargs['scope'] + return payload + + def update_url(self, new_url): + """Update subscription's url + + Args: + new_url (str): new url + + Returns: + Subscription: a new Subscription instance + """ + payload = self.payload_for_update(url=new_url) + response = self.client.subscriptions.update(self.id, payload) + return Subscription.from_response(self.client, response) + + + def update_scope(self, new_scope): + """Update subscription's scope + + Args: + new_scope (str): updated scope + + Returns: + Subscription: a new Subscription instance + """ + payload = self.payload_for_update(scope=new_scope) + response = self.client.subscriptions.update(self.id, payload) + return Subscription.from_response(self.client, response) + + def update_is_active(self, is_active): + """Update subscription's url + + Args: + is_active (bool): active or inactive (T/F) + + Returns: + Subscription: a new Subscription instance + """ + payload = self.payload_for_update(is_active= is_active) + response = self.client.subscriptions.update(self.id, payload) + return Subscription.from_response(self.client, response) + + + diff --git a/synapse_pay_rest/tests/api/__init__.py b/synapse_pay_rest/tests/api/__init__.py index 82d4873..33dddbf 100644 --- a/synapse_pay_rest/tests/api/__init__.py +++ b/synapse_pay_rest/tests/api/__init__.py @@ -1,3 +1,5 @@ from .users_tests import * from .nodes_tests import * -from .trans_tests import * \ No newline at end of file +from .trans_tests import * +from .subnets_tests import * +from .subscriptions_tests import * diff --git a/synapse_pay_rest/tests/api/subnets_tests.py b/synapse_pay_rest/tests/api/subnets_tests.py new file mode 100644 index 0000000..0c6161d --- /dev/null +++ b/synapse_pay_rest/tests/api/subnets_tests.py @@ -0,0 +1,50 @@ +import unittest +from synapse_pay_rest.tests.fixtures.client import * +from synapse_pay_rest.tests.fixtures.user import * +from synapse_pay_rest.tests.fixtures.node import * +from synapse_pay_rest.tests.fixtures.subnet import * + + +class SubnetsTestCases(unittest.TestCase): + def setUp(self): + print('\n{0}.{1}'.format(type(self).__name__, self._testMethodName)) + self.client = test_client + self.user = self.client.users.create(users_create_payload) + refresh_payload = {'refresh_token': self.user['refresh_token']} + self.client.users.refresh(self.user['_id'], refresh_payload) + response = self.client.nodes.create(self.user['_id'], + nodes_create_payload) + self.node = response['nodes'][0] + + def test_create_a_new_subnet(self): + subnet = self.client.subnets.create(self.user['_id'], + self.node['_id'], + subnet_create_payload) + self.assertIsNotNone(subnet['_id']) + + def test_get_existing_subnet(self): + subnet = self.client.subnets.create(self.user['_id'], + self.node['_id'], + subnet_create_payload) + subnet = self.client.subnets.get(self.user['_id'], + self.node['_id'], + subnet['_id']) + self.assertIsNotNone(subnet['_id']) + + def test_get_multiple_subnets(self): + response = self.client.subnets.get(self.user['_id'], + self.node['_id']) + self.assertIsNotNone(response['subnets']) + + def test_lock_subnet(self): + subnet = self.client.subnets.create(self.user['_id'], + self.node['_id'], + subnet_create_payload) + subnet = self.client.subnets.update(self.user['_id'], + self.node['_id'], + subnet['_id'], + subnet_update_payload) + note = subnet['allowed'] + self.assertIsNotNone(subnet['_id']) + self.assertIn(subnet_update_payload['allowed'], note) + diff --git a/synapse_pay_rest/tests/api/subscriptions_tests.py b/synapse_pay_rest/tests/api/subscriptions_tests.py new file mode 100644 index 0000000..62d59b4 --- /dev/null +++ b/synapse_pay_rest/tests/api/subscriptions_tests.py @@ -0,0 +1,30 @@ +import unittest +from synapse_pay_rest.tests.fixtures.client import * +from synapse_pay_rest.tests.fixtures.subscription import * + + +class SubscriptionsTestCases(unittest.TestCase): + def setUp(self): + print('\n{0}.{1}'.format(type(self).__name__, self._testMethodName)) + self.client = test_client + + def test_create_a_new_subscription(self): + subscription = self.client.subscriptions.create(subscription_create_payload) + self.assertIsNotNone(subscription['_id']) + + def test_get_existing_subscription(self): + subscription = self.client.subscriptions.create(subscription_create_payload) + subscription = self.client.subscriptions.get(subscription['_id']) + self.assertIsNotNone(subscription['_id']) + + def test_get_multiple_subscriptions(self): + subscriptions = self.client.subscriptions.get()['subscriptions'] + self.assertIsInstance(subscriptions, list) + + def test_update_subscription_info(self): + subscription = self.client.subscriptions.create(subscription_create_payload) + payload = subscription_update_payload + subscription = self.client.subscriptions.get(subscription['_id']) + subscription = self.client.subscriptions.update(subscription['_id'], payload) + self.assertIsNotNone(subscription['_id']) + self.assertEqual(['USERS|POST'], subscription['scope']) \ No newline at end of file diff --git a/synapse_pay_rest/tests/client_tests.py b/synapse_pay_rest/tests/client_tests.py index bdb7b95..b5785a4 100644 --- a/synapse_pay_rest/tests/client_tests.py +++ b/synapse_pay_rest/tests/client_tests.py @@ -4,6 +4,7 @@ from synapse_pay_rest.api.users import Users from synapse_pay_rest.api.nodes import Nodes from synapse_pay_rest.api.trans import Trans +from synapse_pay_rest.api.subnets import Subnets from synapse_pay_rest.tests.fixtures.client import * @@ -24,10 +25,11 @@ def test_properties(self): self.assertIsInstance(self.client.users, Users) self.assertIsInstance(self.client.nodes, Nodes) self.assertIsInstance(self.client.trans, Trans) + self.assertIsInstance(self.client.subnets, Subnets) def test_passes_correct_base_url_to_http_client(self): sandbox = 'https://uat-api.synapsefi.com/v3.1' - production = 'https://synapsepay.com/api/3' + production = 'https://api.synapsefi.com/v3.1' self.assertEqual(sandbox, self.client.http_client.base_url) prod_client = Client( @@ -47,3 +49,5 @@ def test_passes_header_info_to_http_client(self): self.assertEqual(gateway, headers['X-SP-GATEWAY']) self.assertEqual(user, headers['X-SP-USER']) self.assertEqual(IP_ADDRESS, headers['X-SP-USER-IP']) + + diff --git a/synapse_pay_rest/tests/fixtures/client.py b/synapse_pay_rest/tests/fixtures/client.py index ee37628..e74f38c 100644 --- a/synapse_pay_rest/tests/fixtures/client.py +++ b/synapse_pay_rest/tests/fixtures/client.py @@ -8,7 +8,9 @@ CLIENT_ID = input("Please enter client ID: ") CLIENT_SECRET = input("Please enter client secret: ") -FINGERPRINT = 'test_fp' +# FINGERPRINT = '737321631015eefd8dd6573ccce33319' +FINGERPRINT = 'c7e516361e6e5453de96223616d73a7b' + IP_ADDRESS = '127.0.0.1' test_client = Client( diff --git a/synapse_pay_rest/tests/fixtures/issue_public_key.py b/synapse_pay_rest/tests/fixtures/issue_public_key.py new file mode 100644 index 0000000..612dd6a --- /dev/null +++ b/synapse_pay_rest/tests/fixtures/issue_public_key.py @@ -0,0 +1,3 @@ +public_key_create_args = { + 'scope': 'CLIENT|CONTROLS' +} \ No newline at end of file diff --git a/synapse_pay_rest/tests/fixtures/node.py b/synapse_pay_rest/tests/fixtures/node.py index 0c6ebda..49d6030 100644 --- a/synapse_pay_rest/tests/fixtures/node.py +++ b/synapse_pay_rest/tests/fixtures/node.py @@ -33,3 +33,4 @@ "bank_name": "fake" } } + diff --git a/synapse_pay_rest/tests/fixtures/subnet.py b/synapse_pay_rest/tests/fixtures/subnet.py new file mode 100644 index 0000000..0629719 --- /dev/null +++ b/synapse_pay_rest/tests/fixtures/subnet.py @@ -0,0 +1,7 @@ +subnet_create_payload = { + "nickname": "Test Python Subnet" +} + +subnet_update_payload = { + "allowed": "LOCKED" +} diff --git a/synapse_pay_rest/tests/fixtures/subscription.py b/synapse_pay_rest/tests/fixtures/subscription.py new file mode 100644 index 0000000..4f2dbfb --- /dev/null +++ b/synapse_pay_rest/tests/fixtures/subscription.py @@ -0,0 +1,15 @@ +subscription_create_payload = { + 'scope': [ + 'USERS|POST', + 'USER|PATCH', + 'NODES|POST', + 'NODE|PATCH', + 'TRANS|POST', + 'TRAN|PATCH' + ], + 'url': 'https://requestb.in/1756g2g1' +} + +subscription_update_payload = { + "scope": ['USERS|POST'] +} \ No newline at end of file diff --git a/synapse_pay_rest/tests/models/__init__.py b/synapse_pay_rest/tests/models/__init__.py index 5781d17..8bec823 100644 --- a/synapse_pay_rest/tests/models/__init__.py +++ b/synapse_pay_rest/tests/models/__init__.py @@ -3,3 +3,6 @@ from .transaction_tests import * from .base_document_tests import * from .document_tests import * +from .subnet_tests import * +from .subscription_tests import * +from .issue_public_key import * \ No newline at end of file diff --git a/synapse_pay_rest/tests/models/document_tests.py b/synapse_pay_rest/tests/models/document_tests.py index bb48b53..6f0551a 100644 --- a/synapse_pay_rest/tests/models/document_tests.py +++ b/synapse_pay_rest/tests/models/document_tests.py @@ -72,7 +72,6 @@ def test_virtual_document_with_valid_ssn(self): self.assertIsInstance(doc, VirtualDocument) self.assertEqual(doc.type, type) self.assertEqual(self.base_document.id, doc.base_document.id) - self.assertEqual('SUBMITTED|VALID', doc.status) def test_virtual_document_with_invalid_ssn(self): type = 'SSN' @@ -81,7 +80,6 @@ def test_virtual_document_with_invalid_ssn(self): self.assertIsInstance(doc, VirtualDocument) self.assertEqual(doc.type, type) self.assertEqual(self.base_document.id, doc.base_document.id) - self.assertEqual('SUBMITTED|INVALID', doc.status) @unittest.skip("deprecated") def test_virtual_document_with_ssn_mfa(self): diff --git a/synapse_pay_rest/tests/models/issue_public_key.py b/synapse_pay_rest/tests/models/issue_public_key.py new file mode 100644 index 0000000..eaa2877 --- /dev/null +++ b/synapse_pay_rest/tests/models/issue_public_key.py @@ -0,0 +1,21 @@ +import unittest +import pdb +from synapse_pay_rest.tests.fixtures.client import * +from synapse_pay_rest.tests.fixtures.issue_public_key import * +from synapse_pay_rest.client import Client +from synapse_pay_rest.models import PublicKey + + +class PublicKeyTestCases(unittest.TestCase): + def setUp(self): + print('\n{0}.{1}'.format(type(self).__name__, self._testMethodName)) + self.client = test_client + + def test_create(self): + public_key = PublicKey.issue(self.client, **public_key_create_args) + properties = ['client', 'client_obj_id', 'expires_at', 'expires_in', + 'public_key', 'scope'] + self.assertEqual(self.client, public_key.client) + # check properties assigned + for prop in properties: + self.assertIsNotNone(getattr(public_key, prop)) diff --git a/synapse_pay_rest/tests/models/node_tests.py b/synapse_pay_rest/tests/models/node_tests.py index 9e093f7..163e6bc 100644 --- a/synapse_pay_rest/tests/models/node_tests.py +++ b/synapse_pay_rest/tests/models/node_tests.py @@ -5,6 +5,7 @@ from synapse_pay_rest.tests.fixtures.node import * from synapse_pay_rest.models import User from synapse_pay_rest.models.nodes import * +from synapse_pay_rest.models import BaseDocument class NodeTestCases(unittest.TestCase): @@ -323,3 +324,142 @@ def test_create_wire_us_node(self): 'permission'] for prop in other_props: self.assertIsNotNone(getattr(node, prop)) + + def test_create_deposit_us_node(self): + kwargs = { + 'supp_id': 'ABC123' + } + node = DepositUsNode.create(self.user, 'Python Test DEPOSIT-US Node', + **kwargs) + self.assertIsInstance(node, DepositUsNode) + self.assertEqual(self.user.id, node.user.id) + for prop in kwargs: + self.assertIsNotNone(getattr(node, prop)) + + other_props = ['user', 'nickname', 'id', 'type', 'is_active', + 'permission', 'currency'] + for prop in other_props: + self.assertIsNotNone(getattr(node, prop)) + + def test_create_check_us_node(self): + kwargs = { + 'nickname': 'Python Test CHECK-US Account', + 'payee_name': 'Test McTest', + 'address_street': '1 MARKET ST', + 'address_city': 'SAN FRANCISCO', + 'address_subdivision': 'CA', + 'address_country_code': 'US', + 'address_postal_code': '94105' + } + node = CheckUsNode.create(self.user, **kwargs) + self.assertIsInstance(node, CheckUsNode) + self.assertEqual(self.user.id, node.user.id) + for prop in kwargs: + self.assertIsNotNone(getattr(node, prop)) + + other_props = ['is_active', 'permission', 'type', 'payee_name', + 'address_street', 'address_city', 'address_subdivision', + 'address_country_code', 'address_postal_code'] + for prop in other_props: + self.assertIsNotNone(getattr(node, prop)) + + def test_create_interchange_us_node(self): + user = User.create(self.client, **user_create_args) + args = { + 'email': 'scoobie@doom.com', + 'phone_number': '707-555-5555', + 'ip': '127.0.0.1', + 'name': 'Doctor BaseDoc', + 'alias': 'Basey', + 'entity_type': 'F', + 'entity_scope': 'Arts & Entertainment', + 'day': 28, + 'month': 2, + 'year': 1990, + 'address_street':'1 Market St.', + 'address_city':'SF', + 'address_subdivision':'CA', + 'address_postal_code':'94114', + 'address_country_code':'US', + } + base_document = user.add_base_document(**args) + doc_id = base_document.id + kwargs = { + 'nickname': 'Python Test INTERCHANGE-US Account', + 'card_number': 'nNKBubGyeL+31Hhgim89lIvfezPdfe8hLQxvm9H2wfpI2PxHk6yqvdh0jKwhib74LHBemAI5sRyr/5LmnYOeJoUU5TmkBtpvhxDTAtoCrim7+3KGatDwq1Z6NzV+S46fu+hp2h5DxUx6Os3PPalwz06qgbTG1yIkEvFi23D1FJGj2RM5BwYuy+dASktSoSHejj4+idiG8Sc48rKzOJXkRHSA/GIhyGeL0/GscTqAwiXaA9f9QjW74T0Ux/LRjXqVVK1wmT2M/UHLV/rheVCNZPw9Xq/VPoO3Jb/VbezsSvPwaHEV9M+utmUyn/jPru4vQpX7WM133Zx7OerGsyr/Zg==', + 'exp_date': 'ctA4Zj1CP0WCiMefPYsyewVbIHNilfwA09X9NSCyWxft4WGwFZmZkhsBJh51QL751/iFkUHbd09ZpDYjS86PqyNPZ5LkBueGHDIghLwWyzH1l99RiIs8urOW9c4g3L1USD+kzzRAqG1DBkW47FAX6AhPSi3YgQd94ery1H+asaqDrP79ayzoJ+nRXeEqe83FIgNUk/J5+EcAz3JYnoBmp1sfz7a4zHkvk0eKCxQWLETdqvONyCZyXdC/4CkaCxJ/87VsN3i4+ToULtSluRv8xr1NpRhzipKiEKTYW1nvNDAaJQezTVP/+GxmTmQfnfpVNDpJbXjNrOTej1HgMFpg4w==', + 'document_id': str(doc_id) + } + node = InterchangeUsNode.create(user, **kwargs) + self.assertIsInstance(node, InterchangeUsNode) + self.assertEqual(user.id, node.user.id) + + other_props = ['nickname', 'id', 'type', 'is_active', + 'network', 'card_type', 'document_id', 'card_hash', + 'is_international'] + for prop in other_props: + self.assertIsNotNone(getattr(node, prop)) + + def test_create_ib_deposit_us_node(self): + kwargs = { + 'supp_id': 'ABC123' + } + node = IbDepositUsNode.create(self.user, 'Python Test IB-DEPOSIT-US Node', + **kwargs) + self.assertIsInstance(node, IbDepositUsNode) + self.assertEqual(self.user.id, node.user.id) + for prop in kwargs: + self.assertIsNotNone(getattr(node, prop)) + + other_props = ['user', 'nickname', 'id', 'type', 'is_active', + 'permission', 'currency'] + for prop in other_props: + self.assertIsNotNone(getattr(node, prop)) + + def test_create_ib_subaccount_us_node(self): + kwargs = { + 'supp_id': 'ABC123' + } + node = IbSubaccountUsNode.create(self.user, 'Python Test IB-SUBACCOUNT-US Node', + **kwargs) + self.assertIsInstance(node, IbSubaccountUsNode) + self.assertEqual(self.user.id, node.user.id) + for prop in kwargs: + self.assertIsNotNone(getattr(node, prop)) + + other_props = ['user', 'nickname', 'id', 'type', 'is_active', + 'permission', 'currency'] + for prop in other_props: + self.assertIsNotNone(getattr(node, prop)) + + def test_create_subaccount_us_node(self): + kwargs = { + 'supp_id': 'ABC123' + } + node = SubaccountUsNode.create(self.user, 'Python Test SUBACCOUNT-US Node', + **kwargs) + self.assertIsInstance(node, SubaccountUsNode) + self.assertEqual(self.user.id, node.user.id) + for prop in kwargs: + self.assertIsNotNone(getattr(node, prop)) + + other_props = ['user', 'nickname', 'id', 'type', 'is_active', + 'permission', 'currency'] + for prop in other_props: + self.assertIsNotNone(getattr(node, prop)) + + def test_create_clearing_us_node(self): + kwargs = { + 'supp_id': 'ABC123' + } + node = ClearingUsNode.create(self.user, 'Python Test CLEARING-US Node', + **kwargs) + self.assertIsInstance(node, ClearingUsNode) + self.assertEqual(self.user.id, node.user.id) + for prop in kwargs: + self.assertIsNotNone(getattr(node, prop)) + + other_props = ['user', 'nickname', 'id', 'type', 'is_active', + 'permission', 'currency'] + for prop in other_props: + self.assertIsNotNone(getattr(node, prop)) diff --git a/synapse_pay_rest/tests/models/subnet_tests.py b/synapse_pay_rest/tests/models/subnet_tests.py new file mode 100644 index 0000000..2725a1e --- /dev/null +++ b/synapse_pay_rest/tests/models/subnet_tests.py @@ -0,0 +1,65 @@ +import unittest +import pdb +import time +from synapse_pay_rest.tests.fixtures.client import * +from synapse_pay_rest.tests.fixtures.user import * +from synapse_pay_rest.tests.fixtures.node import * +from synapse_pay_rest.tests.fixtures.subnet import * +from synapse_pay_rest.models import User +from synapse_pay_rest.models.nodes.synapse_us_node import SynapseUsNode +from synapse_pay_rest.models import Subnet +from synapse_pay_rest.errors import * + + +class SubnetTestCases(unittest.TestCase): + def setUp(self): + print('\n{0}.{1}'.format(type(self).__name__, self._testMethodName)) + self.client = test_client + self.user = User.create(self.client, **user_create_args) + self.node = SynapseUsNode.create(self.user, 'Python Test SYNAPSE-US Node') + + def test_create(self): + subnet = Subnet.create(self.node, + 'test Subnet') + self.assertIsInstance(subnet, Subnet) + self.assertEqual(self.node.id, subnet.node.id) + + other_props = ['client_id', 'client_name', 'account_num', 'allowed', + 'nickname', 'node_id', 'routing_num_ach', 'routing_num_wire', + 'user_id'] + + for prop in other_props: + self.assertIsNotNone(getattr(subnet, prop)) + + + def test_by_id(self): + subnet_id = Subnet.create(self.node, + 'ABC123').id + subnet = Subnet.by_id(self.node, subnet_id) + self.assertEqual(self.node.id, subnet.node.id) + self.assertIsInstance(subnet, Subnet) + self.assertEqual(subnet_id, subnet.id) + + def test_all(self): + Subnet.create(self.node, 'ABC123') + Subnet.create(self.node, 'DEF456') + Subnet.create(self.node, 'testing') + Subnet.create(self.node, 'test') + + subnets = Subnet.all(self.node) + self.assertEqual(4, len(subnets)) + self.assertIsInstance(subnets[0], Subnet) + self.assertEqual(self.node.id, subnets[0].node.id) + # with params + per_page = 2 + page1 = Subnet.all(self.node, page=1, per_page=per_page) + page2 = Subnet.all(self.node, page=2, per_page=per_page) + self.assertNotEqual(page1[0].id, page2[0].id) + self.assertEqual(per_page, len(page1)) + + def test_lock(self): + subnet = Subnet.create(self.node, + 'Test Subnet') + response = subnet.lock() + self.assertEqual('LOCKED', response.allowed ) + diff --git a/synapse_pay_rest/tests/models/subscription_tests.py b/synapse_pay_rest/tests/models/subscription_tests.py new file mode 100644 index 0000000..1867b21 --- /dev/null +++ b/synapse_pay_rest/tests/models/subscription_tests.py @@ -0,0 +1,57 @@ +import unittest +import pdb +from synapse_pay_rest.tests.fixtures.client import * +from synapse_pay_rest.tests.fixtures.subscription import * +from synapse_pay_rest.client import Client +from synapse_pay_rest.models import Subscription + + +class SubscriptionTestCases(unittest.TestCase): + def setUp(self): + print('\n{0}.{1}'.format(type(self).__name__, self._testMethodName)) + self.client = test_client + + def test_create(self): + subscription = Subscription.create(self.client, **subscription_create_payload) + properties = ['id', 'client_id', 'is_active', + 'scope', 'url'] + self.assertEqual(self.client, subscription.client) + # check properties assigned + for prop in properties: + self.assertIsNotNone(getattr(subscription, prop)) + + def test_by_id(self): + subscription_id = Subscription.create(self.client, **subscription_create_payload).id + subscription = Subscription.by_id(self.client, subscription_id) + self.assertIsInstance(subscription, Subscription) + self.assertEqual(subscription_id, subscription.id) + + def test_all(self): + subscriptions = Subscription.all(self.client) + self.assertIsInstance(subscriptions, list) + self.assertIsInstance(subscriptions[0], Subscription) + self.assertIsInstance(subscriptions[0].client, Client) + + + def test_update_is_active(self): + subscription = Subscription.create(self.client, **subscription_create_payload) + new_is_active = False + subscription = subscription.update_is_active(new_is_active) + self.assertEqual(new_is_active, subscription.is_active) + + + def test_update_url(self): + subscription = Subscription.create(self.client, **subscription_create_payload) + new_url = 'test.com' + subscription = subscription.update_url(new_url) + self.assertEqual(new_url, subscription.url) + + def test_update_scope(self): + subscription = Subscription.create(self.client, **subscription_create_payload) + new_scope = ["USERS|POST"] + subscription = subscription.update_scope(new_scope) + self.assertEqual(new_scope, subscription.scope) + + + + \ No newline at end of file From 96e564b29471e21a601263c65f3cd42a7e22a7f0 Mon Sep 17 00:00:00 2001 From: tynahuynh Date: Fri, 23 Mar 2018 10:42:31 -0700 Subject: [PATCH 14/41] CARD_US --- samples.md | 91 +++++++++++ synapse_pay_rest/models/__init__.py | 3 +- synapse_pay_rest/models/nodes/__init__.py | 2 + synapse_pay_rest/models/nodes/base_node.py | 16 +- synapse_pay_rest/models/nodes/card_us_node.py | 53 +++++++ synapse_pay_rest/models/nodes/node.py | 15 +- .../models/nodes/subcard_us_node.py | 52 ++++++ synapse_pay_rest/tests/fixtures/user.py | 20 ++- synapse_pay_rest/tests/models/node_tests.py | 148 +++++++++++++++--- .../tests/models/transaction_tests.py | 7 +- 10 files changed, 377 insertions(+), 30 deletions(-) create mode 100644 synapse_pay_rest/models/nodes/card_us_node.py create mode 100644 synapse_pay_rest/models/nodes/subcard_us_node.py diff --git a/samples.md b/samples.md index ad34fb4..8c1f160 100644 --- a/samples.md +++ b/samples.md @@ -772,3 +772,94 @@ transaction = transaction.add_comment('this is my best transaction') ```python transaction = transaction.cancel() ``` + +## Subnet Methods + +#### Retrieve All Subnets Sent from a Node + +```python +from synapse_pay_rest import Subnet + +options = { + 'page': 1, + 'per_page': 20 +} + +subnets = Subnet.all(node, **options) +``` + +#### Retrieve Node's Subnet by Subnet ID + +```python +subnet = Subnet.by_id(node, '57fc1a6886c2732e64a94c25') +``` + +#### Create a Subnet from a Node + +```python +args = { + 'nickname': 'Test Subnet' +} + +subnet = Subnet.create(node, **args) +``` + +#### Lock Subnet + +```python +subnet = subnet.lock() +``` + +## Subscription Methods + +#### Retrieve All Subscriptions + +```python +from synapse_pay_rest import Subscription + +options = { + 'page': 1, + 'per_page': 20 +} + +subscription = Subscription.all(client) +``` + +#### Retrieve Subscription by ID + +```python +subscription = Subscription.by_id(client, '57fc1a6886c2732e64a94c25') +``` + +#### Create a Subscription + +```python +args = { + 'scope': [ + 'USERS|POST', + 'USER|PATCH', + 'NODES|POST', + 'NODE|PATCH', + 'TRANS|POST', + 'TRAN|PATCH' + ], + 'url': 'https://requestb.in/1756g2g1' +} + +subscription = Subscription.create(client, **args) +``` + +#### Update a Subscription's URL + +```python +new_url = 'test.com' +subscription = subscription.update_url(new_url) +``` + +#### Update a Subscription's scope + +```python +new_scope = ["USERS|POST"] +subscription = subscription.update_scope(new_scope) +``` + diff --git a/synapse_pay_rest/models/__init__.py b/synapse_pay_rest/models/__init__.py index ec30e1a..a821c12 100644 --- a/synapse_pay_rest/models/__init__.py +++ b/synapse_pay_rest/models/__init__.py @@ -8,7 +8,8 @@ BaseDocument, Question from .nodes import AchUsNode, EftIndNode, EftNpNode, IouNode, ReserveUsNode,\ SynapseIndNode, SynapseNpNode, SynapseUsNode, WireIntNode,\ - WireUsNode, DepositUsNode, CheckUsNode, InterchangeUsNode, IbSubaccountUsNode, IbDepositUsNode, SubaccountUsNode, ClearingUsNode, Node + WireUsNode, DepositUsNode, CheckUsNode, InterchangeUsNode,\ + IbSubaccountUsNode, IbDepositUsNode, SubaccountUsNode, ClearingUsNode, CardUsNode, SubcardUsNode, Node from .transactions import Transaction from .subnets import Subnet from .subscriptions import Subscription diff --git a/synapse_pay_rest/models/nodes/__init__.py b/synapse_pay_rest/models/nodes/__init__.py index e322320..5bfdbbc 100644 --- a/synapse_pay_rest/models/nodes/__init__.py +++ b/synapse_pay_rest/models/nodes/__init__.py @@ -23,4 +23,6 @@ from .ib_subaccount_us_node import IbSubaccountUsNode from .clearing_us_node import ClearingUsNode from .subaccount_us_node import SubaccountUsNode +from .card_us_node import CardUsNode +from .subcard_us_node import SubcardUsNode from .node import Node diff --git a/synapse_pay_rest/models/nodes/base_node.py b/synapse_pay_rest/models/nodes/base_node.py index 27fa223..6656da1 100644 --- a/synapse_pay_rest/models/nodes/base_node.py +++ b/synapse_pay_rest/models/nodes/base_node.py @@ -40,9 +40,10 @@ def from_response(cls, user, response): 'payee_name': response['info'].get('payee_name'), 'document_id': response['info'].get('document_id'), 'network': response['info'].get('network'), - 'card_type': response['info'].get('type'), + 'interchange_type': response['info'].get('type'), 'card_hash': response['info'].get('card_hash'), - 'is_international': response['info'].get('is_international') + 'is_international': response['info'].get('is_international'), + 'card_type': response['info'].get('card_type') } if response['info'].get('correspondent_info'): @@ -77,6 +78,15 @@ def from_response(cls, user, response): args['address_country_code'] = info.get('address_country_code') args['address_postal_code'] = info.get('address_postal_code') + #cards info(optional) + if response['info'].get('preferences'): + info = response['info']['preferences'] + args['allow_foreign_transactions'] = info.get('allow_foreign_transactions') + args['atm_withdrawal_limit'] = info.get('atm_withdrawal_limit') + args['max_pin_attempts'] = info.get('max_pin_attempts') + args['pos_withdrawal_limit'] = info.get('pos_withdrawal_limit') + args['security_alerts'] = info.get('security_alerts') + return cls(**args) @classmethod @@ -132,6 +142,8 @@ def payload_for_create(cls, type, **kwargs): payload['info']['exp_date'] = kwargs['exp_date'] if 'document_id' in kwargs: payload['info']['document_id'] = kwargs['document_id'] + if 'card_type' in kwargs: + payload['info']['card_type'] = kwargs['card_type'] balance_options = ['currency'] balance = {} diff --git a/synapse_pay_rest/models/nodes/card_us_node.py b/synapse_pay_rest/models/nodes/card_us_node.py new file mode 100644 index 0000000..985db81 --- /dev/null +++ b/synapse_pay_rest/models/nodes/card_us_node.py @@ -0,0 +1,53 @@ +from .base_node import BaseNode + + +class CardUsNode(BaseNode): + """Represents a CARD-US node.""" + + @classmethod + def payload_for_create(cls, nickname, document_id, card_type, **kwargs): + """Build the API 'create node' payload specific to CARD-US.""" + payload = super().payload_for_create('CARD-US', + nickname=nickname, + document_id=document_id, + card_type=card_type, + **kwargs) + return payload + + def update_preferences(self, **kwargs): + """Update CARD-US preferences + + Returns: + CardUsNode: a new instance representing the same API record + """ + payload = self.payload_for_preferences(**kwargs) + response = self.user.client.nodes.update(self.user.id, self.id, payload) + return self.from_response(self.user, response) + + def update_allowed(self, allowed): + """Update CARD-US allowed. Change to INACTIVE if card is misplaced. LOCKED if lost. + + Returns: + CardUsNode: a new instance representing the same API record + """ + payload = {'allowed': allowed} + response = self.user.client.nodes.update(self.user.id, self.id, payload) + return self.from_response(self.user, response) + + def payload_for_preferences(self, **kwargs): + payload = { + 'preferences': {} + } + + if 'allow_foreign_transactions' in kwargs: + payload['preferences']['allow_foreign_transactions'] = kwargs['allow_foreign_transactions'] + if 'atm_withdrawal_limit' in kwargs: + payload['preferences']['atm_withdrawal_limit'] = kwargs['atm_withdrawal_limit'] + if 'max_pin_attempts' in kwargs: + payload['preferences']['max_pin_attempts'] = kwargs['max_pin_attempts'] + if 'pos_withdrawal_limit' in kwargs: + payload['preferences']['pos_withdrawal_limit'] = kwargs['pos_withdrawal_limit'] + if 'security_alerts' in kwargs: + payload['preferences']['security_alerts'] = kwargs['security_alerts'] + return payload + diff --git a/synapse_pay_rest/models/nodes/node.py b/synapse_pay_rest/models/nodes/node.py index 0a0563d..fc33e5f 100644 --- a/synapse_pay_rest/models/nodes/node.py +++ b/synapse_pay_rest/models/nodes/node.py @@ -18,6 +18,8 @@ from .ib_subaccount_us_node import IbSubaccountUsNode from .clearing_us_node import ClearingUsNode from .subaccount_us_node import SubaccountUsNode +from .card_us_node import CardUsNode +from .subcard_us_node import SubcardUsNode class Node(): @@ -45,7 +47,9 @@ class Node(): 'IB-DEPOSIT-US': IbDepositUsNode, 'IB-SUBACCOUNT-US': IbSubaccountUsNode, 'CLEARING-US': ClearingUsNode, - 'SUBACCOUNT-US': SubaccountUsNode + 'SUBACCOUNT-US': SubaccountUsNode, + 'CARD-US': CardUsNode, + 'SUBCARD-US': SubcardUsNode } @classmethod @@ -111,6 +115,15 @@ def from_response(cls, user, response): args['address_country_code'] = info.get('address_country_code') args['address_postal_code'] = info.get('address_postal_code') + #cards info(optional) + if response['info'].get('preferences'): + info = response['info']['preferences'] + args['allow_foreign_transactions'] = info.get('allow_foreign_transactions') + args['atm_withdrawal_limit'] = info.get('atm_withdrawal_limit') + args['max_pin_attempts'] = info.get('max_pin_attempts') + args['pos_withdrawal_limit'] = info.get('pos_withdrawal_limit') + args['security_alerts'] = info.get('security_alerts') + klass = cls.NODE_TYPES_TO_CLASSES.get(response['type'], BaseNode) return klass(**args) diff --git a/synapse_pay_rest/models/nodes/subcard_us_node.py b/synapse_pay_rest/models/nodes/subcard_us_node.py new file mode 100644 index 0000000..92751a5 --- /dev/null +++ b/synapse_pay_rest/models/nodes/subcard_us_node.py @@ -0,0 +1,52 @@ +from .base_node import BaseNode + + +class SubcardUsNode(BaseNode): + """Represents a SUBCARD-US node.""" + + @classmethod + def payload_for_create(cls, nickname, document_id, card_type, **kwargs): + """Build the API 'create node' payload specific to SUBCARD-US.""" + payload = super().payload_for_create('SUBCARD-US', + nickname=nickname, + document_id=document_id, + card_type=card_type, + **kwargs) + return payload + + def update_preferences(self, **kwargs): + """Update SUBCARD-US preferences + + Returns: + SubcardUsNode: a new instance representing the same API record + """ + payload = self.payload_for_preferences(**kwargs) + response = self.user.client.nodes.update(self.user.id, self.id, payload) + return self.from_response(self.user, response) + + def update_allowed(self, allowed): + """Update SUBCARD-US allowed. Change to INACTIVE if card is misplaced. LOCKED if lost. + + Returns: + SubcardUsNode: a new instance representing the same API record + """ + payload = {'allowed': allowed} + response = self.user.client.nodes.update(self.user.id, self.id, payload) + return self.from_response(self.user, response) + + def payload_for_preferences(self, **kwargs): + payload = { + 'preferences': {} + } + + if 'allow_foreign_transactions' in kwargs: + payload['preferences']['allow_foreign_transactions'] = kwargs['allow_foreign_transactions'] + if 'atm_withdrawal_limit' in kwargs: + payload['preferences']['atm_withdrawal_limit'] = kwargs['atm_withdrawal_limit'] + if 'max_pin_attempts' in kwargs: + payload['preferences']['max_pin_attempts'] = kwargs['max_pin_attempts'] + if 'pos_withdrawal_limit' in kwargs: + payload['preferences']['pos_withdrawal_limit'] = kwargs['pos_withdrawal_limit'] + if 'security_alerts' in kwargs: + payload['preferences']['security_alerts'] = kwargs['security_alerts'] + return payload diff --git a/synapse_pay_rest/tests/fixtures/user.py b/synapse_pay_rest/tests/fixtures/user.py index cf21612..a4aae50 100644 --- a/synapse_pay_rest/tests/fixtures/user.py +++ b/synapse_pay_rest/tests/fixtures/user.py @@ -37,7 +37,7 @@ 'legal_name': 'Hello McHello', 'note': ':)', 'supp_id': '123abc', - 'is_business': True, + 'is_business': False, 'cip_tag': 1, 'password': 'password' } @@ -59,3 +59,21 @@ 'address_postal_code': '94114', 'address_country_code': 'US' } + +base_doc_args = { + "email":"test@test.com", + "phone_number":"901.111.1111", + "ip":"127.0.0.1", + "name":"FirstName LastName", + "alias":"Test", + "entity_type":"M", + "entity_scope":"Arts & Entertainment", + "day":2, + "month":5, + "year":1989, + "address_street":"1 Market St.", + "address_city":"SF", + "address_subdivision":"CA", + "address_postal_code":"94114", + "address_country_code":"US" + } diff --git a/synapse_pay_rest/tests/models/node_tests.py b/synapse_pay_rest/tests/models/node_tests.py index 163e6bc..ad2050e 100644 --- a/synapse_pay_rest/tests/models/node_tests.py +++ b/synapse_pay_rest/tests/models/node_tests.py @@ -1,5 +1,6 @@ import unittest import pdb +import time from synapse_pay_rest.tests.fixtures.client import * from synapse_pay_rest.tests.fixtures.user import * from synapse_pay_rest.tests.fixtures.node import * @@ -365,24 +366,24 @@ def test_create_check_us_node(self): def test_create_interchange_us_node(self): user = User.create(self.client, **user_create_args) - args = { - 'email': 'scoobie@doom.com', - 'phone_number': '707-555-5555', - 'ip': '127.0.0.1', - 'name': 'Doctor BaseDoc', - 'alias': 'Basey', - 'entity_type': 'F', - 'entity_scope': 'Arts & Entertainment', - 'day': 28, - 'month': 2, - 'year': 1990, - 'address_street':'1 Market St.', - 'address_city':'SF', - 'address_subdivision':'CA', - 'address_postal_code':'94114', - 'address_country_code':'US', - } - base_document = user.add_base_document(**args) + # args = { + # "email":"test@test.com", + # "phone_number":"901.111.1111", + # "ip":"::1", + # "name":"FirstName LastName", + # "alias":"Test", + # "entity_type":"M", + # "entity_scope":"Arts & Entertainment", + # "day":2, + # "month":5, + # "year":1989, + # "address_street":"1 Market St.", + # "address_city":"SF", + # "address_subdivision":"CA", + # "address_postal_code":"94114", + # "address_country_code":"US" + # } + base_document = user.add_base_document(**base_doc_args) doc_id = base_document.id kwargs = { 'nickname': 'Python Test INTERCHANGE-US Account', @@ -395,7 +396,7 @@ def test_create_interchange_us_node(self): self.assertEqual(user.id, node.user.id) other_props = ['nickname', 'id', 'type', 'is_active', - 'network', 'card_type', 'document_id', 'card_hash', + 'network', 'interchange_type', 'document_id', 'card_hash', 'is_international'] for prop in other_props: self.assertIsNotNone(getattr(node, prop)) @@ -463,3 +464,112 @@ def test_create_clearing_us_node(self): 'permission', 'currency'] for prop in other_props: self.assertIsNotNone(getattr(node, prop)) + + def test_create_card_us_node(self): + user = self.user + base_document = user.add_base_document(**base_doc_args) + doc_id = base_document.id + time.sleep(5) + kwargs = { + 'nickname': 'Python Test CARD-US Account', + 'document_id': str(doc_id), + 'card_type': "VIRTUAL" + } + node = CardUsNode.create(user, **kwargs) + self.assertIsInstance(node, CardUsNode) + self.assertEqual(user.id, node.user.id) + + other_props = ['nickname', 'id', 'type', 'is_active', + 'document_id', 'allow_foreign_transactions', + 'atm_withdrawal_limit', 'max_pin_attempts', 'pos_withdrawal_limit', + 'security_alerts', 'card_type'] + for prop in other_props: + self.assertIsNotNone(getattr(node, prop)) + + def test_create_subcard_us_node(self): + user = self.user + base_document = user.add_base_document(**base_doc_args) + doc_id = base_document.id + time.sleep(5) + kwargs = { + 'nickname': 'Python Test SUBCARD-US Account', + 'document_id': str(doc_id), + 'card_type': "VIRTUAL" + } + node = SubcardUsNode.create(user, **kwargs) + self.assertIsInstance(node, SubcardUsNode) + self.assertEqual(user.id, node.user.id) + + other_props = ['nickname', 'id', 'type', 'is_active', + 'document_id', 'allow_foreign_transactions', + 'atm_withdrawal_limit', 'max_pin_attempts', 'pos_withdrawal_limit', + 'security_alerts', 'card_type'] + for prop in other_props: + self.assertIsNotNone(getattr(node, prop)) + + def test_update_preference_card_us_node(self): + user = self.user + base_document = user.add_base_document(**base_doc_args) + doc_id = base_document.id + time.sleep(5) + kwargs = { + 'nickname': 'Python Test CARD-US Account', + 'document_id': str(doc_id), + 'card_type': "VIRTUAL" + } + node = CardUsNode.create(user, **kwargs) + args2 = { + 'max_pin_attempts': 4 + } + node = node.update_preferences(**args2) + time.sleep(2) + self.assertEqual(4, node.max_pin_attempts) + + def test_update_allowed_card_us_node(self): + user = self.user + base_document = user.add_base_document(**base_doc_args) + doc_id = base_document.id + time.sleep(5) + kwargs = { + 'nickname': 'Python Test CARD-US Account', + 'document_id': str(doc_id), + 'card_type': "VIRTUAL" + } + node = CardUsNode.create(user, **kwargs) + node = node.update_allowed('INACTIVE') + time.sleep(1) + self.assertEqual('INACTIVE', node.permission) + + def test_update_preference_subcard_us_node(self): + user = self.user + base_document = user.add_base_document(**base_doc_args) + doc_id = base_document.id + time.sleep(5) + kwargs = { + 'nickname': 'Python Test SUBCARD-US Account', + 'document_id': str(doc_id), + 'card_type': "VIRTUAL" + } + node = SubcardUsNode.create(user, **kwargs) + args2 = { + 'max_pin_attempts': 4 + } + node = node.update_preferences(**args2) + time.sleep(2) + self.assertEqual(4, node.max_pin_attempts) + + def test_update_allowed_subcard_us_node(self): + user = self.user + base_document = user.add_base_document(**base_doc_args) + doc_id = base_document.id + time.sleep(5) + kwargs = { + 'nickname': 'Python Test SUBCARD-US Account', + 'document_id': str(doc_id), + 'card_type': "VIRTUAL" + } + node = SubcardUsNode.create(user, **kwargs) + node = node.update_allowed('INACTIVE') + time.sleep(1) + self.assertEqual('INACTIVE', node.permission) + diff --git a/synapse_pay_rest/tests/models/transaction_tests.py b/synapse_pay_rest/tests/models/transaction_tests.py index 6fadd5d..03e50c4 100644 --- a/synapse_pay_rest/tests/models/transaction_tests.py +++ b/synapse_pay_rest/tests/models/transaction_tests.py @@ -82,11 +82,6 @@ def test_create_with_fees(self): 'fee': 0.12, 'note': 'Test Fee 1', 'to': {'id': fee_node.id} - }, - { - 'fee': 0.33, - 'note': 'Test Fee 2', - 'to': {'id': fee_node2.id} } ] transaction_id = Transaction.create(self.from_node, @@ -101,7 +96,7 @@ def test_create_with_fees(self): self.assertIsInstance(transaction, Transaction) self.assertEqual(self.from_node.id, transaction.node.id) self.assertEqual(transaction.fees[0]['fee'], 0.12) - self.assertEqual(len(transaction.fees), 2) + self.assertEqual(len(transaction.fees), 1) def test_by_id(self): transaction_id = Transaction.create(self.from_node, From 0abb58d4e240d13ec6ed2d5ed7cebe58ae6b65df Mon Sep 17 00:00:00 2001 From: tynahuynh Date: Fri, 23 Mar 2018 12:08:28 -0700 Subject: [PATCH 15/41] Version Number update --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f4c550a..17a4dc8 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='3.1.1', + version='3.2.0', description='SynapsePay Rest Native Python Library', From 27370370897dfc5dab807502f07c287abebd7e25 Mon Sep 17 00:00:00 2001 From: tynahuynh Date: Thu, 29 Mar 2018 17:35:48 -0700 Subject: [PATCH 16/41] adding atm locator --- samples.md | 33 +++++++++ synapse_pay_rest/__init__.py | 4 +- synapse_pay_rest/api/__init__.py | 1 + synapse_pay_rest/api/atms.py | 18 +++++ synapse_pay_rest/client.py | 2 + synapse_pay_rest/http_client.py | 2 +- synapse_pay_rest/models/__init__.py | 1 + synapse_pay_rest/models/atms/__init__.py | 7 ++ synapse_pay_rest/models/atms/atm.py | 69 +++++++++++++++++++ .../models/nodes/check_us_node.py | 4 +- .../models/nodes/clearing_us_node.py | 2 +- .../models/nodes/deposit_us_node.py | 4 +- .../models/nodes/ib_deposit_us_node.py | 2 +- .../models/nodes/ib_subaccount_us_node.py | 2 +- .../models/nodes/interchange_us_node.py | 2 +- synapse_pay_rest/tests/fixtures/atm.py | 6 ++ synapse_pay_rest/tests/models/__init__.py | 3 +- synapse_pay_rest/tests/models/atm_tests.py | 23 +++++++ synapse_pay_rest/tests/models/node_tests.py | 1 + 19 files changed, 174 insertions(+), 12 deletions(-) create mode 100644 synapse_pay_rest/api/atms.py create mode 100644 synapse_pay_rest/models/atms/__init__.py create mode 100644 synapse_pay_rest/models/atms/atm.py create mode 100644 synapse_pay_rest/tests/fixtures/atm.py create mode 100644 synapse_pay_rest/tests/models/atm_tests.py diff --git a/samples.md b/samples.md index 8c1f160..6f69ac8 100644 --- a/samples.md +++ b/samples.md @@ -863,3 +863,36 @@ new_scope = ["USERS|POST"] subscription = subscription.update_scope(new_scope) ``` +## Issue Public Key Methods + +#### Issue Public Key + +```python +from synapse_pay_rest.models import PublicKey + +options = { + 'scope': 'CLIENT|CONTROLS' +} + +public_key = PublicKey.issue(client, **options) +``` + +## Locate Atm Methods + +#### Locate Atm via Zipcode + +```python +from synapse_pay_rest.models import Atm + +options = { + 'zip': '94113', + 'radius': '5', + 'page': 1, + 'per_page': 20 +} + +atms = Atm.locate(client, **options) + +atm = atms[0] +``` + diff --git a/synapse_pay_rest/__init__.py b/synapse_pay_rest/__init__.py index c96be13..f2d150e 100644 --- a/synapse_pay_rest/__init__.py +++ b/synapse_pay_rest/__init__.py @@ -6,5 +6,5 @@ """ from .client import Client -from .api import Users, Nodes, Trans, Subnets, Subscriptions, ClientEndpoint -from .models import User, Node, Transaction, Subnet, Subscription, PublicKey +from .api import Users, Nodes, Trans, Subnets, Subscriptions, ClientEndpoint, Atms +from .models import User, Node, Transaction, Subnet, Subscription, PublicKey, Atm diff --git a/synapse_pay_rest/api/__init__.py b/synapse_pay_rest/api/__init__.py index 2da61d7..9510941 100644 --- a/synapse_pay_rest/api/__init__.py +++ b/synapse_pay_rest/api/__init__.py @@ -10,3 +10,4 @@ from .subnets import Subnets from .subscriptions import Subscriptions from .client import ClientEndpoint +from .atms import Atms diff --git a/synapse_pay_rest/api/atms.py b/synapse_pay_rest/api/atms.py new file mode 100644 index 0000000..0c24de9 --- /dev/null +++ b/synapse_pay_rest/api/atms.py @@ -0,0 +1,18 @@ +from synapse_pay_rest.http_client import HttpClient + + +class Atms(): + """Abstraction of the /nodes/atm endpoint. + + Used to make atm location-related calls to the API. + https://docs.synapsefi.com/docs/locate-atms + """ + + def __init__(self, client): + self.client = client + + def locate(self, zip=None, **kwargs): + + path = '/nodes/atms?zip=' + zip + response = self.client.get(path, **kwargs) + return response \ No newline at end of file diff --git a/synapse_pay_rest/client.py b/synapse_pay_rest/client.py index 007a7cb..387ca0d 100644 --- a/synapse_pay_rest/client.py +++ b/synapse_pay_rest/client.py @@ -5,6 +5,7 @@ from .api.subnets import Subnets from .api.subscriptions import Subscriptions from .api.client import ClientEndpoint +from .api.atms import Atms class Client(): """Handles configuration and requests to the SynapsePay API. @@ -36,6 +37,7 @@ def __init__(self, **kwargs): self.subnets = Subnets(self.http_client) self.subscriptions = Subscriptions(self.http_client) self.client_endpoint = ClientEndpoint(self.http_client) + self.atms = Atms(self.http_client) def __repr__(self): return '{0}(base_url={1})'.format(self.__class__, self.base_url) \ No newline at end of file diff --git a/synapse_pay_rest/http_client.py b/synapse_pay_rest/http_client.py index 67c90f2..8cbfe61 100644 --- a/synapse_pay_rest/http_client.py +++ b/synapse_pay_rest/http_client.py @@ -49,7 +49,7 @@ def get_headers(self): def get(self, url, **params): """Send a GET request to the API.""" self.log_information(self.logging) - valid_params = ['query', 'page', 'per_page', 'type', 'full_dehydrate'] + valid_params = ['query', 'page', 'per_page', 'type', 'full_dehydrate', 'radius'] parameters = {} for param in valid_params: if param in params: diff --git a/synapse_pay_rest/models/__init__.py b/synapse_pay_rest/models/__init__.py index a821c12..92c9493 100644 --- a/synapse_pay_rest/models/__init__.py +++ b/synapse_pay_rest/models/__init__.py @@ -14,3 +14,4 @@ from .subnets import Subnet from .subscriptions import Subscription from .issue_public_keys import PublicKey +from .atms import Atm diff --git a/synapse_pay_rest/models/atms/__init__.py b/synapse_pay_rest/models/atms/__init__.py new file mode 100644 index 0000000..e454923 --- /dev/null +++ b/synapse_pay_rest/models/atms/__init__.py @@ -0,0 +1,7 @@ +"""This module contains models for objects tied to the /nodes endpoint. + +Currently just the Atm location class. +""" + + +from .atm import Atm diff --git a/synapse_pay_rest/models/atms/atm.py b/synapse_pay_rest/models/atms/atm.py new file mode 100644 index 0000000..96e0d6c --- /dev/null +++ b/synapse_pay_rest/models/atms/atm.py @@ -0,0 +1,69 @@ +import copy +from synapse_pay_rest.client import Client + +class Atm(): + """Represents an atm location record with methods for constructing atm location methods + instances. + + """ + + def __init__(self, **kwargs): + for arg, value in kwargs.items(): + setattr(self, arg, value) + + def __repr__(self): + clean_dict = self.__dict__.copy() + return '{0}({1})'.format(self.__class__, clean_dict) + + @classmethod + def from_response(cls, client, response): + """Construct an Atm location instance from a response dict.""" + return cls( + client = client, + json=response, + address_city=response['atmLocation'].get('address').get('city'), + address_country=response['atmLocation'].get('address').get('country'), + address_postal_code=response['atmLocation'].get('address').get('postalCode'), + address_state=response['atmLocation'].get('address').get('state'), + address_street=response['atmLocation'].get('address').get('street'), + latitude=response['atmLocation'].get('coordinates').get('latitude'), + longitude=response['atmLocation'].get('coordinates').get('longitude'), + id=response['atmLocation']['id'], + isAvailable24Hours=response['atmLocation']['isAvailable24Hours'], + isDepositAvailable=response['atmLocation']['isDepositAvailable'], + isHandicappedAccessible=response['atmLocation']['isHandicappedAccessible'], + isOffPremise=response['atmLocation']['isOffPremise'], + isSeasonal=response['atmLocation']['isSeasonal'], + languageType=response['atmLocation']['languageType'], + locationDescription=response['atmLocation']['locationDescription'], + logoName=response['atmLocation']['logoName'], + name=response['atmLocation']['name'], + distance=response['distance'] + ) + + @classmethod + def multiple_from_response(cls, client, response): + """Construct multiple Atms from a response dict.""" + atms = [cls.from_response(client, atm_data) + for atm_data in response] + return atms + + + @classmethod + def locate(cls,client=None, zip=None, **kwargs): + """Locates nearby atms and create an Atm instance from it. + + Args: + client (Client): an instance of the API Client + zip(string): Zip code for ATM locator + radius(string): radius in miles + page(integer): Page number + per_page(integer): Number of atms per page + Returns: + Atm: an ATM instance corresponding to the record + """ + + response = client.atms.locate(zip, **kwargs) + return cls.multiple_from_response(client, response['atms']) + + \ No newline at end of file diff --git a/synapse_pay_rest/models/nodes/check_us_node.py b/synapse_pay_rest/models/nodes/check_us_node.py index 91f6c49..310ae36 100644 --- a/synapse_pay_rest/models/nodes/check_us_node.py +++ b/synapse_pay_rest/models/nodes/check_us_node.py @@ -2,12 +2,12 @@ class CheckUsNode(BaseNode): - """Represents a SYNAPSE-US node.""" + """Represents a CHECK-US node.""" @classmethod def payload_for_create(cls, nickname, payee_name, address_street, address_city, address_subdivision, address_country_code, address_postal_code, **kwargs): - """Build the API 'create node' payload specific to SYNAPSE-US.""" + """Build the API 'create node' payload specific to CHECK-US.""" payload = super().payload_for_create('CHECK-US', nickname=nickname, payee_name=payee_name, diff --git a/synapse_pay_rest/models/nodes/clearing_us_node.py b/synapse_pay_rest/models/nodes/clearing_us_node.py index abfee21..0c56cb1 100644 --- a/synapse_pay_rest/models/nodes/clearing_us_node.py +++ b/synapse_pay_rest/models/nodes/clearing_us_node.py @@ -2,7 +2,7 @@ class ClearingUsNode(BaseNode): - """Represents a SYNAPSE-US node.""" + """Represents a CLEARING-US node.""" @classmethod def payload_for_create(cls, nickname, **kwargs): diff --git a/synapse_pay_rest/models/nodes/deposit_us_node.py b/synapse_pay_rest/models/nodes/deposit_us_node.py index 6332968..767e51d 100644 --- a/synapse_pay_rest/models/nodes/deposit_us_node.py +++ b/synapse_pay_rest/models/nodes/deposit_us_node.py @@ -2,11 +2,11 @@ class DepositUsNode(BaseNode): - """Represents a SYNAPSE-US node.""" + """Represents a DEPOSIT-US node.""" @classmethod def payload_for_create(cls, nickname, **kwargs): - """Build the API 'create node' payload specific to SYNAPSE-US.""" + """Build the API 'create node' payload specific to DEPOSIT-US.""" payload = super().payload_for_create('DEPOSIT-US', nickname=nickname, **kwargs) diff --git a/synapse_pay_rest/models/nodes/ib_deposit_us_node.py b/synapse_pay_rest/models/nodes/ib_deposit_us_node.py index 819df53..8070fb8 100644 --- a/synapse_pay_rest/models/nodes/ib_deposit_us_node.py +++ b/synapse_pay_rest/models/nodes/ib_deposit_us_node.py @@ -2,7 +2,7 @@ class IbDepositUsNode(BaseNode): - """Represents a SYNAPSE-US node.""" + """Represents a IB-DEPOSIT-US node.""" @classmethod def payload_for_create(cls, nickname, **kwargs): diff --git a/synapse_pay_rest/models/nodes/ib_subaccount_us_node.py b/synapse_pay_rest/models/nodes/ib_subaccount_us_node.py index bb98f64..41b9df7 100644 --- a/synapse_pay_rest/models/nodes/ib_subaccount_us_node.py +++ b/synapse_pay_rest/models/nodes/ib_subaccount_us_node.py @@ -2,7 +2,7 @@ class IbSubaccountUsNode(BaseNode): - """Represents a SYNAPSE-US node.""" + """Represents a IB-SUBACCOUNT-US node.""" @classmethod def payload_for_create(cls, nickname, **kwargs): diff --git a/synapse_pay_rest/models/nodes/interchange_us_node.py b/synapse_pay_rest/models/nodes/interchange_us_node.py index 974fe58..4f3ce0f 100644 --- a/synapse_pay_rest/models/nodes/interchange_us_node.py +++ b/synapse_pay_rest/models/nodes/interchange_us_node.py @@ -6,7 +6,7 @@ class InterchangeUsNode(BaseNode): @classmethod def payload_for_create(cls, nickname, card_number, exp_date, document_id, **kwargs): - """Build the API 'create node' payload specific to SYNAPSE-US.""" + """Build the API 'create node' payload specific to INTERCHANGE-US.""" payload = super().payload_for_create('INTERCHANGE-US', nickname=nickname, card_number=card_number, diff --git a/synapse_pay_rest/tests/fixtures/atm.py b/synapse_pay_rest/tests/fixtures/atm.py new file mode 100644 index 0000000..a42002d --- /dev/null +++ b/synapse_pay_rest/tests/fixtures/atm.py @@ -0,0 +1,6 @@ +atm_args = { + 'zip': '94113', + 'radius': '5', + 'page': 2, + 'per_page': 10 +} \ No newline at end of file diff --git a/synapse_pay_rest/tests/models/__init__.py b/synapse_pay_rest/tests/models/__init__.py index 8bec823..1e06603 100644 --- a/synapse_pay_rest/tests/models/__init__.py +++ b/synapse_pay_rest/tests/models/__init__.py @@ -5,4 +5,5 @@ from .document_tests import * from .subnet_tests import * from .subscription_tests import * -from .issue_public_key import * \ No newline at end of file +from .issue_public_key import * +from .atm_tests import * \ No newline at end of file diff --git a/synapse_pay_rest/tests/models/atm_tests.py b/synapse_pay_rest/tests/models/atm_tests.py new file mode 100644 index 0000000..f0c002b --- /dev/null +++ b/synapse_pay_rest/tests/models/atm_tests.py @@ -0,0 +1,23 @@ +import unittest +import pdb +from synapse_pay_rest.tests.fixtures.client import * +from synapse_pay_rest.tests.fixtures.atm import * +from synapse_pay_rest.client import Client +from synapse_pay_rest.models import Atm + + +class AtmTestCases(unittest.TestCase): + def setUp(self): + print('\n{0}.{1}'.format(type(self).__name__, self._testMethodName)) + self.client = test_client + + def test_locate(self): + atms = Atm.locate(self.client, **atm_args) + atm = atms[0] + properties = ['client', 'address_city', 'address_country', 'address_postal_code', 'address_state', 'address_street', + 'latitude', 'longitude', 'id', 'isAvailable24Hours', 'isDepositAvailable', 'isHandicappedAccessible', 'isOffPremise', + 'isSeasonal','locationDescription', 'logoName', 'name', 'distance'] + self.assertIsInstance(atms[0], Atm) + # check properties assigned + for prop in properties: + self.assertIsNotNone(getattr(atm, prop)) diff --git a/synapse_pay_rest/tests/models/node_tests.py b/synapse_pay_rest/tests/models/node_tests.py index ad2050e..0605c64 100644 --- a/synapse_pay_rest/tests/models/node_tests.py +++ b/synapse_pay_rest/tests/models/node_tests.py @@ -385,6 +385,7 @@ def test_create_interchange_us_node(self): # } base_document = user.add_base_document(**base_doc_args) doc_id = base_document.id + time.sleep(5) kwargs = { 'nickname': 'Python Test INTERCHANGE-US Account', 'card_number': 'nNKBubGyeL+31Hhgim89lIvfezPdfe8hLQxvm9H2wfpI2PxHk6yqvdh0jKwhib74LHBemAI5sRyr/5LmnYOeJoUU5TmkBtpvhxDTAtoCrim7+3KGatDwq1Z6NzV+S46fu+hp2h5DxUx6Os3PPalwz06qgbTG1yIkEvFi23D1FJGj2RM5BwYuy+dASktSoSHejj4+idiG8Sc48rKzOJXkRHSA/GIhyGeL0/GscTqAwiXaA9f9QjW74T0Ux/LRjXqVVK1wmT2M/UHLV/rheVCNZPw9Xq/VPoO3Jb/VbezsSvPwaHEV9M+utmUyn/jPru4vQpX7WM133Zx7OerGsyr/Zg==', From 325d0e41b9108114d81a224d2da3ae3e828fbfd5 Mon Sep 17 00:00:00 2001 From: tynahuynh Date: Fri, 13 Apr 2018 16:48:38 -0700 Subject: [PATCH 17/41] atm locate with lat and lon --- samples.md | 19 ++++++++++++- synapse_pay_rest/api/atms.py | 4 +-- synapse_pay_rest/http_client.py | 2 +- synapse_pay_rest/models/atms/atm.py | 4 +-- .../models/nodes/subaccount_us_node.py | 3 +- synapse_pay_rest/tests/fixtures/atm.py | 8 ++++++ synapse_pay_rest/tests/models/atm_tests.py | 13 ++++++++- synapse_pay_rest/tests/models/node_tests.py | 28 ++++++++++++------- 8 files changed, 62 insertions(+), 19 deletions(-) diff --git a/samples.md b/samples.md index 6f69ac8..f310245 100644 --- a/samples.md +++ b/samples.md @@ -879,7 +879,7 @@ public_key = PublicKey.issue(client, **options) ## Locate Atm Methods -#### Locate Atm via Zipcode +#### Locate nearby Atms with Zipcode ```python from synapse_pay_rest.models import Atm @@ -895,4 +895,21 @@ atms = Atm.locate(client, **options) atm = atms[0] ``` +#### Locate nearby Atms with Lat and Lon Coordinates + +```python +from synapse_pay_rest.models import Atm + +options = { + 'lat': '350.3040400', + 'lon': "-122.33333" + 'radius': '5', + 'page': 1, + 'per_page': 20 +} + +atms = Atm.locate(client, **options) + +atm = atms[0] +``` diff --git a/synapse_pay_rest/api/atms.py b/synapse_pay_rest/api/atms.py index 0c24de9..a8336cc 100644 --- a/synapse_pay_rest/api/atms.py +++ b/synapse_pay_rest/api/atms.py @@ -11,8 +11,8 @@ class Atms(): def __init__(self, client): self.client = client - def locate(self, zip=None, **kwargs): + def locate(self, **kwargs): - path = '/nodes/atms?zip=' + zip + path = '/nodes/atms?' response = self.client.get(path, **kwargs) return response \ No newline at end of file diff --git a/synapse_pay_rest/http_client.py b/synapse_pay_rest/http_client.py index 8cbfe61..17e2cd1 100644 --- a/synapse_pay_rest/http_client.py +++ b/synapse_pay_rest/http_client.py @@ -49,7 +49,7 @@ def get_headers(self): def get(self, url, **params): """Send a GET request to the API.""" self.log_information(self.logging) - valid_params = ['query', 'page', 'per_page', 'type', 'full_dehydrate', 'radius'] + valid_params = ['query', 'page', 'per_page', 'type', 'full_dehydrate', 'radius', 'lat', 'lon', 'zip'] parameters = {} for param in valid_params: if param in params: diff --git a/synapse_pay_rest/models/atms/atm.py b/synapse_pay_rest/models/atms/atm.py index 96e0d6c..fdc5b2d 100644 --- a/synapse_pay_rest/models/atms/atm.py +++ b/synapse_pay_rest/models/atms/atm.py @@ -50,7 +50,7 @@ def multiple_from_response(cls, client, response): @classmethod - def locate(cls,client=None, zip=None, **kwargs): + def locate(cls,client=None, **kwargs): """Locates nearby atms and create an Atm instance from it. Args: @@ -63,7 +63,7 @@ def locate(cls,client=None, zip=None, **kwargs): Atm: an ATM instance corresponding to the record """ - response = client.atms.locate(zip, **kwargs) + response = client.atms.locate(**kwargs) return cls.multiple_from_response(client, response['atms']) \ No newline at end of file diff --git a/synapse_pay_rest/models/nodes/subaccount_us_node.py b/synapse_pay_rest/models/nodes/subaccount_us_node.py index f3c9bb1..4204d57 100644 --- a/synapse_pay_rest/models/nodes/subaccount_us_node.py +++ b/synapse_pay_rest/models/nodes/subaccount_us_node.py @@ -6,8 +6,7 @@ class SubaccountUsNode(BaseNode): @classmethod def payload_for_create(cls, nickname, **kwargs): - """Build the API 'create node' payload specific to SUBACCOUNT-US. - """ + """Build the API 'create node' payload specific to SUBACCOUNT-US.""" payload = super().payload_for_create('SUBACCOUNT-US', nickname=nickname, **kwargs) diff --git a/synapse_pay_rest/tests/fixtures/atm.py b/synapse_pay_rest/tests/fixtures/atm.py index a42002d..c93d3a3 100644 --- a/synapse_pay_rest/tests/fixtures/atm.py +++ b/synapse_pay_rest/tests/fixtures/atm.py @@ -3,4 +3,12 @@ 'radius': '5', 'page': 2, 'per_page': 10 +} + +atm_args2 = { + 'lat': '37.764832', + 'lon': '-122.419304', + 'radius': '5', + 'page': 1, + 'per_page': 10 } \ No newline at end of file diff --git a/synapse_pay_rest/tests/models/atm_tests.py b/synapse_pay_rest/tests/models/atm_tests.py index f0c002b..5e8319e 100644 --- a/synapse_pay_rest/tests/models/atm_tests.py +++ b/synapse_pay_rest/tests/models/atm_tests.py @@ -11,7 +11,7 @@ def setUp(self): print('\n{0}.{1}'.format(type(self).__name__, self._testMethodName)) self.client = test_client - def test_locate(self): + def test_locate_radius(self): atms = Atm.locate(self.client, **atm_args) atm = atms[0] properties = ['client', 'address_city', 'address_country', 'address_postal_code', 'address_state', 'address_street', @@ -21,3 +21,14 @@ def test_locate(self): # check properties assigned for prop in properties: self.assertIsNotNone(getattr(atm, prop)) + + def test_locate_lat_lon(self): + atms = Atm.locate(self.client, **atm_args2) + atm = atms[0] + properties = ['client', 'address_city', 'address_country', 'address_postal_code', 'address_state', 'address_street', + 'latitude', 'longitude', 'id', 'isAvailable24Hours', 'isDepositAvailable', 'isHandicappedAccessible', 'isOffPremise', + 'isSeasonal','locationDescription', 'logoName', 'name', 'distance'] + self.assertIsInstance(atms[0], Atm) + # check properties assigned + for prop in properties: + self.assertIsNotNone(getattr(atm, prop)) diff --git a/synapse_pay_rest/tests/models/node_tests.py b/synapse_pay_rest/tests/models/node_tests.py index 0605c64..618374d 100644 --- a/synapse_pay_rest/tests/models/node_tests.py +++ b/synapse_pay_rest/tests/models/node_tests.py @@ -468,15 +468,17 @@ def test_create_clearing_us_node(self): def test_create_card_us_node(self): user = self.user + time.sleep(3) base_document = user.add_base_document(**base_doc_args) doc_id = base_document.id - time.sleep(5) + time.sleep(3) kwargs = { 'nickname': 'Python Test CARD-US Account', 'document_id': str(doc_id), 'card_type': "VIRTUAL" } node = CardUsNode.create(user, **kwargs) + time.sleep(3) self.assertIsInstance(node, CardUsNode) self.assertEqual(user.id, node.user.id) @@ -489,15 +491,17 @@ def test_create_card_us_node(self): def test_create_subcard_us_node(self): user = self.user + time.sleep(3) base_document = user.add_base_document(**base_doc_args) doc_id = base_document.id - time.sleep(5) + time.sleep(3) kwargs = { 'nickname': 'Python Test SUBCARD-US Account', 'document_id': str(doc_id), 'card_type': "VIRTUAL" } node = SubcardUsNode.create(user, **kwargs) + time.sleep(3) self.assertIsInstance(node, SubcardUsNode) self.assertEqual(user.id, node.user.id) @@ -510,9 +514,10 @@ def test_create_subcard_us_node(self): def test_update_preference_card_us_node(self): user = self.user + time.sleep(3) base_document = user.add_base_document(**base_doc_args) doc_id = base_document.id - time.sleep(5) + time.sleep(3) kwargs = { 'nickname': 'Python Test CARD-US Account', 'document_id': str(doc_id), @@ -523,14 +528,15 @@ def test_update_preference_card_us_node(self): 'max_pin_attempts': 4 } node = node.update_preferences(**args2) - time.sleep(2) + time.sleep(5) self.assertEqual(4, node.max_pin_attempts) def test_update_allowed_card_us_node(self): user = self.user + time.sleep(3) base_document = user.add_base_document(**base_doc_args) doc_id = base_document.id - time.sleep(5) + time.sleep(3) kwargs = { 'nickname': 'Python Test CARD-US Account', 'document_id': str(doc_id), @@ -538,14 +544,15 @@ def test_update_allowed_card_us_node(self): } node = CardUsNode.create(user, **kwargs) node = node.update_allowed('INACTIVE') - time.sleep(1) + time.sleep(5) self.assertEqual('INACTIVE', node.permission) def test_update_preference_subcard_us_node(self): user = self.user + time.sleep(3) base_document = user.add_base_document(**base_doc_args) doc_id = base_document.id - time.sleep(5) + time.sleep(3) kwargs = { 'nickname': 'Python Test SUBCARD-US Account', 'document_id': str(doc_id), @@ -556,14 +563,15 @@ def test_update_preference_subcard_us_node(self): 'max_pin_attempts': 4 } node = node.update_preferences(**args2) - time.sleep(2) + time.sleep(5) self.assertEqual(4, node.max_pin_attempts) def test_update_allowed_subcard_us_node(self): user = self.user + time.sleep(3) base_document = user.add_base_document(**base_doc_args) doc_id = base_document.id - time.sleep(5) + time.sleep(3) kwargs = { 'nickname': 'Python Test SUBCARD-US Account', 'document_id': str(doc_id), @@ -571,6 +579,6 @@ def test_update_allowed_subcard_us_node(self): } node = SubcardUsNode.create(user, **kwargs) node = node.update_allowed('INACTIVE') - time.sleep(1) + time.sleep(5) self.assertEqual('INACTIVE', node.permission) From 8076dc66b110d0c31adbf2008053e9324bb2cf9a Mon Sep 17 00:00:00 2001 From: tynahuynh Date: Mon, 14 May 2018 23:49:12 -0700 Subject: [PATCH 18/41] edit --- synapse_pay_rest/tests/fixtures/atm.py | 4 +-- synapse_pay_rest/tests/models/atm_tests.py | 2 +- synapse_pay_rest/tests/models/node_tests.py | 35 ++++++++++----------- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/synapse_pay_rest/tests/fixtures/atm.py b/synapse_pay_rest/tests/fixtures/atm.py index c93d3a3..cebbd2e 100644 --- a/synapse_pay_rest/tests/fixtures/atm.py +++ b/synapse_pay_rest/tests/fixtures/atm.py @@ -1,7 +1,7 @@ atm_args = { - 'zip': '94113', + 'zip': '95113', 'radius': '5', - 'page': 2, + 'page': 1, 'per_page': 10 } diff --git a/synapse_pay_rest/tests/models/atm_tests.py b/synapse_pay_rest/tests/models/atm_tests.py index 5e8319e..b3a348b 100644 --- a/synapse_pay_rest/tests/models/atm_tests.py +++ b/synapse_pay_rest/tests/models/atm_tests.py @@ -11,7 +11,7 @@ def setUp(self): print('\n{0}.{1}'.format(type(self).__name__, self._testMethodName)) self.client = test_client - def test_locate_radius(self): + def test_locate_zip(self): atms = Atm.locate(self.client, **atm_args) atm = atms[0] properties = ['client', 'address_city', 'address_country', 'address_postal_code', 'address_state', 'address_street', diff --git a/synapse_pay_rest/tests/models/node_tests.py b/synapse_pay_rest/tests/models/node_tests.py index 618374d..65c9ef4 100644 --- a/synapse_pay_rest/tests/models/node_tests.py +++ b/synapse_pay_rest/tests/models/node_tests.py @@ -468,17 +468,17 @@ def test_create_clearing_us_node(self): def test_create_card_us_node(self): user = self.user - time.sleep(3) base_document = user.add_base_document(**base_doc_args) + time.sleep(10) doc_id = base_document.id - time.sleep(3) + kwargs = { 'nickname': 'Python Test CARD-US Account', 'document_id': str(doc_id), 'card_type': "VIRTUAL" } node = CardUsNode.create(user, **kwargs) - time.sleep(3) + time.sleep(15) self.assertIsInstance(node, CardUsNode) self.assertEqual(user.id, node.user.id) @@ -491,17 +491,16 @@ def test_create_card_us_node(self): def test_create_subcard_us_node(self): user = self.user - time.sleep(3) base_document = user.add_base_document(**base_doc_args) + time.sleep(10) doc_id = base_document.id - time.sleep(3) kwargs = { 'nickname': 'Python Test SUBCARD-US Account', 'document_id': str(doc_id), 'card_type': "VIRTUAL" } node = SubcardUsNode.create(user, **kwargs) - time.sleep(3) + time.sleep(15) self.assertIsInstance(node, SubcardUsNode) self.assertEqual(user.id, node.user.id) @@ -514,71 +513,71 @@ def test_create_subcard_us_node(self): def test_update_preference_card_us_node(self): user = self.user - time.sleep(3) base_document = user.add_base_document(**base_doc_args) + time.sleep(10) doc_id = base_document.id - time.sleep(3) kwargs = { 'nickname': 'Python Test CARD-US Account', 'document_id': str(doc_id), 'card_type': "VIRTUAL" } node = CardUsNode.create(user, **kwargs) + time.sleep(15) args2 = { 'max_pin_attempts': 4 } node = node.update_preferences(**args2) - time.sleep(5) + time.sleep(10) self.assertEqual(4, node.max_pin_attempts) def test_update_allowed_card_us_node(self): user = self.user - time.sleep(3) base_document = user.add_base_document(**base_doc_args) + time.sleep(10) doc_id = base_document.id - time.sleep(3) kwargs = { 'nickname': 'Python Test CARD-US Account', 'document_id': str(doc_id), 'card_type': "VIRTUAL" } node = CardUsNode.create(user, **kwargs) + time.sleep(15) node = node.update_allowed('INACTIVE') - time.sleep(5) + time.sleep(10) self.assertEqual('INACTIVE', node.permission) def test_update_preference_subcard_us_node(self): user = self.user - time.sleep(3) base_document = user.add_base_document(**base_doc_args) + time.sleep(10) doc_id = base_document.id - time.sleep(3) kwargs = { 'nickname': 'Python Test SUBCARD-US Account', 'document_id': str(doc_id), 'card_type': "VIRTUAL" } node = SubcardUsNode.create(user, **kwargs) + time.sleep(15) args2 = { 'max_pin_attempts': 4 } node = node.update_preferences(**args2) - time.sleep(5) + time.sleep(10) self.assertEqual(4, node.max_pin_attempts) def test_update_allowed_subcard_us_node(self): user = self.user - time.sleep(3) base_document = user.add_base_document(**base_doc_args) + time.sleep(10) doc_id = base_document.id - time.sleep(3) kwargs = { 'nickname': 'Python Test SUBCARD-US Account', 'document_id': str(doc_id), 'card_type': "VIRTUAL" } node = SubcardUsNode.create(user, **kwargs) + time.sleep(15) node = node.update_allowed('INACTIVE') - time.sleep(5) + time.sleep(10) self.assertEqual('INACTIVE', node.permission) From b1f4fc5fb2f8060ed5ea62df39c487a2f9fdad4e Mon Sep 17 00:00:00 2001 From: Easak Hong Date: Mon, 10 Sep 2018 15:48:40 -0700 Subject: [PATCH 19/41] Configure backwards compatibility with python 2 --- synapse_pay_rest/models/nodes/ach_us_node.py | 4 +-- synapse_pay_rest/models/nodes/base_node.py | 4 +-- synapse_pay_rest/models/nodes/card_us_node.py | 3 +-- .../models/nodes/check_us_node.py | 2 +- .../models/nodes/clearing_us_node.py | 2 +- .../models/nodes/deposit_us_node.py | 2 +- synapse_pay_rest/models/nodes/eft_ind_node.py | 2 +- synapse_pay_rest/models/nodes/eft_np_node.py | 2 +- .../models/nodes/ib_deposit_us_node.py | 2 +- .../models/nodes/ib_subaccount_us_node.py | 2 +- .../models/nodes/interchange_us_node.py | 2 +- synapse_pay_rest/models/nodes/iou_node.py | 2 +- .../models/nodes/reserve_us_node.py | 2 +- .../models/nodes/subaccount_us_node.py | 2 +- .../models/nodes/subcard_us_node.py | 2 +- .../models/nodes/synapse_ind_node.py | 2 +- .../models/nodes/synapse_np_node.py | 2 +- .../models/nodes/synapse_us_node.py | 2 +- .../nodes/triumph_subaccount_us_node.py | 2 +- .../models/nodes/wire_int_node.py | 2 +- synapse_pay_rest/models/nodes/wire_us_node.py | 2 +- synapse_pay_rest/models/users/document.py | 2 +- .../models/users/virtual_document.py | 2 +- synapse_pay_rest/tests/fixtures/__init__.py | 0 synapse_pay_rest/tests/fixtures/client.py | 27 ++++++++++++++----- 25 files changed, 46 insertions(+), 32 deletions(-) create mode 100644 synapse_pay_rest/tests/fixtures/__init__.py diff --git a/synapse_pay_rest/models/nodes/ach_us_node.py b/synapse_pay_rest/models/nodes/ach_us_node.py index 664eed8..246ffac 100644 --- a/synapse_pay_rest/models/nodes/ach_us_node.py +++ b/synapse_pay_rest/models/nodes/ach_us_node.py @@ -31,7 +31,7 @@ def create_via_bank_login(cls, user=None, bank_name=None, username=None, list: if no MFA, returns a list of AchUsNodes AchUsNode: if MFA, returns an AchUsNode with mfa_verified=False """ - payload = super().payload_for_create('ACH-US', + payload = super(AchUsNode, cls).payload_for_create('ACH-US', bank_name=bank_name, username=username, password=password, @@ -48,7 +48,7 @@ def create_via_bank_login(cls, user=None, bank_name=None, username=None, def payload_for_create(cls, nickname, account_number, routing_number, account_type, account_class, **kwargs): """Build the API 'create node' payload specific to ACH-US.""" - payload = super().payload_for_create('ACH-US', + payload = super(AchUsNode, cls).payload_for_create('ACH-US', nickname=nickname, account_number=account_number, routing_number=routing_number, diff --git a/synapse_pay_rest/models/nodes/base_node.py b/synapse_pay_rest/models/nodes/base_node.py index 6656da1..30fd1c8 100644 --- a/synapse_pay_rest/models/nodes/base_node.py +++ b/synapse_pay_rest/models/nodes/base_node.py @@ -1,4 +1,4 @@ -class BaseNode(): +class BaseNode(object): """Ancestor of the various node types. Stores common functionality of child classes, but should not be @@ -161,7 +161,7 @@ def payload_for_create(cls, type, **kwargs): if extra: payload['extra'] = extra - payee_address_options = ['address_street', 'address_city', 'address_subdivision', + payee_address_options = ['address_street', 'address_city', 'address_subdivision', 'address_country_code', 'address_postal_code'] payee_address = {} for option in payee_address_options: diff --git a/synapse_pay_rest/models/nodes/card_us_node.py b/synapse_pay_rest/models/nodes/card_us_node.py index 985db81..9b4d812 100644 --- a/synapse_pay_rest/models/nodes/card_us_node.py +++ b/synapse_pay_rest/models/nodes/card_us_node.py @@ -7,7 +7,7 @@ class CardUsNode(BaseNode): @classmethod def payload_for_create(cls, nickname, document_id, card_type, **kwargs): """Build the API 'create node' payload specific to CARD-US.""" - payload = super().payload_for_create('CARD-US', + payload = super(CardUsNode, cls).payload_for_create('CARD-US', nickname=nickname, document_id=document_id, card_type=card_type, @@ -50,4 +50,3 @@ def payload_for_preferences(self, **kwargs): if 'security_alerts' in kwargs: payload['preferences']['security_alerts'] = kwargs['security_alerts'] return payload - diff --git a/synapse_pay_rest/models/nodes/check_us_node.py b/synapse_pay_rest/models/nodes/check_us_node.py index 310ae36..9c8234d 100644 --- a/synapse_pay_rest/models/nodes/check_us_node.py +++ b/synapse_pay_rest/models/nodes/check_us_node.py @@ -8,7 +8,7 @@ class CheckUsNode(BaseNode): def payload_for_create(cls, nickname, payee_name, address_street, address_city, address_subdivision, address_country_code, address_postal_code, **kwargs): """Build the API 'create node' payload specific to CHECK-US.""" - payload = super().payload_for_create('CHECK-US', + payload = super(CheckUsNode, cls).payload_for_create('CHECK-US', nickname=nickname, payee_name=payee_name, address_street=address_street, diff --git a/synapse_pay_rest/models/nodes/clearing_us_node.py b/synapse_pay_rest/models/nodes/clearing_us_node.py index 0c56cb1..464b9a2 100644 --- a/synapse_pay_rest/models/nodes/clearing_us_node.py +++ b/synapse_pay_rest/models/nodes/clearing_us_node.py @@ -7,7 +7,7 @@ class ClearingUsNode(BaseNode): @classmethod def payload_for_create(cls, nickname, **kwargs): """Build the API 'create node' payload specific to CLEARING-US.""" - payload = super().payload_for_create('CLEARING-US', + payload = super(ClearingUsNode, cls).payload_for_create('CLEARING-US', nickname=nickname, **kwargs) return payload diff --git a/synapse_pay_rest/models/nodes/deposit_us_node.py b/synapse_pay_rest/models/nodes/deposit_us_node.py index 767e51d..dc9ff7a 100644 --- a/synapse_pay_rest/models/nodes/deposit_us_node.py +++ b/synapse_pay_rest/models/nodes/deposit_us_node.py @@ -7,7 +7,7 @@ class DepositUsNode(BaseNode): @classmethod def payload_for_create(cls, nickname, **kwargs): """Build the API 'create node' payload specific to DEPOSIT-US.""" - payload = super().payload_for_create('DEPOSIT-US', + payload = super(DepositUsNode, cls).payload_for_create('DEPOSIT-US', nickname=nickname, **kwargs) return payload diff --git a/synapse_pay_rest/models/nodes/eft_ind_node.py b/synapse_pay_rest/models/nodes/eft_ind_node.py index b80ef67..ce4cd7c 100644 --- a/synapse_pay_rest/models/nodes/eft_ind_node.py +++ b/synapse_pay_rest/models/nodes/eft_ind_node.py @@ -7,7 +7,7 @@ class EftIndNode(BaseNode): @classmethod def payload_for_create(cls, nickname, account_number, ifsc, **kwargs): """Build the API 'create node' payload specific to EFT-IND.""" - payload = super().payload_for_create('EFT-IND', + payload = super(EftIndNode, cls).payload_for_create('EFT-IND', nickname=nickname, account_number=account_number, ifsc=ifsc, diff --git a/synapse_pay_rest/models/nodes/eft_np_node.py b/synapse_pay_rest/models/nodes/eft_np_node.py index f636bfa..5d8b6ad 100644 --- a/synapse_pay_rest/models/nodes/eft_np_node.py +++ b/synapse_pay_rest/models/nodes/eft_np_node.py @@ -7,7 +7,7 @@ class EftNpNode(BaseNode): @classmethod def payload_for_create(cls, nickname, bank_name, account_number, **kwargs): """Build the API 'create node' payload specific to EFT-NP.""" - payload = super().payload_for_create('EFT-NP', + payload = super(EftNpNode, cls).payload_for_create('EFT-NP', nickname=nickname, account_number=account_number, bank_name=bank_name, **kwargs) diff --git a/synapse_pay_rest/models/nodes/ib_deposit_us_node.py b/synapse_pay_rest/models/nodes/ib_deposit_us_node.py index 8070fb8..cea34d6 100644 --- a/synapse_pay_rest/models/nodes/ib_deposit_us_node.py +++ b/synapse_pay_rest/models/nodes/ib_deposit_us_node.py @@ -7,7 +7,7 @@ class IbDepositUsNode(BaseNode): @classmethod def payload_for_create(cls, nickname, **kwargs): """Build the API 'create node' payload specific to IB-DEPOSIT-US.""" - payload = super().payload_for_create('IB-DEPOSIT-US', + payload = super(IbDepositUsNode, cls).payload_for_create('IB-DEPOSIT-US', nickname=nickname, **kwargs) return payload diff --git a/synapse_pay_rest/models/nodes/ib_subaccount_us_node.py b/synapse_pay_rest/models/nodes/ib_subaccount_us_node.py index 41b9df7..6830a32 100644 --- a/synapse_pay_rest/models/nodes/ib_subaccount_us_node.py +++ b/synapse_pay_rest/models/nodes/ib_subaccount_us_node.py @@ -7,7 +7,7 @@ class IbSubaccountUsNode(BaseNode): @classmethod def payload_for_create(cls, nickname, **kwargs): """Build the API 'create node' payload specific to IB-SUBACCOUNT-US.""" - payload = super().payload_for_create('IB-SUBACCOUNT-US', + payload = super(IbSubaccountUsNode, cls).payload_for_create('IB-SUBACCOUNT-US', nickname=nickname, **kwargs) return payload diff --git a/synapse_pay_rest/models/nodes/interchange_us_node.py b/synapse_pay_rest/models/nodes/interchange_us_node.py index 4f3ce0f..4528062 100644 --- a/synapse_pay_rest/models/nodes/interchange_us_node.py +++ b/synapse_pay_rest/models/nodes/interchange_us_node.py @@ -7,7 +7,7 @@ class InterchangeUsNode(BaseNode): @classmethod def payload_for_create(cls, nickname, card_number, exp_date, document_id, **kwargs): """Build the API 'create node' payload specific to INTERCHANGE-US.""" - payload = super().payload_for_create('INTERCHANGE-US', + payload = super(InterchangeUsNode, cls).payload_for_create('INTERCHANGE-US', nickname=nickname, card_number=card_number, exp_date=exp_date, diff --git a/synapse_pay_rest/models/nodes/iou_node.py b/synapse_pay_rest/models/nodes/iou_node.py index 63d0a8e..a12eeba 100644 --- a/synapse_pay_rest/models/nodes/iou_node.py +++ b/synapse_pay_rest/models/nodes/iou_node.py @@ -7,7 +7,7 @@ class IouNode(BaseNode): @classmethod def payload_for_create(cls, nickname, currency, **kwargs): """Build the API 'create node' payload specific to IOU.""" - payload = super().payload_for_create('IOU', + payload = super(IouNode, cls).payload_for_create('IOU', nickname=nickname, currency=currency, **kwargs) return payload diff --git a/synapse_pay_rest/models/nodes/reserve_us_node.py b/synapse_pay_rest/models/nodes/reserve_us_node.py index 173ea7b..3f9d8c6 100644 --- a/synapse_pay_rest/models/nodes/reserve_us_node.py +++ b/synapse_pay_rest/models/nodes/reserve_us_node.py @@ -7,7 +7,7 @@ class ReserveUsNode(BaseNode): @classmethod def payload_for_create(cls, nickname, **kwargs): """Build the API 'create node' payload specific to RESERVE-US.""" - payload = super().payload_for_create('RESERVE-US', + payload = super(ReserveUsNode, cls).payload_for_create('RESERVE-US', nickname=nickname, **kwargs) return payload diff --git a/synapse_pay_rest/models/nodes/subaccount_us_node.py b/synapse_pay_rest/models/nodes/subaccount_us_node.py index 4204d57..abb621c 100644 --- a/synapse_pay_rest/models/nodes/subaccount_us_node.py +++ b/synapse_pay_rest/models/nodes/subaccount_us_node.py @@ -7,7 +7,7 @@ class SubaccountUsNode(BaseNode): @classmethod def payload_for_create(cls, nickname, **kwargs): """Build the API 'create node' payload specific to SUBACCOUNT-US.""" - payload = super().payload_for_create('SUBACCOUNT-US', + payload = super(SubaccountUsNode, cls).payload_for_create('SUBACCOUNT-US', nickname=nickname, **kwargs) return payload diff --git a/synapse_pay_rest/models/nodes/subcard_us_node.py b/synapse_pay_rest/models/nodes/subcard_us_node.py index 92751a5..e6afa3c 100644 --- a/synapse_pay_rest/models/nodes/subcard_us_node.py +++ b/synapse_pay_rest/models/nodes/subcard_us_node.py @@ -7,7 +7,7 @@ class SubcardUsNode(BaseNode): @classmethod def payload_for_create(cls, nickname, document_id, card_type, **kwargs): """Build the API 'create node' payload specific to SUBCARD-US.""" - payload = super().payload_for_create('SUBCARD-US', + payload = super(SubcardUsNode, cls).payload_for_create('SUBCARD-US', nickname=nickname, document_id=document_id, card_type=card_type, diff --git a/synapse_pay_rest/models/nodes/synapse_ind_node.py b/synapse_pay_rest/models/nodes/synapse_ind_node.py index f3317fc..1f9ac4d 100644 --- a/synapse_pay_rest/models/nodes/synapse_ind_node.py +++ b/synapse_pay_rest/models/nodes/synapse_ind_node.py @@ -7,7 +7,7 @@ class SynapseIndNode(BaseNode): @classmethod def payload_for_create(cls, nickname, **kwargs): """Build the API 'create node' payload specific to SYNAPSE-IND.""" - payload = super().payload_for_create('SYNAPSE-IND', + payload = super(SynapseIndNode, cls).payload_for_create('SYNAPSE-IND', nickname=nickname, **kwargs) return payload diff --git a/synapse_pay_rest/models/nodes/synapse_np_node.py b/synapse_pay_rest/models/nodes/synapse_np_node.py index 27f650c..f4d2eac 100644 --- a/synapse_pay_rest/models/nodes/synapse_np_node.py +++ b/synapse_pay_rest/models/nodes/synapse_np_node.py @@ -7,7 +7,7 @@ class SynapseNpNode(BaseNode): @classmethod def payload_for_create(cls, nickname, **kwargs): """Build the API 'create node' payload specific to SYNAPSE-NP.""" - payload = super().payload_for_create('SYNAPSE-NP', + payload = super(SynapseNpNode, cls).payload_for_create('SYNAPSE-NP', nickname=nickname, **kwargs) return payload diff --git a/synapse_pay_rest/models/nodes/synapse_us_node.py b/synapse_pay_rest/models/nodes/synapse_us_node.py index 5223e6e..9f3c657 100644 --- a/synapse_pay_rest/models/nodes/synapse_us_node.py +++ b/synapse_pay_rest/models/nodes/synapse_us_node.py @@ -7,7 +7,7 @@ class SynapseUsNode(BaseNode): @classmethod def payload_for_create(cls, nickname, **kwargs): """Build the API 'create node' payload specific to SYNAPSE-US.""" - payload = super().payload_for_create('SYNAPSE-US', + payload = super(SynapseUsNode, cls).payload_for_create('SYNAPSE-US', nickname=nickname, **kwargs) return payload diff --git a/synapse_pay_rest/models/nodes/triumph_subaccount_us_node.py b/synapse_pay_rest/models/nodes/triumph_subaccount_us_node.py index 1cca1d8..2c7e49f 100644 --- a/synapse_pay_rest/models/nodes/triumph_subaccount_us_node.py +++ b/synapse_pay_rest/models/nodes/triumph_subaccount_us_node.py @@ -8,7 +8,7 @@ class TriumphSubaccountUsNode(BaseNode): def payload_for_create(cls, nickname, **kwargs): """Build the API 'create node' payload specific to TRIUMPH-SUBACCOUNT-US. """ - payload = super().payload_for_create('TRIUMPH-SUBACCOUNT-US', + payload = super(TriumphSubaccountUsNode, cls).payload_for_create('TRIUMPH-SUBACCOUNT-US', nickname=nickname, **kwargs) return payload diff --git a/synapse_pay_rest/models/nodes/wire_int_node.py b/synapse_pay_rest/models/nodes/wire_int_node.py index 1154483..7bee03d 100644 --- a/synapse_pay_rest/models/nodes/wire_int_node.py +++ b/synapse_pay_rest/models/nodes/wire_int_node.py @@ -8,7 +8,7 @@ class WireIntNode(BaseNode): def payload_for_create(cls, nickname, bank_name, account_number, swift, name_on_account, address, **kwargs): """Build the API 'create node' payload specific to WIRE-INT.""" - payload = super().payload_for_create('WIRE-INT', + payload = super(WireIntNode, cls).payload_for_create('WIRE-INT', nickname=nickname, bank_name=bank_name, account_number=account_number, diff --git a/synapse_pay_rest/models/nodes/wire_us_node.py b/synapse_pay_rest/models/nodes/wire_us_node.py index 2dccc9e..b3fd2a6 100644 --- a/synapse_pay_rest/models/nodes/wire_us_node.py +++ b/synapse_pay_rest/models/nodes/wire_us_node.py @@ -8,7 +8,7 @@ class WireUsNode(BaseNode): def payload_for_create(cls, nickname, account_number, routing_number, name_on_account, address, **kwargs): """Build the API 'create node' payload specific to WIRE-US.""" - payload = super().payload_for_create('WIRE-US', + payload = super(WireUsNode, cls).payload_for_create('WIRE-US', nickname=nickname, account_number=account_number, routing_number=routing_number, diff --git a/synapse_pay_rest/models/users/document.py b/synapse_pay_rest/models/users/document.py index 8f2eae2..2292140 100644 --- a/synapse_pay_rest/models/users/document.py +++ b/synapse_pay_rest/models/users/document.py @@ -1,4 +1,4 @@ -class Document(): +class Document(object): """Ancestor of PhysicalDocument, SocialDocument, and VirtualDocument. Stores common functionality of child classes, but should not be diff --git a/synapse_pay_rest/models/users/virtual_document.py b/synapse_pay_rest/models/users/virtual_document.py index 2342fe2..5702566 100644 --- a/synapse_pay_rest/models/users/virtual_document.py +++ b/synapse_pay_rest/models/users/virtual_document.py @@ -30,7 +30,7 @@ def create(cls, base_document, type=None, value=None): @classmethod def from_response(cls, response): """Construct a VirtualDocument from a response dict.""" - doc = super().from_response(response) + doc = super(VirtualDocument, cls).from_response(response) if response.get('status') == 'SUBMITTED|MFA_PENDING': question_data = response['meta']['question_set']['questions'] question_set = Question.multiple_from_response(question_data) diff --git a/synapse_pay_rest/tests/fixtures/__init__.py b/synapse_pay_rest/tests/fixtures/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/synapse_pay_rest/tests/fixtures/client.py b/synapse_pay_rest/tests/fixtures/client.py index e74f38c..2182002 100644 --- a/synapse_pay_rest/tests/fixtures/client.py +++ b/synapse_pay_rest/tests/fixtures/client.py @@ -1,12 +1,27 @@ import os +import sys from synapse_pay_rest.client import Client -try: - CLIENT_ID = os.environ['TEST_CLIENT_ID'] - CLIENT_SECRET = os.environ['TEST_CLIENT_SECRET'] -except KeyError: - CLIENT_ID = input("Please enter client ID: ") - CLIENT_SECRET = input("Please enter client secret: ") +# try: +# CLIENT_ID = os.environ['TEST_CLIENT_ID'] +# CLIENT_SECRET = os.environ['TEST_CLIENT_SECRET'] +# except KeyError: +# CLIENT_ID = input("Please enter client ID: ") +# CLIENT_SECRET = input("Please enter client secret: ") +if sys.version_info[0] >= 3: + try: + CLIENT_ID = os.environ['TEST_CLIENT_ID'] + CLIENT_SECRET = os.environ['TEST_CLIENT_SECRET'] + except KeyError: + CLIENT_ID = input("Please enter client ID: ") + CLIENT_SECRET = input("Please enter client secret: ") +else: + try: + CLIENT_ID = os.environ['TEST_CLIENT_ID'] + CLIENT_SECRET = os.environ['TEST_CLIENT_SECRET'] + except KeyError: + CLIENT_ID = raw_input("Please enter client ID: ") + CLIENT_SECRET = raw_input("Please enter client secret: ") # FINGERPRINT = '737321631015eefd8dd6573ccce33319' FINGERPRINT = 'c7e516361e6e5453de96223616d73a7b' From e54f0498b27fc17f2c9fe360bce4a000528f9664 Mon Sep 17 00:00:00 2001 From: easak Date: Mon, 10 Sep 2018 17:51:37 -0700 Subject: [PATCH 20/41] Change version num --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 17a4dc8..8e69627 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='3.2.0', + version='3.3.0', description='SynapsePay Rest Native Python Library', From c6b984e88f5d7328fc800c7667cec4620db995c2 Mon Sep 17 00:00:00 2001 From: easak Date: Mon, 10 Sep 2018 19:33:53 -0700 Subject: [PATCH 21/41] Update setup --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 8e69627..c1995d2 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,7 @@ # Specify the Python versions you support here. In particular, ensure # that you indicate whether you support Python 2, Python 3 or both. + 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.5' ], From d82193f346ac049e08b3384b53b0f37f3a86f5d6 Mon Sep 17 00:00:00 2001 From: easak Date: Thu, 18 Oct 2018 13:57:19 -0700 Subject: [PATCH 22/41] Fix subnet response fields --- synapse_pay_rest/models/subnets/subnet.py | 3 +-- synapse_pay_rest/tests/client_tests.py | 2 -- synapse_pay_rest/tests/http_client_tests.py | 4 ++-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/synapse_pay_rest/models/subnets/subnet.py b/synapse_pay_rest/models/subnets/subnet.py index 9b9b8ea..f072674 100644 --- a/synapse_pay_rest/models/subnets/subnet.py +++ b/synapse_pay_rest/models/subnets/subnet.py @@ -21,8 +21,8 @@ def from_response(cls, node, response): node=node, json=response, id=response['_id'], + account_class=response['account_class'], account_num=response['account_num'], - allowed=response['allowed'], client_id=response['client']['id'], client_name=response['client']['name'], nickname=response['nickname'], @@ -115,4 +115,3 @@ def lock(self): else: # API v3.1.1 return self.from_response(self.node, response) - diff --git a/synapse_pay_rest/tests/client_tests.py b/synapse_pay_rest/tests/client_tests.py index b5785a4..dca121c 100644 --- a/synapse_pay_rest/tests/client_tests.py +++ b/synapse_pay_rest/tests/client_tests.py @@ -49,5 +49,3 @@ def test_passes_header_info_to_http_client(self): self.assertEqual(gateway, headers['X-SP-GATEWAY']) self.assertEqual(user, headers['X-SP-USER']) self.assertEqual(IP_ADDRESS, headers['X-SP-USER-IP']) - - diff --git a/synapse_pay_rest/tests/http_client_tests.py b/synapse_pay_rest/tests/http_client_tests.py index 4b36b06..a869b3f 100644 --- a/synapse_pay_rest/tests/http_client_tests.py +++ b/synapse_pay_rest/tests/http_client_tests.py @@ -11,12 +11,12 @@ def setUp(self): client_secret=CLIENT_SECRET, fingerprint=FINGERPRINT, ip_address=IP_ADDRESS, - base_url='https://sandbox.synapsepay.com/api/3', + base_url='https://uat-api.synapsefi.com/v3.1', logging=True ) def test_properties_are_set(self): - self.assertEqual('https://sandbox.synapsepay.com/api/3', + self.assertEqual('https://uat-api.synapsefi.com/v3.1', self.http_client.base_url) self.assertTrue(self.http_client.logging) From e2e178c689f2c180b4f3b7f3333deb801b209b2d Mon Sep 17 00:00:00 2001 From: easak Date: Thu, 18 Oct 2018 14:43:27 -0700 Subject: [PATCH 23/41] Update versioning --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c1995d2..fc24ae6 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='3.3.0', + version='3.3.1', description='SynapsePay Rest Native Python Library', From d1fb5fe414f0c68346ef6bf0f4d248a87ca17785 Mon Sep 17 00:00:00 2001 From: Felix Le Dem Date: Thu, 18 Oct 2018 20:39:05 -0400 Subject: [PATCH 24/41] Added mfa_type to unverified from response This is needed to know whether an mfa is of type `code` or `question`. --- synapse_pay_rest/models/nodes/ach_us_node.py | 1 + 1 file changed, 1 insertion(+) diff --git a/synapse_pay_rest/models/nodes/ach_us_node.py b/synapse_pay_rest/models/nodes/ach_us_node.py index 246ffac..e641c26 100644 --- a/synapse_pay_rest/models/nodes/ach_us_node.py +++ b/synapse_pay_rest/models/nodes/ach_us_node.py @@ -14,6 +14,7 @@ def unverified_from_response(cls, user, response): return cls(user=user, mfa_access_token=response['mfa']['access_token'], mfa_message=response['mfa']['message'], + mfa_type=response['mfa']['type'], mfa_verified=False) @classmethod From 473c05a7044de562f5619b1da92dc958d1638375 Mon Sep 17 00:00:00 2001 From: easak Date: Tue, 23 Oct 2018 11:09:45 -0700 Subject: [PATCH 25/41] Add institutions --- synapse_pay_rest/__init__.py | 4 +- synapse_pay_rest/api/__init__.py | 2 + synapse_pay_rest/api/institutions.py | 18 +++++++ synapse_pay_rest/client.py | 6 ++- .../models/institutions/__init__.py | 7 +++ .../models/institutions/institution.py | 51 +++++++++++++++++++ 6 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 synapse_pay_rest/api/institutions.py create mode 100644 synapse_pay_rest/models/institutions/__init__.py create mode 100644 synapse_pay_rest/models/institutions/institution.py diff --git a/synapse_pay_rest/__init__.py b/synapse_pay_rest/__init__.py index f2d150e..6b8ec85 100644 --- a/synapse_pay_rest/__init__.py +++ b/synapse_pay_rest/__init__.py @@ -6,5 +6,5 @@ """ from .client import Client -from .api import Users, Nodes, Trans, Subnets, Subscriptions, ClientEndpoint, Atms -from .models import User, Node, Transaction, Subnet, Subscription, PublicKey, Atm +from .api import Users, Nodes, Trans, Subnets, Subscriptions, ClientEndpoint, Atms, Institutions, Statements +from .models import User, Node, Transaction, Subnet, Subscription, PublicKey, Atm, Institution, Statement diff --git a/synapse_pay_rest/api/__init__.py b/synapse_pay_rest/api/__init__.py index 9510941..4296345 100644 --- a/synapse_pay_rest/api/__init__.py +++ b/synapse_pay_rest/api/__init__.py @@ -11,3 +11,5 @@ from .subscriptions import Subscriptions from .client import ClientEndpoint from .atms import Atms +from .institutions import Institutions +from .statements import Statements diff --git a/synapse_pay_rest/api/institutions.py b/synapse_pay_rest/api/institutions.py new file mode 100644 index 0000000..0be27aa --- /dev/null +++ b/synapse_pay_rest/api/institutions.py @@ -0,0 +1,18 @@ +from synapse_pay_rest.http_client import HttpClient + + +class Institutions(): + """Abstraction of the /institutions endpoint. + + Used to make insitution location-related calls to the API. + https://docs.synapsefi.com/docs/institutions + """ + + def __init__(self, client): + self.client = client + + def find(self, **kwargs): + + path = '/institutions' + response = self.client.get(path, **kwargs) + return response diff --git a/synapse_pay_rest/client.py b/synapse_pay_rest/client.py index 387ca0d..61cf2eb 100644 --- a/synapse_pay_rest/client.py +++ b/synapse_pay_rest/client.py @@ -6,6 +6,8 @@ from .api.subscriptions import Subscriptions from .api.client import ClientEndpoint from .api.atms import Atms +from .api.institutions import Institutions +from .api.statements import Statements class Client(): """Handles configuration and requests to the SynapsePay API. @@ -38,6 +40,8 @@ def __init__(self, **kwargs): self.subscriptions = Subscriptions(self.http_client) self.client_endpoint = ClientEndpoint(self.http_client) self.atms = Atms(self.http_client) + self.institutions = Institutions(self.http_client) + self.statements = Statements(self.http_client) def __repr__(self): - return '{0}(base_url={1})'.format(self.__class__, self.base_url) \ No newline at end of file + return '{0}(base_url={1})'.format(self.__class__, self.base_url) diff --git a/synapse_pay_rest/models/institutions/__init__.py b/synapse_pay_rest/models/institutions/__init__.py new file mode 100644 index 0000000..cba8225 --- /dev/null +++ b/synapse_pay_rest/models/institutions/__init__.py @@ -0,0 +1,7 @@ +"""This module contains models for objects tied to the /institutions endpoint. + +Currently just the Institution location class. +""" + + +from .institution import Institution diff --git a/synapse_pay_rest/models/institutions/institution.py b/synapse_pay_rest/models/institutions/institution.py new file mode 100644 index 0000000..7227598 --- /dev/null +++ b/synapse_pay_rest/models/institutions/institution.py @@ -0,0 +1,51 @@ +import copy +from synapse_pay_rest.client import Client + +class Institution(): + """Respresents an institution location record with methods for constructing institution location + instances. + + """ + + def __init__(self, **kwargs): + for arg, value in kwargs.items(): + setattr(self, arg, value) + + def __repr__(self): + clean_dict = self.__dict__.copy() + return '{0}({1})'.format(self.__class__, clean_dict) + + @classmethod + def from_response(cls, client, response): + """Construct an Institution location instance from a response dict.""" + return cls( + client = client, + json = response, + bank_code = response['bank_code'], + bank_name = response['bank_name'], + features = response['features'], + forgotten_password = response['forgotten_password'], + is_active = response['is_active'], + logo = response['logo'], + tx_history_months = response['tx_history_months'] + ) + + @classmethod + def multiple_from_response(cls, client, response): + """Construct multiple Institutions from a response dict.""" + institutions = [cls.from_response(client, institution_data) + for institution_data in response] + return institutions + + @classmethod + def find(cls, client=None, **kwargs): + """Finds all institutions and creates an Institution instance from it. + + Args: + client (Client): an instance of the API Client + Returns: + Institution: an Institution instance corresponding to the record + """ + + response = client.institutions.find(**kwargs) + return cls.multiple_from_response(client, response['banks']) From a260c04163580b4e5f6aee1a80c4aeaa2fef9ffb Mon Sep 17 00:00:00 2001 From: easak Date: Tue, 23 Oct 2018 11:15:15 -0700 Subject: [PATCH 26/41] Add tests for institutions --- synapse_pay_rest/models/__init__.py | 2 ++ synapse_pay_rest/tests/models/__init__.py | 4 +++- .../tests/models/institution_tests.py | 20 +++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 synapse_pay_rest/tests/models/institution_tests.py diff --git a/synapse_pay_rest/models/__init__.py b/synapse_pay_rest/models/__init__.py index 92c9493..f838fff 100644 --- a/synapse_pay_rest/models/__init__.py +++ b/synapse_pay_rest/models/__init__.py @@ -15,3 +15,5 @@ from .subscriptions import Subscription from .issue_public_keys import PublicKey from .atms import Atm +from .institutions import Institution +from .statements import Statement diff --git a/synapse_pay_rest/tests/models/__init__.py b/synapse_pay_rest/tests/models/__init__.py index 1e06603..8e23c98 100644 --- a/synapse_pay_rest/tests/models/__init__.py +++ b/synapse_pay_rest/tests/models/__init__.py @@ -6,4 +6,6 @@ from .subnet_tests import * from .subscription_tests import * from .issue_public_key import * -from .atm_tests import * \ No newline at end of file +from .atm_tests import * +from .institution_tests import * +from .statement_tests import * diff --git a/synapse_pay_rest/tests/models/institution_tests.py b/synapse_pay_rest/tests/models/institution_tests.py new file mode 100644 index 0000000..e94c783 --- /dev/null +++ b/synapse_pay_rest/tests/models/institution_tests.py @@ -0,0 +1,20 @@ +import unittest +import pdb +from synapse_pay_rest.tests.fixtures.client import * +from synapse_pay_rest.client import Client +from synapse_pay_rest.models import Institution + + +class InstitutionTestCases(unittest.TestCase): + def setUp(self): + print('\n{0}.{1}'.format(type(self).__name__, self._testMethodName)) + self.client = test_client + + def test_find_institutions(self): + institutions = Institution.find(self.client) + institution = institutions[0] + properties = ['client', 'bank_code', 'bank_name', 'features', 'forgotten_password', 'is_active', 'logo', 'tx_history_months'] + self.assertIsInstance(institution, Institution) + + for prop in properties: + self.assertIsNotNone(getattr(institution, prop)) From d394718c947b92167ec91f7a27698a01bf6525f9 Mon Sep 17 00:00:00 2001 From: easak Date: Tue, 23 Oct 2018 11:23:22 -0700 Subject: [PATCH 27/41] Add statements by user and node --- synapse_pay_rest/api/statements.py | 20 ++++++++ .../models/statements/__init__.py | 1 + .../models/statements/statement.py | 46 +++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 synapse_pay_rest/api/statements.py create mode 100644 synapse_pay_rest/models/statements/__init__.py create mode 100644 synapse_pay_rest/models/statements/statement.py diff --git a/synapse_pay_rest/api/statements.py b/synapse_pay_rest/api/statements.py new file mode 100644 index 0000000..9e8d42a --- /dev/null +++ b/synapse_pay_rest/api/statements.py @@ -0,0 +1,20 @@ +from synapse_pay_rest.http_client import HttpClient + + +class Statements(): + + def __init__(self, client): + self.client = client + + def create_path(self, user_id, node_id=None): + path = '/users/{0}'.format(user_id) + if node_id: + return path + '/nodes/' + node_id + '/statements' + else: + return path + '/statements' + + def retrieve(self, user_id, node_id=None, **kwargs): + + path = self.create_path(user_id, node_id) + response = self.client.get(path, **kwargs) + return response diff --git a/synapse_pay_rest/models/statements/__init__.py b/synapse_pay_rest/models/statements/__init__.py new file mode 100644 index 0000000..c62c2b8 --- /dev/null +++ b/synapse_pay_rest/models/statements/__init__.py @@ -0,0 +1 @@ +from .statement import Statement diff --git a/synapse_pay_rest/models/statements/statement.py b/synapse_pay_rest/models/statements/statement.py new file mode 100644 index 0000000..c906d96 --- /dev/null +++ b/synapse_pay_rest/models/statements/statement.py @@ -0,0 +1,46 @@ +import copy +from synapse_pay_rest.client import Client + + +class Statement(): + + + def __init__(self, **kwargs): + for arg, value in kwargs.items(): + setattr(self, arg, value) + + def __repr__(self): + clean_dict = self.__dict__.copy() + return '{0}({1})'.format(self.__class__, clean_dict) + + @classmethod + def from_response(cls, user, response): + return cls( + user = user, + json = response, + _id = response['_id'], + client_id = response['client_id'], + date_end = response['date_end'], + date_start = response['date_start'], + ending_balance = response.get('ending_balance', None), + is_active = response['is_active'], + node_id = response['node_id'], + opening_balance = response.get('opening_balance', None), + status = response['status'], + csv_url = response['urls']['csv'], + json_url = response['urls'].get('json', None), + pdf_url = response['urls']['pdf'], + user_id = response['user_id'] + ) + + @classmethod + def multiple_from_response(cls, user, response): + + statements = [cls.from_response(user, statement_data) + for statement_data in response] + return statements + + @classmethod + def retrieve(cls, user, node_id=None, **kwargs): + response = user.client.statements.retrieve(user.id, **kwargs) + return cls.multiple_from_response(user, response['statements']) From 81a3e540c49fc3cf865a15f5549996f2ea066a8e Mon Sep 17 00:00:00 2001 From: easak Date: Tue, 23 Oct 2018 11:27:45 -0700 Subject: [PATCH 28/41] Add tests for statements --- .../tests/models/statement_tests.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 synapse_pay_rest/tests/models/statement_tests.py diff --git a/synapse_pay_rest/tests/models/statement_tests.py b/synapse_pay_rest/tests/models/statement_tests.py new file mode 100644 index 0000000..e0fc6d5 --- /dev/null +++ b/synapse_pay_rest/tests/models/statement_tests.py @@ -0,0 +1,31 @@ +import unittest +import pdb +from synapse_pay_rest.tests.fixtures.client import * +from synapse_pay_rest.models import User +from synapse_pay_rest.models import Statement + +class StatementTestCases(unittest.TestCase): + def setUp(self): + print('\n{0}.{1}'.format(type(self).__name__, self._testMethodName)) + self.client = test_client + self.user = User.by_id(self.client, '5a271c2592571b0034c0d9d8') + + def test_by_user(self): + statements = Statement.retrieve(self.user) + statement = statements[0] + properties = ['user', '_id', 'client_id', 'date_end', 'date_start', 'ending_balance', 'is_active', 'node_id', 'opening_balance', 'status', 'csv_url', 'json_url', 'pdf_url', 'user_id'] + + self.assertIsInstance(statement, Statement) + + for prop in properties: + self.assertIsNotNone(getattr(statement, prop)) + + def test_by_node(self): + statements = Statement.retrieve(self.user, '5a399beece31670034632427') + statement = statements[0] + properties = ['user', '_id', 'client_id', 'date_end', 'date_start', 'ending_balance', 'is_active', 'node_id', 'opening_balance', 'status', 'csv_url', 'json_url', 'pdf_url', 'user_id'] + + self.assertIsInstance(statement, Statement) + + for prop in properties: + self.assertIsNotNone(getattr(statement, prop)) From a61aff489d6a94ef86fd27b8459e8e74d218f1e8 Mon Sep 17 00:00:00 2001 From: easak Date: Tue, 23 Oct 2018 11:53:04 -0700 Subject: [PATCH 29/41] Final clean up for institutions and statements --- synapse_pay_rest/tests/fixtures/client.py | 9 +-------- synapse_pay_rest/tests/models/statement_tests.py | 4 ++-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/synapse_pay_rest/tests/fixtures/client.py b/synapse_pay_rest/tests/fixtures/client.py index 2182002..dcbc923 100644 --- a/synapse_pay_rest/tests/fixtures/client.py +++ b/synapse_pay_rest/tests/fixtures/client.py @@ -2,12 +2,6 @@ import sys from synapse_pay_rest.client import Client -# try: -# CLIENT_ID = os.environ['TEST_CLIENT_ID'] -# CLIENT_SECRET = os.environ['TEST_CLIENT_SECRET'] -# except KeyError: -# CLIENT_ID = input("Please enter client ID: ") -# CLIENT_SECRET = input("Please enter client secret: ") if sys.version_info[0] >= 3: try: CLIENT_ID = os.environ['TEST_CLIENT_ID'] @@ -23,8 +17,7 @@ CLIENT_ID = raw_input("Please enter client ID: ") CLIENT_SECRET = raw_input("Please enter client secret: ") -# FINGERPRINT = '737321631015eefd8dd6573ccce33319' -FINGERPRINT = 'c7e516361e6e5453de96223616d73a7b' +FINGERPRINT = 'INPUT_USER_FINGERPRINT_HERE' IP_ADDRESS = '127.0.0.1' diff --git a/synapse_pay_rest/tests/models/statement_tests.py b/synapse_pay_rest/tests/models/statement_tests.py index e0fc6d5..36a06ee 100644 --- a/synapse_pay_rest/tests/models/statement_tests.py +++ b/synapse_pay_rest/tests/models/statement_tests.py @@ -8,7 +8,7 @@ class StatementTestCases(unittest.TestCase): def setUp(self): print('\n{0}.{1}'.format(type(self).__name__, self._testMethodName)) self.client = test_client - self.user = User.by_id(self.client, '5a271c2592571b0034c0d9d8') + self.user = User.by_id(self.client, 'INPUT_USER_ID_HERE') def test_by_user(self): statements = Statement.retrieve(self.user) @@ -21,7 +21,7 @@ def test_by_user(self): self.assertIsNotNone(getattr(statement, prop)) def test_by_node(self): - statements = Statement.retrieve(self.user, '5a399beece31670034632427') + statements = Statement.retrieve(self.user, 'INPUT_NODE_ID_HERE') statement = statements[0] properties = ['user', '_id', 'client_id', 'date_end', 'date_start', 'ending_balance', 'is_active', 'node_id', 'opening_balance', 'status', 'csv_url', 'json_url', 'pdf_url', 'user_id'] From a77b4bcd41cce8240b07ee1e4e5c5ba92a83bf26 Mon Sep 17 00:00:00 2001 From: easak Date: Tue, 23 Oct 2018 12:20:11 -0700 Subject: [PATCH 30/41] Update versioning --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fc24ae6..1fd5aec 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='3.3.1', + version='3.4.0', description='SynapsePay Rest Native Python Library', From 6b7e666d6e3a609774d47f64aa27a8f5d2a8ed15 Mon Sep 17 00:00:00 2001 From: easak Date: Fri, 2 Nov 2018 14:48:13 -0700 Subject: [PATCH 31/41] Update versioning for request module --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6f5275b..c20f36f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -requests==2.13.0 +requests==2.20.0 From c65d4a6c9c5456cce54e67cda50b88454fdb5267 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 10 Dec 2018 14:36:10 -0800 Subject: [PATCH 32/41] adding add_documents to user file --- synapse_pay_rest/models/users/user.py | 28 ++- synapse_pay_rest/tests/fixtures/.gitignore | 1 + synapse_pay_rest/tests/fixtures/user.py | 200 +++++++++++++++++++++ synapse_pay_rest/tests/user_tests.py | 29 +++ 4 files changed, 254 insertions(+), 4 deletions(-) create mode 100644 synapse_pay_rest/tests/fixtures/.gitignore create mode 100644 synapse_pay_rest/tests/user_tests.py diff --git a/synapse_pay_rest/models/users/user.py b/synapse_pay_rest/models/users/user.py index e8f81a0..8256bb1 100644 --- a/synapse_pay_rest/models/users/user.py +++ b/synapse_pay_rest/models/users/user.py @@ -145,7 +145,7 @@ def all(cls, client=None, **kwargs): Returns: list: containing 0 or more User instances """ - response = client.users.get(**kwargs) + response = client.users.get(**kwargs) # Really?! what if the client is None! return cls.multiple_from_response(client, response['users']) def authenticate(self): @@ -187,7 +187,20 @@ def payload_for_update(self, **kwargs): def payload_for_refresh(self): """Build the API 'oauth user' payload.""" - return {'refresh_token': self.refresh_token} + return { 'refresh_token': self.refresh_token } + + def add_documents(self, docs): + """Adds documents + + Args: + docs (dict): dictionary containing the docs to add to user + + Returns: + User: a new instance of a user + """ + payload = { 'documents': [docs] } + response = self.client.users.update(self.id, payload) + return User.from_response(self.client, response, oauth=False) def add_base_document(self, **kwargs): """Add a BaseDocument to the User. @@ -341,7 +354,11 @@ def select_2fa_device(self, device): """ payload = self.payload_for_refresh() payload['phone_number'] = device - self.client.users.refresh(self.id, payload) + + response = self.client.users.refresh(self.id, payload) + + # if int(response.get('error_code', 0)) > 10: + # return False return True def confirm_2fa_pin(self, device, pin): @@ -358,5 +375,8 @@ def confirm_2fa_pin(self, device, pin): payload = self.payload_for_refresh() payload['phone_number'] = device payload['validation_pin'] = pin - self.client.users.refresh(self.id, payload) + response = self.client.users.refresh(self.id, payload) + + # if int(response.get('error_code', 0)) > 0: + # return False return True diff --git a/synapse_pay_rest/tests/fixtures/.gitignore b/synapse_pay_rest/tests/fixtures/.gitignore new file mode 100644 index 0000000..5ce4f37 --- /dev/null +++ b/synapse_pay_rest/tests/fixtures/.gitignore @@ -0,0 +1 @@ +test_client.py \ No newline at end of file diff --git a/synapse_pay_rest/tests/fixtures/user.py b/synapse_pay_rest/tests/fixtures/user.py index a4aae50..f76c3ef 100644 --- a/synapse_pay_rest/tests/fixtures/user.py +++ b/synapse_pay_rest/tests/fixtures/user.py @@ -77,3 +77,203 @@ "address_postal_code":"94114", "address_country_code":"US" } + + +user_from_response = { + "_id": "5c09b6d84884002c47b53c27", + "_links": { + "self": { + "href": "https://uat-api.synapsefi.com/v3.1/users/5c09b6d84884002c47b53c27" + } + }, + "client": { + "id": "5be38afd6a785e6bddfffe68", + "name": "Test User" + }, + "doc_status": { + "physical_doc": "MISSING|INVALID", + "virtual_doc": "MISSING|INVALID" + }, + "documents": [], + "emails": [], + "extra": { + "cip_tag": 1, + "date_joined": 1544140503244, + "extra_security": False, + "is_business": False, + "last_updated": 1544140503244, + "public_note": None, + "supp_id": "122eddfgbeafrfvbbb" + }, + "is_hidden": False, + "legal_names": [ + "basic user" + ], + "logins": [ + { + "email": "test@synapsefi.com", + "scope": "READ_AND_WRITE" + } + ], + "permission": "UNVERIFIED", + "phone_numbers": [ + "901.111.1111", + "test@synapsefi.com" + ], + "photos": [], + "refresh_token": "refresh_R61lpOv9PtUHc2geAK4uifXyB5FodxCLhM0N78r3" +} + +test_docs = { + "email":"test@test.com", + "phone_number":"901.111.1111", + "ip":"::1", + "name":"Test User", + "alias":"Test", + "entity_type":"M", + "entity_scope":"Arts & Entertainment", + "day":2, + "month":5, + "year":1989, + "address_street":"1 Market St.", + "address_city":"San Francisco", + "address_subdivision":"CA", + "address_postal_code":"94114", + "address_country_code":"US", + "virtual_docs":[{ + "document_value":"2222", + "document_type":"SSN" + }], + "physical_docs":[{ + "document_value": "data:image/gif;base64,SUQs==", + "document_type": "GOVT_ID" + }], + "social_docs":[{ + "document_value":"https://www.facebook.com/valid", + "document_type":"FACEBOOK" + }] + } + +docs_response = { + "_id": "5c0abeb9970f8426abc8df67", + "_links": { + "self": { + "href": "https://uat-api.synapsefi.com/v3.1/users/5c0abeb9970f8426abc8df67" + } + }, + "client": { + "id": "5be38afd6a785e6bddfffe68", + "name": "Test User" + }, + "doc_status": { + "physical_doc": "SUBMITTED|VALID", + "virtual_doc": "SUBMITTED|VALID" + }, + "documents": [ + { + "entity_scope": "Arts & Entertainment", + "entity_type": "M", + "id": "2a4a5957a3a62aaac1a0dd0edcae96ea2cdee688ec6337b20745eed8869e3ac8", + "name": "Test User", + "permission_scope": "UNVERIFIED", + "physical_docs": [ + { + "document_type": "GOVT_ID", + "id": "9afdc3b20c3880f506525e71b3ea7b6eb4fa4c9cfe46b3d70136cbf0e5e59ea9", + "last_updated": 1544478983941, + "status": "SUBMITTED|REVIEWING" + } + ], + "social_docs": [ + { + "document_type": "EMAIL", + "id": "2c45158f6431ca874bbe82f63d5905567854dde4d8b81539944e5779e5eee741", + "last_updated": 1544478983973, + "status": "SUBMITTED|REVIEWING" + }, + { + "document_type": "FACEBOOK", + "id": "8f314a6a53f36ee569455761e49a2a7fe790d251c5611c65255befdb303602b7", + "last_updated": 1544478983962, + "status": "SUBMITTED|REVIEWING" + }, + { + "document_type": "PHONE_NUMBER", + "id": "fda60784d6375bc44edafaaeae149626c4c13dcb92e85a2a7a00eec2cdfd2b6f", + "last_updated": 1544478984012, + "status": "SUBMITTED|REVIEWING" + }, + { + "document_type": "DATE", + "id": "2b52edae636ca2fbe12ab1b08a344d381dabc3d2b92844cf7a8d8b6052b26d8e", + "last_updated": 1544478984419, + "status": "SUBMITTED|REVIEWING" + }, + { + "document_type": "ADDRESS", + "id": "9253436898627bc318ef39059558ccce8617ecab27dc1bb17d73caa8040f6980", + "last_updated": 1544478984308, + "status": "SUBMITTED|REVIEWING" + }, + { + "document_type": "IP", + "id": "28d9177b22c127d9a51d8903893864accf6e553ac326704a4c0d585eaad2516a", + "last_updated": 1544478984052, + "status": "SUBMITTED|REVIEWING" + } + ], + "virtual_docs": [ + { + "document_type": "SSN", + "id": "ee596c2896dddc19b76c07a184fe7d3cf5a04b8e94b9108190cac7890739017f", + "last_updated": 1544478983978, + "status": "SUBMITTED|REVIEWING" + } + ] + } + ], + "emails": [], + "extra": { + "cip_tag": 1, + "date_joined": 1544208044991, + "extra_security": False, + "is_business": False, + "last_updated": 1544478982302, + "public_note": None, + "supp_id": "my_user_id" + }, + "is_hidden": False, + "legal_names": [ + "Test User" + ], + "logins": [ + { + "email": "test2@synapsepay.com", + "scope": "READ_AND_WRITE" + } + ], + "permission": "SEND-AND-RECEIVE", + "phone_numbers": [ + "901.111.1111" + ], + "photos": [], + "refresh_token": "refresh_thaC1YjAMrWcQPm7gB5SqRe9kpDxTViOG62I0J8L" +} + +test_good_2fa_device = { + "error_code": "10", + "http_code": "202", + "message": { + "en": "MFA sent to easak@synapsefi.com." + }, + "success": True +} + +test_bad_2fa_device = { + "error_code": "20", + "http_code": "202", + "message": { + "en": "bad response" + }, + "success": False +} \ No newline at end of file diff --git a/synapse_pay_rest/tests/user_tests.py b/synapse_pay_rest/tests/user_tests.py new file mode 100644 index 0000000..e6ce103 --- /dev/null +++ b/synapse_pay_rest/tests/user_tests.py @@ -0,0 +1,29 @@ +import unittest +from unittest import TestCase, mock + +from synapse_pay_rest.tests.fixtures.test_client import * +from synapse_pay_rest.tests.fixtures.user import * +from synapse_pay_rest.models.users.user import User +from synapse_pay_rest.api.users import Users + +class UserTests(TestCase): + + def setUp(self): + self.user = User.from_response(test_client, user_from_response, oauth=False) + + @mock.patch('synapse_pay_rest.api.Users.update', autospec=True) + def test_add_documents(self, mock_update): + mock_update.return_value = docs_response + user = self.user.add_documents(test_docs) + self.assertIsInstance(user, User) + + @mock.patch('synapse_pay_rest.api.Users.refresh', autospec=True) + def test_select_2fa_device(self, mock_refresh): + pass + + @mock.patch('synapse_pay_rest.api.Users.refresh', autospec=True) + def test_confirm_2fa_pin(self, mock_refresh): + pass + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From c427658473fa483baeecd2e6d40761b343b430cb Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 10 Dec 2018 14:58:51 -0800 Subject: [PATCH 33/41] adding sample code and docstring --- samples.md | 35 +++++++++++++++++++++++++++ synapse_pay_rest/models/users/user.py | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/samples.md b/samples.md index f310245..fd36f59 100644 --- a/samples.md +++ b/samples.md @@ -152,6 +152,41 @@ user = user.change_cip_tag(1) ## Adding Documents to Users +#### Add multiple KYC +```python +document = { + "email":"test@test.com", + "phone_number":"901.111.1111", + "ip":"::1", + "name":"Test User", + "alias":"Test", + "entity_type":"M", + "entity_scope":"Arts & Entertainment", + "day":2, + "month":5, + "year":1989, + "address_street":"1 Market St.", + "address_city":"San Francisco", + "address_subdivision":"CA", + "address_postal_code":"94114", + "address_country_code":"US", + "virtual_docs":[{ + "document_value":"2222", + "document_type":"SSN" + }], + "physical_docs":[{ + "document_value": "data:image/gif;base64,SUQs==", + "document_type": "GOVT_ID" + }], + "social_docs":[{ + "document_value":"https://www.facebook.com/valid", + "document_type":"FACEBOOK" + }] + } + +new_user = user.add_document(document) +``` + #### Add a CIP Base Document to a User ```python options = { diff --git a/synapse_pay_rest/models/users/user.py b/synapse_pay_rest/models/users/user.py index 8256bb1..4f53fcc 100644 --- a/synapse_pay_rest/models/users/user.py +++ b/synapse_pay_rest/models/users/user.py @@ -190,7 +190,7 @@ def payload_for_refresh(self): return { 'refresh_token': self.refresh_token } def add_documents(self, docs): - """Adds documents + """Adds a document to the current user Args: docs (dict): dictionary containing the docs to add to user From a7869e75fa3411d4ce2658d3b165c367b5660b0a Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 11 Dec 2018 13:34:27 -0800 Subject: [PATCH 34/41] changing sample.md so updating KYC is clearer --- samples.md | 5 +++-- synapse_pay_rest/models/users/user.py | 4 +--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/samples.md b/samples.md index fd36f59..8d2ee6d 100644 --- a/samples.md +++ b/samples.md @@ -152,7 +152,8 @@ user = user.change_cip_tag(1) ## Adding Documents to Users -#### Add multiple KYC +#### Add Multiple KYC +Updates the current user's KYC and returns the updated user as a User object ```python document = { "email":"test@test.com", @@ -184,7 +185,7 @@ document = { }] } -new_user = user.add_document(document) +user = user.add_document(document) ``` #### Add a CIP Base Document to a User diff --git a/synapse_pay_rest/models/users/user.py b/synapse_pay_rest/models/users/user.py index 4f53fcc..ee525ee 100644 --- a/synapse_pay_rest/models/users/user.py +++ b/synapse_pay_rest/models/users/user.py @@ -376,7 +376,5 @@ def confirm_2fa_pin(self, device, pin): payload['phone_number'] = device payload['validation_pin'] = pin response = self.client.users.refresh(self.id, payload) - - # if int(response.get('error_code', 0)) > 0: - # return False + return True From 0a402bb221a79683f9bf22da4719937d29e66cc0 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 11 Dec 2018 13:40:05 -0800 Subject: [PATCH 35/41] editing samples.md --- samples.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples.md b/samples.md index 8d2ee6d..8a15349 100644 --- a/samples.md +++ b/samples.md @@ -153,7 +153,7 @@ user = user.change_cip_tag(1) ## Adding Documents to Users #### Add Multiple KYC -Updates the current user's KYC and returns the updated user as a User object +Adds KYC documents to the current user and returns the updated user as a User object ```python document = { "email":"test@test.com", From 51523e30308d75a4c617bb699477ea5c3f02e0a7 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 11 Dec 2018 14:54:54 -0800 Subject: [PATCH 36/41] adding example code for adding user with basedocs and without, also added force_refresh functionality in get node --- samples.md | 54 +++++++++++++++++++++++++-- synapse_pay_rest/http_client.py | 2 +- synapse_pay_rest/models/nodes/node.py | 5 ++- synapse_pay_rest/models/users/user.py | 3 +- 4 files changed, 56 insertions(+), 8 deletions(-) diff --git a/samples.md b/samples.md index 8a15349..9abe967 100644 --- a/samples.md +++ b/samples.md @@ -123,7 +123,7 @@ user.base_documents[0] = } ``` -#### Create a User +#### Create a User w/o Base Documents ```python args = { @@ -136,7 +136,53 @@ args = { 'cip_tag': 1 } -user = User.create(client, **args) +user_without_basedocs = User.create(client, **args) +``` + +#### Create a User w/ Base Documents + +```python +docs = { + "email":"test@test.com", + "phone_number":"901.111.1111", + "ip":"::1", + "name":"Test User", + "alias":"Test", + "entity_type":"M", + "entity_scope":"Arts & Entertainment", + "day":2, + "month":5, + "year":1989, + "address_street":"1 Market St.", + "address_city":"San Francisco", + "address_subdivision":"CA", + "address_postal_code":"94114", + "address_country_code":"US", + "virtual_docs":[{ + "document_value":"2222", + "document_type":"SSN" + }], + "physical_docs":[{ + "document_value": "data:image/gif;base64,SUQs==", + "document_type": "GOVT_ID" + }], + "social_docs":[{ + "document_value":"https://www.facebook.com/valid", + "document_type":"FACEBOOK" + }] + } + +args = { + 'email': 'hello@synapsepay.com', + 'phone_number': '555-555-5555', + 'legal_name': 'Hello McHello', + 'note': ':)', # optional + 'supp_id': '123abc', # optional + 'is_business': True, + 'cip_tag': 1 +} + +user_with_basedocs = User.create(client, base_docs=docs, **args) ``` #### Update a User's Personal Info @@ -309,8 +355,8 @@ nodes = Node.all(user, **options) ```python node = Node.by_id(user, '57ec57be86c27345b3f8a159') -optional: -node = Node.by_id(user, '57ec57be86c27345b3f8a159', full_dehydrate='yes') +optional parameters: +node = Node.by_id(user, '57ec57be86c27345b3f8a159', full_dehydrate='yes', force_refresh='yes') Example of node response with full_dehydrate: **Please note: if full_dehydrate='no', some fields will return as 'None' diff --git a/synapse_pay_rest/http_client.py b/synapse_pay_rest/http_client.py index 17e2cd1..f35aea6 100644 --- a/synapse_pay_rest/http_client.py +++ b/synapse_pay_rest/http_client.py @@ -49,7 +49,7 @@ def get_headers(self): def get(self, url, **params): """Send a GET request to the API.""" self.log_information(self.logging) - valid_params = ['query', 'page', 'per_page', 'type', 'full_dehydrate', 'radius', 'lat', 'lon', 'zip'] + valid_params = ['query', 'page', 'per_page', 'type', 'full_dehydrate', 'radius', 'lat', 'lon', 'zip', 'force_refresh'] parameters = {} for param in valid_params: if param in params: diff --git a/synapse_pay_rest/models/nodes/node.py b/synapse_pay_rest/models/nodes/node.py index fc33e5f..faf5ef4 100644 --- a/synapse_pay_rest/models/nodes/node.py +++ b/synapse_pay_rest/models/nodes/node.py @@ -136,18 +136,19 @@ def multiple_from_response(cls, user, response): return nodes @classmethod - def by_id(cls, user=None, id=None, full_dehydrate='no'): + def by_id(cls, user=None, id=None, full_dehydrate='no', force_refresh='no'): """Retrieve a node record by id and create a BaseNode instance from it. Args: user (User): the User that the node belongs to id (str): id of the node to retrieve full_dehydrate(optional, str): if 'yes', returns transaction data on node + force_refresh(optional, str): if 'yes', returns synced account data Returns: BaseNode: a BaseNode instance corresponding to the record """ - response = user.client.nodes.get(user.id, id, full_dehydrate=full_dehydrate) + response = user.client.nodes.get(user.id, id, full_dehydrate=full_dehydrate, force_refresh=force_refresh) return cls.from_response(user, response) @classmethod diff --git a/synapse_pay_rest/models/users/user.py b/synapse_pay_rest/models/users/user.py index ee525ee..6fb7bb9 100644 --- a/synapse_pay_rest/models/users/user.py +++ b/synapse_pay_rest/models/users/user.py @@ -104,6 +104,7 @@ def create(cls, client=None, email=None, phone_number=None, read_only (bool): (opt) False if admin user (default) phone_number (str): user's phone number legal_name (str): user's legal name + base_doc (dict): (opt) user's basedocs note (str): (opt) note to SynapsePay supp_id (str): (opt) supplemental id is_business (bool): (opt) False if personal user (default) @@ -376,5 +377,5 @@ def confirm_2fa_pin(self, device, pin): payload['phone_number'] = device payload['validation_pin'] = pin response = self.client.users.refresh(self.id, payload) - + return True From 9cf177c276a3663644569921a5a2d841cb6af528 Mon Sep 17 00:00:00 2001 From: Matthew Bernardo Date: Thu, 20 Dec 2018 15:13:13 -0800 Subject: [PATCH 37/41] adding other option to extra_options in base_node --- setup.py | 2 +- synapse_pay_rest/models/nodes/base_node.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 1fd5aec..f89814c 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='3.4.0', + version='3.4.5', description='SynapsePay Rest Native Python Library', diff --git a/synapse_pay_rest/models/nodes/base_node.py b/synapse_pay_rest/models/nodes/base_node.py index 30fd1c8..828299b 100644 --- a/synapse_pay_rest/models/nodes/base_node.py +++ b/synapse_pay_rest/models/nodes/base_node.py @@ -153,7 +153,7 @@ def payload_for_create(cls, type, **kwargs): if balance: payload['info']['balance'] = balance - extra_options = ['supp_id', 'gateway_restricted'] + extra_options = ['supp_id', 'gateway_restricted', 'other'] extra = {} for option in extra_options: if option in kwargs: From 0fbadd92c4ee08d10a2df8d42ce9acddbca0407d Mon Sep 17 00:00:00 2001 From: Matthew Bernardo Date: Mon, 11 Feb 2019 13:56:46 -0800 Subject: [PATCH 38/41] updated statements to support parsing --- synapse_pay_rest/models/statements/statement.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/synapse_pay_rest/models/statements/statement.py b/synapse_pay_rest/models/statements/statement.py index c906d96..d238281 100644 --- a/synapse_pay_rest/models/statements/statement.py +++ b/synapse_pay_rest/models/statements/statement.py @@ -1,6 +1,4 @@ import copy -from synapse_pay_rest.client import Client - class Statement(): @@ -18,19 +16,7 @@ def from_response(cls, user, response): return cls( user = user, json = response, - _id = response['_id'], - client_id = response['client_id'], - date_end = response['date_end'], - date_start = response['date_start'], - ending_balance = response.get('ending_balance', None), - is_active = response['is_active'], - node_id = response['node_id'], - opening_balance = response.get('opening_balance', None), - status = response['status'], - csv_url = response['urls']['csv'], - json_url = response['urls'].get('json', None), - pdf_url = response['urls']['pdf'], - user_id = response['user_id'] + **response ) @classmethod From 7914824c8fc16dc729e7df873d364f66c7d7e35d Mon Sep 17 00:00:00 2001 From: Easak Hong Date: Fri, 22 Mar 2019 16:22:01 -0700 Subject: [PATCH 39/41] Fix get statements by node --- setup.py | 2 +- synapse_pay_rest/models/statements/statement.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index f89814c..abfae60 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='3.4.5', + version='3.4.6', description='SynapsePay Rest Native Python Library', diff --git a/synapse_pay_rest/models/statements/statement.py b/synapse_pay_rest/models/statements/statement.py index d238281..a63bc75 100644 --- a/synapse_pay_rest/models/statements/statement.py +++ b/synapse_pay_rest/models/statements/statement.py @@ -28,5 +28,9 @@ def multiple_from_response(cls, user, response): @classmethod def retrieve(cls, user, node_id=None, **kwargs): - response = user.client.statements.retrieve(user.id, **kwargs) + response = None + if not node_id: + response = user.client.statements.retrieve(user.id, **kwargs) + else: + response = user.client.statements.retrieve(user.id, node_id, **kwargs) return cls.multiple_from_response(user, response['statements']) From 9edbe941a60580ac178a1a4d59bcb9d9bdada4c7 Mon Sep 17 00:00:00 2001 From: Easak Hong Date: Fri, 22 Mar 2019 16:25:01 -0700 Subject: [PATCH 40/41] Fix version number in setup file --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index abfae60..75e7e31 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='3.4.6', + version='3.4.7', description='SynapsePay Rest Native Python Library', From e7647191b386bdda84c0f2f1eb097569e36c27ae Mon Sep 17 00:00:00 2001 From: Matthew Bernardo Date: Mon, 29 Apr 2019 14:56:13 -0700 Subject: [PATCH 41/41] adding AchUsNode.resend_microdeposits() --- samples.md | 8 ++++++++ setup.py | 2 +- synapse_pay_rest/api/nodes.py | 5 +++-- synapse_pay_rest/http_client.py | 4 ++-- synapse_pay_rest/models/nodes/ach_us_node.py | 9 +++++++++ 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/samples.md b/samples.md index 9abe967..15dfddd 100644 --- a/samples.md +++ b/samples.md @@ -778,6 +778,14 @@ required = { node = AchUsNode.create(user, **required) ``` +##### Resend Microdeposits + +Resend microdeposits if user failed verification + +```python +node = node.resend_microdeposits() +``` + ##### Verify Microdeposits ACH-US nodes added by account/routing must be verified with microdeposits: diff --git a/setup.py b/setup.py index 75e7e31..91c37ea 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='3.4.7', + version='3.4.8', description='SynapsePay Rest Native Python Library', diff --git a/synapse_pay_rest/api/nodes.py b/synapse_pay_rest/api/nodes.py index e2e84a9..9867703 100644 --- a/synapse_pay_rest/api/nodes.py +++ b/synapse_pay_rest/api/nodes.py @@ -54,7 +54,7 @@ def get(self, user_id, node_id=None, **params): response = self.client.get(path, **params) return response - def update(self, user_id, node_id, payload): + def update(self, user_id, node_id, payload, **params): """Updates a node record via PATCH request to the API. Used to edit node information (verify microdeposits). @@ -65,12 +65,13 @@ def update(self, user_id, node_id, payload): user_id (str): id of the user the node belongs to node_id (str): id of the node to update payload (dict): See the docs for exact payload structure + params (kwargs): Query parameters Returns: dict: response body (single node record) """ path = self.create_node_path(user_id, node_id) - response = self.client.patch(path, payload) + response = self.client.patch(path, payload, **params) return response def verify(self, user_id, payload, node_id=None, **kwargs): diff --git a/synapse_pay_rest/http_client.py b/synapse_pay_rest/http_client.py index f35aea6..8af4b04 100644 --- a/synapse_pay_rest/http_client.py +++ b/synapse_pay_rest/http_client.py @@ -67,11 +67,11 @@ def post(self, url, payload, **kwargs): response = self.session.post(self.base_url + url, data=data) return self.parse_response(response) - def patch(self, url, payload): + def patch(self, url, payload, **params): """Send a PATCH request to the API.""" self.log_information(self.logging) data = json.dumps(payload) - response = self.session.patch(self.base_url + url, data=data) + response = self.session.patch(self.base_url + url, data=data, params=params) return self.parse_response(response) def delete(self, url): diff --git a/synapse_pay_rest/models/nodes/ach_us_node.py b/synapse_pay_rest/models/nodes/ach_us_node.py index e641c26..5bc5356 100644 --- a/synapse_pay_rest/models/nodes/ach_us_node.py +++ b/synapse_pay_rest/models/nodes/ach_us_node.py @@ -58,6 +58,15 @@ def payload_for_create(cls, nickname, account_number, routing_number, **kwargs) return payload + def resend_microdeposits(self): + """Resend the microdeposits required for ACH Node verification + Returns: + AchUsNode: a new instance representing the same API record + """ + response = self.user.client.nodes.update(self.user.id, self.id, {}, resend_micro='YES') + + return self.from_response(self.user, response) + def verify_microdeposits(self, amount1, amount2): """Verify the microdeposits to activate ACH-US added by acct/routing.