From bf778aa616c8ff92baebbed3eaf3e3f1000b399b Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 20 Jan 2016 01:07:31 +0200 Subject: [PATCH] Initial work on OFX support --- app/Console/Commands/TestOFX.php | 30 +++ app/Console/Kernel.php | 1 + app/Http/Controllers/AccountController.php | 17 ++ .../Controllers/BankAccountController.php | 152 ++++++++++++ app/Http/Middleware/StartupCheck.php | 2 +- app/Http/routes.php | 11 + app/Libraries/OFX.php | 225 ++++++++++++++++++ app/Libraries/Utils.php | 33 +++ app/Models/Account.php | 6 + app/Models/Bank.php | 15 ++ app/Models/BankAccount.php | 23 ++ .../Repositories/BankAccountRepository.php | 24 ++ app/Services/BankAccountService.php | 107 +++++++++ composer.json | 3 +- .../2016_01_18_195351_add_bank_accounts.php | 56 +++++ database/seeds/BanksSeeder.php | 44 ++++ database/seeds/DatabaseSeeder.php | 3 +- resources/lang/en/texts.php | 17 +- .../views/accounts/bank_account.blade.php | 199 ++++++++++++++++ resources/views/accounts/banks.blade.php | 31 +++ 20 files changed, 995 insertions(+), 4 deletions(-) create mode 100644 app/Console/Commands/TestOFX.php create mode 100644 app/Http/Controllers/BankAccountController.php create mode 100644 app/Libraries/OFX.php create mode 100644 app/Models/Bank.php create mode 100644 app/Models/BankAccount.php create mode 100644 app/Ninja/Repositories/BankAccountRepository.php create mode 100644 app/Services/BankAccountService.php create mode 100644 database/migrations/2016_01_18_195351_add_bank_accounts.php create mode 100644 database/seeds/BanksSeeder.php create mode 100644 resources/views/accounts/bank_account.blade.php create mode 100644 resources/views/accounts/banks.blade.php diff --git a/app/Console/Commands/TestOFX.php b/app/Console/Commands/TestOFX.php new file mode 100644 index 000000000000..637451fbba68 --- /dev/null +++ b/app/Console/Commands/TestOFX.php @@ -0,0 +1,30 @@ +bankAccountService = $bankAccountService; + } + + public function fire() + { + $this->info(date('Y-m-d').' Running TestOFX...'); + + $bankId = env('TEST_BANK_ID'); + $username = env('TEST_BANK_USERNAME'); + $password = env('TEST_BANK_PASSWORD'); + + $data = $this->bankAccountService->loadBankAccounts($bankId, $username, $password, false); + + print "
".print_r($data, 1)."
"; + } +} \ No newline at end of file diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 03b6ce776484..e281afb926d4 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -17,6 +17,7 @@ class Kernel extends ConsoleKernel 'App\Console\Commands\CheckData', 'App\Console\Commands\SendRenewalInvoices', 'App\Console\Commands\SendReminders', + 'App\Console\Commands\TestOFX', ]; /** diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index e77bdf0b23ce..ddf43bb33050 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -144,6 +144,8 @@ class AccountController extends BaseController return self::showLocalization(); } elseif ($section == ACCOUNT_PAYMENTS) { return self::showOnlinePayments(); + } elseif ($section == ACCOUNT_BANKS) { + return self::showBankAccounts(); } elseif ($section == ACCOUNT_INVOICE_SETTINGS) { return self::showInvoiceSettings(); } elseif ($section == ACCOUNT_IMPORT_EXPORT) { @@ -263,6 +265,21 @@ class AccountController extends BaseController return View::make('accounts.localization', $data); } + private function showBankAccounts() + { + $account = Auth::user()->account; + $account->load('bank_accounts'); + $count = count($account->bank_accounts); + + if ($count == 0) { + return Redirect::to('bank_accounts/create'); + } else { + return View::make('accounts.banks', [ + 'title' => trans('texts.bank_accounts') + ]); + } + } + private function showOnlinePayments() { $account = Auth::user()->account; diff --git a/app/Http/Controllers/BankAccountController.php b/app/Http/Controllers/BankAccountController.php new file mode 100644 index 000000000000..3b0f190ef1ec --- /dev/null +++ b/app/Http/Controllers/BankAccountController.php @@ -0,0 +1,152 @@ +bankAccountService = $bankAccountService; + } + + public function index() + { + return Redirect::to('settings/' . ACCOUNT_BANKS); + } + + public function getDatatable() + { + return $this->bankAccountService->getDatatable(Auth::user()->account_id); + } + + public function edit($publicId) + { + $bankAccount = BankAccount::scope($publicId)->firstOrFail(); + $bankAccount->username = str_repeat('*', 16); + + $data = [ + 'url' => 'bank_accounts/' . $publicId, + 'method' => 'PUT', + 'title' => trans('texts.edit_bank_account'), + 'banks' => Cache::get('banks'), + 'bankAccount' => $bankAccount, + ]; + + return View::make('accounts.bank_account', $data); + } + + public function update($publicId) + { + return $this->save($publicId); + } + + public function store() + { + return $this->save(); + } + + /** + * Displays the form for account creation + * + */ + public function create() + { + $data = [ + 'url' => 'bank_accounts', + 'method' => 'POST', + 'title' => trans('texts.add_bank_account'), + 'banks' => Cache::get('banks'), + 'bankAccount' => null, + ]; + + return View::make('accounts.bank_account', $data); + } + + public function bulk() + { + $action = Input::get('bulk_action'); + $ids = Input::get('bulk_public_id'); + $count = $this->bankAccountService->bulk($ids, $action); + + Session::flash('message', trans('texts.archived_bank_account')); + + return Redirect::to('settings/' . ACCOUNT_BANKS); + } + + /** + * Stores new account + * + */ + public function save($bankAccountPublicId = false) + { + $account = Auth::user()->account; + $bankId = Input::get('bank_id'); + $username = Input::get('bank_username'); + + $rules = [ + 'bank_id' => $bankAccountPublicId ? '' : 'required', + 'bank_username' => 'required', + ]; + + $validator = Validator::make(Input::all(), $rules); + + if ($validator->fails()) { + return Redirect::to('bank_accounts/create') + ->withErrors($validator) + ->withInput(); + } else { + if ($bankAccountPublicId) { + $bankAccount = BankAccount::scope($bankAccountPublicId)->firstOrFail(); + } else { + $bankAccount = BankAccount::createNew(); + $bankAccount->bank_id = $bankId; + } + + if ($username != str_repeat('*', strlen($username))) { + $bankAccount->username = Crypt::encrypt(trim($username)); + } + + if ($bankAccountPublicId) { + $bankAccount->save(); + $message = trans('texts.updated_bank_account'); + } else { + $account->bank_accounts()->save($bankAccount); + $message = trans('texts.created_bank_account'); + } + + Session::flash('message', $message); + return Redirect::to("bank_accounts/{$bankAccount->public_id}/edit"); + } + } + + public function test() + { + $bankId = Input::get('bank_id'); + $username = Input::get('bank_username'); + $password = Input::get('bank_password'); + + return json_encode($this->bankAccountService->loadBankAccounts($bankId, $username, $password, false)); + } + +} diff --git a/app/Http/Middleware/StartupCheck.php b/app/Http/Middleware/StartupCheck.php index e3ad3a6ba5fa..bfda7bcb4916 100644 --- a/app/Http/Middleware/StartupCheck.php +++ b/app/Http/Middleware/StartupCheck.php @@ -163,7 +163,7 @@ class StartupCheck $orderBy = 'num_days'; } elseif ($name == 'fonts') { $orderBy = 'sort_order'; - } elseif (in_array($name, ['currencies', 'industries', 'languages', 'countries'])) { + } elseif (in_array($name, ['currencies', 'industries', 'languages', 'countries', 'banks'])) { $orderBy = 'name'; } else { $orderBy = 'id'; diff --git a/app/Http/routes.php b/app/Http/routes.php index 4381c4177dee..a5e903eb3cf8 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -132,6 +132,11 @@ Route::group(['middleware' => 'auth'], function() { Route::get('api/gateways', array('as'=>'api.gateways', 'uses'=>'AccountGatewayController@getDatatable')); Route::post('account_gateways/bulk', 'AccountGatewayController@bulk'); + Route::resource('bank_accounts', 'BankAccountController'); + Route::get('api/bank_accounts', array('as'=>'api.bank_accounts', 'uses'=>'BankAccountController@getDatatable')); + Route::post('bank_accounts/bulk', 'BankAccountController@bulk'); + Route::post('bank_accounts/test', 'BankAccountController@test'); + Route::resource('clients', 'ClientController'); Route::get('api/clients', array('as'=>'api.clients', 'uses'=>'ClientController@getDatatable')); Route::get('api/activities/{client_id?}', array('as'=>'api.activities', 'uses'=>'ActivityController@getDatatable')); @@ -253,6 +258,7 @@ if (!defined('CONTACT_EMAIL')) { define('ENTITY_QUOTE', 'quote'); define('ENTITY_TASK', 'task'); define('ENTITY_ACCOUNT_GATEWAY', 'account_gateway'); + define('ENTITY_BANK_ACCOUNT', 'bank_account'); define('ENTITY_USER', 'user'); define('ENTITY_TOKEN', 'token'); define('ENTITY_TAX_RATE', 'tax_rate'); @@ -271,6 +277,7 @@ if (!defined('CONTACT_EMAIL')) { define('ACCOUNT_NOTIFICATIONS', 'notifications'); define('ACCOUNT_IMPORT_EXPORT', 'import_export'); define('ACCOUNT_PAYMENTS', 'online_payments'); + define('ACCOUNT_BANKS', 'bank_accounts'); define('ACCOUNT_MAP', 'import_map'); define('ACCOUNT_EXPORT', 'export'); define('ACCOUNT_TAX_RATES', 'tax_rates'); @@ -518,6 +525,8 @@ if (!defined('CONTACT_EMAIL')) { define('EMAIL_DESIGN_LIGHT', 2); define('EMAIL_DESIGN_DARK', 3); + define('BANK_LIBRARY_OFX', 1); + $creditCards = [ 1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'], 2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'], @@ -543,6 +552,7 @@ if (!defined('CONTACT_EMAIL')) { 'frequencies' => 'App\Models\Frequency', 'gateways' => 'App\Models\Gateway', 'fonts' => 'App\Models\Font', + 'banks' => 'App\Models\Bank', ]; define('CACHED_TABLES', serialize($cachedTables)); @@ -596,3 +606,4 @@ if (Auth::check() && Auth::user()->id === 1) Auth::loginUsingId(1); } */ + diff --git a/app/Libraries/OFX.php b/app/Libraries/OFX.php new file mode 100644 index 000000000000..734a27be30b5 --- /dev/null +++ b/app/Libraries/OFX.php @@ -0,0 +1,225 @@ +bank = $bank; + $this->request = $request; + } + public function go() + { + $c = curl_init(); + curl_setopt($c, CURLOPT_URL, $this->bank->url); + curl_setopt($c, CURLOPT_POST, 1); + curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-Type: application/x-ofx')); + curl_setopt($c, CURLOPT_POSTFIELDS, $this->request); + curl_setopt($c, CURLOPT_RETURNTRANSFER, 1); + //curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false); + $this->response = curl_exec($c); + curl_close($c); + $tmp = explode('', $this->response); + $this->responseHeader = $tmp[0]; + $this->responseBody = ''.$tmp[1]; + } + public function xml() + { + $xml = $this->responseBody; + self::closeTags($xml); + $x = new SimpleXMLElement($xml); + + return $x; + } + public static function closeTags(&$x) + { + $x = preg_replace('/(<([^<\/]+)>)(?!.*?<\/\2>)([^<]+)/', '\1\3', $x); + } +} + +class Finance +{ + public $banks; +} + +class Bank +{ + public $logins; // array of class User + public $finance; // the Finance object that hold this Bank object + public $fid; + public $org; + public $url; + public function __construct($finance, $fid, $url, $org) + { + $this->finance = $finance; + $this->fid = $fid; + $this->url = $url; + $this->org = $org; + } +} + +class Login +{ + public $accounts; + public $bank; + public $id; + public $pass; + public function __construct($bank, $id, $pass) + { + $this->bank = $bank; + $this->id = $id; + $this->pass = $pass; + } + public function setup() + { + $ofxRequest = + "OFXHEADER:100\n". + "DATA:OFXSGML\n". + "VERSION:102\n". + "SECURITY:NONE\n". + "ENCODING:USASCII\n". + "CHARSET:1252\n". + "COMPRESSION:NONE\n". + "OLDFILEUID:NONE\n". + "NEWFILEUID:NONE\n". + "\n". + "\n". + "\n". + "\n". + "20110412162900.000[-7:MST]\n". + "".$this->id."\n". + "".$this->pass."\n". + "N\n". + "ENG\n". + "\n". + "".$this->bank->org."\n". + "".$this->bank->fid."\n". + "\n". + "QMOFX\n". + "1900\n". + "\n". + "\n". + "\n". + "\n". + "".md5(time().$this->bank->url.$this->id)."\n". + "\n". + "19900101\n". + "\n". + " \n". + "\n". + "\n"; + $o = new OFX($this->bank, $ofxRequest); + $o->go(); + $x = $o->xml(); + foreach ($x->xpath('/OFX/SIGNUPMSGSRSV1/ACCTINFOTRNRS/ACCTINFORS/ACCTINFO/BANKACCTINFO/BANKACCTFROM') as $a) { + $this->accounts[] = new Account($this, (string) $a->ACCTID, 'BANK', (string) $a->ACCTTYPE, (string) $a->BANKID); + } + foreach ($x->xpath('/OFX/SIGNUPMSGSRSV1/ACCTINFOTRNRS/ACCTINFORS/ACCTINFO/CCACCTINFO/CCACCTFROM') as $a) { + $this->accounts[] = new Account($this, (string) $a->ACCTID, 'CC'); + } + } +} + +class Account +{ + public $login; + public $id; + public $type; + public $subType; + public $bankId; + public $ledgerBalance; + public $availableBalance; + public $response; + public function __construct($login, $id, $type, $subType = null, $bankId = null) + { + $this->login = $login; + $this->id = $id; + $this->type = $type; + $this->subType = $subType; + $this->bankId = $bankId; + } + public function setup($includeTransactions = true) + { + $ofxRequest = + "OFXHEADER:100\n". + "DATA:OFXSGML\n". + "VERSION:102\n". + "SECURITY:NONE\n". + "ENCODING:USASCII\n". + "CHARSET:1252\n". + "COMPRESSION:NONE\n". + "OLDFILEUID:NONE\n". + "NEWFILEUID:NONE\n". + "\n". + "\n". + "\n". + "\n". + "20110412162900.000[-7:MST]\n". + "".$this->login->id."\n". + "".$this->login->pass."\n". + "ENG\n". + "\n". + "".$this->login->bank->org."\n". + "".$this->login->bank->fid."\n". + "\n". + "QMOFX\n". + "1900\n". + "\n". + "\n"; + if ($this->type == 'BANK') { + $ofxRequest .= + " \n". + " \n". + " ".md5(time().$this->login->bank->url.$this->id)."\n". + " \n". + " \n". + " ".$this->bankId."\n". + " ".$this->id."\n". + " ".$this->subType."\n". + " \n". + " \n". + " 20110301\n". + " ".($includeTransactions ? 'Y' : 'N')."\n". + " \n". + " \n". + " \n". + " \n"; + } elseif ($this->type == 'CC') { + $ofxRequest .= + " \n". + " \n". + " ".md5(time().$this->login->bank->url.$this->id)."\n". + " \n". + " \n". + " ".$this->id."\n". + " \n". + " \n". + " 20110320\n". + " ".($includeTransactions ? 'Y' : 'N')."\n". + " \n". + " \n". + " \n". + " \n"; + } + $ofxRequest .= + ""; + $o = new OFX($this->login->bank, $ofxRequest); + $o->go(); + $this->response = $o->response; + $x = $o->xml(); + $a = $x->xpath('/OFX/*/*/*/LEDGERBAL/BALAMT'); + $this->ledgerBalance = (double) $a[0]; + $a = $x->xpath('/OFX/*/*/*/AVAILBAL/BALAMT'); + if (isset($a[0])) { + $this->availableBalance = (double) $a[0]; + } + } +} diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index 4ea9c70a53e1..7f1134d5fc31 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -311,6 +311,39 @@ class Utils return $string; } + public static function maskAccountNumber($value) + { + $length = strlen($value); + if ($length < 4) { + str_repeat('*', 16); + } + + $lastDigits = substr($value, -4); + return str_repeat('*', $length - 4) . $lastDigits; + } + + // http://wephp.co/detect-credit-card-type-php/ + public static function getCardType($number) + { + $number = preg_replace('/[^\d]/', '', $number); + + if (preg_match('/^3[47][0-9]{13}$/', $number)) { + return 'American Express'; + } elseif (preg_match('/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/', $number)) { + return 'Diners Club'; + } elseif (preg_match('/^6(?:011|5[0-9][0-9])[0-9]{12}$/', $number)) { + return 'Discover'; + } elseif (preg_match('/^(?:2131|1800|35\d{3})\d{11}$/', $number)) { + return 'JCB'; + } elseif (preg_match('/^5[1-5][0-9]{14}$/', $number)) { + return 'MasterCard'; + } elseif (preg_match('/^4[0-9]{12}(?:[0-9]{3})?$/', $number)) { + return 'Visa'; + } else { + return 'Unknown'; + } + } + public static function toArray($data) { return json_decode(json_encode((array) $data), true); diff --git a/app/Models/Account.php b/app/Models/Account.php index b10aa597ae43..51ec3aa88277 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -26,6 +26,7 @@ class Account extends Eloquent ACCOUNT_USER_DETAILS, ACCOUNT_LOCALIZATION, ACCOUNT_PAYMENTS, + ACCOUNT_BANKS, ACCOUNT_TAX_RATES, ACCOUNT_PRODUCTS, ACCOUNT_NOTIFICATIONS, @@ -79,6 +80,11 @@ class Account extends Eloquent return $this->hasMany('App\Models\AccountGateway'); } + public function bank_accounts() + { + return $this->hasMany('App\Models\BankAccount'); + } + public function tax_rates() { return $this->hasMany('App\Models\TaxRate'); diff --git a/app/Models/Bank.php b/app/Models/Bank.php new file mode 100644 index 000000000000..0874bed64a08 --- /dev/null +++ b/app/Models/Bank.php @@ -0,0 +1,15 @@ +config); + + return new \App\Libraries\Bank($finance, $config->fid, $config->url, $config->org); + } +} diff --git a/app/Models/BankAccount.php b/app/Models/BankAccount.php new file mode 100644 index 000000000000..01ae612dc839 --- /dev/null +++ b/app/Models/BankAccount.php @@ -0,0 +1,23 @@ +belongsTo('App\Models\Bank'); + } + +} + diff --git a/app/Ninja/Repositories/BankAccountRepository.php b/app/Ninja/Repositories/BankAccountRepository.php new file mode 100644 index 000000000000..5ab3148e381b --- /dev/null +++ b/app/Ninja/Repositories/BankAccountRepository.php @@ -0,0 +1,24 @@ +join('banks', 'banks.id', '=', 'bank_accounts.bank_id') + ->where('bank_accounts.deleted_at', '=', null) + ->where('bank_accounts.account_id', '=', $accountId) + ->select('bank_accounts.public_id', 'banks.name as bank_name', 'bank_accounts.deleted_at', 'banks.bank_library_id'); + } +} diff --git a/app/Services/BankAccountService.php b/app/Services/BankAccountService.php new file mode 100644 index 000000000000..6145f6391128 --- /dev/null +++ b/app/Services/BankAccountService.php @@ -0,0 +1,107 @@ +bankAccountRepo = $bankAccountRepo; + $this->datatableService = $datatableService; + } + + protected function getRepo() + { + return $this->bankAccountRepo; + } + + /* + public function save() + { + return null; + } + */ + + public function loadBankAccounts($bankId, $username, $password, $includeTransactions = true) + { + if ( ! $bankId || ! $username || ! $password) { + return false; + } + + $bank = Utils::getFromCache($bankId, 'banks'); + $data = []; + + try { + $finance = new Finance(); + $finance->banks[$bankId] = $bank->getOFXBank($finance); + $finance->banks[$bankId]->logins[] = new Login($finance->banks[$bankId], $username, $password); + + foreach ($finance->banks as $bank) { + foreach ($bank->logins as $login) { + $login->setup(); + foreach ($login->accounts as $account) { + $account->setup($includeTransactions); + $obj = new stdClass; + $obj->account_number = Utils::maskAccountNumber($account->id); + $obj->type = $account->type; + $obj->balance = Utils::formatMoney($account->ledgerBalance, CURRENCY_DOLLAR); + $data[] = $obj; + } + } + } + + return $data; + } catch (\Exception $e) { + return false; + } + } + + public function getDatatable($accountId) + { + $query = $this->bankAccountRepo->find($accountId); + + return $this->createDatatable(ENTITY_BANK_ACCOUNT, $query, false); + } + + protected function getDatatableColumns($entityType, $hideClient) + { + return [ + [ + 'bank_name', + function ($model) { + return link_to("bank_accounts/{$model->public_id}/edit", $model->bank_name); + } + ], + [ + 'bank_library_id', + function ($model) { + return 'OFX'; + } + ], + ]; + } + + protected function getDatatableActions($entityType) + { + return [ + [ + uctrans('texts.edit_bank_account'), + function ($model) { + return URL::to("bank_accounts/{$model->public_id}/edit"); + } + ] + ]; + } + +} \ No newline at end of file diff --git a/composer.json b/composer.json index c02299ac324b..21a91f6e18a7 100644 --- a/composer.json +++ b/composer.json @@ -89,7 +89,8 @@ "App\\": "app/" }, "files": [ - "app/Libraries/lib_autolink.php" + "app/Libraries/lib_autolink.php", + "app/Libraries/OFX.php" ] }, "autoload-dev": { diff --git a/database/migrations/2016_01_18_195351_add_bank_accounts.php b/database/migrations/2016_01_18_195351_add_bank_accounts.php new file mode 100644 index 000000000000..587672a496b6 --- /dev/null +++ b/database/migrations/2016_01_18_195351_add_bank_accounts.php @@ -0,0 +1,56 @@ +increments('id'); + $table->string('name'); + $table->string('remote_id'); + $table->integer('bank_library_id')->default(BANK_LIBRARY_OFX); + $table->text('config'); + }); + + Schema::create('bank_accounts', function($table) + { + $table->increments('id'); + $table->unsignedInteger('account_id'); + $table->unsignedInteger('bank_id'); + $table->unsignedInteger('user_id'); + $table->string('username'); + + $table->timestamps(); + $table->softDeletes(); + + $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('bank_id')->references('id')->on('banks'); + + $table->unsignedInteger('public_id')->index(); + $table->unique(['account_id', 'public_id']); + }); + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('bank_accounts'); + Schema::drop('banks'); + } + +} diff --git a/database/seeds/BanksSeeder.php b/database/seeds/BanksSeeder.php new file mode 100644 index 000000000000..28fa6d300a6d --- /dev/null +++ b/database/seeds/BanksSeeder.php @@ -0,0 +1,44 @@ +createBanks(); + } + + // Source: http://www.ofxhome.com/ + private function createBanks() + { + $banks = [ + [ + 'remote_id' => 425, + 'name' => 'American Express Card', + 'config' => json_encode([ + 'fid' => 3101, + 'org' => 'AMEX', + 'url' => 'https://online.americanexpress.com/myca/ofxdl/desktop/desktopDownload.do?request_type=nl_ofxdownload', + ]) + ], + [ + 'remote_id' => 497, + 'name' => 'AIM Investments', + 'config' => json_encode([ + 'fid' => '', + 'org' => '', + 'url' => 'https://ofx3.financialtrans.com/tf/OFXServer?tx=OFXController&cz=702110804131918&cl=3000812', + ]) + ], + ]; + + foreach ($banks as $bank) { + if (!DB::table('banks')->where('remote_id', '=', $bank['remote_id'])->get()) { + Bank::create($bank); + } + } + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 67ea8073da53..47cc52a9a713 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -16,7 +16,8 @@ class DatabaseSeeder extends Seeder { $this->call('ConstantsSeeder'); $this->call('CountriesSeeder'); $this->call('PaymentLibrariesSeeder'); - $this->call('FontsSeeder'); + $this->call('FontsSeeder'); + $this->call('BanksSeeder'); } } \ No newline at end of file diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index f1b099fe796b..ddec22ce8012 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1038,6 +1038,21 @@ return array( 'quote_message_button' => 'To view your quote for :amount, click the button below.', 'payment_message_button' => 'Thank you for your payment of :amount.', 'payment_type_direct_debit' => 'Direct Debit', - + 'bank_accounts' => 'Bank Accounts', + 'add_bank_account' => 'Add Bank Account', + 'bank_id' => 'bank', + 'integration_type' => 'Integration Type', + 'updated_bank_account' => 'Successfully updated bank account', + 'edit_bank_account' => 'Edit Bank Account', + 'archive_bank_account' => 'Archive Bank Account', + 'archived_bank_account' => 'Successfully archived bank account', + 'created_bank_account' => 'Successfully created bank account', + 'test' => 'Test', + 'test_bank_account' => 'Test Bank Account', + 'bank_password_help' => 'Note: your password is transmitted securely and never stored on our servers.', + 'bank_password_warning' => 'Warning: your password may be transmitted in plain text, consider enabling HTTPS.', + 'username' => 'Username', + 'account_number' => 'Account Number', + 'bank_account_error' => 'Failed to retreive account details, please check your credentials.', ); \ No newline at end of file diff --git a/resources/views/accounts/bank_account.blade.php b/resources/views/accounts/bank_account.blade.php new file mode 100644 index 000000000000..384ffe3e96fd --- /dev/null +++ b/resources/views/accounts/bank_account.blade.php @@ -0,0 +1,199 @@ +@extends('header') + +@section('head') + @parent + + + +@stop + +@section('content') + @parent + + @include('accounts.nav', ['selected' => ACCOUNT_BANKS]) + + {!! Former::open($url) + ->method($method) + ->rule() + ->addClass('main-form warn-on-exit') !!} + +
+
+

{!! trans($title) !!}

+
+
+ + @if ($bankAccount) + {!! Former::populateField('bank_id', $bankAccount->bank_id) !!} + @endif + + {!! Former::select('bank_id') + ->data_bind('dropdown: bank_id') + ->addOption('', '') + ->fromQuery($banks, 'name', 'id') !!} + + {!! Former::password('bank_username') + ->data_bind("value: bank_username, valueUpdate: 'afterkeydown'") + ->label(trans('texts.username')) + ->blockHelp(trans(Request::secure() ? 'texts.bank_password_help' : 'texts.bank_password_warning')) !!} + +
+
+ + + + + +

 

+ + {!! Former::actions( + count(Cache::get('banks')) > 0 ? Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/settings/bank_accounts'))->appendIcon(Icon::create('remove-circle')) : false, + (!$bankAccount ? + Button::primary(trans('texts.test')) + ->withAttributes([ + 'data-bind' => 'css: {disabled: disableMainButton}', + 'onclick' => 'showTest()' + ]) + ->large() + ->appendIcon(Icon::create('download-alt')) + : false), + Button::success(trans('texts.save')) + ->submit()->large() + ->withAttributes([ + 'data-bind' => 'css: {disabled: disableMainButton}', + ]) + ->appendIcon(Icon::create('floppy-disk'))) !!} + + {!! Former::close() !!} + + + + +@stop \ No newline at end of file diff --git a/resources/views/accounts/banks.blade.php b/resources/views/accounts/banks.blade.php new file mode 100644 index 000000000000..5922e22fbb66 --- /dev/null +++ b/resources/views/accounts/banks.blade.php @@ -0,0 +1,31 @@ +@extends('header') + +@section('content') + @parent + @include('accounts.nav', ['selected' => ACCOUNT_BANKS]) + + {!! Button::primary(trans('texts.add_bank_account')) + ->asLinkTo(URL::to('/bank_accounts/create')) + ->withAttributes(['class' => 'pull-right']) + ->appendIcon(Icon::create('plus-sign')) !!} + + @include('partials.bulk_form', ['entityType' => ENTITY_BANK_ACCOUNT]) + + {!! Datatable::table() + ->addColumn( + trans('texts.name'), + trans('texts.integration_type'), + trans('texts.action')) + ->setUrl(url('api/bank_accounts/')) + ->setOptions('sPaginationType', 'bootstrap') + ->setOptions('bFilter', false) + ->setOptions('bAutoWidth', false) + ->setOptions('aoColumns', [[ "sWidth"=> "50%" ], [ "sWidth"=> "30%" ], ["sWidth"=> "20%"]]) + ->setOptions('aoColumnDefs', [['bSortable'=>false, 'aTargets'=>[2]]]) + ->render('datatable') !!} + + + +@stop \ No newline at end of file