Improve bunq import.

This commit is contained in:
James Cole 2018-03-19 08:17:15 +01:00
parent 6ab03bb228
commit 40787bc29a
No known key found for this signature in database
GPG Key ID: C16961E655E74B5E
14 changed files with 682 additions and 51 deletions

View File

@ -25,7 +25,8 @@ 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 FireflyIII\Support\Import\Configuration\Bunq\HasAccounts;
use FireflyIII\Support\Import\Configuration\Bunq\HaveAccounts;
use Log;
/**
@ -65,8 +66,6 @@ class BunqConfigurator implements ConfiguratorInterface
$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 */
@ -102,20 +101,7 @@ class BunqConfigurator implements ConfiguratorInterface
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);
@ -139,17 +125,10 @@ class BunqConfigurator implements ConfiguratorInterface
}
$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';
return 'import.bunq.accounts';
default:
return '';
@ -179,16 +158,10 @@ class BunqConfigurator implements ConfiguratorInterface
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');

View File

@ -29,12 +29,18 @@ use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Services\Bunq\Id\DeviceServerId;
use FireflyIII\Services\Bunq\Object\DeviceServer;
use FireflyIII\Services\Bunq\Object\MonetaryAccountBank;
use FireflyIII\Services\Bunq\Object\ServerPublicKey;
use FireflyIII\Services\Bunq\Object\UserCompany;
use FireflyIII\Services\Bunq\Object\UserPerson;
use FireflyIII\Services\Bunq\Request\DeviceServerRequest;
use FireflyIII\Services\Bunq\Request\DeviceSessionRequest;
use FireflyIII\Services\Bunq\Request\InstallationTokenRequest;
use FireflyIII\Services\Bunq\Request\ListDeviceServerRequest;
use FireflyIII\Services\Bunq\Request\ListMonetaryAccountRequest;
use FireflyIII\Services\Bunq\Request\ListPaymentRequest;
use FireflyIII\Services\Bunq\Token\InstallationToken;
use FireflyIII\Services\Bunq\Token\SessionToken;
use Illuminate\Support\Collection;
use Log;
use Preferences;
@ -60,9 +66,11 @@ use Requests;
*
* Get list of bank accounts
*
* Stage 'has-accounts'
* Stage 'have-accounts'
*
* Do customer statement export (in CSV?)
* Map accounts to existing accounts
*
* Stage 'do-import'?
*
*
*/
@ -122,19 +130,57 @@ class BunqRoutine implements RoutineInterface
{
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';
// this method continues with the job and is called by whenever a stage is
// finished
$this->continueJob();
return true;
}
/**
* @param ImportJob $job
*/
public function setJob(ImportJob $job)
{
$this->job = $job;
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($job->user);
}
/**
* @throws FireflyException
*/
protected function continueJob()
{
// if in "configuring"
if ($this->getStatus() === 'configuring') {
Log::debug('Job is in configuring stage, will do nothing.');
return;
}
$stage = $this->getConfig()['stage'] ?? 'unknown';
Log::debug(sprintf('Now in continueJob() for stage %s', $stage));
switch ($stage) {
case 'initial':
// register device and get tokens.
$this->runStageInitial();
$this->continueJob();
break;
case 'registered':
// get all bank accounts of user.
$this->runStageRegistered();
$this->continueJob();
break;
case 'logged-in':
$this->runStageLoggedIn();
break;
case 'have-accounts':
// do nothing in this stage. Job should revert to config routine.
break;
case 'have-account-mapping':
$this->runStageHaveAccountMapping();
break;
default:
throw new FireflyException(sprintf('No action for stage %s!', $stage));
break;
@ -154,18 +200,6 @@ class BunqRoutine implements RoutineInterface
//
// 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);
}
/**
@ -206,18 +240,22 @@ class BunqRoutine implements RoutineInterface
$request->setServerPublicKey($serverPublicKey);
$request->setSecret($apiKey);
$request->call();
$this->addStep();
// todo store objects in job!
$deviceSession = $request->getDeviceSessionId();
$userPerson = $request->getUserPerson();
$userCompany = $request->getUserCompany();
$sessionToken = $request->getSessionToken();
$config = $this->getConfig();
$config['device_session_id'] = $deviceSession->toArray();
$config['user_person'] = $userPerson->toArray();
$config['user_company'] = $userCompany->toArray();
$config['session_token'] = $sessionToken->toArray();
$config['stage'] = 'logged-in';
$this->setConfig($config);
$this->addStep();
return;
}
@ -524,6 +562,95 @@ class BunqRoutine implements RoutineInterface
return $deviceServerId;
}
/**
* Will download the transactions for each account that is selected to be imported from.
* Will of course also update the number of steps and what-not.
*
* @throws FireflyException
*/
private function runStageHaveAccountMapping(): void
{
$config = $this->getConfig();
$user = new UserPerson($config['user_person']);
$mapping = $config['accounts-mapped'];
$token = new SessionToken($config['session_token']);
$count = 0;
if ($user->getId() === 0) {
$user = new UserCompany($config['user_company']);
}
foreach ($config['accounts'] as $accountData) {
$account = new MonetaryAccountBank($accountData);
$importId = $account->getId();
if ($mapping[$importId] === 1) {
// grab all transactions
$request = new ListPaymentRequest();
$request->setPrivateKey($this->getPrivateKey());
$request->setServerPublicKey($this->getServerPublicKey());
$request->setSessionToken($token);
$request->setUserId($user->getId());
$request->setAccount($account);
$request->call();
exit;
// store in array
$all[$account->getId()] = [
'account' => $account,
'import_id' => $importId,
'transactions' => $transactions,
];
$count += count($transactions);
}
Log::debug(sprintf('Total number of transactions: %d', $count));
$this->addStep();
//$this->importTransactions($all);
}
exit;
}
/**
*
* @throws FireflyException
*/
private function runStageLoggedIn(): void
{
// grab new session token:
$config = $this->getConfig();
$token = new SessionToken($config['session_token']);
$user = new UserPerson($config['user_person']);
if ($user->getId() === 0) {
$user = new UserCompany($config['user_company']);
}
// list accounts request
$request = new ListMonetaryAccountRequest();
$request->setServerPublicKey($this->getServerPublicKey());
$request->setPrivateKey($this->getPrivateKey());
$request->setUserId($user->getId());
$request->setSessionToken($token);
$request->call();
$accounts = $request->getMonetaryAccounts();
$arr = [];
Log::debug(sprintf('Get monetary accounts, found %d accounts.', $accounts->count()));
/** @var MonetaryAccountBank $account */
foreach ($accounts as $account) {
$arr[] = $account->toArray();
}
$config = $this->getConfig();
$config['accounts'] = $arr;
$config['stage'] = 'have-accounts';
$this->setConfig($config);
// once the accounts are stored, go to configuring stage:
// update job, set status to "configuring".
$this->setStatus('configuring');
return;
}
/**
* Shorthand.
*

View File

@ -71,4 +71,16 @@ class Alias extends BunqObject
{
return $this->value;
}
/**
* @return array
*/
public function toArray(): array
{
return [
'type' => $this->type,
'name' => $this->name,
'value' => $this->value,
];
}
}

