Fast link provisioning

This commit is contained in:
David Bomba 2022-08-10 11:56:46 +10:00
parent 957cc727f1
commit d80ec520f9
11 changed files with 118 additions and 35 deletions

View File

@ -61,6 +61,7 @@ use App\Helpers\Bank\AccountTransformerInterface;
) )
*/ */
class AccountTransformer implements AccountTransformerInterface class AccountTransformer implements AccountTransformerInterface
{ {
@ -86,6 +87,10 @@ class AccountTransformer implements AccountTransformerInterface
'account_name' => $account->accountName, 'account_name' => $account->accountName,
'account_status' => $account->accountStatus, 'account_status' => $account->accountStatus,
'account_number' => $account->accountNumber, 'account_number' => $account->accountNumber,
'provider_account_id' => $account->providerAccountId,
'provider_id' => $account->providerId,
'provider_name' => $account->providerName,
'nickname' => $account?->nickname,
'current_balance' => property_exists($account, 'currentBalance') ? $account->currentBalance->amount : 0, 'current_balance' => property_exists($account, 'currentBalance') ? $account->currentBalance->amount : 0,
'account_currency' => property_exists($account, 'currency') ? $account->currentBalance->currency : '', 'account_currency' => property_exists($account, 'currency') ? $account->currentBalance->currency : '',
]; ];

View File

