def test_connect_step_device_phone(): client = Client('test_id', 'test_secret', access_token='test_chase') response = client.connect_step('chase', None, options={ 'send_method': {'type': 'phone'} }) assert response.status_code == 201 assert to_json(response)['type'] == 'device' assert to_json(response)['mfa']['message'] == 'Code sent to xxx-xxx-5309'
def test_connect_step_device_email(): client = Client('test_id', 'test_secret', access_token='test_chase') response = client.connect_step('chase', None, options={ 'send_method': {'type': 'email'} }) assert response.status_code == 201 assert to_json(response)['type'] == 'device' assert to_json(response)['mfa']['message'] == 'Code sent to [email protected]'
def get_response(): client = Client(client_id='test_id', secret='test_secret', access_token='usertoken') client.config({'url':'https://tartan.plaid.com'}) account_type = 'ins_100088' user = '******' pwd = 'plaid_good' response = client.connect(account_type, { 'username': user, 'password': pwd }) response = client.connect_step(account_type, 'tomato') return response.json() # This is a dict
def test_connection(self): client = Client( client_id='57f28ea8062e8c1d58ff9391', secret='580f72db7f49c97b4d279b504eda2d' ) try: response = client.connect(account_type, { 'username': '******', 'password': '******' }) self.assertEqual(response.json()['type'], 'questions') self.assertEqual(response.status_code, 201) response = client.connect_step(account_type, 'tomato') self.assertEqual(response.status_code, 200) response = client.connect_get() for account in response.json()["accounts"]: process_account_creation(account) for transaction in response.json()["transactions"]: process_transaction(transaction) print Account.objects.values_list('_id', 'pend_transfer') except plaid_errors.UnauthorizedError: traceback.print_exc()
def test_connect_step_question_loop(): client = Client('test_id', 'test_secret', access_token='test_bofa') response = client.connect_step('bofa', 'again') assert response.status_code == 201 assert to_json(response)['type'] == 'questions'
def test_connect_step_question(): client = Client('test_id', 'test_secret', access_token='test_bofa') response = client.connect_step('bofa', 'tomato') assert response.status_code == 200 assert to_json(response)['access_token'] == 'test_bofa'
class PlaidAccess(): def __init__(self, client_id=None, secret=None, account_id=None, access_token=None): if client_id and secret: self.client_id = client_id self.secret = secret else: self.client_id, self.secret = cm.get_plaid_config() self.client = Client(client_id=self.client_id, secret=self.secret) # if access_token: self.acceess_token = access_token # if account_id: self.account_id = account_id def get_transactions(self, access_token, account_id, start_date=None, end_date=None): """Get transaction for a given account for the given dates""" self.client.access_token = access_token options = {} options["account"] = account_id # options["pending"] = True #get all transaction up to and including today options['lte'] = datetime.date.today().strftime('%Y-%m-%d') self.connect_response = self.client.connect_get(options).json() return self.connect_response['transactions'] def add_account(self, nickname): try: self._get_available_institutions() except Exception as e: raise Exception( "There was a problem obtaining a list of institutions. Try again later." ) from e selected_institution = self._get_institution() if selected_institution is None: print("Quitting, no account added", file=sys.stdout) sys.exit(0) self.active_institution = self.available_institutions[ selected_institution][1] self.account_type = self.active_institution["type"] un, pwd, pin = self._get_needed_creds() # self.client.client_id = "test_id" # self.client.secret = "test_secret" # un = "plaid_test" # pwd = "plaid_good" login = {'username': un, 'password': pwd} if pin: login['pin'] = pin try: self.connect_response = self.client.connect( self.account_type, login) except plaid_errors.PlaidError as e: print(e.message) return else: cont = True try: while not self._check_status(): cont = self._process_mfa(self.connect_response.json()) if not cont: break except plaid_errors.PlaidError as e: print( "The following error has occurred, no account added:\n{}". format(e.message), file=sys.stderr) sys.exit(1) else: if not cont: print("Quitting, no account added", file=sys.stderr) sys.exit(1) accts = self.connect_response.json()['accounts'] if not self._present_accounts(accts): print("Quitting, no account added", file=sys.stderr) sys.exit(1) self._save_account_section(nickname) def _save_account_section(self, nickname): acct_section = OrderedDict() acct_section[nickname] = OrderedDict() section = acct_section[nickname] section['access_token'] = self.client.access_token section['account'] = self.selected_account['_id'] a = prompt( "What account is used for posting transactions [{}]: ".format( cm.CONFIG_DEFAULTS.posting_account)) if a: section['posting_account'] = a else: section['posting_account'] = cm.CONFIG_DEFAULTS.posting_account a = prompt( "What currency does this account use for posting transactions [{}]: " .format(cm.CONFIG_DEFAULTS.currency)) if a: section['currency'] = a else: section['currency'] = cm.CONFIG_DEFAULTS.currency a = self._present_file_options(nickname, 'mapping', cm.FILE_DEFAULTS.mapping_file) if a: section['mapping_file'] = a a = self._present_file_options(nickname, 'journal', cm.FILE_DEFAULTS.journal_file) if a: section['journal_file'] = a a = self._present_file_options(nickname, 'accounts', cm.FILE_DEFAULTS.accounts_file) if a: section['accounts_file'] = a a = self._present_file_options(nickname, 'template', cm.FILE_DEFAULTS.template_file) if a: section['template_file'] = a cm.write_section(acct_section) def _present_file_options(self, nickname, file_type, default_file): a = prompt( "Create a separate {} file configuration setting for this account [Y/n]: " .format(file_type), validator=YesNoValidator()).lower() if not bool(a) or a.startswith("y"): cont = True while cont: path = prompt( "Enter the path for the {} file used for this account [{}]: " .format(file_type, default_file), completer=PATH_COMPLETER) if path: path = os.path.expanduser(path) file_exists = os.path.isfile(path) if not file_exists: create = prompt( "{} does not exist. Do you want to create it? [Y/n]: " .format(path), validator=YesNoValidator()).lower() if not bool(create) or create.startswith("y"): if not os.path.exists(path): cm._create_directory_tree(path) cm._create_directory_tree(path) cm.touch(path) cont = False else: cont = True else: cont = False else: #use default create = prompt( "{} does not exist. Do you want to create it? [Y/n]: ". format(default_file), validator=YesNoValidator()).lower() if not bool(create) or create.startswith("y"): if not os.path.exists(default_file): cm._create_directory_tree(default_file) cm.touch(default_file) path = default_file cont = False else: cont = True if cont: print("Invalid path {}. Please enter a valid path.".format( path)) else: cont = False if not path: path = cm.get_custom_file_path(nickname, file_type, create_file=True) return path elif a.startswith("n"): return None def _process_mfa(self, data): type = data['type'] mfa = data['mfa'] if type == 'questions': return self._present_question([q['question'] for q in mfa]) elif type == 'list': return self._present_list(mfa) elif type == 'selection': return self._present_selection(mfa) else: raise Exception("Unknown mfa type from Plaid") def _present_question(self, question): clear_screen() print(question[0]) answer = prompt("Answer: ", validator=NullValidator( message="You must enter your answer", allow_quit=True)) if answer.lower() == "q": return False #we have abandoned ship self.connect_response = self.client.connect_step( self.account_type, answer) return True def _present_accounts(self, data): clear_screen() accounts = list(enumerate(data, start=1)) message = ["Which account do you want to add:\n"] for i, d in accounts: a = {} a['choice'] = str(i) a['name'] = d['meta']['name'] a['type'] = d['subtype'] if 'subtype' in d else d['type'] message.append("{choice:<2}. {name:<40} {type}\n".format(**a)) message.append("\nEnter your selection: ") answer = prompt( "".join(message), validator=NumberValidator( message="Enter the NUMBER of the account you want to add", allow_quit=True, max_number=len(accounts))) if answer.lower() == "q": return False #we have abandoned ship self.selected_account = accounts[int(answer) - 1][1] return True def _present_list(self, data): clear_screen() devices = list(enumerate(data, start=1)) message = ["Where do you want to send the verification code:\n"] for i, d in devices: message.append("{:<4}{}\n".format(i, d["mask"])) message.append("\nEnter your selection: ") answer = prompt( "".join(message), validator=NumberValidator( message= "Enter the NUMBER where you want to send verification code", allow_quit=True, max_number=len(devices))) if answer.lower() == "q": return False #we have abandoned ship dev = devices[int(answer) - 1][1] print("Code will be sent to: {}".format(dev["mask"])) self.connect_response = self.client.connect_step( self.account_type, None, options={'send_method': { 'type': dev['type'] }}) code = prompt("Enter the code you received: ", validator=NumberValidator()) self.connect_response = self.client.connect_step( self.account_type, code) return True def _present_selection(self, data): """ Could not test: needs implementation """ clear_screen() raise NotImplementedError( "MFA selections are not yet implemented, sorry.") def _get_needed_creds(self): credentials = self.active_institution["credentials"] clear_screen() print("Enter the required credentials for {}\n".format( self.active_institution["name"])) user_prompt = "Username ({}): ".format(credentials["username"]) pass_prompt = "Password ({}): ".format(credentials["password"]) pin = None username = prompt( user_prompt, validator=NullValidator(message="Please enter your username")) password = prompt( pass_prompt, is_password=True, validator=NullValidator(message="You must enter your password")) if "pin" in credentials: pin = prompt( "PIN: ", validator=NumLengthValidator( message="Pin must be at least {} characters long")) if not bool(pin) or pin.lower() == "q": pin = None #make sure we have something return username, password, pin def _get_available_institutions(self): # limit to just those institutions that have connect abilities, # as that is the service we will be using to get transactions institutions = self.client.institutions().json() self.available_institutions = list( enumerate([i for i in institutions if 'connect' in i['products']], start=1)) def _get_institution(self): accounts = [] total_institutions = len(self.available_institutions) for account in self.available_institutions: num, info = account accounts.append("{num:<4}{inst}".format(num=num, inst=info['name'])) institution_prompt = textwrap.dedent( """What bank is this account going to be for? \n{}\n\nEnter Number [q to quit]:""" .format('\n'.join(accounts))) clear_screen() res = prompt(institution_prompt, validator=NumberValidator( message="You must enter the chosen number", allow_quit=True, max_number=total_institutions)) if res.isdigit(): choice = int(res) - 1 elif res.lower() == "q": choice = None return choice def _check_status(self): status = self.connect_response.status_code if status == 200: # good, user connected return True elif status == 201: # MFA required return False else: return False
return client.connect_step(account_type, answer) try: response = client.connect(account_type, { 'username': username, 'password': password }) except plaid_errors.PlaidError, e: pass else: if response.status_code == 200: # User connected data = json.loads(response.content) elif response.status_code == 201: # MFA required try: mfa_response = answer_mfa(json.loads(response.content)) except plaid_errors.PlaidError, e: pass else: # check for 200 vs 201 responses # 201 indicates that additional MFA steps required phone_code = raw_input('Enter your ID code from your phone: ') connected = client.connect_step(account_type, phone_code) balances = client.balance().json() response = client.connect_get() transactions = json.loads(response.content)