View File

@ -60,4 +60,15 @@ class Amount extends BunqObject
{
return $this->value;
}
/**
* @return array
*/
public function toArray(): array
{
return [
'currency' => $this->currency,
'value' => $this->value,
];
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* LabelMonetaryAccount.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Services\Bunq\Object;
/**
* Class LabelMonetaryAccount
*/
class LabelMonetaryAccount extends BunqObject
{
}

View File

@ -89,14 +89,10 @@ class MonetaryAccountBank extends BunqObject
$this->status = $data['status'];
$this->subStatus = $data['sub_status'];
$this->userId = $data['user_id'];
$this->status = $data['status'];
$this->subStatus = $data['sub_status'];
$this->monetaryAccountProfile = new MonetaryAccountProfile($data['monetary_account_profile']);
$this->setting = new MonetaryAccountSetting($data['setting']);
$this->overdraftLimit = new Amount($data['overdraft_limit']);
$this->publicUuid = $data['public_uuid'];
// create aliases:
foreach ($data['alias'] as $alias) {
$this->aliases[] = new Alias($alias);
@ -106,6 +102,10 @@ class MonetaryAccountBank extends BunqObject
$this->notificationFilters[] = new NotificationFilter($filter);
}
// TODO avatar
// TODO reason
// TODO reason description
return;
}
@ -156,4 +156,46 @@ class MonetaryAccountBank extends BunqObject
{
return $this->setting;
}
/**
* @return array
*/
public function toArray(): array
{
$data = [
'id' => $this->id,
'created' => $this->created->format('Y-m-d H:i:s.u'),
'updated' => $this->updated->format('Y-m-d H:i:s.u'),
'balance' => $this->balance->toArray(),
'currency' => $this->currency,
'daily_limit' => $this->dailyLimit->toArray(),
'daily_spent' => $this->dailySpent->toArray(),
'description' => $this->description,
'public_uuid' => $this->publicUuid,
'status' => $this->status,
'sub_status' => $this->subStatus,
'user_id' => $this->userId,
'monetary_account_profile' => $this->monetaryAccountProfile->toArray(),
'setting' => $this->setting->toArray(),
'overdraft_limit' => $this->overdraftLimit->toArray(),
'alias' => [],
'notification_filters' => [],
];
/** @var Alias $alias */
foreach ($this->aliases as $alias) {
$data['alias'][] = $alias->toArray();
}
/** @var NotificationFilter $filter */
foreach ($this->notificationFilters as $filter) {
$data['notification_filters'][] = $filter->toArray();
}
// TODO avatar
// TODO reason
// TODO reason description
return $data;
}
}