@ -14,6 +14,7 @@ namespace App\Helpers\Bank\Yodlee;
use App\Helpers\Bank\Yodlee\Transformer\AccountTransformer; use App\Helpers\Bank\Yodlee\Transformer\AccountTransformer;
use App\Helpers\Bank\Yodlee\Transformer\IncomeTransformer; use App\Helpers\Bank\Yodlee\Transformer\IncomeTransformer;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
class Yodlee class Yodlee
{ {
@ -22,9 +23,15 @@ class Yodlee
private string $api_endpoint = 'https://production.api.yodlee.com/ysl'; private string $api_endpoint = 'https://production.api.yodlee.com/ysl';
private string $test_api_endpoint = 'https://sandbox.api.yodlee.com/ysl'; // private string $test_api_endpoint = 'https://sandbox.api.yodlee.com/ysl';
private string $test_api_endpoint = 'https://development.api.yodlee.com/ysl';
public string $fast_track_url = 'https://fl4.sandbox.yodlee.com/authenticate/restserver/fastlink'; //public string $test_fast_track_url = 'https://fl4.sandbox.yodlee.com/authenticate/restserver/fastlink';
public string $test_fast_track_url = 'https://fl4.preprod.yodlee.com/authenticate/USDevexPreProd3-449/fastlink?channelAppName=usdevexpreprod3';
public string $production_track_url = 'https://fl4.prod.yodlee.com/authenticate/USDevexProd3-331/fastlink?channelAppName=usdevexprod3';
protected string $client_id; protected string $client_id;
@ -47,6 +54,11 @@ class Yodlee
} }
public function getFastTrackUrl()
{
return $this->test_mode ? $this->test_fast_track_url : $this->production_track_url;
}
public function setTestMode() public function setTestMode()
{ {
$this->test_mode = true; $this->test_mode = true;
@ -56,9 +68,7 @@ class Yodlee
public function getEndpoint() public function getEndpoint()
{ {
return $this->test_mode ? $this->test_api_endpoint : $this->api_endpoint; return $this->test_mode ? $this->test_api_endpoint : $this->api_endpoint;
} }
/** /**
@ -73,19 +83,19 @@ class Yodlee
$user = $this->bank_account_id ?: $this->admin_name; $user = $this->bank_account_id ?: $this->admin_name;
$response = $this->bankFormRequest('/auth/token', 'post', [], ['loginName' => $user]); $response = $this->bankFormRequest('/auth/token', 'post', [], ['loginName' => $user]);
//catch failures here //catch failures here
// nlog($response); nlog($response);
return $response->token->accessToken; return $response->token->accessToken;
} }
public function createUser() public function createUser($company)
{ {
$token = $this->getAccessToken(true); $token = $this->getAccessToken(true);
$user['user'] = [ $user['user'] = [
'loginName' => 'test123', 'loginName' => Str::uuid(),
]; ];
/* /*
@ -139,10 +149,10 @@ class Yodlee
$response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->getEndpoint(). "/accounts", $params); $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->getEndpoint(). "/accounts", $params);
if($response->successful()){ if($response->successful()){
$at = new AccountTransformer(); $at = new AccountTransformer();
nlog($response->object());
return $at->transform($response->object()); return $at->transform($response->object());
// return $response->object(); // return $response->object();
} }

View File

@ -13,12 +13,13 @@ namespace App\Http\Controllers\Bank;
use App\Helpers\Bank\Yodlee\Yodlee; use App\Helpers\Bank\Yodlee\Yodlee;
use App\Http\Controllers\BaseController; use App\Http\Controllers\BaseController;
use App\Http\Requests\Yodlee\YodleeAuthRequest;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class YodleeController extends BaseController class YodleeController extends BaseController
{ {
public function auth(Request $request) public function auth(YodleeAuthRequest $request)
{ {
// create a user at this point // create a user at this point
@ -26,11 +27,36 @@ class YodleeController extends BaseController
//store the user_account_id on the accounts table //store the user_account_id on the accounts table
$yodlee = new Yodlee(true); $yodlee = new Yodlee();
$yodlee->setTestMode();
$company = $request->getCompany();
if($company->account->bank_integration_account_id){
$flow = 'edit';
$token = $company->account->bank_integration_account_id;
}
else{
$flow = 'add';
$response = $yodlee->createUser($company);
$token = $response->user->loginName;
$company->account->bank_integration_account_id = $token;
$company->push();
$yodlee = new Yodlee($token);
$yodlee->setTestMode();
}
if(!is_string($token))
dd($token);
$data = [ $data = [
'access_token' => $yodlee->getAccessToken('sbMem62e1e69547bfb1'), 'access_token' => $yodlee->getAccessToken(),
'fasttrack_url' => $yodlee->fast_track_url 'fasttrack_url' => $yodlee->getFastTrackUrl(),
'config_name' => 'testninja',
'flow' => $flow,
]; ];
return view('bank.yodlee.auth', $data); return view('bank.yodlee.auth', $data);

View File

@ -98,6 +98,8 @@ class BillingPortalPurchase extends Component
*/ */
public $payment_method_id; public $payment_method_id;
private $user_coupon;
/** /**
* List of steps that frontend form follows. * List of steps that frontend form follows.
* *
@ -436,32 +438,45 @@ class BillingPortalPurchase extends Component
*/ */
public function updateQuantity(string $option): int public function updateQuantity(string $option): int
{ {
$this->handleCoupon();
if ($this->quantity == 1 && $option == 'decrement') { if ($this->quantity == 1 && $option == 'decrement') {
$this->price = $this->price * 1;
return $this->quantity; return $this->quantity;
} }
if ($this->quantity >= $this->subscription->max_seats_limit && $option == 'increment') { if ($this->quantity > $this->subscription->max_seats_limit && $option == 'increment') {
$this->price = $this->price * $this->subscription->max_seats_limit;
return $this->quantity; return $this->quantity;
} }
if ($option == 'increment') { if ($option == 'increment') {
$this->quantity++; $this->quantity++;
$this->price = $this->subscription->promo_price * $this->quantity; $this->price = $this->price * $this->quantity;
return $this->quantity; return $this->quantity;
} }
$this->quantity--; $this->quantity--;
$this->price = $this->subscription->promo_price * $this->quantity; $this->price = $this->price * $this->quantity;
return $this->quantity; return $this->quantity;
} }
public function handleCoupon() public function handleCoupon()
{ {
if($this->steps['discount_applied']){
$this->price = $this->subscription->promo_price;
return;
}
if ($this->coupon == $this->subscription->promo_code) { if ($this->coupon == $this->subscription->promo_code) {
$this->price = $this->subscription->promo_price; $this->price = $this->subscription->promo_price;
$this->quantity = 1;
$this->steps['discount_applied'] = true; $this->steps['discount_applied'] = true;
} }
else
$this->price = $this->subscription->price;
} }
public function passwordlessLogin() public function passwordlessLogin()

View File

@ -42,10 +42,7 @@ class NinjaMailer extends Mailable
$ninja_mailable = $this->from(config('mail.from.address'), $from_name) $ninja_mailable = $this->from(config('mail.from.address'), $from_name)
->subject($this->mail_obj->subject) ->subject($this->mail_obj->subject)
->view($this->mail_obj->markdown, $this->mail_obj->data) ->view($this->mail_obj->markdown, $this->mail_obj->data);
->withSymfonyMessage(function ($message) {
$message->getHeaders()->addTextHeader('Tag', $this->mail_obj->tag);
});
if (property_exists($this->mail_obj, 'text_view')) { if (property_exists($this->mail_obj, 'text_view')) {
$ninja_mailable->text($this->mail_obj->text_view, $this->mail_obj->data); $ninja_mailable->text($this->mail_obj->text_view, $this->mail_obj->data);

View File

@ -95,7 +95,7 @@ class ReminderJob implements ShouldQueue
(Ninja::isSelfHost() || $invoice->company->account->isPaidHostedClient())) { (Ninja::isSelfHost() || $invoice->company->account->isPaidHostedClient())) {
$invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) { $invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) {
EmailEntity::dispatch($invitation, $invitation->company, $reminder_template); EmailEntity::dispatch($invitation, $invitation->company, $reminder_template);
nlog("Firing reminder email for invoice {$invoice->number}"); nlog("Firing reminder email for invoice {$invoice->number} - {$reminder_template}");
}); });
if ($invoice->invitations->count() > 0) { if ($invoice->invitations->count() > 0) {

View File

@ -100,7 +100,6 @@ class InvoiceEmailEngine extends BaseEmailEngine
$subject_template = $this->client->getSetting('email_subject_'.$this->reminder_template); $subject_template = $this->client->getSetting('email_subject_'.$this->reminder_template);
} else { } else {
$subject_template = EmailTemplateDefaults::getDefaultTemplate('email_subject_'.$this->reminder_template, $this->client->locale()); $subject_template = EmailTemplateDefaults::getDefaultTemplate('email_subject_'.$this->reminder_template, $this->client->locale());
// $subject_template = $this->client->getSetting('email_subject_'.$this->reminder_template);
} }
if (iconv_strlen($subject_template) == 0) { if (iconv_strlen($subject_template) == 0) {

View File

@ -51,7 +51,7 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder1_sent) && if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'after_invoice_date') { $this->settings->schedule_reminder1 == 'after_invoice_date') {
$reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset); $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder1);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) {
$date_collection->push($reminder_date); $date_collection->push($reminder_date);
@ -61,7 +61,7 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder1_sent) && if (is_null($this->invoice->reminder1_sent) &&
$this->invoice->due_date && $this->invoice->due_date &&
$this->settings->schedule_reminder1 == 'before_due_date') { $this->settings->schedule_reminder1 == 'before_due_date') {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder1)->addSeconds($offset); $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder1);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) {
$date_collection->push($reminder_date); $date_collection->push($reminder_date);
@ -71,7 +71,7 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder1_sent) && if (is_null($this->invoice->reminder1_sent) &&
$this->invoice->due_date && $this->invoice->due_date &&
$this->settings->schedule_reminder1 == 'after_due_date') { $this->settings->schedule_reminder1 == 'after_due_date') {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset); $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder1);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) {
$date_collection->push($reminder_date); $date_collection->push($reminder_date);
@ -80,7 +80,7 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder2_sent) && if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'after_invoice_date') { $this->settings->schedule_reminder2 == 'after_invoice_date') {
$reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset); $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder2);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) {
$date_collection->push($reminder_date); $date_collection->push($reminder_date);
@ -90,7 +90,7 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder2_sent) && if (is_null($this->invoice->reminder2_sent) &&
$this->invoice->due_date && $this->invoice->due_date &&
$this->settings->schedule_reminder2 == 'before_due_date') { $this->settings->schedule_reminder2 == 'before_due_date') {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder2)->addSeconds($offset); $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder2);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) {
$date_collection->push($reminder_date); $date_collection->push($reminder_date);
@ -100,7 +100,7 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder2_sent) && if (is_null($this->invoice->reminder2_sent) &&
$this->invoice->due_date && $this->invoice->due_date &&
$this->settings->schedule_reminder2 == 'after_due_date') { $this->settings->schedule_reminder2 == 'after_due_date') {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset); $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder2);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) {
$date_collection->push($reminder_date); $date_collection->push($reminder_date);
@ -109,7 +109,7 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder3_sent) && if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'after_invoice_date') { $this->settings->schedule_reminder3 == 'after_invoice_date') {
$reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset); $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder3);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) {
$date_collection->push($reminder_date); $date_collection->push($reminder_date);
@ -119,7 +119,7 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder3_sent) && if (is_null($this->invoice->reminder3_sent) &&
$this->invoice->due_date && $this->invoice->due_date &&
$this->settings->schedule_reminder3 == 'before_due_date') { $this->settings->schedule_reminder3 == 'before_due_date') {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder3)->addSeconds($offset); $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder3);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) {
$date_collection->push($reminder_date); $date_collection->push($reminder_date);
@ -129,7 +129,7 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder3_sent) && if (is_null($this->invoice->reminder3_sent) &&
$this->invoice->due_date && $this->invoice->due_date &&
$this->settings->schedule_reminder3 == 'after_due_date') { $this->settings->schedule_reminder3 == 'after_due_date') {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset); $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder3);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) {
$date_collection->push($reminder_date); $date_collection->push($reminder_date);
@ -150,7 +150,7 @@ class UpdateReminder extends AbstractService
} }
if ($date_collection->count() >= 1 && $date_collection->sort()->first()->gte(now())) { if ($date_collection->count() >= 1 && $date_collection->sort()->first()->gte(now())) {
$this->invoice->next_send_date = $date_collection->sort()->first(); $this->invoice->next_send_date = $date_collection->sort()->first()->addSeconds($offset);
} else { } else {
$this->invoice->next_send_date = null; $this->invoice->next_send_date = null;
} }

