From e488d7d84c1eeec153dff47aac09ad7dd9727e2d Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 9 Dec 2017 19:13:00 +0100 Subject: [PATCH] More code for Spectre import. --- .../Controllers/Import/BankController.php | 81 +---- .../Configurator/SpectreConfigurator.php | 178 ++++++++++ .../Configuration/Spectre/SelectBank.php | 76 ++++ .../Configuration/Spectre/SelectCountry.php | 329 ++++++++++++++++++ .../Import/Information/SpectreInformation.php | 207 +++++++++++ .../Prerequisites/SpectrePrerequisites.php | 57 +-- config/firefly.php | 4 +- resources/lang/en_US/bank.php | 14 +- resources/lang/en_US/form.php | 1 + .../views/import/spectre/select-bank.twig | 42 +++ .../views/import/spectre/select-country.twig | 42 +++ routes/web.php | 5 +- 12 files changed, 910 insertions(+), 126 deletions(-) create mode 100644 app/Import/Configurator/SpectreConfigurator.php create mode 100644 app/Support/Import/Configuration/Spectre/SelectBank.php create mode 100644 app/Support/Import/Configuration/Spectre/SelectCountry.php create mode 100644 app/Support/Import/Information/SpectreInformation.php create mode 100644 resources/views/import/spectre/select-bank.twig create mode 100644 resources/views/import/spectre/select-country.twig diff --git a/app/Http/Controllers/Import/BankController.php b/app/Http/Controllers/Import/BankController.php index fe4e6790ce..0bf671d4dc 100644 --- a/app/Http/Controllers/Import/BankController.php +++ b/app/Http/Controllers/Import/BankController.php @@ -24,7 +24,7 @@ namespace FireflyIII\Http\Controllers\Import; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; -use FireflyIII\Support\Import\Information\InformationInterface; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Support\Import\Prerequisites\PrerequisitesInterface; use Illuminate\Http\Request; use Log; @@ -32,72 +32,27 @@ use Session; class BankController extends Controller { - /** - * This method must ask the user all parameters necessary to start importing data. This may not be enough - * to finish the import itself (ie. mapping) but it should be enough to begin: accounts to import from, - * accounts to import into, data ranges, etc. - * - * @param string $bank - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View - */ - public function form(string $bank) - { - $class = config(sprintf('firefly.import_pre.%s', $bank)); - if(!class_exists($class)) { - throw new FireflyException(sprintf('Cannot find class %s', $class)); - } - /** @var PrerequisitesInterface $object */ - $object = app($class); - $object->setUser(auth()->user()); - - if ($object->hasPrerequisites()) { - return redirect(route('import.bank.prerequisites', [$bank])); - } - $class = config(sprintf('firefly.import_info.%s', $bank)); - /** @var InformationInterface $object */ - $object = app($class); - $object->setUser(auth()->user()); - $remoteAccounts = $object->getAccounts(); - - return view('import.bank.form', compact('remoteAccounts', 'bank')); - } /** - * With the information given in the submitted form Firefly III will call upon the bank's classes to return transaction - * information as requested. The user will be able to map unknown data and continue. Or maybe, it's put into some kind of - * fake CSV file and forwarded to the import routine. + * Once there are no prerequisites, this method will create an importjob object and + * redirect the user to a view where this object can be used by a bank specific + * class to process. * - * @param Request $request - * @param string $bank + * @param ImportJobRepositoryInterface $repository + * @param string $bank * * @return \Illuminate\Http\RedirectResponse|null + * @throws FireflyException */ - public function postForm(Request $request, string $bank) + public function createJob(ImportJobRepositoryInterface $repository, string $bank) { $class = config(sprintf('firefly.import_pre.%s', $bank)); - if(!class_exists($class)) { + if (!class_exists($class)) { throw new FireflyException(sprintf('Cannot find class %s', $class)); } - /** @var PrerequisitesInterface $object */ - $object = app($class); - $object->setUser(auth()->user()); + $importJob = $repository->create($bank); - if ($object->hasPrerequisites()) { - return redirect(route('import.bank.prerequisites', [$bank])); - } - $remoteAccounts = $request->get('do_import'); - if (!is_array($remoteAccounts) || 0 === count($remoteAccounts)) { - Session::flash('error', 'Must select accounts'); - - return redirect(route('import.bank.form', [$bank])); - } - $remoteAccounts = array_keys($remoteAccounts); - $class = config(sprintf('firefly.import_pre.%s', $bank)); - // get import file - unset($remoteAccounts, $class); - - // get import config + return redirect(route('import.bank.configure', [$bank, $importJob->key])); } /** @@ -112,12 +67,13 @@ class BankController extends Controller * @param string $bank * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @throws FireflyException */ public function postPrerequisites(Request $request, string $bank) { Log::debug(sprintf('Now in postPrerequisites for %s', $bank)); $class = config(sprintf('firefly.import_pre.%s', $bank)); - if(!class_exists($class)) { + if (!class_exists($class)) { throw new FireflyException(sprintf('Cannot find class %s', $class)); } /** @var PrerequisitesInterface $object */ @@ -126,7 +82,7 @@ class BankController extends Controller if (!$object->hasPrerequisites()) { Log::debug(sprintf('No more prerequisites for %s, move to form.', $bank)); - return redirect(route('import.bank.form', [$bank])); + return redirect(route('import.bank.create-job', [$bank])); } Log::debug('Going to store entered preprerequisites.'); // store post data @@ -138,7 +94,7 @@ class BankController extends Controller return redirect(route('import.bank.prerequisites', [$bank])); } - return redirect(route('import.bank.form', [$bank])); + return redirect(route('import.bank.create-job', [$bank])); } /** @@ -148,11 +104,12 @@ class BankController extends Controller * @param string $bank * * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View + * @throws FireflyException */ public function prerequisites(string $bank) { $class = config(sprintf('firefly.import_pre.%s', $bank)); - if(!class_exists($class)) { + if (!class_exists($class)) { throw new FireflyException(sprintf('Cannot find class %s', $class)); } /** @var PrerequisitesInterface $object */ @@ -161,12 +118,12 @@ class BankController extends Controller if ($object->hasPrerequisites()) { $view = $object->getView(); - $parameters = ['title' => strval(trans('firefly.import_index_title')),'mainTitleIcon' => 'fa-archive']; + $parameters = ['title' => strval(trans('firefly.import_index_title')), 'mainTitleIcon' => 'fa-archive']; $parameters = $object->getViewParameters() + $parameters; return view($view, $parameters); } - return redirect(route('import.bank.form', [$bank])); + return redirect(route('import.bank.create-job', [$bank])); } } diff --git a/app/Import/Configurator/SpectreConfigurator.php b/app/Import/Configurator/SpectreConfigurator.php new file mode 100644 index 0000000000..9a8d295101 --- /dev/null +++ b/app/Import/Configurator/SpectreConfigurator.php @@ -0,0 +1,178 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Import\Configurator; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\ImportJob; +use FireflyIII\Support\Import\Configuration\ConfigurationInterface; +use FireflyIII\Support\Import\Configuration\Spectre\SelectBank; +use FireflyIII\Support\Import\Configuration\Spectre\SelectCountry; +use Log; + +/** + * Class SpectreConfigurator. + */ +class SpectreConfigurator implements ConfiguratorInterface +{ + /** @var ImportJob */ + private $job; + + /** @var string */ + private $warning = ''; + + /** + * ConfiguratorInterface constructor. + */ + public function __construct() + { + } + + /** + * Store any data from the $data array into the job. + * + * @param array $data + * + * @return bool + * + * @throws FireflyException + */ + public function configureJob(array $data): bool + { + $class = $this->getConfigurationClass(); + $job = $this->job; + /** @var ConfigurationInterface $object */ + $object = new $class($this->job); + $object->setJob($job); + $result = $object->storeConfiguration($data); + $this->warning = $object->getWarningMessage(); + + return $result; + } + + /** + * Return the data required for the next step in the job configuration. + * + * @return array + * + * @throws FireflyException + */ + public function getNextData(): array + { + $class = $this->getConfigurationClass(); + $job = $this->job; + /** @var ConfigurationInterface $object */ + $object = app($class); + $object->setJob($job); + + return $object->getData(); + } + + /** + * @return string + * + * @throws FireflyException + */ + public function getNextView(): string + { + if (!$this->job->configuration['selected-country']) { + return 'import.spectre.select-country'; + } + if (!$this->job->configuration['selected-bank']) { + return 'import.spectre.select-bank'; + } + + throw new FireflyException('No view for state'); + } + + /** + * Return possible warning to user. + * + * @return string + */ + public function getWarningMessage(): string + { + return $this->warning; + } + + /** + * @return bool + */ + public function isJobConfigured(): bool + { + $config = $this->job->configuration; + $config['selected-country'] = $config['selected-country'] ?? false; + $config['selected-bank'] = $config['selected-bank'] ?? false; + $this->job->configuration = $config; + $this->job->save(); + + if ($config['selected-country'] && $config['selected-bank'] && false) { + return true; + } + + return false; + } + + /** + * @param ImportJob $job + */ + public function setJob(ImportJob $job) + { + $this->job = $job; + if (null === $this->job->configuration || 0 === count($this->job->configuration)) { + Log::debug(sprintf('Gave import job %s initial configuration.', $this->job->key)); + $this->job->configuration = [ + 'selected-country' => false, + ]; + $this->job->save(); + } + } + + /** + * @return string + * + * @throws FireflyException + */ + private function getConfigurationClass(): string + { + $class = false; + switch (true) { + case !$this->job->configuration['selected-country']: + $class = SelectCountry::class; + break; + case !$this->job->configuration['selected-bank']: + $class = SelectBank::class; + break; + default: + break; + } + + if (false === $class || 0 === strlen($class)) { + throw new FireflyException('Cannot handle current job state in getConfigurationClass().'); + } + if (!class_exists($class)) { + throw new FireflyException(sprintf('Class %s does not exist in getConfigurationClass().', $class)); + } + + return $class; + } +} diff --git a/app/Support/Import/Configuration/Spectre/SelectBank.php b/app/Support/Import/Configuration/Spectre/SelectBank.php new file mode 100644 index 0000000000..1a9622f710 --- /dev/null +++ b/app/Support/Import/Configuration/Spectre/SelectBank.php @@ -0,0 +1,76 @@ +job->configuration; + $selection = SpectreProvider::where('country_code', $config['country'])->where('status', 'active')->get(); + $providers = []; + /** @var SpectreProvider $provider */ + foreach ($selection as $provider) { + $providerId = $provider->spectre_id; + $name = $provider->data['name']; + $providers[$providerId] = $name; + } + + return compact('providers'); + } + + /** + * Return possible warning to user. + * + * @return string + */ + public function getWarningMessage(): string + { + return ''; + } + + /** + * @param ImportJob $job + * + * @return ConfigurationInterface + */ + public function setJob(ImportJob $job) + { + $this->job = $job; + } + + /** + * Store the result. + * + * @param array $data + * + * @return bool + */ + public function storeConfiguration(array $data): bool + { + $config = $this->job->configuration; + $config['bank'] = intval($data['bank_code']) ?? 0; // default to fake country. + $config['selected-bank'] = true; + $this->job->configuration = $config; + $this->job->save(); + + return true; + } +} \ No newline at end of file diff --git a/app/Support/Import/Configuration/Spectre/SelectCountry.php b/app/Support/Import/Configuration/Spectre/SelectCountry.php new file mode 100644 index 0000000000..de49d9545d --- /dev/null +++ b/app/Support/Import/Configuration/Spectre/SelectCountry.php @@ -0,0 +1,329 @@ + 'Afghanistan', + 'AX' => 'Aland Islands', + 'AL' => 'Albania', + 'DZ' => 'Algeria', + 'AS' => 'American Samoa', + 'AD' => 'Andorra', + 'AO' => 'Angola', + 'AI' => 'Anguilla', + 'AQ' => 'Antarctica', + 'AG' => 'Antigua and Barbuda', + 'AR' => 'Argentina', + 'AM' => 'Armenia', + 'AW' => 'Aruba', + 'AU' => 'Australia', + 'AT' => 'Austria', + 'AZ' => 'Azerbaijan', + 'BS' => 'Bahamas', + 'BH' => 'Bahrain', + 'BD' => 'Bangladesh', + 'BB' => 'Barbados', + 'BY' => 'Belarus', + 'BE' => 'Belgium', + 'BZ' => 'Belize', + 'BJ' => 'Benin', + 'BM' => 'Bermuda', + 'BT' => 'Bhutan', + 'BO' => 'Bolivia', + 'BQ' => 'Bonaire, Saint Eustatius and Saba', + 'BA' => 'Bosnia and Herzegovina', + 'BW' => 'Botswana', + 'BV' => 'Bouvet Island', + 'BR' => 'Brazil', + 'IO' => 'British Indian Ocean Territory', + 'VG' => 'British Virgin Islands', + 'BN' => 'Brunei', + 'BG' => 'Bulgaria', + 'BF' => 'Burkina Faso', + 'BI' => 'Burundi', + 'KH' => 'Cambodia', + 'CM' => 'Cameroon', + 'CA' => 'Canada', + 'CV' => 'Cape Verde', + 'KY' => 'Cayman Islands', + 'CF' => 'Central African Republic', + 'TD' => 'Chad', + 'CL' => 'Chile', + 'CN' => 'China', + 'CX' => 'Christmas Island', + 'CC' => 'Cocos Islands', + 'CO' => 'Colombia', + 'KM' => 'Comoros', + 'CK' => 'Cook Islands', + 'CR' => 'Costa Rica', + 'HR' => 'Croatia', + 'CU' => 'Cuba', + 'CW' => 'Curacao', + 'CY' => 'Cyprus', + 'CZ' => 'Czech Republic', + 'CD' => 'Democratic Republic of the Congo', + 'DK' => 'Denmark', + 'DJ' => 'Djibouti', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'TL' => 'East Timor', + 'EC' => 'Ecuador', + 'EG' => 'Egypt', + 'SV' => 'El Salvador', + 'GQ' => 'Equatorial Guinea', + 'ER' => 'Eritrea', + 'EE' => 'Estonia', + 'ET' => 'Ethiopia', + 'FK' => 'Falkland Islands', + 'FO' => 'Faroe Islands', + 'FJ' => 'Fiji', + 'FI' => 'Finland', + 'FR' => 'France', + 'GF' => 'French Guiana', + 'PF' => 'French Polynesia', + 'TF' => 'French Southern Territories', + 'GA' => 'Gabon', + 'GM' => 'Gambia', + 'GE' => 'Georgia', + 'DE' => 'Germany', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GR' => 'Greece', + 'GL' => 'Greenland', + 'GD' => 'Grenada', + 'GP' => 'Guadeloupe', + 'GU' => 'Guam', + 'GT' => 'Guatemala', + 'GG' => 'Guernsey', + 'GN' => 'Guinea', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HT' => 'Haiti', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HK' => 'Hong Kong', + 'HU' => 'Hungary', + 'IS' => 'Iceland', + 'IN' => 'India', + 'ID' => 'Indonesia', + 'IR' => 'Iran', + 'IQ' => 'Iraq', + 'IE' => 'Ireland', + 'IM' => 'Isle of Man', + 'IL' => 'Israel', + 'IT' => 'Italy', + 'CI' => 'Ivory Coast', + 'JM' => 'Jamaica', + 'JP' => 'Japan', + 'JE' => 'Jersey', + 'JO' => 'Jordan', + 'KZ' => 'Kazakhstan', + 'KE' => 'Kenya', + 'KI' => 'Kiribati', + 'XK' => 'Kosovo', + 'KW' => 'Kuwait', + 'KG' => 'Kyrgyzstan', + 'LA' => 'Laos', + 'LV' => 'Latvia', + 'LB' => 'Lebanon', + 'LS' => 'Lesotho', + 'LR' => 'Liberia', + 'LY' => 'Libya', + 'LI' => 'Liechtenstein', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'MO' => 'Macao', + 'MK' => 'Macedonia', + 'MG' => 'Madagascar', + 'MW' => 'Malawi', + 'MY' => 'Malaysia', + 'MV' => 'Maldives', + 'ML' => 'Mali', + 'MT' => 'Malta', + 'MH' => 'Marshall Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MU' => 'Mauritius', + 'YT' => 'Mayotte', + 'MX' => 'Mexico', + 'FM' => 'Micronesia', + 'MD' => 'Moldova', + 'MC' => 'Monaco', + 'MN' => 'Mongolia', + 'ME' => 'Montenegro', + 'MS' => 'Montserrat', + 'MA' => 'Morocco', + 'MZ' => 'Mozambique', + 'MM' => 'Myanmar', + 'NA' => 'Namibia', + 'NR' => 'Nauru', + 'NP' => 'Nepal', + 'NL' => 'Netherlands', + 'NC' => 'New Caledonia', + 'NZ' => 'New Zealand', + 'NI' => 'Nicaragua', + 'NE' => 'Niger', + 'NG' => 'Nigeria', + 'NU' => 'Niue', + 'NF' => 'Norfolk Island', + 'KP' => 'North Korea', + 'MP' => 'Northern Mariana Islands', + 'NO' => 'Norway', + 'OM' => 'Oman', + 'PK' => 'Pakistan', + 'PW' => 'Palau', + 'PS' => 'Palestinian Territory', + 'PA' => 'Panama', + 'PG' => 'Papua New Guinea', + 'PY' => 'Paraguay', + 'PE' => 'Peru', + 'PH' => 'Philippines', + 'PN' => 'Pitcairn', + 'PL' => 'Poland', + 'PT' => 'Portugal', + 'PR' => 'Puerto Rico', + 'QA' => 'Qatar', + 'CG' => 'Republic of the Congo', + 'RE' => 'Reunion', + 'RO' => 'Romania', + 'RU' => 'Russia', + 'RW' => 'Rwanda', + 'BL' => 'Saint Barthelemy', + 'SH' => 'Saint Helena', + 'KN' => 'Saint Kitts and Nevis', + 'LC' => 'Saint Lucia', + 'MF' => 'Saint Martin', + 'PM' => 'Saint Pierre and Miquelon', + 'VC' => 'Saint Vincent and the Grenadines', + 'WS' => 'Samoa', + 'SM' => 'San Marino', + 'ST' => 'Sao Tome and Principe', + 'SA' => 'Saudi Arabia', + 'SN' => 'Senegal', + 'RS' => 'Serbia', + 'SC' => 'Seychelles', + 'SL' => 'Sierra Leone', + 'SG' => 'Singapore', + 'SX' => 'Sint Maarten', + 'SK' => 'Slovakia', + 'SI' => 'Slovenia', + 'SB' => 'Solomon Islands', + 'SO' => 'Somalia', + 'ZA' => 'South Africa', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'KR' => 'South Korea', + 'SS' => 'South Sudan', + 'ES' => 'Spain', + 'LK' => 'Sri Lanka', + 'SD' => 'Sudan', + 'SR' => 'Suriname', + 'SJ' => 'Svalbard and Jan Mayen', + 'SZ' => 'Swaziland', + 'SE' => 'Sweden', + 'CH' => 'Switzerland', + 'SY' => 'Syria', + 'TW' => 'Taiwan', + 'TJ' => 'Tajikistan', + 'TZ' => 'Tanzania', + 'TH' => 'Thailand', + 'TG' => 'Togo', + 'TK' => 'Tokelau', + 'TO' => 'Tonga', + 'TT' => 'Trinidad and Tobago', + 'TN' => 'Tunisia', + 'TR' => 'Turkey', + 'TM' => 'Turkmenistan', + 'TC' => 'Turks and Caicos Islands', + 'TV' => 'Tuvalu', + 'VI' => 'U.S. Virgin Islands', + 'UG' => 'Uganda', + 'UA' => 'Ukraine', + 'AE' => 'United Arab Emirates', + 'GB' => 'United Kingdom', + 'US' => 'United States', + 'UM' => 'United States Minor Outlying Islands', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VU' => 'Vanuatu', + 'VA' => 'Vatican', + 'VE' => 'Venezuela', + 'VN' => 'Vietnam', + 'WF' => 'Wallis and Futuna', + 'EH' => 'Western Sahara', + 'YE' => 'Yemen', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', + 'XF' => 'Fake Country (for testing)', + 'XO' => 'Other financial applications', + ]; + /** @var ImportJob */ + private $job; + + /** + * Get the data necessary to show the configuration screen. + * + * @return array + */ + public function getData(): array + { + $providers = SpectreProvider::get(); + $countries = []; + /** @var SpectreProvider $provider */ + foreach ($providers as $provider) { + $countries[$provider->country_code] = $this->allCountries[$provider->country_code] ?? $provider->country_code; + } + asort($countries); + + return compact('countries'); + } + + /** + * Return possible warning to user. + * + * @return string + */ + public function getWarningMessage(): string + { + return ''; + } + + /** + * @param ImportJob $job + * + * @return ConfigurationInterface + */ + public function setJob(ImportJob $job) + { + $this->job = $job; + } + + /** + * Store the result. + * + * @param array $data + * + * @return bool + */ + public function storeConfiguration(array $data): bool + { + $config = $this->job->configuration; + $config['country'] = $data['country_code'] ?? 'XF'; // default to fake country. + $config['selected-country'] = true; + $this->job->configuration = $config; + $this->job->save(); + + return true; + } +} \ No newline at end of file diff --git a/app/Support/Import/Information/SpectreInformation.php b/app/Support/Import/Information/SpectreInformation.php new file mode 100644 index 0000000000..04a3037c3b --- /dev/null +++ b/app/Support/Import/Information/SpectreInformation.php @@ -0,0 +1,207 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Support\Import\Information; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Services\Bunq\Object\Alias; +use FireflyIII\Services\Bunq\Object\MonetaryAccountBank; +use FireflyIII\Services\Bunq\Request\DeleteDeviceSessionRequest; +use FireflyIII\Services\Bunq\Request\DeviceSessionRequest; +use FireflyIII\Services\Bunq\Request\ListMonetaryAccountRequest; +use FireflyIII\Services\Bunq\Request\ListUserRequest; +use FireflyIII\Services\Bunq\Token\SessionToken; +use FireflyIII\Support\CacheProperties; +use FireflyIII\User; +use Illuminate\Support\Collection; +use Log; +use Preferences; + +/** + * Class SpectreInformation + */ +class SpectreInformation implements InformationInterface +{ + /** @var User */ + private $user; + + /** + * Returns a collection of accounts. Preferrably, these follow a uniform Firefly III format so they can be managed over banks. + * + * The format for these bank accounts is basically this: + * + * id: bank specific id + * name: bank appointed name + * number: account number (usually IBAN) + * currency: ISO code of currency + * balance: current balance + * + * + * any other fields are optional but can be useful: + * image: logo or account specific thing + * color: any associated color. + * + * @return array + */ + public function getAccounts(): array + { + // cache for an hour: + $cache = new CacheProperties; + $cache->addProperty('bunq.get-accounts'); + $cache->addProperty(date('dmy h')); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + Log::debug('Now in getAccounts()'); + $sessionToken = $this->startSession(); + $userId = $this->getUserInformation($sessionToken); + // get list of Bunq accounts: + $accounts = $this->getMonetaryAccounts($sessionToken, $userId); + $return = []; + /** @var MonetaryAccountBank $account */ + foreach ($accounts as $account) { + $current = [ + 'id' => $account->getId(), + 'name' => $account->getDescription(), + 'currency' => $account->getCurrency(), + 'balance' => $account->getBalance()->getValue(), + 'color' => $account->getSetting()->getColor(), + ]; + /** @var Alias $alias */ + foreach ($account->getAliases() as $alias) { + if ('IBAN' === $alias->getType()) { + $current['number'] = $alias->getValue(); + } + } + $return[] = $current; + } + $cache->store($return); + + $this->closeSession($sessionToken); + + return $return; + } + + /** + * Set the user for this Prerequisites-routine. Class is expected to implement and save this. + * + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + } + + /** + * @param SessionToken $sessionToken + */ + private function closeSession(SessionToken $sessionToken): void + { + Log::debug('Going to close session'); + $apiKey = Preferences::getForUser($this->user, 'bunq_api_key')->data; + $serverPublicKey = Preferences::getForUser($this->user, 'bunq_server_public_key')->data; + $privateKey = Preferences::getForUser($this->user, 'bunq_private_key')->data; + $request = new DeleteDeviceSessionRequest(); + $request->setSecret($apiKey); + $request->setPrivateKey($privateKey); + $request->setServerPublicKey($serverPublicKey); + $request->setSessionToken($sessionToken); + $request->call(); + + return; + } + + /** + * @param SessionToken $sessionToken + * @param int $userId + * + * @return Collection + */ + private function getMonetaryAccounts(SessionToken $sessionToken, int $userId): Collection + { + $apiKey = Preferences::getForUser($this->user, 'bunq_api_key')->data; + $serverPublicKey = Preferences::getForUser($this->user, 'bunq_server_public_key')->data; + $privateKey = Preferences::getForUser($this->user, 'bunq_private_key')->data; + $request = new ListMonetaryAccountRequest; + + $request->setSessionToken($sessionToken); + $request->setSecret($apiKey); + $request->setServerPublicKey($serverPublicKey); + $request->setPrivateKey($privateKey); + $request->setUserId($userId); + $request->call(); + + return $request->getMonetaryAccounts(); + } + + /** + * @param SessionToken $sessionToken + * + * @return int + * + * @throws FireflyException + */ + private function getUserInformation(SessionToken $sessionToken): int + { + $apiKey = Preferences::getForUser($this->user, 'bunq_api_key')->data; + $serverPublicKey = Preferences::getForUser($this->user, 'bunq_server_public_key')->data; + $privateKey = Preferences::getForUser($this->user, 'bunq_private_key')->data; + $request = new ListUserRequest; + $request->setSessionToken($sessionToken); + $request->setSecret($apiKey); + $request->setServerPublicKey($serverPublicKey); + $request->setPrivateKey($privateKey); + $request->call(); + // return the first that isn't null? + $company = $request->getUserCompany(); + if ($company->getId() > 0) { + return $company->getId(); + } + $user = $request->getUserPerson(); + if ($user->getId() > 0) { + return $user->getId(); + } + throw new FireflyException('Expected user or company from Bunq, but got neither.'); + } + + /** + * @return SessionToken + */ + private function startSession(): SessionToken + { + Log::debug('Now in startSession.'); + $apiKey = Preferences::getForUser($this->user, 'bunq_api_key')->data; + $serverPublicKey = Preferences::getForUser($this->user, 'bunq_server_public_key')->data; + $privateKey = Preferences::getForUser($this->user, 'bunq_private_key')->data; + $installationToken = Preferences::getForUser($this->user, 'bunq_installation_token')->data; + $request = new DeviceSessionRequest(); + $request->setSecret($apiKey); + $request->setServerPublicKey($serverPublicKey); + $request->setPrivateKey($privateKey); + $request->setInstallationToken($installationToken); + $request->call(); + $sessionToken = $request->getSessionToken(); + Log::debug(sprintf('Now have got session token: %s', serialize($sessionToken))); + + return $sessionToken; + } +} diff --git a/app/Support/Import/Prerequisites/SpectrePrerequisites.php b/app/Support/Import/Prerequisites/SpectrePrerequisites.php index a55c0f87b6..d67960b139 100644 --- a/app/Support/Import/Prerequisites/SpectrePrerequisites.php +++ b/app/Support/Import/Prerequisites/SpectrePrerequisites.php @@ -1,6 +1,6 @@ user, 'spectre_private_key', null); - if (null === $preference) { - Log::debug('private key is null'); - // create key pair - $this->createKeyPair(); - } - $preference = Preferences::getForUser($this->user, 'spectre_private_key', null); - Log::debug('Return private key for user'); - - return $preference->data; - } - /** * Get a public key from the users preferences. * @@ -197,33 +173,4 @@ class SpectrePrerequisites implements PrerequisitesInterface return $preference->data; } - - /** - * Request users server remote IP. Let's assume this value will not change any time soon. - * - * @return string - * - * @throws FireflyException - */ - private function getRemoteIp(): string - { - $preference = Preferences::getForUser($this->user, 'external_ip', null); - if (null === $preference) { - try { - $response = Requests::get('https://api.ipify.org'); - } catch (Requests_Exception $e) { - throw new FireflyException(sprintf('Could not retrieve external IP: %s', $e->getMessage())); - } - if (200 !== $response->status_code) { - throw new FireflyException(sprintf('Could not retrieve external IP: %d %s', $response->status_code, $response->body)); - } - $serverIp = $response->body; - Preferences::setForUser($this->user, 'external_ip', $serverIp); - - return $serverIp; - } - - return $preference->data; - } - } diff --git a/config/firefly.php b/config/firefly.php index b4219692d4..f0b20f60ff 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -41,10 +41,12 @@ return [ 'csv' => 'FireflyIII\Export\Exporter\CsvExporter', ], 'import_formats' => [ - 'csv' => 'FireflyIII\Import\Configurator\CsvConfigurator', + 'csv' => 'FireflyIII\Import\Configurator\CsvConfigurator', + 'spectre' => '', ], 'import_configurators' => [ 'csv' => 'FireflyIII\Import\Configurator\CsvConfigurator', + 'spectre' => 'FireflyIII\Import\Configurator\SpectreConfigurator', ], 'import_processors' => [ 'csv' => 'FireflyIII\Import\FileProcessor\CsvProcessor', diff --git a/resources/lang/en_US/bank.php b/resources/lang/en_US/bank.php index 369f72e145..1c970e1e7e 100644 --- a/resources/lang/en_US/bank.php +++ b/resources/lang/en_US/bank.php @@ -3,12 +3,14 @@ declare(strict_types=1); return [ - 'bunq_prerequisites_title' => 'Prerequisites for an import from bunq', - 'bunq_prerequisites_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app.', + 'bunq_prerequisites_title' => 'Prerequisites for an import from bunq', + 'bunq_prerequisites_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app.', // Spectre: - 'spectre_title' => 'Import using Spectre', - 'spectre_prerequisites_title' => 'Prerequisites for an import using Spectre', - 'spectre_prerequisites_text' => 'In order to import data using the Spectre API, you need to prove some secrets. They can be found on the secrets page.', - 'spectre_enter_pub_key' => 'The import will only work when you enter this public key on your security page.', + 'spectre_title' => 'Import using Spectre', + 'spectre_prerequisites_title' => 'Prerequisites for an import using Spectre', + 'spectre_prerequisites_text' => 'In order to import data using the Spectre API, you need to prove some secrets. They can be found on the secrets page.', + 'spectre_enter_pub_key' => 'The import will only work when you enter this public key on your security page.', + 'spectre_select_country_title' => 'Select a country', + 'spectre_select_country_text' => 'Firefly III has a large selection of banks and sites from which Spectre can download transactional data. These banks are sorted by country. Please not that there is a "Fake Country" for when you wish to test something. If you wish to import from other financial tools, please use the imaginary country called "Other financial applications". By default, Spectre only allows you to download data from fake banks. Make sure your status is "Live" on your Dashboard if you wish to download from real banks.', ]; diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index 9e3af6052c..bb3f1fa635 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -190,6 +190,7 @@ return [ 'service_secret' => 'Service secret', 'app_secret' => 'App secret', 'public_key' => 'Public key', + 'country_code' => 'Country code', 'due_date' => 'Due date', diff --git a/resources/views/import/spectre/select-bank.twig b/resources/views/import/spectre/select-bank.twig new file mode 100644 index 0000000000..af0a8d56f4 --- /dev/null +++ b/resources/views/import/spectre/select-bank.twig @@ -0,0 +1,42 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.renderIfExists }} +{% endblock %} +{% block content %} +
+
+ +
+
+
+

{{ trans('bank.spectre_select_bank_title') }}

+
+
+
+
+

+ {{ trans('bank.spectre_select_bank_title') }} +

+
+
+ +
+
+ {{ ExpandedForm.select('bank_code', data.providers, null)|raw }} +
+
+ +
+
+ +
+{% endblock %} +{% block scripts %} +{% endblock %} +{% block styles %} +{% endblock %} diff --git a/resources/views/import/spectre/select-country.twig b/resources/views/import/spectre/select-country.twig new file mode 100644 index 0000000000..5c4f36a199 --- /dev/null +++ b/resources/views/import/spectre/select-country.twig @@ -0,0 +1,42 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.renderIfExists }} +{% endblock %} +{% block content %} +
+
+ +
+
+
+

{{ trans('bank.spectre_select_country_title') }}

+
+
+
+
+

+ {{ trans('bank.spectre_select_country_text') }} +

+
+
+ +
+
+ {{ ExpandedForm.select('country_code', data.countries, null)|raw }} +
+
+ +
+
+ +
+{% endblock %} +{% block scripts %} +{% endblock %} +{% block styles %} +{% endblock %} diff --git a/routes/web.php b/routes/web.php index 86b29cb942..ff61ef929a 100755 --- a/routes/web.php +++ b/routes/web.php @@ -420,8 +420,9 @@ Route::group( Route::get('bank/{bank}/prerequisites', ['uses' => 'Import\BankController@prerequisites', 'as' => 'bank.prerequisites']); Route::post('bank/{bank}/prerequisites', ['uses' => 'Import\BankController@postPrerequisites', 'as' => 'bank.prerequisites.post']); - Route::get('bank/{bank}/form', ['uses' => 'Import\BankController@form', 'as' => 'bank.form']); - Route::post('bank/{bank}/form', ['uses' => 'Import\BankController@postForm', 'as' => 'bank.form.post']); + Route::get('bank/{bank}/create', ['uses' => 'Import\BankController@createJob', 'as' => 'bank.create-job']); + Route:: get('bank/{bank}/configure/{importJob}', ['uses' => 'Import\BankController@configure', 'as' => 'bank.configure']); + Route::post('bank/{bank}/configure/{importJob}', ['uses' => 'Import\BankController@postConfigure', 'as' => 'bank.configure.post']); } );