View File

@ -54,4 +54,15 @@ class MonetaryAccountProfile extends BunqObject
return;
}
/**
* @return array
*/
public function toArray(): array
{
return [
'profile_action_required' => $this->profileActionRequired,
'profile_amount_required' => $this->profileAmountRequired->toArray(),
];
}
}

View File

@ -71,4 +71,16 @@ class MonetaryAccountSetting extends BunqObject
{
return $this->restrictionChat;
}
/**
* @return array
*/
public function toArray(): array
{
return [
'color' => $this->color,
'default_avatar_status' => $this->defaultAvatarStatus,
'restriction_chat' => $this->restrictionChat,
];
}
}

View File

@ -36,4 +36,12 @@ class NotificationFilter extends BunqObject
{
unset($data);
}
/**
* @return array
*/
public function toArray(): array
{
return [];
}
}

View File

@ -0,0 +1,70 @@
<?php
/**
* Payment.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Services\Bunq\Object;
use Carbon\Carbon;
/**
* Class Payment
*/
class Payment extends BunqObject
{
/** @var int */
private $id;
/** @var Carbon */
private $created;
/** @var Carbon */
private $updated;
/** @var int */
private $monetaryAccountId;
/** @var Amount */
private $amount;
/** @var string */
private $description;
/** @var string */
private $type;
/** @var string */
private $merchantReference;
/** @var LabelMonetaryAccount */
private $counterParty;
/** @var array */
private $attachments = [];
/** @var string */
private $subType;
/**
* Payment constructor.
*
* @param array $data
*/
public function __construct(array $data)
{
$this->id = $data['id'];
$this->created = new Carbon();
var_dump($data);
exit;
}
}

View File

@ -39,7 +39,7 @@ class ListMonetaryAccountRequest extends BunqRequest
private $userId = 0;
/**
* @throws \Exception
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function call(): void
{

View File

@ -0,0 +1,95 @@
<?php
/**
* ListPaymentRequest.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Services\Bunq\Request;
use FireflyIII\Services\Bunq\Object\MonetaryAccountBank;
use FireflyIII\Services\Bunq\Object\Payment;
use FireflyIII\Services\Bunq\Token\SessionToken;
use Illuminate\Support\Collection;
/**
* Class ListPaymentRequest
*/
class ListPaymentRequest extends BunqRequest
{
/** @var MonetaryAccountBank */
private $account;
/** @var Collection */
private $payments;
/** @var SessionToken */
private $sessionToken;
/** @var int */
private $userId = 0;
/**
* TODO support pagination.
*
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function call(): void
{
$this->payments = new Collection;
$uri = sprintf('user/%d/monetary-account/%d/payment', $this->userId, $this->account->getId());
$data = [];
$headers = $this->getDefaultHeaders();
$headers['X-Bunq-Client-Authentication'] = $this->sessionToken->getToken();
$response = $this->sendSignedBunqGet($uri, $data, $headers);
// create payment objects:
$raw = $this->getArrayFromResponse('Payment', $response);
foreach ($raw as $entry) {
$account = new Payment($entry);
$this->payments->push($account);
}
return;
}
/**
* @param MonetaryAccountBank $account
*/
public function setAccount(MonetaryAccountBank $account): void
{
$this->account = $account;
}
/**
* @param SessionToken $sessionToken
*/
public function setSessionToken(SessionToken $sessionToken): void
{
$this->sessionToken = $sessionToken;
}
/**
* @param int $userId
*/
public function setUserId(int $userId): void
{
$this->userId = $userId;
}
}

