mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Initial work on OFX support
This commit is contained in:
parent
3d790d29a1
commit
bf778aa616
30
app/Console/Commands/TestOFX.php
Normal file
30
app/Console/Commands/TestOFX.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php namespace app\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use App\Services\BankAccountService;
|
||||||
|
|
||||||
|
class TestOFX extends Command
|
||||||
|
{
|
||||||
|
protected $name = 'ninja:test-ofx';
|
||||||
|
protected $description = 'Test OFX';
|
||||||
|
|
||||||
|
public function __construct(BankAccountService $bankAccountService)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->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 "<pre>".print_r($data, 1)."</pre>";
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ class Kernel extends ConsoleKernel
|
|||||||
'App\Console\Commands\CheckData',
|
'App\Console\Commands\CheckData',
|
||||||
'App\Console\Commands\SendRenewalInvoices',
|
'App\Console\Commands\SendRenewalInvoices',
|
||||||
'App\Console\Commands\SendReminders',
|
'App\Console\Commands\SendReminders',
|
||||||
|
'App\Console\Commands\TestOFX',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -144,6 +144,8 @@ class AccountController extends BaseController
|
|||||||
return self::showLocalization();
|
return self::showLocalization();
|
||||||
} elseif ($section == ACCOUNT_PAYMENTS) {
|
} elseif ($section == ACCOUNT_PAYMENTS) {
|
||||||
return self::showOnlinePayments();
|
return self::showOnlinePayments();
|
||||||
|
} elseif ($section == ACCOUNT_BANKS) {
|
||||||
|
return self::showBankAccounts();
|
||||||
} elseif ($section == ACCOUNT_INVOICE_SETTINGS) {
|
} elseif ($section == ACCOUNT_INVOICE_SETTINGS) {
|
||||||
return self::showInvoiceSettings();
|
return self::showInvoiceSettings();
|
||||||
} elseif ($section == ACCOUNT_IMPORT_EXPORT) {
|
} elseif ($section == ACCOUNT_IMPORT_EXPORT) {
|
||||||
@ -263,6 +265,21 @@ class AccountController extends BaseController
|
|||||||
return View::make('accounts.localization', $data);
|
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()
|
private function showOnlinePayments()
|
||||||
{
|
{
|
||||||
$account = Auth::user()->account;
|
$account = Auth::user()->account;
|
||||||
|
152
app/Http/Controllers/BankAccountController.php
Normal file
152
app/Http/Controllers/BankAccountController.php
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
<?php namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Crypt;
|
||||||
|
use Cache;
|
||||||
|
use Auth;
|
||||||
|
use Datatable;
|
||||||
|
use DB;
|
||||||
|
use Input;
|
||||||
|
use Redirect;
|
||||||
|
use Session;
|
||||||
|
use View;
|
||||||
|
use Validator;
|
||||||
|
use stdClass;
|
||||||
|
use URL;
|
||||||
|
use Utils;
|
||||||
|
use App\Models\Gateway;
|
||||||
|
use App\Models\Account;
|
||||||
|
use App\Models\BankAccount;
|
||||||
|
use App\Ninja\Repositories\AccountRepository;
|
||||||
|
use App\Services\BankAccountService;
|
||||||
|
|
||||||
|
class BankAccountController extends BaseController
|
||||||
|
{
|
||||||
|
protected $bankAccountService;
|
||||||
|
|
||||||
|
public function __construct(BankAccountService $bankAccountService)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -163,7 +163,7 @@ class StartupCheck
|
|||||||
$orderBy = 'num_days';
|
$orderBy = 'num_days';
|
||||||
} elseif ($name == 'fonts') {
|
} elseif ($name == 'fonts') {
|
||||||
$orderBy = 'sort_order';
|
$orderBy = 'sort_order';
|
||||||
} elseif (in_array($name, ['currencies', 'industries', 'languages', 'countries'])) {
|
} elseif (in_array($name, ['currencies', 'industries', 'languages', 'countries', 'banks'])) {
|
||||||
$orderBy = 'name';
|
$orderBy = 'name';
|
||||||
} else {
|
} else {
|
||||||
$orderBy = 'id';
|
$orderBy = 'id';
|
||||||
|
@ -132,6 +132,11 @@ Route::group(['middleware' => 'auth'], function() {
|
|||||||
Route::get('api/gateways', array('as'=>'api.gateways', 'uses'=>'AccountGatewayController@getDatatable'));
|
Route::get('api/gateways', array('as'=>'api.gateways', 'uses'=>'AccountGatewayController@getDatatable'));
|
||||||
Route::post('account_gateways/bulk', 'AccountGatewayController@bulk');
|
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::resource('clients', 'ClientController');
|
||||||
Route::get('api/clients', array('as'=>'api.clients', 'uses'=>'ClientController@getDatatable'));
|
Route::get('api/clients', array('as'=>'api.clients', 'uses'=>'ClientController@getDatatable'));
|
||||||
Route::get('api/activities/{client_id?}', array('as'=>'api.activities', 'uses'=>'ActivityController@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_QUOTE', 'quote');
|
||||||
define('ENTITY_TASK', 'task');
|
define('ENTITY_TASK', 'task');
|
||||||
define('ENTITY_ACCOUNT_GATEWAY', 'account_gateway');
|
define('ENTITY_ACCOUNT_GATEWAY', 'account_gateway');
|
||||||
|
define('ENTITY_BANK_ACCOUNT', 'bank_account');
|
||||||
define('ENTITY_USER', 'user');
|
define('ENTITY_USER', 'user');
|
||||||
define('ENTITY_TOKEN', 'token');
|
define('ENTITY_TOKEN', 'token');
|
||||||
define('ENTITY_TAX_RATE', 'tax_rate');
|
define('ENTITY_TAX_RATE', 'tax_rate');
|
||||||
@ -271,6 +277,7 @@ if (!defined('CONTACT_EMAIL')) {
|
|||||||
define('ACCOUNT_NOTIFICATIONS', 'notifications');
|
define('ACCOUNT_NOTIFICATIONS', 'notifications');
|
||||||
define('ACCOUNT_IMPORT_EXPORT', 'import_export');
|
define('ACCOUNT_IMPORT_EXPORT', 'import_export');
|
||||||
define('ACCOUNT_PAYMENTS', 'online_payments');
|
define('ACCOUNT_PAYMENTS', 'online_payments');
|
||||||
|
define('ACCOUNT_BANKS', 'bank_accounts');
|
||||||
define('ACCOUNT_MAP', 'import_map');
|
define('ACCOUNT_MAP', 'import_map');
|
||||||
define('ACCOUNT_EXPORT', 'export');
|
define('ACCOUNT_EXPORT', 'export');
|
||||||
define('ACCOUNT_TAX_RATES', 'tax_rates');
|
define('ACCOUNT_TAX_RATES', 'tax_rates');
|
||||||
@ -518,6 +525,8 @@ if (!defined('CONTACT_EMAIL')) {
|
|||||||
define('EMAIL_DESIGN_LIGHT', 2);
|
define('EMAIL_DESIGN_LIGHT', 2);
|
||||||
define('EMAIL_DESIGN_DARK', 3);
|
define('EMAIL_DESIGN_DARK', 3);
|
||||||
|
|
||||||
|
define('BANK_LIBRARY_OFX', 1);
|
||||||
|
|
||||||
$creditCards = [
|
$creditCards = [
|
||||||
1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'],
|
1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'],
|
||||||
2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'],
|
2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'],
|
||||||
@ -543,6 +552,7 @@ if (!defined('CONTACT_EMAIL')) {
|
|||||||
'frequencies' => 'App\Models\Frequency',
|
'frequencies' => 'App\Models\Frequency',
|
||||||
'gateways' => 'App\Models\Gateway',
|
'gateways' => 'App\Models\Gateway',
|
||||||
'fonts' => 'App\Models\Font',
|
'fonts' => 'App\Models\Font',
|
||||||
|
'banks' => 'App\Models\Bank',
|
||||||
];
|
];
|
||||||
define('CACHED_TABLES', serialize($cachedTables));
|
define('CACHED_TABLES', serialize($cachedTables));
|
||||||
|
|
||||||
@ -596,3 +606,4 @@ if (Auth::check() && Auth::user()->id === 1)
|
|||||||
Auth::loginUsingId(1);
|
Auth::loginUsingId(1);
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
225
app/Libraries/OFX.php
Normal file
225
app/Libraries/OFX.php
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
<?php namespace App\Libraries;
|
||||||
|
|
||||||
|
// https://github.com/denvertimothy/OFX
|
||||||
|
|
||||||
|
use SimpleXMLElement;
|
||||||
|
|
||||||
|
class OFX
|
||||||
|
{
|
||||||
|
public $bank;
|
||||||
|
public $request;
|
||||||
|
public $response;
|
||||||
|
public $responseHeader;
|
||||||
|
public $responseBody;
|
||||||
|
public function __construct($bank, $request)
|
||||||
|
{
|
||||||
|
$this->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('<OFX>', $this->response);
|
||||||
|
$this->responseHeader = $tmp[0];
|
||||||
|
$this->responseBody = '<OFX>'.$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</\2>', $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".
|
||||||
|
"<OFX>\n".
|
||||||
|
"<SIGNONMSGSRQV1>\n".
|
||||||
|
"<SONRQ>\n".
|
||||||
|
"<DTCLIENT>20110412162900.000[-7:MST]\n".
|
||||||
|
"<USERID>".$this->id."\n".
|
||||||
|
"<USERPASS>".$this->pass."\n".
|
||||||
|
"<GENUSERKEY>N\n".
|
||||||
|
"<LANGUAGE>ENG\n".
|
||||||
|
"<FI>\n".
|
||||||
|
"<ORG>".$this->bank->org."\n".
|
||||||
|
"<FID>".$this->bank->fid."\n".
|
||||||
|
"</FI>\n".
|
||||||
|
"<APPID>QMOFX\n".
|
||||||
|
"<APPVER>1900\n".
|
||||||
|
"</SONRQ>\n".
|
||||||
|
"</SIGNONMSGSRQV1>\n".
|
||||||
|
"<SIGNUPMSGSRQV1>\n".
|
||||||
|
"<ACCTINFOTRNRQ>\n".
|
||||||
|
"<TRNUID>".md5(time().$this->bank->url.$this->id)."\n".
|
||||||
|
"<ACCTINFORQ>\n".
|
||||||
|
"<DTACCTUP>19900101\n".
|
||||||
|
"</ACCTINFORQ>\n".
|
||||||
|
"</ACCTINFOTRNRQ> \n".
|
||||||
|
"</SIGNUPMSGSRQV1>\n".
|
||||||
|
"</OFX>\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".
|
||||||
|
"<OFX>\n".
|
||||||
|
"<SIGNONMSGSRQV1>\n".
|
||||||
|
"<SONRQ>\n".
|
||||||
|
"<DTCLIENT>20110412162900.000[-7:MST]\n".
|
||||||
|
"<USERID>".$this->login->id."\n".
|
||||||
|
"<USERPASS>".$this->login->pass."\n".
|
||||||
|
"<LANGUAGE>ENG\n".
|
||||||
|
"<FI>\n".
|
||||||
|
"<ORG>".$this->login->bank->org."\n".
|
||||||
|
"<FID>".$this->login->bank->fid."\n".
|
||||||
|
"</FI>\n".
|
||||||
|
"<APPID>QMOFX\n".
|
||||||
|
"<APPVER>1900\n".
|
||||||
|
"</SONRQ>\n".
|
||||||
|
"</SIGNONMSGSRQV1>\n";
|
||||||
|
if ($this->type == 'BANK') {
|
||||||
|
$ofxRequest .=
|
||||||
|
" <BANKMSGSRQV1>\n".
|
||||||
|
" <STMTTRNRQ>\n".
|
||||||
|
" <TRNUID>".md5(time().$this->login->bank->url.$this->id)."\n".
|
||||||
|
" <STMTRQ>\n".
|
||||||
|
" <BANKACCTFROM>\n".
|
||||||
|
" <BANKID>".$this->bankId."\n".
|
||||||
|
" <ACCTID>".$this->id."\n".
|
||||||
|
" <ACCTTYPE>".$this->subType."\n".
|
||||||
|
" </BANKACCTFROM>\n".
|
||||||
|
" <INCTRAN>\n".
|
||||||
|
" <DTSTART>20110301\n".
|
||||||
|
" <INCLUDE>".($includeTransactions ? 'Y' : 'N')."\n".
|
||||||
|
" </INCTRAN>\n".
|
||||||
|
" </STMTRQ>\n".
|
||||||
|
" </STMTTRNRQ>\n".
|
||||||
|
" </BANKMSGSRQV1>\n";
|
||||||
|
} elseif ($this->type == 'CC') {
|
||||||
|
$ofxRequest .=
|
||||||
|
" <CREDITCARDMSGSRQV1>\n".
|
||||||
|
" <CCSTMTTRNRQ>\n".
|
||||||
|
" <TRNUID>".md5(time().$this->login->bank->url.$this->id)."\n".
|
||||||
|
" <CCSTMTRQ>\n".
|
||||||
|
" <CCACCTFROM>\n".
|
||||||
|
" <ACCTID>".$this->id."\n".
|
||||||
|
" </CCACCTFROM>\n".
|
||||||
|
" <INCTRAN>\n".
|
||||||
|
" <DTSTART>20110320\n".
|
||||||
|
" <INCLUDE>".($includeTransactions ? 'Y' : 'N')."\n".
|
||||||
|
" </INCTRAN>\n".
|
||||||
|
" </CCSTMTRQ>\n".
|
||||||
|
" </CCSTMTTRNRQ>\n".
|
||||||
|
" </CREDITCARDMSGSRQV1>\n";
|
||||||
|
}
|
||||||
|
$ofxRequest .=
|
||||||
|
"</OFX>";
|
||||||
|
$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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -311,6 +311,39 @@ class Utils
|
|||||||
return $string;
|
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)
|
public static function toArray($data)
|
||||||
{
|
{
|
||||||
return json_decode(json_encode((array) $data), true);
|
return json_decode(json_encode((array) $data), true);
|
||||||
|
@ -26,6 +26,7 @@ class Account extends Eloquent
|
|||||||
ACCOUNT_USER_DETAILS,
|
ACCOUNT_USER_DETAILS,
|
||||||
ACCOUNT_LOCALIZATION,
|
ACCOUNT_LOCALIZATION,
|
||||||
ACCOUNT_PAYMENTS,
|
ACCOUNT_PAYMENTS,
|
||||||
|
ACCOUNT_BANKS,
|
||||||
ACCOUNT_TAX_RATES,
|
ACCOUNT_TAX_RATES,
|
||||||
ACCOUNT_PRODUCTS,
|
ACCOUNT_PRODUCTS,
|
||||||
ACCOUNT_NOTIFICATIONS,
|
ACCOUNT_NOTIFICATIONS,
|
||||||
@ -79,6 +80,11 @@ class Account extends Eloquent
|
|||||||
return $this->hasMany('App\Models\AccountGateway');
|
return $this->hasMany('App\Models\AccountGateway');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function bank_accounts()
|
||||||
|
{
|
||||||
|
return $this->hasMany('App\Models\BankAccount');
|
||||||
|
}
|
||||||
|
|
||||||
public function tax_rates()
|
public function tax_rates()
|
||||||
{
|
{
|
||||||
return $this->hasMany('App\Models\TaxRate');
|
return $this->hasMany('App\Models\TaxRate');
|
||||||
|
15
app/Models/Bank.php
Normal file
15
app/Models/Bank.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php namespace App\Models;
|
||||||
|
|
||||||
|
use Eloquent;
|
||||||
|
|
||||||
|
class Bank extends Eloquent
|
||||||
|
{
|
||||||
|
public $timestamps = false;
|
||||||
|
|
||||||
|
public function getOFXBank($finance)
|
||||||
|
{
|
||||||
|
$config = json_decode($this->config);
|
||||||
|
|
||||||
|
return new \App\Libraries\Bank($finance, $config->fid, $config->url, $config->org);
|
||||||
|
}
|
||||||
|
}
|
23
app/Models/BankAccount.php
Normal file
23
app/Models/BankAccount.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php namespace App\Models;
|
||||||
|
|
||||||
|
use Crypt;
|
||||||
|
use App\Models\Bank;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
class BankAccount extends EntityModel
|
||||||
|
{
|
||||||
|
use SoftDeletes;
|
||||||
|
protected $dates = ['deleted_at'];
|
||||||
|
|
||||||
|
public function getEntityType()
|
||||||
|
{
|
||||||
|
return ENTITY_BANK_ACCOUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bank()
|
||||||
|
{
|
||||||
|
return $this->belongsTo('App\Models\Bank');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
24
app/Ninja/Repositories/BankAccountRepository.php
Normal file
24
app/Ninja/Repositories/BankAccountRepository.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php namespace App\Ninja\Repositories;
|
||||||
|
|
||||||
|
use DB;
|
||||||
|
use Utils;
|
||||||
|
use Session;
|
||||||
|
use App\Models\BankAccount;
|
||||||
|
use App\Ninja\Repositories\BaseRepository;
|
||||||
|
|
||||||
|
class BankAccountRepository extends BaseRepository
|
||||||
|
{
|
||||||
|
public function getClassName()
|
||||||
|
{
|
||||||
|
return 'App\Models\BankAccount';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find($accountId)
|
||||||
|
{
|
||||||
|
return DB::table('bank_accounts')
|
||||||
|
->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');
|
||||||
|
}
|
||||||
|
}
|
107
app/Services/BankAccountService.php
Normal file
107
app/Services/BankAccountService.php
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<?php namespace App\Services;
|
||||||
|
|
||||||
|
use stdClass;
|
||||||
|
use Utils;
|
||||||
|
use URL;
|
||||||
|
use App\Models\Gateway;
|
||||||
|
use App\Services\BaseService;
|
||||||
|
use App\Ninja\Repositories\BankAccountRepository;
|
||||||
|
|
||||||
|
use App\Libraries\Finance;
|
||||||
|
use App\Libraries\Login;
|
||||||
|
|
||||||
|
class BankAccountService extends BaseService
|
||||||
|
{
|
||||||
|
protected $bankAccountRepo;
|
||||||
|
protected $datatableService;
|
||||||
|
|
||||||
|
public function __construct(BankAccountRepository $bankAccountRepo, DatatableService $datatableService)
|
||||||
|
{
|
||||||
|
$this->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");
|
||||||
|
}
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -89,7 +89,8 @@
|
|||||||
"App\\": "app/"
|
"App\\": "app/"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"app/Libraries/lib_autolink.php"
|
"app/Libraries/lib_autolink.php",
|
||||||
|
"app/Libraries/OFX.php"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
|
56
database/migrations/2016_01_18_195351_add_bank_accounts.php
Normal file
56
database/migrations/2016_01_18_195351_add_bank_accounts.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class AddBankAccounts extends Migration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('banks', function($table)
|
||||||
|
{
|
||||||
|
$table->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');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
44
database/seeds/BanksSeeder.php
Normal file
44
database/seeds/BanksSeeder.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Bank;
|
||||||
|
|
||||||
|
class BanksSeeder extends Seeder
|
||||||
|
{
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
Eloquent::unguard();
|
||||||
|
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,8 @@ class DatabaseSeeder extends Seeder {
|
|||||||
$this->call('ConstantsSeeder');
|
$this->call('ConstantsSeeder');
|
||||||
$this->call('CountriesSeeder');
|
$this->call('CountriesSeeder');
|
||||||
$this->call('PaymentLibrariesSeeder');
|
$this->call('PaymentLibrariesSeeder');
|
||||||
$this->call('FontsSeeder');
|
$this->call('FontsSeeder');
|
||||||
|
$this->call('BanksSeeder');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1038,6 +1038,21 @@ return array(
|
|||||||
'quote_message_button' => 'To view your quote for :amount, click the button below.',
|
'quote_message_button' => 'To view your quote for :amount, click the button below.',
|
||||||
'payment_message_button' => 'Thank you for your payment of :amount.',
|
'payment_message_button' => 'Thank you for your payment of :amount.',
|
||||||
'payment_type_direct_debit' => 'Direct Debit',
|
'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.',
|
||||||
|
|
||||||
);
|
);
|
199
resources/views/accounts/bank_account.blade.php
Normal file
199
resources/views/accounts/bank_account.blade.php
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
@extends('header')
|
||||||
|
|
||||||
|
@section('head')
|
||||||
|
@parent
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
table.accounts-table > thead > tr > th.header {
|
||||||
|
background-color: #e37329 !important;
|
||||||
|
color:#fff !important;
|
||||||
|
padding-top:8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
@stop
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
@parent
|
||||||
|
|
||||||
|
@include('accounts.nav', ['selected' => ACCOUNT_BANKS])
|
||||||
|
|
||||||
|
{!! Former::open($url)
|
||||||
|
->method($method)
|
||||||
|
->rule()
|
||||||
|
->addClass('main-form warn-on-exit') !!}
|
||||||
|
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">{!! trans($title) !!}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body form-padding-right">
|
||||||
|
|
||||||
|
@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')) !!}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="modal fade" id="testModal" tabindex="-1" role="dialog" aria-labelledby="testModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog" style="min-width:150px">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h4 class="modal-title" id="testModalLabel">{!! trans('texts.test_bank_account') !!}</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-body row">
|
||||||
|
<div class="form-group" style="padding-bottom:30px">
|
||||||
|
<label for="username" class="control-label col-lg-4 col-sm-4">{{ trans('texts.password') }}</label>
|
||||||
|
<div class="col-lg-6 col-sm-6">
|
||||||
|
<input class="form-control" id="bank_password" name="bank_password" type="password" data-bind="value: bank_password, valueUpdate: 'afterkeydown'"><br/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-12 col-sm-12" data-bind="visible: state() == 'loading'">
|
||||||
|
<div class="progress">
|
||||||
|
<div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 100%">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-12 col-sm-12" data-bind="visible: state() == 'error'">
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
|
||||||
|
{{ trans('texts.bank_account_error') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-12 col-sm-12" data-bind="visible: bank_accounts().length">
|
||||||
|
<table class="table table-striped accounts-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="header">{{ trans('texts.account_number') }}</th>
|
||||||
|
<th class="header">{{ trans('texts.type') }}</th>
|
||||||
|
<th class="header">{{ trans('texts.balance') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody data-bind="foreach: bank_accounts">
|
||||||
|
<tr>
|
||||||
|
<td data-bind="text: account_number"></td>
|
||||||
|
<td data-bind="text: type"></td>
|
||||||
|
<td data-bind="text: balance"></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer" style="margin-top: 0px; padding-top:30px;">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('texts.close') }}</button>
|
||||||
|
<button type="button" class="btn btn-success" onclick="doTest()" data-bind="css: { disabled: disableDoTest }">{{ trans('texts.test') }}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<p/> <p/>
|
||||||
|
|
||||||
|
{!! 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() !!}
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
function showTest() {
|
||||||
|
$('#testModal').modal('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function doTest() {
|
||||||
|
model.state('loading');
|
||||||
|
$.post('{{ URL::to('/bank_accounts/test') }}', $('.main-form').serialize())
|
||||||
|
.done(function(data) {
|
||||||
|
model.state('');
|
||||||
|
data = JSON.parse(data);
|
||||||
|
if (!data || !data.length) {
|
||||||
|
model.state('error');
|
||||||
|
} else {
|
||||||
|
for (var i=0; i<data.length; i++) {
|
||||||
|
model.bank_accounts.push(data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).fail(function() {
|
||||||
|
model.state('error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
@if ($bankAccount)
|
||||||
|
$('#bank_id').prop('disabled', true);
|
||||||
|
@else
|
||||||
|
$('#bank_id').combobox().on('change', function(e) {
|
||||||
|
model.bank_id($('#bank_id').val());
|
||||||
|
});
|
||||||
|
@endif
|
||||||
|
|
||||||
|
$('#testModal').on('shown.bs.modal', function() {
|
||||||
|
$('#bank_password').focus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Here's my data model
|
||||||
|
var ViewModel = function() {
|
||||||
|
var self = this;
|
||||||
|
self.bank_id = ko.observable({{ $bankAccount ? $bankAccount->bank_id : 0 }});
|
||||||
|
self.bank_username = ko.observable('{{ $bankAccount ? $bankAccount->username : false }}');
|
||||||
|
self.bank_password = ko.observable();
|
||||||
|
self.bank_accounts = ko.observableArray();
|
||||||
|
|
||||||
|
self.state = ko.observable(false);
|
||||||
|
|
||||||
|
self.disableMainButton = ko.computed(function() {
|
||||||
|
return !self.bank_id() || !self.bank_username();
|
||||||
|
}, self);
|
||||||
|
|
||||||
|
self.disableDoTest = ko.computed(function() {
|
||||||
|
return !self.bank_id() || !self.bank_username() || !self.bank_password();
|
||||||
|
}, self);
|
||||||
|
|
||||||
|
$('#bank_id').focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.model = new ViewModel();
|
||||||
|
ko.applyBindings(model);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
@stop
|
31
resources/views/accounts/banks.blade.php
Normal file
31
resources/views/accounts/banks.blade.php
Normal file
@ -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') !!}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.onDatatableReady = actionListHandler;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
@stop
|
Loading…
x
Reference in New Issue
Block a user