Initial code to get providers from Spectre.

This commit is contained in:
James Cole 2017-12-09 12:23:28 +01:00
parent 0774258516
commit aa9500f5ad
No known key found for this signature in database
GPG Key ID: C16961E655E74B5E
16 changed files with 964 additions and 27 deletions

View File

@ -0,0 +1,77 @@
<?php
namespace FireflyIII\Jobs;
use FireflyIII\Models\Configuration;
use FireflyIII\Models\SpectreProvider;
use FireflyIII\Services\Spectre\Request\ListProvidersRequest;
use FireflyIII\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Log;
class GetSpectreProviders implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $user;
/**
* Create a new job instance.
*/
public function __construct(User $user)
{
$this->user = $user;
Log::debug('Constructed job GetSpectreProviders');
}
/**
* Execute the job.
*/
public function handle()
{
/** @var Configuration $configValue */
$configValue = app('fireflyconfig')->get('spectre_provider_download', 0);
$now = time();
if ($now - intval($configValue->data) < 86400) {
Log::debug(sprintf('Difference is %d, so will NOT execute job.', ($now - intval($configValue->data))));
return;
}
Log::debug(sprintf('Difference is %d, so will execute job.', ($now - intval($configValue->data))));
// get user
// fire away!
$request = new ListProvidersRequest($this->user);
$request->call();
// store all providers:
$providers = $request->getProviders();
foreach ($providers as $provider) {
// find provider?
$dbProvider = SpectreProvider::where('spectre_id', $provider['id'])->first();
if (is_null($dbProvider)) {
$dbProvider = new SpectreProvider;
}
// update fields:
$dbProvider->spectre_id = $provider['id'];
$dbProvider->code = $provider['code'];
$dbProvider->mode = $provider['mode'];
$dbProvider->status = $provider['status'];
$dbProvider->interactive = $provider['interactive'] === 1;
$dbProvider->automatic_fetch = $provider['automatic_fetch'] === 1;
$dbProvider->country_code = $provider['country_code'];
$dbProvider->data = $provider;
$dbProvider->save();
Log::debug(sprintf('Stored provider #%d under ID #%d', $provider['id'], $dbProvider->id));
}
app('fireflyconfig')->set('spectre_provider_download', time());
return;
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Models;
use Illuminate\Database\Eloquent\Model;
/**
* Class SpectreProvider
*/
class SpectreProvider extends Model
{
/**
* The attributes that should be casted to native types.
*
* @var array
*/
protected $casts
= [
'spectre_id' => 'int',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
'interactive' => 'boolean',
'automatic_fetch' => 'boolean',
'data' => 'array',
];
protected $fillable = ['spectre_id', 'code', 'mode', 'name', 'status', 'interactive', 'automatic_fetch', 'country_code', 'data'];
}

View File

@ -160,6 +160,9 @@ abstract class BunqRequest
return $result;
}
/**
* @return array
*/
protected function getDefaultHeaders(): array
{
$userAgent = sprintf('FireflyIII v%s', config('firefly.version'));

View File

@ -0,0 +1,80 @@
<?php
/**
* ListUserRequest.php
* Copyright (c) 2017 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\Spectre\Request;
use Log;
/**
* Class ListUserRequest.
*/
class ListProvidersRequest extends SpectreRequest
{
protected $providers = [];
/**
*
*/
public function call(): void
{
$hasNextPage = true;
$nextId = 0;
while ($hasNextPage) {
Log::debug(sprintf('Now calling for next_id %d', $nextId));
$parameters = ['include_fake_providers' => 'true', 'include_provider_fields' => 'true', 'from_id' => $nextId];
$uri = '/api/v3/providers?' . http_build_query($parameters);
$response = $this->sendSignedSpectreGet($uri, []);
// count entries:
Log::debug(sprintf('Found %d entries in data-array', count($response['data'])));
// extract next ID
$hasNextPage = false;
if (isset($response['meta']['next_id']) && intval($response['meta']['next_id']) > $nextId) {
$hasNextPage = true;
$nextId = $response['meta']['next_id'];
Log::debug(sprintf('Next ID is now %d.', $nextId));
} else {
Log::debug('No next page.');
}
// store providers:
foreach ($response['data'] as $providerArray) {
$providerId = $providerArray['id'];
$this->providers[$providerId] = $providerArray;
Log::debug(sprintf('Stored provider #%d', $providerId));
}
}
return;
}
/**
* @return array
*/
public function getProviders(): array
{
return $this->providers;
}
}

View File

@ -0,0 +1,378 @@
<?php
/**
* BunqRequest.php
* Copyright (c) 2017 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\Spectre\Request;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\User;
use Log;
use Requests;
use Requests_Exception;
//use FireflyIII\Services\Bunq\Object\ServerPublicKey;
/**
* Class BunqRequest.
*/
abstract class SpectreRequest
{
/** @var string */
protected $clientId = '';
protected $expiresAt = 0;
/** @var ServerPublicKey */
protected $serverPublicKey;
/** @var string */
protected $serviceSecret = '';
/** @var string */
private $privateKey = '';
/** @var string */
private $server = '';
/** @var User */
private $user;
/**
* SpectreRequest constructor.
*/
public function __construct(User $user)
{
$this->user = $user;
$this->server = config('firefly.spectre.server');
$this->expiresAt = time() + 180;
$privateKey = app('preferences')->get('spectre_private_key', null);
$this->privateKey = $privateKey->data;
// set client ID
$clientId = app('preferences')->get('spectre_client_id', null);
$this->clientId = $clientId->data;
// set service secret
$serviceSecret = app('preferences')->get('spectre_service_secret', null);
$this->serviceSecret = $serviceSecret->data;
}
/**
*
*/
abstract public function call(): void;
/**
* @return string
*/
public function getClientId(): string
{
return $this->clientId;
}
/**
* @param string $clientId
*/
public function setClientId(string $clientId): void
{
$this->clientId = $clientId;
}
/**
* @return string
*/
public function getServer(): string
{
return $this->server;
}
/**
* @return ServerPublicKey
*/
public function getServerPublicKey(): ServerPublicKey
{
return $this->serverPublicKey;
}
/**
* @param ServerPublicKey $serverPublicKey
*/
public function setServerPublicKey(ServerPublicKey $serverPublicKey)
{
$this->serverPublicKey = $serverPublicKey;
}
/**
* @return string
*/
public function getServiceSecret(): string
{
return $this->serviceSecret;
}
/**
* @param string $serviceSecret
*/
public function setServiceSecret(string $serviceSecret): void
{
$this->serviceSecret = $serviceSecret;
}
/**
* @param string $privateKey
*/
public function setPrivateKey(string $privateKey)
{
$this->privateKey = $privateKey;
}
/**
* @param string $secret
*/
public function setSecret(string $secret)
{
$this->secret = $secret;
}
/**
* @param string $method
* @param string $uri
* @param string $data
*
* @return string
*
* @throws FireflyException
*/
protected function generateSignature(string $method, string $uri, string $data): string
{
if (0 === strlen($this->privateKey)) {
throw new FireflyException('No private key present.');
}
if ('get' === strtolower($method) || 'delete' === strtolower($method)) {
$data = '';
}
// base64(sha1_signature(private_key, "Expires-at|request_method|original_url|post_body|md5_of_uploaded_file|")))
// Prepare the signature
$toSign = $this->expiresAt . '|' . strtoupper($method) . '|' . $uri . '|' . $data . ''; // no file so no content there.
Log::debug(sprintf('String to sign: %s', $toSign));
$signature = '';
// Sign the data
openssl_sign($toSign, $signature, $this->privateKey, OPENSSL_ALGO_SHA1);
$signature = base64_encode($signature);
return $signature;
}
/**
* @return array
*/
protected function getDefaultHeaders(): array
{
$userAgent = sprintf('FireflyIII v%s', config('firefly.version'));
return [
'Client-Id' => $this->getClientId(),
'Service-Secret' => $this->getServiceSecret(),
'Accept' => 'application/json',
'Content-type' => 'application/json',
'Cache-Control' => 'no-cache',
'User-Agent' => $userAgent,
'Expires-at' => $this->expiresAt,
];
}
/**
* @param string $uri
* @param array $headers
*
* @return array
*
* @throws Exception
*/
protected function sendSignedBunqDelete(string $uri, array $headers): array
{
if (0 === strlen($this->server)) {
throw new FireflyException('No bunq server defined');
}
$fullUri = $this->server . $uri;
$signature = $this->generateSignature('delete', $uri, $headers, '');
$headers['X-Bunq-Client-Signature'] = $signature;
try {
$response = Requests::delete($fullUri, $headers);
} catch (Requests_Exception $e) {
return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()]]];
}
$body = $response->body;
$array = json_decode($body, true);
$responseHeaders = $response->headers->getAll();
$statusCode = intval($response->status_code);
$array['ResponseHeaders'] = $responseHeaders;
$array['ResponseStatusCode'] = $statusCode;
Log::debug(sprintf('Response to DELETE %s is %s', $fullUri, $body));
if ($this->isErrorResponse($array)) {
$this->throwResponseError($array);
}
if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) {
throw new FireflyException(sprintf('Could not verify signature for request to "%s"', $uri));
}
return $array;
}
/**
* @param string $uri
* @param array $data
* @param array $headers
*
* @return array
*
* @throws Exception
*/
protected function sendSignedBunqPost(string $uri, array $data, array $headers): array
{
$body = json_encode($data);
$fullUri = $this->server . $uri;
$signature = $this->generateSignature('post', $uri, $headers, $body);
$headers['X-Bunq-Client-Signature'] = $signature;
try {
$response = Requests::post($fullUri, $headers, $body);
} catch (Requests_Exception $e) {
return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()]]];
}
$body = $response->body;
$array = json_decode($body, true);
$responseHeaders = $response->headers->getAll();
$statusCode = intval($response->status_code);
$array['ResponseHeaders'] = $responseHeaders;
$array['ResponseStatusCode'] = $statusCode;
if ($this->isErrorResponse($array)) {
$this->throwResponseError($array);
}
if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) {
throw new FireflyException(sprintf('Could not verify signature for request to "%s"', $uri));
}
return $array;
}
/**
* @param string $uri
* @param array $data
* @param array $headers
*
* @return array
*
* @throws Exception
*/
protected function sendSignedSpectreGet(string $uri, array $data): array
{
if (0 === strlen($this->server)) {
throw new FireflyException('No Spectre server defined');
}
$headers = $this->getDefaultHeaders();
$body = json_encode($data);
$fullUri = $this->server . $uri;
$signature = $this->generateSignature('get', $fullUri, $body);
$headers['Signature'] = $signature;
Log::debug('Final headers for spectre signed get request:', $headers);
try {
$response = Requests::get($fullUri, $headers);
} catch (Requests_Exception $e) {
throw new FireflyException(sprintf('Request Exception: %s', $e->getMessage()));
}
$statusCode = intval($response->status_code);
if ($statusCode !== 200) {
throw new FireflyException(sprintf('Status code %d: %s', $statusCode, $response->body));
}
$body = $response->body;
$array = json_decode($body, true);
$responseHeaders = $response->headers->getAll();
$array['ResponseHeaders'] = $responseHeaders;
$array['ResponseStatusCode'] = $statusCode;
return $array;
}
/**
* @param string $uri
* @param array $headers
*
* @return array
*/
protected function sendUnsignedBunqDelete(string $uri, array $headers): array
{
$fullUri = $this->server . $uri;
try {
$response = Requests::delete($fullUri, $headers);
} catch (Requests_Exception $e) {
return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()]]];
}
$body = $response->body;
$array = json_decode($body, true);
$responseHeaders = $response->headers->getAll();
$statusCode = $response->status_code;
$array['ResponseHeaders'] = $responseHeaders;
$array['ResponseStatusCode'] = $statusCode;
if ($this->isErrorResponse($array)) {
$this->throwResponseError($array);
}
return $array;
}
/**
* @param string $uri
* @param array $data
* @param array $headers
*
* @return array
*/
protected function sendUnsignedBunqPost(string $uri, array $data, array $headers): array
{
$body = json_encode($data);
$fullUri = $this->server . $uri;
try {
$response = Requests::post($fullUri, $headers, $body);
} catch (Requests_Exception $e) {
return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()]]];
}
$body = $response->body;
$array = json_decode($body, true);
$responseHeaders = $response->headers->getAll();
$statusCode = $response->status_code;
$array['ResponseHeaders'] = $responseHeaders;
$array['ResponseStatusCode'] = $statusCode;
if ($this->isErrorResponse($array)) {
$this->throwResponseError($array);
}
return $array;
}
}