View File

@ -0,0 +1,154 @@
<?php
/**
* HaveAccounts.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Import\Configuration\Bunq;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\Import\Configuration\ConfigurationInterface;
use Illuminate\Support\Collection;
/**
* Class HaveAccounts
*/
class HaveAccounts implements ConfigurationInterface
{
/** @var ImportJob */
private $job;
/**
* Get the data necessary to show the configuration screen.
*
* @return array
*/
public function getData(): array
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
/** @var CurrencyRepositoryInterface $currencyRepository */
$currencyRepository = app(CurrencyRepositoryInterface::class);
$config = $this->job->configuration;
$collection = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$defaultCurrency = app('amount')->getDefaultCurrency();
$dbAccounts = [];
/** @var Account $dbAccount */
foreach ($collection as $dbAccount) {
$id = $dbAccount->id;
$currencyId = intval($accountRepository->getMetaValue($dbAccount, 'currency_id'));
$currency = $currencyRepository->findNull($currencyId);
$dbAccounts[$id] = [
'account' => $dbAccount,
'currency' => is_null($currency) ? $defaultCurrency : $currency,
];
}
// loop Bunq accounts:
/**
* @var int $index
* @var array $bunqAccount
*/
foreach ($config['accounts'] as $index => $bunqAccount) {
// find accounts with currency code
$code = $bunqAccount['currency'];
$selection = $this->filterAccounts($dbAccounts, $code);
$config['accounts'][$index]['options'] = app('expandedform')->makeSelectList($selection);
}
$data = [
'config' => $config,
];
return $data;
}
/**
* 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;
return $this;
}
/**
* Store the result.
*
* @param array $data
*
* @return bool
*/
public function storeConfiguration(array $data): bool
{
$accounts = $data['bunq_account_id'] ?? [];
$mapping = [];
foreach ($accounts as $bunqId) {
$bunqId = intval($bunqId);
$doImport = intval($data['do_import'][$bunqId] ?? 0) === 1;
$account = intval($data['import'][$bunqId] ?? 0);
if ($doImport) {
$mapping[$bunqId] = $account;
}
}
$config = $this->job->configuration;
$config['accounts-mapped'] = $mapping;
$this->job->configuration = $config;
$this->job->save();
return true;
}
/**
* @param array $dbAccounts
* @param string $code
*
* @return Collection
*/
private function filterAccounts(array $dbAccounts, string $code): Collection
{
$collection = new Collection;
foreach ($dbAccounts as $accountId => $data) {
if ($data['currency']->code === $code) {
$collection->push($data['account']);
}
}
return $collection;
}
}

View File

@ -0,0 +1,83 @@
{% extends "./layout/default" %}
{% block breadcrumbs %}
{{ Breadcrumbs.render }}
{% endblock %}
{% block content %}
<div class="row">
<form class="form-horizontal" action="{{ route('import.configure.post',[job.key]) }}" method="post">
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
<div class="col-lg-12 col-md-12 col-sm-12">
<div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title">{{ trans('import.bunq_accounts_title') }}</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-lg-8">
<p>
{{ trans('import.bunq_accounts_text')|raw }}
</p>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>&nbsp;</th>
<th>{{ trans('list.account_at_bunq') }}</th>
<th>{{ trans('list.account') }}</th>
<th>{{ trans('list.do_import') }}</th>
</tr>
</thead>
<tbody>
{% for account in data.config.accounts %}
<tr>
<td style="background:{{ account.setting.color }};"></td>
<td>
<input type="hidden" name="bunq_account_id[]" value="{{ account.id }}" />
<strong>{{ account.description }}</strong><br />
{% for alias in account.aliases %}
{% if alias.type == 'IBAN' %}
{{ alias.name }}: {{ alias.value }}
{% endif %}
{% endfor %}
</td>
<td>
<select class="form-control" name="import[{{ account.id }}]">
{% for id,name in account.options %}
<option value="{{ id }}" label="{{ name }}">{{ name }}</option>
{% endfor %}
</select>
</td>
<td>
<div class="checkbox">
<label>
<input type="checkbox" value="1" name="do_import[{{ account.id }}]" checked>
{{ trans('import.bunq_do_import') }}
</label>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="box-footer">
<button type="submit" class="btn pull-right btn-success">
{{ ('submit')|_ }}
</button>
</div>
</div>
</div>
</form>
</div>
{% endblock %}
{% block scripts %}
{% endblock %}
{% block styles %}
{% endblock %}