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/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 diff --git a/samples.md b/samples.md index ff58841..15dfddd 100644 --- a/samples.md +++ b/samples.md @@ -37,11 +37,141 @@ users = User.all(client, **options) ```python user = User.by_id(client, '57e97ab786c2737f4ccd4dc1') + +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 w/o Base Documents + +```python +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_without_basedocs = User.create(client, **args) ``` -#### Create a User +#### 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', @@ -52,7 +182,7 @@ args = { 'cip_tag': 1 } -user = User.create(client, **args) +user_with_basedocs = User.create(client, base_docs=docs, **args) ``` #### Update a User's Personal Info @@ -68,6 +198,42 @@ user = user.change_cip_tag(1) ## Adding Documents to Users +#### Add Multiple KYC +Adds KYC documents to the current user and returns the updated user as a User object +```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" + }] + } + +user = user.add_document(document) +``` + #### Add a CIP Base Document to a User ```python options = { @@ -167,28 +333,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 @@ -210,6 +354,379 @@ nodes = Node.all(user, **options) ```python node = Node.by_id(user, '57ec57be86c27345b3f8a159') + +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' + +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 @@ -261,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: @@ -337,3 +862,144 @@ 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) +``` + +## 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 nearby Atms with 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] +``` +#### 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/setup.py b/setup.py index 94c59e0..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.0.3', + version='3.4.8', 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 @@ -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' ], diff --git a/synapse_pay_rest/__init__.py b/synapse_pay_rest/__init__.py index bcfa179..6b8ec85 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, 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 1643c18..4296345 100644 --- a/synapse_pay_rest/api/__init__.py +++ b/synapse_pay_rest/api/__init__.py @@ -7,3 +7,9 @@ from .users import Users from .nodes import Nodes from .trans import Trans +from .subnets import Subnets +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/atms.py b/synapse_pay_rest/api/atms.py new file mode 100644 index 0000000..a8336cc --- /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, **kwargs): + + path = '/nodes/atms?' + response = self.client.get(path, **kwargs) + return response \ No newline at end of file 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/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/api/nodes.py b/synapse_pay_rest/api/nodes.py index 97cbf1f..9867703 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) @@ -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/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/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/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/client.py b/synapse_pay_rest/client.py index dea298e..61cf2eb 100644 --- a/synapse_pay_rest/client.py +++ b/synapse_pay_rest/client.py @@ -2,7 +2,12 @@ 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 +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. @@ -23,7 +28,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 +36,12 @@ 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) + 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) diff --git a/synapse_pay_rest/http_client.py b/synapse_pay_rest/http_client.py index 6d5473c..8af4b04 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', 'radius', 'lat', 'lon', 'zip', 'force_refresh'] parameters = {} for param in valid_params: if param in params: @@ -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/__init__.py b/synapse_pay_rest/models/__init__.py index bddae2c..f838fff 100644 --- a/synapse_pay_rest/models/__init__.py +++ b/synapse_pay_rest/models/__init__.py @@ -8,5 +8,12 @@ BaseDocument, Question from .nodes import AchUsNode, EftIndNode, EftNpNode, IouNode, ReserveUsNode,\ SynapseIndNode, SynapseNpNode, SynapseUsNode, WireIntNode,\ - WireUsNode, Node + WireUsNode, DepositUsNode, CheckUsNode, InterchangeUsNode,\ + IbSubaccountUsNode, IbDepositUsNode, SubaccountUsNode, ClearingUsNode, CardUsNode, SubcardUsNode, Node from .transactions import Transaction +from .subnets import Subnet +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/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..fdc5b2d --- /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, **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(**kwargs) + return cls.multiple_from_response(client, response['atms']) + + \ No newline at end of file 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']) 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..5bfdbbc 100644 --- a/synapse_pay_rest/models/nodes/__init__.py +++ b/synapse_pay_rest/models/nodes/__init__.py @@ -16,4 +16,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 +from .card_us_node import CardUsNode +from .subcard_us_node import SubcardUsNode from .node import Node diff --git a/synapse_pay_rest/models/nodes/ach_us_node.py b/synapse_pay_rest/models/nodes/ach_us_node.py index 664eed8..5bc5356 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 @@ -31,7 +32,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 +49,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, @@ -57,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. diff --git a/synapse_pay_rest/models/nodes/base_node.py b/synapse_pay_rest/models/nodes/base_node.py index 83e79e2..828299b 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 @@ -36,7 +36,14 @@ 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'), + 'interchange_type': response['info'].get('type'), + 'card_hash': response['info'].get('card_hash'), + 'is_international': response['info'].get('is_international'), + 'card_type': response['info'].get('card_type') } if response['info'].get('correspondent_info'): @@ -62,6 +69,24 @@ 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') + + #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 @@ -109,6 +134,16 @@ 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'] + if 'card_type' in kwargs: + payload['info']['card_type'] = kwargs['card_type'] balance_options = ['currency'] balance = {} @@ -118,13 +153,22 @@ 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: 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/card_us_node.py b/synapse_pay_rest/models/nodes/card_us_node.py new file mode 100644 index 0000000..9b4d812 --- /dev/null +++ b/synapse_pay_rest/models/nodes/card_us_node.py @@ -0,0 +1,52 @@ +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(CardUsNode, cls).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/check_us_node.py b/synapse_pay_rest/models/nodes/check_us_node.py new file mode 100644 index 0000000..9c8234d --- /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 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 CHECK-US.""" + payload = super(CheckUsNode, cls).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..464b9a2 --- /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 CLEARING-US node.""" + + @classmethod + def payload_for_create(cls, nickname, **kwargs): + """Build the API 'create node' payload specific to 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 new file mode 100644 index 0000000..dc9ff7a --- /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 DEPOSIT-US node.""" + + @classmethod + def payload_for_create(cls, nickname, **kwargs): + """Build the API 'create node' payload specific to 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 new file mode 100644 index 0000000..cea34d6 --- /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 IB-DEPOSIT-US node.""" + + @classmethod + def payload_for_create(cls, nickname, **kwargs): + """Build the API 'create node' payload specific to 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 new file mode 100644 index 0000000..6830a32 --- /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 IB-SUBACCOUNT-US node.""" + + @classmethod + def payload_for_create(cls, nickname, **kwargs): + """Build the API 'create node' payload specific to 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 new file mode 100644 index 0000000..4528062 --- /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 INTERCHANGE-US.""" + payload = super(InterchangeUsNode, cls).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/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/node.py b/synapse_pay_rest/models/nodes/node.py index 1259e83..faf5ef4 100644 --- a/synapse_pay_rest/models/nodes/node.py +++ b/synapse_pay_rest/models/nodes/node.py @@ -11,6 +11,15 @@ 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 .card_us_node import CardUsNode +from .subcard_us_node import SubcardUsNode class Node(): @@ -31,7 +40,16 @@ 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, + 'CARD-US': CardUsNode, + 'SUBCARD-US': SubcardUsNode } @classmethod @@ -55,7 +73,16 @@ 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), + 'billpay_info': response['extra']['other'].get('billpay_info',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) @@ -79,6 +106,24 @@ 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') + + #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) @@ -91,17 +136,19 @@ 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', 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) + 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/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 new file mode 100644 index 0000000..abb621c --- /dev/null +++ b/synapse_pay_rest/models/nodes/subaccount_us_node.py @@ -0,0 +1,13 @@ +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(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 new file mode 100644 index 0000000..e6afa3c --- /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(SubcardUsNode, cls).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/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/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..a63bc75 --- /dev/null +++ b/synapse_pay_rest/models/statements/statement.py @@ -0,0 +1,36 @@ +import copy + +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, + **response + ) + + @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 = 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']) 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..f072674 --- /dev/null +++ b/synapse_pay_rest/models/subnets/subnet.py @@ -0,0 +1,117 @@ +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_class=response['account_class'], + account_num=response['account_num'], + 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/models/users/base_document.py b/synapse_pay_rest/models/users/base_document.py index 661f45d..fa21883 100644 --- a/synapse_pay_rest/models/users/base_document.py +++ b/synapse_pay_rest/models/users/base_document.py @@ -36,6 +36,21 @@ 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('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, diff --git a/synapse_pay_rest/models/users/document.py b/synapse_pay_rest/models/users/document.py index d3af4fc..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 @@ -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 ffada35..6fb7bb9 100644 --- a/synapse_pay_rest/models/users/user.py +++ b/synapse_pay_rest/models/users/user.py @@ -64,10 +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): @@ -80,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) @@ -94,17 +119,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 @@ -120,7 +146,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): @@ -129,7 +155,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): @@ -160,7 +188,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 a document to the current user + + 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. @@ -314,7 +355,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): @@ -331,5 +376,6 @@ 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) + return True 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/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..dca121c 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( 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/__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/atm.py b/synapse_pay_rest/tests/fixtures/atm.py new file mode 100644 index 0000000..cebbd2e --- /dev/null +++ b/synapse_pay_rest/tests/fixtures/atm.py @@ -0,0 +1,14 @@ +atm_args = { + 'zip': '95113', + 'radius': '5', + 'page': 1, + '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/fixtures/client.py b/synapse_pay_rest/tests/fixtures/client.py index ee37628..dcbc923 100644 --- a/synapse_pay_rest/tests/fixtures/client.py +++ b/synapse_pay_rest/tests/fixtures/client.py @@ -1,14 +1,24 @@ 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: ") +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 = 'INPUT_USER_FINGERPRINT_HERE' -FINGERPRINT = 'test_fp' 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/fixtures/user.py b/synapse_pay_rest/tests/fixtures/user.py index c87c410..f76c3ef 100644 --- a/synapse_pay_rest/tests/fixtures/user.py +++ b/synapse_pay_rest/tests/fixtures/user.py @@ -37,7 +37,243 @@ 'legal_name': 'Hello McHello', 'note': ':)', 'supp_id': '123abc', - 'is_business': True, + 'is_business': False, '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' +} + +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" + } + + +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/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) diff --git a/synapse_pay_rest/tests/models/__init__.py b/synapse_pay_rest/tests/models/__init__.py index 5781d17..8e23c98 100644 --- a/synapse_pay_rest/tests/models/__init__.py +++ b/synapse_pay_rest/tests/models/__init__.py @@ -3,3 +3,9 @@ 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 * +from .atm_tests import * +from .institution_tests import * +from .statement_tests import * 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..b3a348b --- /dev/null +++ b/synapse_pay_rest/tests/models/atm_tests.py @@ -0,0 +1,34 @@ +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_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', + '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)) + + 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/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/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)) 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..65c9ef4 100644 --- a/synapse_pay_rest/tests/models/node_tests.py +++ b/synapse_pay_rest/tests/models/node_tests.py @@ -1,10 +1,12 @@ 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.models import User from synapse_pay_rest.models.nodes import * +from synapse_pay_rest.models import BaseDocument class NodeTestCases(unittest.TestCase): @@ -323,3 +325,259 @@ 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":"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 + 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==', + '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', 'interchange_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)) + + def test_create_card_us_node(self): + user = self.user + base_document = user.add_base_document(**base_doc_args) + time.sleep(10) + doc_id = base_document.id + + kwargs = { + 'nickname': 'Python Test CARD-US Account', + 'document_id': str(doc_id), + 'card_type': "VIRTUAL" + } + node = CardUsNode.create(user, **kwargs) + time.sleep(15) + 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) + time.sleep(10) + doc_id = base_document.id + kwargs = { + 'nickname': 'Python Test SUBCARD-US Account', + 'document_id': str(doc_id), + 'card_type': "VIRTUAL" + } + node = SubcardUsNode.create(user, **kwargs) + time.sleep(15) + 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) + time.sleep(10) + doc_id = base_document.id + 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(10) + 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) + time.sleep(10) + doc_id = base_document.id + 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(10) + 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) + time.sleep(10) + doc_id = base_document.id + 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(10) + 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) + time.sleep(10) + doc_id = base_document.id + 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(10) + self.assertEqual('INACTIVE', node.permission) + 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..36a06ee --- /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, 'INPUT_USER_ID_HERE') + + 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, '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'] + + self.assertIsInstance(statement, Statement) + + for prop in properties: + self.assertIsNotNone(getattr(statement, 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 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, 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) 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