diff --git a/app/Http/Controllers/Import/PrerequisitesController.php b/app/Http/Controllers/Import/PrerequisitesController.php index 42a7b3d243..6dc0827962 100644 --- a/app/Http/Controllers/Import/PrerequisitesController.php +++ b/app/Http/Controllers/Import/PrerequisitesController.php @@ -128,11 +128,12 @@ class PrerequisitesController extends Controller Log::debug('Going to store entered prerequisites.'); // store post data $result = $object->storePrerequisites($request); + Log::debug(sprintf('Result of storePrerequisites has message count: %d', $result->count())); if ($result->count() > 0) { $request->session()->flash('error', $result->first()); } - return redirect(route('import.prerequisites', [$bank])); + return redirect(route('import.index')); } } diff --git a/app/Import/Configuration/BunqConfigurator.php b/app/Import/Configuration/BunqConfigurator.php new file mode 100644 index 0000000000..cd49747090 --- /dev/null +++ b/app/Import/Configuration/BunqConfigurator.php @@ -0,0 +1,247 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Import\Configuration; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use FireflyIII\Support\Import\Configuration\Spectre\HaveAccounts; +use Log; + +/** + * Class BunqConfigurator. + */ +class BunqConfigurator implements ConfiguratorInterface +{ + /** @var ImportJob */ + private $job; + + /** @var ImportJobRepositoryInterface */ + private $repository; + + /** @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 + { + if (is_null($this->job)) { + throw new FireflyException('Cannot call configureJob() without a job.'); + } + $stage = $this->getConfig()['stage'] ?? 'initial'; + Log::debug(sprintf('in getNextData(), for stage "%s".', $stage)); + + throw new FireflyException('configureJob: Will try to handle stage ' . $stage); + + switch ($stage) { + case 'have-accounts': + /** @var HaveAccounts $class */ + $class = app(HaveAccounts::class); + $class->setJob($this->job); + $class->storeConfiguration($data); + + // update job for next step and set to "configured". + $config = $this->getConfig(); + $config['stage'] = 'have-account-mapping'; + $this->repository->setConfiguration($this->job, $config); + + return true; + default: + throw new FireflyException(sprintf('Cannot store configuration when job is in state "%s"', $stage)); + break; + } + } + + /** + * Return the data required for the next step in the job configuration. + * + * @return array + * @throws FireflyException + */ + public function getNextData(): array + { + if (is_null($this->job)) { + throw new FireflyException('Cannot call configureJob() without a job.'); + } + $config = $this->getConfig(); + $stage = $config['stage'] ?? 'initial'; + + Log::debug(sprintf('in getNextData(), for stage "%s".', $stage)); + + throw new FireflyException('getNextData: Will try to handle stage ' . $stage); + + switch ($stage) { + case 'has-token': + // simply redirect to Spectre. + $config['is-redirected'] = true; + $config['stage'] = 'user-logged-in'; + $status = 'configured'; + + // update config and status: + $this->repository->setConfiguration($this->job, $config); + $this->repository->setStatus($this->job, $status); + + return $this->repository->getConfiguration($this->job); + case 'have-accounts': + /** @var HaveAccounts $class */ + $class = app(HaveAccounts::class); + $class->setJob($this->job); + $data = $class->getData(); + + return $data; + default: + return []; + } + } + + /** + * @return string + * @throws FireflyException + */ + public function getNextView(): string + { + if (is_null($this->job)) { + throw new FireflyException('Cannot call configureJob() without a job.'); + } + $stage = $this->getConfig()['stage'] ?? 'initial'; + + throw new FireflyException('Will try to handle stage ' . $stage); + + Log::debug(sprintf('getNextView: in getNextView(), for stage "%s".', $stage)); + switch ($stage) { + case 'has-token': + // redirect to Spectre. + Log::info('User is being redirected to Spectre.'); + + return 'import.spectre.redirect'; + case 'have-accounts': + return 'import.spectre.accounts'; + default: + return ''; + + } + } + + /** + * Return possible warning to user. + * + * @return string + */ + public function getWarningMessage(): string + { + return $this->warning; + } + + /** + * @return bool + * @throws FireflyException + */ + public function isJobConfigured(): bool + { + if (is_null($this->job)) { + throw new FireflyException('Cannot call configureJob() without a job.'); + } + $stage = $this->getConfig()['stage'] ?? 'initial'; + + Log::debug(sprintf('in isJobConfigured(), for stage "%s".', $stage)); + switch ($stage) { + case 'has-token': + case 'have-accounts': + Log::debug('isJobConfigured returns false'); + + return false; + case 'initial': + Log::debug('isJobConfigured returns true'); + return true; + break; + default: + Log::debug('isJobConfigured returns true'); + + return true; + } + } + + /** + * @param ImportJob $job + */ + public function setJob(ImportJob $job): void + { + // make repository + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($job->user); + + // set default config: + $defaultConfig = [ + // 'has-token' => false, + // 'token' => '', + // 'token-expires' => 0, + // 'token-url' => '', + 'is-redirected' => false, + // 'customer' => null, + // 'login' => null, + 'stage' => 'initial', + // 'accounts' => '', + // 'accounts-mapped' => '', + 'auto-start' => true, + 'apply-rules' => true, + 'match-bills' => false, + ]; + $currentConfig = $this->repository->getConfiguration($job); + $finalConfig = array_merge($defaultConfig, $currentConfig); + + // set default extended status: + $extendedStatus = $this->repository->getExtendedStatus($job); + $extendedStatus['steps'] = 6; + + // save to job: + $job = $this->repository->setConfiguration($job, $finalConfig); + $job = $this->repository->setExtendedStatus($job, $extendedStatus); + $this->job = $job; + + return; + } + + /** + * Shorthand method. + * + * @return array + */ + private function getConfig(): array + { + return $this->repository->getConfiguration($this->job); + } +} diff --git a/app/Import/Prerequisites/BunqPrerequisites.php b/app/Import/Prerequisites/BunqPrerequisites.php index a82345c8db..5c7dc15e59 100644 --- a/app/Import/Prerequisites/BunqPrerequisites.php +++ b/app/Import/Prerequisites/BunqPrerequisites.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Import\Prerequisites; +use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Services\Bunq\Id\DeviceServerId; use FireflyIII\Services\Bunq\Object\DeviceServer; @@ -53,6 +54,8 @@ class BunqPrerequisites implements PrerequisitesInterface */ public function getView(): string { + Log::debug('Now in BunqPrerequisites::getView()'); + return 'import.bunq.prerequisites'; } @@ -63,7 +66,14 @@ class BunqPrerequisites implements PrerequisitesInterface */ public function getViewParameters(): array { - return []; + Log::debug('Now in BunqPrerequisites::getViewParameters()'); + $apiKey = Preferences::getForUser($this->user, 'bunq_api_key', null); + $string = ''; + if (!is_null($apiKey)) { + $string = $apiKey->data; + } + + return ['key' => $string]; } /** @@ -75,9 +85,17 @@ class BunqPrerequisites implements PrerequisitesInterface */ public function hasPrerequisites(): bool { - $apiKey = Preferences::getForUser($this->user, 'bunq_api_key', false); + Log::debug('Now in BunqPrerequisites::hasPrerequisites()'); + $apiKey = Preferences::getForUser($this->user, 'bunq_api_key', false); + $deviceId = Preferences::getForUser($this->user, 'bunq_device_server_id', null); + $result = (false === $apiKey->data || null === $apiKey->data) || is_null($deviceId); - return false === $apiKey->data || null === $apiKey->data; + Log::debug(sprintf('Is device ID NULL? %s', var_export(null === $deviceId, true))); + Log::debug(sprintf('Is apiKey->data false? %s', var_export(false === $apiKey->data, true))); + Log::debug(sprintf('Is apiKey->data NULL? %s', var_export(null === $apiKey->data, true))); + Log::debug(sprintf('Result is: %s', var_export($result, true))); + + return $result; } /** @@ -87,6 +105,7 @@ class BunqPrerequisites implements PrerequisitesInterface */ public function setUser(User $user): void { + Log::debug(sprintf('Now in setUser(#%d)', $user->id)); $this->user = $user; return; @@ -109,9 +128,11 @@ class BunqPrerequisites implements PrerequisitesInterface $serverId = null; $messages = new MessageBag; try { + Log::debug('Going to try and get the device registered.'); $serverId = $this->registerDevice(); Log::debug(sprintf('Found device server with id %d', $serverId->getId())); } catch (FireflyException $e) { + Log::error(sprintf('Could not register device because: %s: %s', $e->getMessage(), $e->getTraceAsString())); $messages->add('error', $e->getMessage()); } @@ -125,6 +146,16 @@ class BunqPrerequisites implements PrerequisitesInterface */ private function createKeyPair(): void { + Log::debug('Now in createKeyPair()'); + $private = Preferences::getForUser($this->user, 'bunq_private_key', null); + $public = Preferences::getForUser($this->user, 'bunq_public_key', null); + + if (!(null === $private && null === $public)) { + Log::info('Already have public and private key, return NULL.'); + + return; + } + Log::debug('Generate new key pair for user.'); $keyConfig = [ 'digest_alg' => 'sha512', @@ -143,7 +174,7 @@ class BunqPrerequisites implements PrerequisitesInterface Preferences::setForUser($this->user, 'bunq_private_key', $privKey); Preferences::setForUser($this->user, 'bunq_public_key', $pubKey['key']); - Log::debug('Created key pair'); + Log::debug('Created and stored key pair'); return; } @@ -155,10 +186,10 @@ class BunqPrerequisites implements PrerequisitesInterface * @return DeviceServerId * * @throws FireflyException - * @throws \Exception */ private function getExistingDevice(): DeviceServerId { + Log::debug('Now in getExistingDevice()'); $installationToken = $this->getInstallationToken(); $serverPublicKey = $this->getServerPublicKey(); $request = new ListDeviceServerRequest; @@ -181,22 +212,25 @@ class BunqPrerequisites implements PrerequisitesInterface * Get the installation token, either from the users preferences or from Bunq. * * @return InstallationToken + * @throws FireflyException */ private function getInstallationToken(): InstallationToken { - Log::debug('Get installation token.'); + Log::debug('Now in getInstallationToken().'); $token = Preferences::getForUser($this->user, 'bunq_installation_token', null); if (null !== $token) { + Log::debug('Have installation token, return it.'); + return $token->data; } - Log::debug('Have no token, request one.'); + Log::debug('Have no installation token, request one.'); // verify bunq api code: $publicKey = $this->getPublicKey(); $request = new InstallationTokenRequest; $request->setPublicKey($publicKey); $request->call(); - Log::debug('Sent request'); + Log::debug('Sent request for installation token.'); $installationToken = $request->getInstallationToken(); $installationId = $request->getInstallationId(); @@ -206,6 +240,8 @@ class BunqPrerequisites implements PrerequisitesInterface Preferences::setForUser($this->user, 'bunq_installation_id', $installationId); Preferences::setForUser($this->user, 'bunq_server_public_key', $serverPublicKey); + Log::debug('Stored token, ID and pub key.'); + return $installationToken; } @@ -236,10 +272,10 @@ class BunqPrerequisites implements PrerequisitesInterface */ private function getPublicKey(): string { - Log::debug('get public key'); + Log::debug('Now in getPublicKey()'); $preference = Preferences::getForUser($this->user, 'bunq_public_key', null); if (null === $preference) { - Log::debug('public key is null'); + Log::debug('public key is NULL.'); // create key pair $this->createKeyPair(); } @@ -262,7 +298,7 @@ class BunqPrerequisites implements PrerequisitesInterface if (null === $preference) { try { $response = Requests::get('https://api.ipify.org'); - } catch (Requests_Exception $e) { + } catch (Requests_Exception|Exception $e) { throw new FireflyException(sprintf('Could not retrieve external IP: %s', $e->getMessage())); } if (200 !== $response->status_code) { @@ -295,8 +331,6 @@ class BunqPrerequisites implements PrerequisitesInterface * - Use the installation token each time we need a session. * * @throws FireflyException - * @throws FireflyException - * @throws \Exception */ private function registerDevice(): DeviceServerId { @@ -308,10 +342,12 @@ class BunqPrerequisites implements PrerequisitesInterface return $deviceServerId->data; } - Log::debug('Device server id is null, do register.'); + Log::debug('Device server ID is null, we have to register.'); $installationToken = $this->getInstallationToken(); $serverPublicKey = $this->getServerPublicKey(); $apiKey = Preferences::getForUser($this->user, 'bunq_api_key', ''); + + Log::debug('Going to create new DeviceServerRequest()'); $request = new DeviceServerRequest; $request->setPrivateKey($this->getPrivateKey()); $request->setDescription('Firefly III v' . config('firefly.version') . ' for ' . $this->user->email); @@ -326,12 +362,13 @@ class BunqPrerequisites implements PrerequisitesInterface $deviceServerId = $request->getDeviceServerId(); } catch (FireflyException $e) { Log::error($e->getMessage()); + // we really have to quit at this point :( + throw new FireflyException($e->getMessage()); } if (null === $deviceServerId) { // try get the current from a list: $deviceServerId = $this->getExistingDevice(); } - Preferences::setForUser($this->user, 'bunq_device_server_id', $deviceServerId); Log::debug(sprintf('Server ID: %s', serialize($deviceServerId))); diff --git a/app/Import/Routine/BunqRoutine.php b/app/Import/Routine/BunqRoutine.php new file mode 100644 index 0000000000..5d7a106ae3 --- /dev/null +++ b/app/Import/Routine/BunqRoutine.php @@ -0,0 +1,226 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Import\Routine; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use Illuminate\Support\Collection; +use Log; + +/** + * Class BunqRoutine + */ +class BunqRoutine implements RoutineInterface +{ + /** @var Collection */ + public $errors; + /** @var Collection */ + public $journals; + /** @var int */ + public $lines = 0; + /** @var ImportJob */ + private $job; + + /** @var ImportJobRepositoryInterface */ + private $repository; + + /** + * ImportRoutine constructor. + */ + public function __construct() + { + $this->journals = new Collection; + $this->errors = new Collection; + } + + /** + * @return Collection + */ + public function getErrors(): Collection + { + return $this->errors; + } + + /** + * @return Collection + */ + public function getJournals(): Collection + { + return $this->journals; + } + + /** + * @return int + */ + public function getLines(): int + { + return $this->lines; + } + + /** + * + * @return bool + * @throws FireflyException + */ + public function run(): bool + { + Log::info(sprintf('Start with import job %s using Bunq.', $this->job->key)); + set_time_limit(0); + // check if job has token first! + $stage = $this->getConfig()['stage'] ?? 'unknown'; + + switch ($stage) { + case 'initial': + // get customer and token: + $this->runStageInitial(); + break; + case 'default': + throw new FireflyException(sprintf('No action for stage %s!', $stage)); + break; + // case 'has-token': + + // // import routine does nothing at this point: + // break; + // case 'user-logged-in': + // $this->runStageLoggedIn(); + // break; + // case 'have-account-mapping': + // $this->runStageHaveMapping(); + // break; + // default: + // throw new FireflyException(sprintf('Cannot handle stage %s', $stage)); + // } + // + // return true; + } + + return true; + } + + /** + * @param ImportJob $job + */ + public function setJob(ImportJob $job) + { + $this->job = $job; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($job->user); + } + + /** + * + */ + protected function runStageInitial() + { + $this->setStatus('running'); + + // get session server + + + + + die(' in run stage initial'); + } + + /** + * Shorthand method. + */ + private function addStep() + { + $this->repository->addStepsDone($this->job, 1); + } + + /** + * Shorthand + * + * @param int $steps + */ + private function addTotalSteps(int $steps) + { + $this->repository->addTotalSteps($this->job, $steps); + } + + /** + * @return array + */ + private function getConfig(): array + { + return $this->repository->getConfiguration($this->job); + } + + /** + * Shorthand method. + * + * @return array + */ + private function getExtendedStatus(): array + { + return $this->repository->getExtendedStatus($this->job); + } + + /** + * Shorthand method. + * + * @return string + */ + private function getStatus(): string + { + return $this->repository->getStatus($this->job); + } + + /** + * Shorthand. + * + * @param array $config + */ + private function setConfig(array $config): void + { + $this->repository->setConfiguration($this->job, $config); + + return; + } + + /** + * Shorthand method. + * + * @param array $extended + */ + private function setExtendedStatus(array $extended): void + { + $this->repository->setExtendedStatus($this->job, $extended); + + return; + } + + /** + * Shorthand. + * + * @param string $status + */ + private function setStatus(string $status): void + { + $this->repository->setStatus($this->job, $status); + } +} \ No newline at end of file diff --git a/app/Services/Bunq/Request/BunqRequest.php b/app/Services/Bunq/Request/BunqRequest.php index ef30bf91b9..2ab47e436b 100644 --- a/app/Services/Bunq/Request/BunqRequest.php +++ b/app/Services/Bunq/Request/BunqRequest.php @@ -50,13 +50,17 @@ abstract class BunqRequest 'x-bunq-client-response-id' => 'X-Bunq-Client-Response-Id', 'x-bunq-client-request-id' => 'X-Bunq-Client-Request-Id', ]; + /** @var string */ + private $version = 'v1'; /** * BunqRequest constructor. */ public function __construct() { - $this->server = config('firefly.bunq.server'); + $this->server = strval(config('import.options.bunq.server')); + $this->version = strval(config('import.options.bunq.version')); + Log::debug(sprintf('Created new BunqRequest with server "%s" and version "%s"', $this->server, $this->version)); } /** @@ -122,9 +126,11 @@ abstract class BunqRequest if ('get' === strtolower($method) || 'delete' === strtolower($method)) { $data = ''; } + $uri = sprintf('/%s/%s', $this->version, $uri); + $toSign = sprintf("%s %s\n", strtoupper($method), $uri); + + Log::debug(sprintf('Message to sign (without data): %s', $toSign)); - $uri = str_replace(['https://api.bunq.com', 'https://sandbox.public.api.bunq.com'], '', $uri); - $toSign = sprintf("%s %s\n", strtoupper($method), $uri); $headersToSign = ['Cache-Control', 'User-Agent']; ksort($headers); foreach ($headers as $name => $value) { @@ -135,6 +141,7 @@ abstract class BunqRequest $toSign .= "\n" . $data; $signature = ''; + openssl_sign($toSign, $signature, $this->privateKey, OPENSSL_ALGO_SHA256); $signature = base64_encode($signature); @@ -207,7 +214,7 @@ abstract class BunqRequest * * @return array * - * @throws Exception + * @throws FireflyException */ protected function sendSignedBunqDelete(string $uri, array $headers): array { @@ -215,9 +222,12 @@ abstract class BunqRequest throw new FireflyException('No bunq server defined'); } - $fullUri = $this->server . $uri; + $fullUri = $this->makeUri($uri); $signature = $this->generateSignature('delete', $uri, $headers, ''); $headers['X-Bunq-Client-Signature'] = $signature; + + Log::debug(sprintf('Going to send a signed bunq DELETE to %s', $fullUri)); + try { $response = Requests::delete($fullUri, $headers); } catch (Requests_Exception $e) { @@ -250,7 +260,7 @@ abstract class BunqRequest * * @return array * - * @throws Exception + * @throws FireflyException */ protected function sendSignedBunqGet(string $uri, array $data, array $headers): array { @@ -259,9 +269,12 @@ abstract class BunqRequest } $body = json_encode($data); - $fullUri = $this->server . $uri; + $fullUri = $this->makeUri($uri); $signature = $this->generateSignature('get', $uri, $headers, $body); $headers['X-Bunq-Client-Signature'] = $signature; + + Log::debug(sprintf('Going to send a signed bunq GET to %s', $fullUri)); + try { $response = Requests::get($fullUri, $headers); } catch (Requests_Exception $e) { @@ -292,21 +305,23 @@ abstract class BunqRequest * @param array $headers * * @return array - * - * @throws Exception + * @throws FireflyException */ protected function sendSignedBunqPost(string $uri, array $data, array $headers): array { $body = json_encode($data); - $fullUri = $this->server . $uri; + $fullUri = $this->makeUri($uri); $signature = $this->generateSignature('post', $uri, $headers, $body); $headers['X-Bunq-Client-Signature'] = $signature; + + Log::debug(sprintf('Going to send a signed bunq POST request to: %s', $fullUri)); + try { $response = Requests::post($fullUri, $headers, $body); - } catch (Requests_Exception $e) { + } catch (Requests_Exception|Exception $e) { return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()]]]; } - + Log::debug('Seems to have NO exceptions in Response'); $body = $response->body; $array = json_decode($body, true); $responseHeaders = $response->headers->getAll(); @@ -330,15 +345,17 @@ abstract class BunqRequest * @param array $headers * * @return array - * - * @throws Exception + * @throws FireflyException */ protected function sendUnsignedBunqDelete(string $uri, array $headers): array { - $fullUri = $this->server . $uri; + $fullUri = $this->makeUri($uri); + + Log::debug(sprintf('Going to send a UNsigned bunq DELETE to %s', $fullUri)); + try { $response = Requests::delete($fullUri, $headers); - } catch (Requests_Exception $e) { + } catch (Requests_Exception|Exception $e) { return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()]]]; } $body = $response->body; @@ -361,16 +378,18 @@ abstract class BunqRequest * @param array $headers * * @return array - * - * @throws Exception + * @throws FireflyException */ protected function sendUnsignedBunqPost(string $uri, array $data, array $headers): array { $body = json_encode($data); - $fullUri = $this->server . $uri; + $fullUri = $this->makeUri($uri); + + Log::debug(sprintf('Going to send an UNsigned bunq POST to: %s', $fullUri)); + try { $response = Requests::post($fullUri, $headers, $body); - } catch (Requests_Exception $e) { + } catch (Requests_Exception|Exception $e) { return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()]]]; } $body = $response->body; @@ -394,10 +413,14 @@ abstract class BunqRequest */ private function isErrorResponse(array $response): bool { + $key = key($response); if ('Error' === $key) { + Log::error('Response IS an error response!'); + return true; } + Log::debug('Response is not an error response'); return false; } @@ -417,10 +440,22 @@ abstract class BunqRequest return $string; } + /** + * Make full API URI + * + * @param string $uri + * + * @return string + */ + private function makeUri(string $uri): string + { + return 'https://' . $this->server . '/' . $this->version . '/' . $uri; + } + /** * @param array $response * - * @throws Exception + * @throws FireflyException */ private function throwResponseError(array $response) { @@ -439,8 +474,7 @@ abstract class BunqRequest * @param int $statusCode * * @return bool - * - * @throws Exception + * @throws FireflyException */ private function verifyServerSignature(string $body, array $headers, int $statusCode): bool { diff --git a/app/Services/Bunq/Request/DeleteDeviceSessionRequest.php b/app/Services/Bunq/Request/DeleteDeviceSessionRequest.php index 0351dc4d52..545b9e7814 100644 --- a/app/Services/Bunq/Request/DeleteDeviceSessionRequest.php +++ b/app/Services/Bunq/Request/DeleteDeviceSessionRequest.php @@ -39,7 +39,7 @@ class DeleteDeviceSessionRequest extends BunqRequest public function call(): void { Log::debug('Going to send bunq delete session request.'); - $uri = sprintf('/v1/session/%d', $this->sessionToken->getId()); + $uri = sprintf('session/%d', $this->sessionToken->getId()); $headers = $this->getDefaultHeaders(); $headers['X-Bunq-Client-Authentication'] = $this->sessionToken->getToken(); $this->sendSignedBunqDelete($uri, $headers); diff --git a/app/Services/Bunq/Request/DeviceServerRequest.php b/app/Services/Bunq/Request/DeviceServerRequest.php index e17cd8b42e..08cbb3219f 100644 --- a/app/Services/Bunq/Request/DeviceServerRequest.php +++ b/app/Services/Bunq/Request/DeviceServerRequest.php @@ -24,6 +24,7 @@ namespace FireflyIII\Services\Bunq\Request; use FireflyIII\Services\Bunq\Id\DeviceServerId; use FireflyIII\Services\Bunq\Token\InstallationToken; +use Log; /** * Class DeviceServerRequest. @@ -40,14 +41,21 @@ class DeviceServerRequest extends BunqRequest private $permittedIps = []; /** - * @throws \Exception + * @throws \FireflyIII\Exceptions\FireflyException */ public function call(): void { - $uri = '/v1/device-server'; - $data = ['description' => $this->description, 'secret' => $this->secret, 'permitted_ips' => $this->permittedIps]; + Log::debug('Now in DeviceServerRequest::call()'); + $uri = 'device-server'; + $data = ['description' => $this->description, 'secret' => $this->secret, 'permitted_ips' => $this->permittedIps]; + + Log::debug('Data we send along: ', $data); + $headers = $this->getDefaultHeaders(); $headers['X-Bunq-Client-Authentication'] = $this->installationToken->getToken(); + + Log::debug('Headers we send along: ', $headers); + $response = $this->sendSignedBunqPost($uri, $data, $headers); $deviceServerId = new DeviceServerId; $deviceServerId->setId(intval($response['Response'][0]['Id']['id'])); diff --git a/app/Services/Bunq/Request/DeviceSessionRequest.php b/app/Services/Bunq/Request/DeviceSessionRequest.php index 4f6bf028b5..498ab86a94 100644 --- a/app/Services/Bunq/Request/DeviceSessionRequest.php +++ b/app/Services/Bunq/Request/DeviceSessionRequest.php @@ -50,7 +50,7 @@ class DeviceSessionRequest extends BunqRequest */ public function call(): void { - $uri = '/v1/session-server'; + $uri = 'session-server'; $data = ['secret' => $this->secret]; $headers = $this->getDefaultHeaders(); $headers['X-Bunq-Client-Authentication'] = $this->installationToken->getToken(); diff --git a/app/Services/Bunq/Request/InstallationTokenRequest.php b/app/Services/Bunq/Request/InstallationTokenRequest.php index 03eef6d185..8eb54531ff 100644 --- a/app/Services/Bunq/Request/InstallationTokenRequest.php +++ b/app/Services/Bunq/Request/InstallationTokenRequest.php @@ -40,23 +40,24 @@ class InstallationTokenRequest extends BunqRequest private $publicKey = ''; /** - * @throws \Exception + * @throws \FireflyIII\Exceptions\FireflyException */ public function call(): void { - $uri = '/v1/installation'; + Log::debug('Now in InstallationTokenRequest::call()'); + $uri = 'installation'; $data = ['client_public_key' => $this->publicKey]; $headers = $this->getDefaultHeaders(); $response = $this->sendUnsignedBunqPost($uri, $data, $headers); - Log::debug('Installation request response', $response); + //Log::debug('Installation request response', $response); $this->installationId = $this->extractInstallationId($response); $this->serverPublicKey = $this->extractServerPublicKey($response); $this->installationToken = $this->extractInstallationToken($response); - - Log::debug(sprintf('Installation ID: %s', serialize($this->installationId))); - Log::debug(sprintf('Installation token: %s', serialize($this->installationToken))); - Log::debug(sprintf('server public key: %s', serialize($this->serverPublicKey))); + Log::debug('No errors! We have installation ID!'); + Log::debug(sprintf('Installation ID: %s', $this->installationId->getId())); + Log::debug(sprintf('Installation token: %s', $this->installationToken->getToken())); + Log::debug('Server public key: (not included)'); return; } diff --git a/app/Services/Bunq/Request/ListDeviceServerRequest.php b/app/Services/Bunq/Request/ListDeviceServerRequest.php index dd72623152..72deda39bd 100644 --- a/app/Services/Bunq/Request/ListDeviceServerRequest.php +++ b/app/Services/Bunq/Request/ListDeviceServerRequest.php @@ -43,11 +43,10 @@ class ListDeviceServerRequest extends BunqRequest } /** - * @throws \Exception */ public function call(): void { - $uri = '/v1/device-server'; + $uri = 'device-server'; $data = []; $headers = $this->getDefaultHeaders(); $headers['X-Bunq-Client-Authentication'] = $this->installationToken->getToken(); diff --git a/app/Services/Bunq/Request/ListMonetaryAccountRequest.php b/app/Services/Bunq/Request/ListMonetaryAccountRequest.php index a1c92be526..761e203b86 100644 --- a/app/Services/Bunq/Request/ListMonetaryAccountRequest.php +++ b/app/Services/Bunq/Request/ListMonetaryAccountRequest.php @@ -44,7 +44,7 @@ class ListMonetaryAccountRequest extends BunqRequest public function call(): void { $this->monetaryAccounts = new Collection; - $uri = sprintf('/v1/user/%d/monetary-account', $this->userId); + $uri = sprintf('user/%d/monetary-account', $this->userId); $data = []; $headers = $this->getDefaultHeaders(); $headers['X-Bunq-Client-Authentication'] = $this->sessionToken->getToken(); diff --git a/app/Services/Bunq/Request/ListUserRequest.php b/app/Services/Bunq/Request/ListUserRequest.php index 514b62a533..241a819129 100644 --- a/app/Services/Bunq/Request/ListUserRequest.php +++ b/app/Services/Bunq/Request/ListUserRequest.php @@ -46,7 +46,7 @@ class ListUserRequest extends BunqRequest */ public function call(): void { - $uri = '/v1/user'; + $uri = 'user'; $data = []; $headers = $this->getDefaultHeaders(); $headers['X-Bunq-Client-Authentication'] = $this->sessionToken->getToken(); diff --git a/config/firefly.php b/config/firefly.php index d8da56e3a8..4564509448 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -103,9 +103,6 @@ return [ 'export_formats' => [ 'csv' => 'FireflyIII\Export\Exporter\CsvExporter', ], - 'bunq' => [ - 'server' => 'https://sandbox.public.api.bunq.com', - ], 'spectre' => [ 'server' => 'https://www.saltedge.com', ], diff --git a/config/import.php b/config/import.php index 003d0ac062..db2373f9cb 100644 --- a/config/import.php +++ b/config/import.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ 'enabled' => [ 'file' => true, - 'bunq' => false, + 'bunq' => true, 'spectre' => true, 'plaid' => false, ], @@ -52,10 +52,14 @@ return [ 'file' => [ 'import_formats' => ['csv'], // mt940 'default_import_format' => 'csv', - 'processors' => [ + 'processors' => [ 'csv' => 'FireflyIII\Import\FileProcessor\CsvProcessor', ], ], + 'bunq' => [ + 'server' => 'sandbox.public.api.bunq.com', + 'version' => 'v1', + ], ], 'default_config' => [ 'file' => [ diff --git a/public/js/ff/import/status.js b/public/js/ff/import/status.js index 99deca6f09..73f621239c 100644 --- a/public/js/ff/import/status.js +++ b/public/js/ff/import/status.js @@ -51,6 +51,10 @@ $(function () { * Downloads some JSON and responds to its content to see what the status is of the current import. */ function checkJobStatus() { + console.log('In checkJobStatus()'); + if(jobFailed) { + return; + } $.getJSON(jobStatusUri).done(reportOnJobStatus).fail(reportFailedJob); } @@ -58,6 +62,7 @@ function checkJobStatus() { * This method is called when the JSON query returns an error. If possible, this error is relayed to the user. */ function reportFailedJob(jqxhr, textStatus, error) { + console.log('In reportFailedJob()'); // hide all possible boxes: $('.statusbox').hide(); @@ -77,9 +82,10 @@ function reportFailedJob(jqxhr, textStatus, error) { * @param data */ function reportOnJobStatus(data) { - + console.log('In reportOnJobStatus()'); switch (data.status) { case "configured": + console.log('Job reports configured.'); // job is ready. Do not check again, just show the start-box. Hide the rest. if (!job.configuration['auto-start']) { $('.statusbox').hide(); @@ -95,6 +101,7 @@ function reportOnJobStatus(data) { } break; case "running": + console.log('Job reports running.'); // job is running! Show the running box: $('.statusbox').hide(); $('.status_running').show(); @@ -117,6 +124,7 @@ function reportOnJobStatus(data) { } break; case "finished": + console.log('Job reports finished.'); $('.statusbox').hide(); $('.status_finished').show(); // report on detected errors: @@ -125,6 +133,7 @@ function reportOnJobStatus(data) { $('#import-status-more-info').html(data.finishedText); break; case "error": + console.log('Job reports ERROR.'); // hide all possible boxes: $('.statusbox').hide(); @@ -137,6 +146,7 @@ function reportOnJobStatus(data) { $('.fatal_error').show(); break; case "configuring": + console.log('Job reports configuring.'); // redirect back to configure screen. window.location = jobConfigureUri; break; @@ -151,6 +161,7 @@ function reportOnJobStatus(data) { * Shows a fatal error when the job seems to be stalled. */ function showStalledBox() { + console.log('In showStalledBox().'); $('.statusbox').hide(); $('.fatal_error').show(); $('.fatal_error_txt').text(langImportTimeOutError); @@ -162,17 +173,21 @@ function showStalledBox() { * @param data */ function jobIsStalled(data) { + console.log('In jobIsStalled().'); if (data.done === numberOfSteps) { numberOfReports++; + console.log('Number of reports ' + numberOfReports); } if (data.done !== numberOfSteps) { numberOfReports = 0; + console.log('Number of reports ' + numberOfReports); } if (numberOfReports > 20) { + console.log('Number of reports > 20! ' + numberOfReports); return true; } numberOfSteps = data.done; - + console.log('Number of steps ' + numberOfSteps); return false; } @@ -181,7 +196,9 @@ function jobIsStalled(data) { * Only when job is in "configured" state. */ function startJob() { + console.log('In startJob().'); if (job.status === "configured") { + console.log('Job status = configured.'); // disable the button, add loading thing. $('.start-job').prop('disabled', true).text('...'); $.post(jobStartUri, {_token: token}).fail(reportOnSubmitError); @@ -190,12 +207,14 @@ function startJob() { timeOutId = setTimeout(checkJobStatus, startInterval); return; } + console.log('Job.status = ' + job.status); } /** * When the start button fails (returns error code) this function reports. It assumes a time out. */ function reportOnSubmitError(jqxhr, textStatus, error) { + console.log('In reportOnSubmitError().'); // stop the refresh thing clearTimeout(timeOutId); @@ -217,6 +236,7 @@ function reportOnSubmitError(jqxhr, textStatus, error) { * This method updates the percentage bar thing if the job is running! */ function updateBar(data) { + console.log('In updateBar().'); var bar = $('#import-status-bar'); if (data.show_percentage) { bar.addClass('progress-bar-success').removeClass('progress-bar-info'); @@ -238,6 +258,7 @@ function updateBar(data) { */ function updateStatusText(data) { "use strict"; + console.log('In updateStatusText().'); $('#import-status-txt').removeClass('text-danger').text(data.statusText); } @@ -246,6 +267,7 @@ function updateStatusText(data) { * @param data */ function reportOnErrors(data) { + console.log('In reportOnErrors().'); if (knownErrors === data.errors.length) { return; } diff --git a/resources/views/import/bunq/prerequisites.twig b/resources/views/import/bunq/prerequisites.twig index 063bad3942..5ff2448bb7 100644 --- a/resources/views/import/bunq/prerequisites.twig +++ b/resources/views/import/bunq/prerequisites.twig @@ -5,25 +5,25 @@ {% endblock %} {% block content %}
-
+
-

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

+

{{ trans('import.bunq_prerequisites_title') }}

- {{ trans('bank.bunq_prerequisites_text') }} + {{ trans('import.bunq_prerequisites_text') }}

- {{ ExpandedForm.text('api_key') }} + {{ ExpandedForm.text('api_key', key) }}