View File

@ -40,6 +40,27 @@ return new class extends Migration
Schema::table('accounts', function (Blueprint $table) { Schema::table('accounts', function (Blueprint $table) {
$table->text('bank_integration_account_id')->nullable(); $table->text('bank_integration_account_id')->nullable();
}); });
Schema::create('bank_transactions', function (Blueprint $table){
$table->id();
$table->unsignedInteger('company_id');
$table->unsignedInteger('user_id');
$table->unsignedBigInteger('transaction_id')->nullable();
$table->decimal('amount', 20, 6);
$table->string('currency_code');
$table->string('account_type');
$table->unsignedInteger('category_id');
$table->string('category_type');
$table->date('date');
$table->unsignedBigInteger('account_id');
$table->text('description');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade');
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade')->onUpdate('cascade');
});
} }
/** /**
@ -49,6 +70,6 @@ return new class extends Migration
*/ */
public function down() public function down()
{ {
//
} }
}; };

View File

@ -27,11 +27,11 @@
'click', 'click',
function() { function() {
window.fastlink.open({ window.fastlink.open({
flow: 'edit',//flow changes depending on what we are doing sometimes it could be add/edit etc etc flow: '{{ $flow }}',//flow changes depending on what we are doing sometimes it could be add/edit etc etc
fastLinkURL: '{{ $fasttrack_url }}', fastLinkURL: '{{ $fasttrack_url }}',
accessToken: 'Bearer {{ $access_token }}', accessToken: 'Bearer {{ $access_token }}',
params: { params: {
configName : 'Aggregation' configName : '{{ $config_name }}'
}, },
onSuccess: function (data) { onSuccess: function (data) {
// will be called on success. For list of possible message, refer to onSuccess(data) Method. // will be called on success. For list of possible message, refer to onSuccess(data) Method.

View File

@ -73,6 +73,16 @@ class YodleeApiTest extends TestCase
return str_contains($value->description, 'tinker'); return str_contains($value->description, 'tinker');
}); });
$invoice = $transaction->first(function ($value, $key) {
return str_contains($value->number, 'tinker');
});
$this->assertNotNull($invoice);
} }
public function testYodleeInstance() public function testYodleeInstance()