View File

@ -0,0 +1,229 @@
<?php
/**
* BunqPrerequisites.php
* Copyright (c) 2017 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\Prerequisites;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Jobs\GetSpectreProviders;
use FireflyIII\Models\Configuration;
use FireflyIII\Models\Preference;
use FireflyIII\User;
use Illuminate\Http\Request;
use Illuminate\Support\MessageBag;
use Log;
use Preferences;
use Requests;
use Requests_Exception;
/**
* This class contains all the routines necessary to connect to Bunq.
*/
class SpectrePrerequisites implements PrerequisitesInterface
{
/** @var User */
private $user;
/**
* Returns view name that allows user to fill in prerequisites. Currently asks for the API key.
*
* @return string
*/
public function getView(): string
{
return 'import.spectre.prerequisites';
}
/**
* Returns any values required for the prerequisites-view.
*
* @return array
*/
public function getViewParameters(): array
{
$publicKey = $this->getPublicKey();
$subTitle = strval(trans('bank.spectre_title'));
$subTitleIcon = 'fa-archive';
return compact('publicKey', 'subTitle', 'subTitleIcon');
}
/**
* Returns if this import method has any special prerequisites such as config
* variables or other things. The only thing we verify is the presence of the API key. Everything else
* tumbles into place: no installation token? Will be requested. No device server? Will be created. Etc.
*
* @return bool
*/
public function hasPrerequisites(): bool
{
$values = [
Preferences::getForUser($this->user, 'spectre_client_id', false),
Preferences::getForUser($this->user, 'spectre_app_secret', false),
Preferences::getForUser($this->user, 'spectre_service_secret', false),
];
/** @var Preference $value */
foreach ($values as $value) {
if (false === $value->data || null === $value->data) {
Log::info(sprintf('Config var "%s" is missing.', $value->name));
return true;
}
}
Log::debug('All prerequisites are here!');
// at this point, check if all providers are present. Providers are shared amongst
// users in a multi-user environment.
GetSpectreProviders::dispatch($this->user);
return false;
}
/**
* 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;
return;
}
/**
* This method responds to the user's submission of an API key. It tries to register this instance as a new Firefly III device.
* If this fails, the error is returned in a message bag and the user is notified (this is fairly friendly).
*
* @param Request $request
*
* @return MessageBag
*/
public function storePrerequisites(Request $request): MessageBag
{
Log::debug('Storing Spectre API keys..');
Preferences::setForUser($this->user, 'spectre_client_id', $request->get('client_id'));
Preferences::setForUser($this->user, 'spectre_app_secret', $request->get('app_secret'));
Preferences::setForUser($this->user, 'spectre_service_secret', $request->get('service_secret'));
Log::debug('Done!');
return new MessageBag;
}
/**
* This method creates a new public/private keypair for the user. This isn't really secure, since the key is generated on the fly with
* no regards for HSM's, smart cards or other things. It would require some low level programming to get this right. But the private key
* is stored encrypted in the database so it's something.
*/
private function createKeyPair(): void
{
Log::debug('Generate new Spectre key pair for user.');
$keyConfig = [
'digest_alg' => 'sha512',
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
];
// Create the private and public key
$res = openssl_pkey_new($keyConfig);
// Extract the private key from $res to $privKey
$privKey = '';
openssl_pkey_export($res, $privKey);
// Extract the public key from $res to $pubKey
$pubKey = openssl_pkey_get_details($res);
Preferences::setForUser($this->user, 'spectre_private_key', $privKey);
Preferences::setForUser($this->user, 'spectre_public_key', $pubKey['key']);
Log::debug('Created key pair');
return;
}
/**
* Get the private key from the users preferences.
*
* @return string
*/
private function getPrivateKey(): string
{
Log::debug('get private key');
$preference = Preferences::getForUser($this->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.
*
* @return string
*/
private function getPublicKey(): string
{
Log::debug('get public key');
$preference = Preferences::getForUser($this->user, 'spectre_public_key', null);
if (null === $preference) {
Log::debug('public key is null');
// create key pair
$this->createKeyPair();
}
$preference = Preferences::getForUser($this->user, 'spectre_public_key', null);
Log::debug('Return public key for user');
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;
}
}

View File

@ -28,39 +28,49 @@ declare(strict_types=1);
*/
return [
'configuration' => [
'configuration' => [
'single_user_mode' => true,
'is_demo_site' => false,
],
'encryption' => (is_null(env('USE_ENCRYPTION')) || env('USE_ENCRYPTION') === true),
'version' => '4.6.11.1',
'maxUploadSize' => 15242880,
'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf','text/plain'],
'list_length' => 10,
'export_formats' => [
'encryption' => (is_null(env('USE_ENCRYPTION')) || env('USE_ENCRYPTION') === true),
'version' => '4.6.11.1',
'maxUploadSize' => 15242880,
'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf', 'text/plain'],
'list_length' => 10,
'export_formats' => [
'csv' => 'FireflyIII\Export\Exporter\CsvExporter',
],
'import_formats' => [
'import_formats' => [
'csv' => 'FireflyIII\Import\Configurator\CsvConfigurator',
],
'import_configurators' => [
'import_configurators' => [
'csv' => 'FireflyIII\Import\Configurator\CsvConfigurator',
],
'import_processors' => [
'import_processors' => [
'csv' => 'FireflyIII\Import\FileProcessor\CsvProcessor',
],
'import_pre' => [
'bunq' => 'FireflyIII\Support\Import\Prerequisites\BunqPrerequisites',
'import_pre' => [
'bunq' => 'FireflyIII\Support\Import\Prerequisites\BunqPrerequisites',
'spectre' => 'FireflyIII\Support\Import\Prerequisites\SpectrePrerequisites',
'plaid' => 'FireflyIII\Support\Import\Prerequisites\PlairPrerequisites',
],
'import_info' => [
'bunq' => 'FireflyIII\Support\Import\Information\BunqInformation',
'import_info' => [
'bunq' => 'FireflyIII\Support\Import\Information\BunqInformation',
'spectre' => 'FireflyIII\Support\Import\Information\SpectreInformation',
'plaid' => 'FireflyIII\Support\Import\Information\PlaidInformation',
],
'import_transactions' => [
'bunq' => 'FireflyIII\Support\Import\Transactions\BunqTransactions',
'import_transactions' => [
'bunq' => 'FireflyIII\Support\Import\Transactions\BunqTransactions',
'spectre' => 'FireflyIII\Support\Import\Transactions\SpectreTransactions',
'plaid' => 'FireflyIII\Support\Import\Transactions\PlaidTransactions',
],
'bunq' => [
'bunq' => [
'server' => 'https://sandbox.public.api.bunq.com',
],
'spectre' => [
'server' => 'https://www.saltedge.com',
],
'default_export_format' => 'csv',
'default_import_format' => 'csv',
'bill_periods' => ['weekly', 'monthly', 'quarterly', 'half-year', 'yearly'],

View File

@ -0,0 +1,47 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class ChangesForSpectre extends Migration
{
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
// create provider table:
if (!Schema::hasTable('spectre_providers')) {
Schema::create(
'spectre_providers',
function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
$table->softDeletes();
//'spectre_id', 'code', 'mode', 'name', 'status', 'interactive', 'automatic_fetch', 'country_code', 'data'
$table->integer('spectre_id', false, true);
$table->string('code', 100);
$table->string('mode', 20);
$table->string('status', 20);
$table->boolean('interactive')->default(0);
$table->boolean('automatic_fetch')->default(0);
$table->string('country_code', 3);
$table->text('data');
}
);
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
public/images/logos/csv.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -3,6 +3,12 @@ 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 <a href="https://www.saltedge.com/clients/profile/secrets">secrets page</a>.',
'spectre_enter_pub_key' => 'The import will only work when you enter this public key on your <a href="https://www.saltedge.com/clients/security/edit">security page</a>.',
];

View File

@ -186,6 +186,10 @@ return [
'csv_delimiter' => 'CSV field delimiter',
'csv_import_account' => 'Default import account',
'csv_config' => 'CSV import configuration',
'client_id' => 'Client ID',
'service_secret' => 'Service secret',
'app_secret' => 'App secret',
'public_key' => 'Public key',
'due_date' => 'Due date',

View File

@ -20,21 +20,34 @@
</div>
<div class="row">
<div class="col-lg-8">
<p>
<div class="col-lg-1 text-center">
{# file import #}
<a href="{{ route('import.file.index') }}" class="btn btn-app">
<i class="fa fa-file-text-o"></i>
<a href="{{ route('import.file.index') }}">
<img src="images/logos/csv.png" alt="bunq"/><br />
{{ 'import_general_index_csv_file'|_ }}
</a>
</div>
<div class="col-lg-1 text-center">
{# bunq import #}
{#
<a href="{{ route('import.bank.prerequisites', ['bunq']) }}" class="btn btn-app">
<a href="{{ route('import.bank.prerequisites', ['bunq']) }}">
<img src="images/logos/bunq.png" alt="bunq"/><br />
Import from bunq
</a>
#}
</p>
</div>
<div class="col-lg-1 text-center">
{# import from Spectre #}
<a href="{{ route('import.bank.prerequisites', ['spectre']) }}">
<img src="images/logos/spectre.png" alt="Spectre"/><br />
Import using Spectre
</a>
</div>
<div class="col-lg-1 text-center">
{# import from Plaid #}
<a href="{{ route('import.bank.prerequisites', ['plaid']) }}">
<img src="images/logos/plaid.png" alt="Plaid"/><br />
Import using Plaid
</a>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,58 @@
{% extends "./layout/default" %}
{% block breadcrumbs %}
{{ Breadcrumbs.renderIfExists }}
{% endblock %}
{% block content %}
<div class="row">
<form class="form-horizontal" action="{{ route('import.bank.prerequisites.post',['spectre']) }}" 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('bank.spectre_prerequisites_title') }}</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-lg-8">
<p>
{{ trans('bank.spectre_prerequisites_text')|raw }}
</p>
</div>
</div>
<div class="row">
<div class="col-lg-8">
{{ ExpandedForm.text('client_id') }}
{{ ExpandedForm.text('service_secret') }}
{{ ExpandedForm.text('app_secret') }}
</div>
</div>
<div class="row">
<div class="col-lg-8">
<p>{{ trans('bank.spectre_enter_pub_key')|raw }}</p>
<div class="form-group" id="pub_key_holder">
<label for="ffInput_pub_key_holder" class="col-sm-4 control-label">{{ trans('form.public_key') }}</label>
<div class="col-sm-8">
<textarea class="form-control"
rows="10"
id="ffInput_pub_key_holder" name="pub_key_holder" contenteditable="false">{{ publicKey }}</textarea>
</div>
</div>
</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 %}