From 1599711deeabfcc0b33bba265949f64528d4f8db Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 5 Mar 2021 21:18:28 +1100 Subject: [PATCH 01/30] Observers and policy scaffold --- app/Console/Kernel.php | 3 + app/Jobs/Cron/BillingSubscriptionCron.php | 52 ++++++++++++++ app/Models/BillingSubscription.php | 19 +++++ app/Models/ClientSubscription.php | 19 +++++ app/Observers/BillingSubscriptionObserver.php | 72 +++++++++++++++++++ app/Observers/ClientSubscriptionObserver.php | 72 +++++++++++++++++++ app/Policies/BillingSubscriptionPolicy.php | 31 ++++++++ app/Providers/AppServiceProvider.php | 8 ++- app/Providers/AuthServiceProvider.php | 3 + 9 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 app/Jobs/Cron/BillingSubscriptionCron.php create mode 100644 app/Models/BillingSubscription.php create mode 100644 app/Models/ClientSubscription.php create mode 100644 app/Observers/BillingSubscriptionObserver.php create mode 100644 app/Observers/ClientSubscriptionObserver.php create mode 100644 app/Policies/BillingSubscriptionPolicy.php diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 24a661eae68f..1d3be1016fce 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -11,6 +11,7 @@ namespace App\Console; +use App\Jobs\Cron\BillingSubscriptionCron; use App\Jobs\Cron\RecurringInvoicesCron; use App\Jobs\Ninja\AdjustEmailQuota; use App\Jobs\Ninja\CompanySizeCheck; @@ -53,6 +54,8 @@ class Kernel extends ConsoleKernel $schedule->job(new UpdateExchangeRates)->daily()->withoutOverlapping(); + $schedule->job(new BillingSubscriptionCron)->daily()->withoutOverlapping(); + $schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping(); /* Run hosted specific jobs */ diff --git a/app/Jobs/Cron/BillingSubscriptionCron.php b/app/Jobs/Cron/BillingSubscriptionCron.php new file mode 100644 index 000000000000..993605593365 --- /dev/null +++ b/app/Jobs/Cron/BillingSubscriptionCron.php @@ -0,0 +1,52 @@ +isAdmin() || $user->hasPermission('create_billing_subscription') || $user->hasPermission('create_all'); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 3d96377fa950..c017572692c3 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -12,7 +12,9 @@ namespace App\Providers; use App\Models\Account; +use App\Models\BillingSubscription; use App\Models\Client; +use App\Models\ClientSubscription; use App\Models\Company; use App\Models\CompanyGateway; use App\Models\CompanyToken; @@ -26,7 +28,9 @@ use App\Models\Quote; use App\Models\Task; use App\Models\User; use App\Observers\AccountObserver; +use App\Observers\BillingSubscriptionObserver; use App\Observers\ClientObserver; +use App\Observers\ClientSubscriptionObserver; use App\Observers\CompanyGatewayObserver; use App\Observers\CompanyObserver; use App\Observers\CompanyTokenObserver; @@ -75,9 +79,10 @@ class AppServiceProvider extends ServiceProvider Schema::defaultStringLength(191); - User::observe(UserObserver::class); Account::observe(AccountObserver::class); + BillingSubscription::observe(BillingSubscriptionObserver::class); Client::observe(ClientObserver::class); + ClientSubscription::observe(ClientSubscriptionObserver::class); Company::observe(CompanyObserver::class); CompanyGateway::observe(CompanyGatewayObserver::class); CompanyToken::observe(CompanyTokenObserver::class); @@ -89,6 +94,7 @@ class AppServiceProvider extends ServiceProvider Proposal::observe(ProposalObserver::class); Quote::observe(QuoteObserver::class); Task::observe(TaskObserver::class); + User::observe(UserObserver::class); // Queue::before(function (JobProcessing $event) { // // \Log::info('Event Job '.$event->connectionName); diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 23d6f3a59220..60281db3b3fe 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -12,6 +12,7 @@ namespace App\Providers; use App\Models\Activity; +use App\Models\BillingSubscription; use App\Models\Client; use App\Models\Company; use App\Models\CompanyGateway; @@ -37,6 +38,7 @@ use App\Models\User; use App\Models\Vendor; use App\Models\Webhook; use App\Policies\ActivityPolicy; +use App\Policies\BillingSubscriptionPolicy; use App\Policies\ClientPolicy; use App\Policies\CompanyGatewayPolicy; use App\Policies\CompanyPolicy; @@ -73,6 +75,7 @@ class AuthServiceProvider extends ServiceProvider */ protected $policies = [ Activity::class => ActivityPolicy::class, + BillingSubscription::class => BillingSubscriptionPolicy::class, Client::class => ClientPolicy::class, Company::class => CompanyPolicy::class, CompanyToken::class => CompanyTokenPolicy::class, From 097d9ac1b8909d5a0284df1c45bdce81fac2b626 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 6 Mar 2021 11:47:05 +1100 Subject: [PATCH 02/30] Padding out functionality --- app/Jobs/Cron/BillingSubscriptionCron.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/Jobs/Cron/BillingSubscriptionCron.php b/app/Jobs/Cron/BillingSubscriptionCron.php index 993605593365..453ab3befa02 100644 --- a/app/Jobs/Cron/BillingSubscriptionCron.php +++ b/app/Jobs/Cron/BillingSubscriptionCron.php @@ -49,4 +49,14 @@ class BillingSubscriptionCron } } } + + /* Our daily cron should check + + 1. Is the subscription still in trial phase? + 2. Check the recurring invoice and its remaining_cycles to see whether we need to cancel or perform any other function. + */ + private function processSubscription($client_subscription) + { + + } } From 93493116ed5ede879f062c327b191469b012cb7b Mon Sep 17 00:00:00 2001 From: = Date: Sat, 6 Mar 2021 17:19:57 +1100 Subject: [PATCH 03/30] Scaffolds for billing_subscriptions --- app/Jobs/Cron/BillingSubscriptionCron.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/app/Jobs/Cron/BillingSubscriptionCron.php b/app/Jobs/Cron/BillingSubscriptionCron.php index 453ab3befa02..091cb10c5f83 100644 --- a/app/Jobs/Cron/BillingSubscriptionCron.php +++ b/app/Jobs/Cron/BillingSubscriptionCron.php @@ -35,25 +35,34 @@ class BillingSubscriptionCron */ public function handle() : void { - /* Get all invoices where the send date is less than NOW + 30 minutes() */ if (! config('ninja.db.multi_db_enabled')) { - + $this->loopSubscriptions(); } else { //multiDB environment, need to foreach (MultiDB::$dbs as $db) { MultiDB::setDB($db); - + $this->loopSubscriptions(); } } } + private function loopSubscriptions() + { + $client_subs = ClientSubscription::whereNull('deleted_at') + ->cursor() + ->each(function ($cs){ + $this->processSubscription($cs); + }); + } + /* Our daily cron should check 1. Is the subscription still in trial phase? 2. Check the recurring invoice and its remaining_cycles to see whether we need to cancel or perform any other function. + 3. Any notifications that need to fire? */ private function processSubscription($client_subscription) { From 2de12ea973f87d67798a35b9fadf7b26f99e5200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Mon, 8 Mar 2021 15:17:40 +0100 Subject: [PATCH 04/30] Routes for billing subscriptions --- routes/api.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/routes/api.php b/routes/api.php index a503f03d9765..a2122998f186 100644 --- a/routes/api.php +++ b/routes/api.php @@ -68,7 +68,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a Route::post('emails', 'EmailController@send')->name('email.send')->middleware('user_verified'); Route::resource('expenses', 'ExpenseController'); // name = (expenses. index / create / show / update / destroy / edit - Route::put('expenses/{expense}/upload', 'ExpenseController@upload'); + Route::put('expenses/{expense}/upload', 'ExpenseController@upload'); Route::post('expenses/bulk', 'ExpenseController@bulk')->name('expenses.bulk'); Route::resource('expense_categories', 'ExpenseCategoryController'); // name = (expense_categories. index / create / show / update / destroy / edit @@ -98,7 +98,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a Route::resource('payments', 'PaymentController'); // name = (payments. index / create / show / update / destroy / edit Route::post('payments/refund', 'PaymentController@refund')->name('payments.refund'); Route::post('payments/bulk', 'PaymentController@bulk')->name('payments.bulk'); - Route::put('payments/{payment}/upload', 'PaymentController@upload'); + Route::put('payments/{payment}/upload', 'PaymentController@upload'); Route::resource('payment_terms', 'PaymentTermController'); // name = (payments. index / create / show / update / destroy / edit Route::post('payment_terms/bulk', 'PaymentTermController@bulk')->name('payment_terms.bulk'); @@ -107,20 +107,20 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a Route::resource('products', 'ProductController'); // name = (products. index / create / show / update / destroy / edit Route::post('products/bulk', 'ProductController@bulk')->name('products.bulk'); - Route::put('products/{product}/upload', 'ProductController@upload'); + Route::put('products/{product}/upload', 'ProductController@upload'); Route::resource('projects', 'ProjectController'); // name = (projects. index / create / show / update / destroy / edit Route::post('projects/bulk', 'ProjectController@bulk')->name('projects.bulk'); Route::put('projects/{project}/upload', 'ProjectController@upload')->name('projects.upload'); - + Route::resource('quotes', 'QuoteController'); // name = (quotes. index / create / show / update / destroy / edit Route::get('quotes/{quote}/{action}', 'QuoteController@action')->name('quotes.action'); Route::post('quotes/bulk', 'QuoteController@bulk')->name('quotes.bulk'); - Route::put('quotes/{quote}/upload', 'QuoteController@upload'); + Route::put('quotes/{quote}/upload', 'QuoteController@upload'); Route::resource('recurring_invoices', 'RecurringInvoiceController'); // name = (recurring_invoices. index / create / show / update / destroy / edit Route::post('recurring_invoices/bulk', 'RecurringInvoiceController@bulk')->name('recurring_invoices.bulk'); - Route::put('recurring_invoices/{recurring_invoice}/upload', 'RecurringInvoiceController@upload'); + Route::put('recurring_invoices/{recurring_invoice}/upload', 'RecurringInvoiceController@upload'); Route::resource('recurring_quotes', 'RecurringQuoteController'); // name = (recurring_invoices. index / create / show / update / destroy / edit Route::post('recurring_quotes/bulk', 'RecurringQuoteController@bulk')->name('recurring_quotes.bulk'); @@ -137,7 +137,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a Route::resource('tasks', 'TaskController'); // name = (tasks. index / create / show / update / destroy / edit Route::post('tasks/bulk', 'TaskController@bulk')->name('tasks.bulk'); - Route::put('tasks/{task}/upload', 'TaskController@upload'); + Route::put('tasks/{task}/upload', 'TaskController@upload'); Route::resource('task_statuses', 'TaskStatusController'); // name = (task_statuses. index / create / show / update / destroy / edit Route::post('task_statuses/bulk', 'TaskStatusController@bulk')->name('task_statuses.bulk'); @@ -155,7 +155,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a Route::resource('vendors', 'VendorController'); // name = (vendors. index / create / show / update / destroy / edit Route::post('vendors/bulk', 'VendorController@bulk')->name('vendors.bulk'); - Route::put('vendors/{vendor}/upload', 'VendorController@upload'); + Route::put('vendors/{vendor}/upload', 'VendorController@upload'); Route::get('users', 'UserController@index'); Route::put('users/{user}', 'UserController@update')->middleware('password_protected'); @@ -173,7 +173,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a // Route::post('hooks', 'SubscriptionController@subscribe')->name('hooks.subscribe'); // Route::delete('hooks/{subscription_id}', 'SubscriptionController@unsubscribe')->name('hooks.unsubscribe'); - + Route::resource('billing/subscriptions', 'BillingSubscriptionController'); }); Route::match(['get', 'post'], 'payment_webhook/{company_key}/{company_gateway_id}', 'PaymentWebhookController') From ea5117ecbe007659cbd994d1f3de8407e9680f2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Mon, 8 Mar 2021 15:18:14 +0100 Subject: [PATCH 05/30] Billing subscriptions: Factory, repository & transformer --- app/Factory/BillingSubscriptionFactory.php | 17 +++++ .../BillingSubscriptionRepository.php | 28 ++++++++ .../BillingSubscriptionTransformer.php | 70 +++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 app/Factory/BillingSubscriptionFactory.php create mode 100644 app/Repositories/BillingSubscriptionRepository.php create mode 100644 app/Transformers/BillingSubscriptionTransformer.php diff --git a/app/Factory/BillingSubscriptionFactory.php b/app/Factory/BillingSubscriptionFactory.php new file mode 100644 index 000000000000..38708bd6ed79 --- /dev/null +++ b/app/Factory/BillingSubscriptionFactory.php @@ -0,0 +1,17 @@ +fill($data) + ->save(); + + return $billing_subscription; + } +} diff --git a/app/Transformers/BillingSubscriptionTransformer.php b/app/Transformers/BillingSubscriptionTransformer.php new file mode 100644 index 000000000000..4969f4dfa018 --- /dev/null +++ b/app/Transformers/BillingSubscriptionTransformer.php @@ -0,0 +1,70 @@ + $this->encodePrimaryKey($billing_subscription->id), + 'user_id' => $this->encodePrimaryKey($billing_subscription->user_id), + 'product_id' => $this->encodePrimaryKey($billing_subscription->product_id), + 'assigned_user_id' => $this->encodePrimaryKey($billing_subscription->assigned_user_id), + 'company_id' => $this->encodePrimaryKey($billing_subscription->company_id), + 'is_recurring' => (bool)$billing_subscription->is_recurring, + 'frequency_id' => (string)$billing_subscription->frequency_id, + 'auto_bill' => (string)$billing_subscription->auto_bill, + 'promo_code' => (string)$billing_subscription->promo_code, + 'promo_discount' => (string)$billing_subscription->promo_discount, + 'is_amount_discount' => (bool)$billing_subscription->is_amount_discount, + 'allow_cancellation' => (bool)$billing_subscription->allow_cancellation, + 'per_set_enabled' => (bool)$billing_subscription->per_set_enabled, + 'min_seats_limit' => (int)$billing_subscription->min_seats_limit, + 'max_seats_limit' => (int)$billing_subscription->max_seats_limit, + 'trial_enabled' => (bool)$billing_subscription->trial_enabled, + 'trial_duration' => (string)$billing_subscription->trial_duration, + 'allow_query_overrides' => (bool)$billing_subscription->allow_query_overrides, + 'allow_plan_changes' => (bool)$billing_subscription->allow_plan_changes, + 'plan_map' => (string)$billing_subscription->plan_map, + 'refund_period' => (string)$billing_subscription->refund_period, + 'webhook_configuration' => (string)$billing_subscription->webhook_configuration, + 'is_deleted' => (bool)$billing_subscription->is_deleted, + ]; + } + + public function includeProducts(BillingSubscription $billing_subscription): \League\Fractal\Resource\Item + { + $transformer = new ProductTransformer($this->serializer); + + return $this->includeItem($billing_subscription->product, $transformer, Product::class); + } +} From 9c1a3368e94d1295cf072f39f19f55ef345fea97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Mon, 8 Mar 2021 15:18:34 +0100 Subject: [PATCH 06/30] Billing subscriptions: Database schema & model --- app/Models/BillingSubscription.php | 61 ++++++++++++++++ ...729_create_billing_subscriptions_table.php | 71 +++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 app/Models/BillingSubscription.php create mode 100644 database/migrations/2021_03_08_123729_create_billing_subscriptions_table.php diff --git a/app/Models/BillingSubscription.php b/app/Models/BillingSubscription.php new file mode 100644 index 000000000000..57f8706f2075 --- /dev/null +++ b/app/Models/BillingSubscription.php @@ -0,0 +1,61 @@ +belongsTo(Company::class); + } + + public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(User::class); + } + + public function product(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(Product::class); + } +} diff --git a/database/migrations/2021_03_08_123729_create_billing_subscriptions_table.php b/database/migrations/2021_03_08_123729_create_billing_subscriptions_table.php new file mode 100644 index 000000000000..640a21becc0c --- /dev/null +++ b/database/migrations/2021_03_08_123729_create_billing_subscriptions_table.php @@ -0,0 +1,71 @@ +increments('id'); + $table->unsignedInteger('user_id'); + $table->unsignedInteger('assigned_user_id'); + $table->unsignedInteger('company_id'); + $table->unsignedInteger('product_id'); + $table->boolean('is_recurring')->default(false); + $table->unsignedInteger('frequency_id'); + $table->string('auto_bill')->default(''); + $table->string('promo_code')->default(''); + $table->float('promo_discount')->default(0); + $table->boolean('is_amount_discount')->default(false); + $table->boolean('allow_cancellation')->default(true); + $table->boolean('per_set_enabled')->default(false); + $table->unsignedInteger('min_seats_limit'); + $table->unsignedInteger('max_seats_limit'); + $table->boolean('trial_enabled')->default(false); + $table->unsignedInteger('trial_duration'); + $table->boolean('allow_query_overrides')->default(false); + $table->boolean('allow_plan_changes')->default(false); + $table->mediumText('plan_map'); + $table->unsignedInteger('refund_period')->nullable(); + $table->mediumText('webhook_configuration'); + $table->softDeletes('deleted_at', 6); + $table->boolean('is_deleted')->default(false); + $table->timestamps(); + $table->foreign('product_id')->references('id')->on('products'); + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + $table->index(['company_id', 'deleted_at']); + }); + + Schema::create('client_subscriptions', function (Blueprint $table) { + $table->increments('id'); + $table->unsignedInteger('subscription_id'); + $table->unsignedInteger('recurring_invoice_id'); + $table->unsignedInteger('client_id'); + $table->unsignedInteger('trial_started')->nullable(); + $table->unsignedInteger('trial_ends')->nullable(); + $table->timestamps(); + $table->foreign('subscription_id')->references('id')->on('billing_subscriptions'); + $table->foreign('recurring_invoice_id')->references('id')->on('recurring_invoices'); + $table->foreign('client_id')->references('id')->on('clients'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('billing_subscriptions'); + Schema::dropIfExists('client_subscriptions'); + } +} From 420d9045510c7705c9292cb8ba1dc460944a7a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Mon, 8 Mar 2021 15:18:48 +0100 Subject: [PATCH 07/30] Client subscriptions: Model --- app/Models/ClientSubscription.php | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 app/Models/ClientSubscription.php diff --git a/app/Models/ClientSubscription.php b/app/Models/ClientSubscription.php new file mode 100644 index 000000000000..8ca8caa87d37 --- /dev/null +++ b/app/Models/ClientSubscription.php @@ -0,0 +1,11 @@ + Date: Mon, 8 Mar 2021 15:19:04 +0100 Subject: [PATCH 08/30] Billing subscriptions: Controller --- .../BillingSubscriptionController.php | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 app/Http/Controllers/BillingSubscriptionController.php diff --git a/app/Http/Controllers/BillingSubscriptionController.php b/app/Http/Controllers/BillingSubscriptionController.php new file mode 100644 index 000000000000..421e2e2768f4 --- /dev/null +++ b/app/Http/Controllers/BillingSubscriptionController.php @@ -0,0 +1,93 @@ +billing_subscription_repo = $billing_subscription_repo; + } + + public function index(): \Illuminate\Http\Response + { + $billing_subscriptions = BillingSubscription::query()->company(); + + return $this->listResponse($billing_subscriptions); + } + + public function create(CreateBillingSubscriptionRequest $request): \Illuminate\Http\Response + { + $billing_subscription = BillingSubscriptionFactory::create(auth()->user()->company()->id, auth()->user()->id); + + return $this->itemResponse($billing_subscription); + } + + public function store(StoreBillingSubscriptionRequest $request): \Illuminate\Http\Response + { + $billing_subscription = $this->billing_subscription_repo->save($request->all(), BillingSubscriptionFactory::create(auth()->user()->company()->id, auth()->user()->id)); + + event(new BillingsubscriptionWasCreated($billing_subscription, $billing_subscription->company, Ninja::eventVars())); + + return $this->itemResponse($billing_subscription); + } + + public function show(ShowBillingSubscriptionRequest $request, BillingSubscription $billing_subscription): \Illuminate\Http\Response + { + return $this->itemResponse($billing_subscription); + } + + public function edit(EditBillingSubscriptionRequest $request, BillingSubscription $billing_subscription): \Illuminate\Http\Response + { + return $this->itemResponse($billing_subscription); + } + + public function update(UpdateBillingSubscriptionRequest $request, BillingSubscription $billing_subscription) + { + if ($request->entityIsDeleted($billing_subscription)) { + return $request->disallowUpdate(); + } + + $billing_subscription = $this->billing_subscription_repo->save($request->all(), $billing_subscription); + + return $this->itemResponse($billing_subscription); + } + + public function destroy(DestroyBillingSubscriptionRequest $request, BillingSubscription $billing_subscription): \Illuminate\Http\Response + { + $this->billing_subscription_repo->delete($billing_subscription); + + return $this->listResponse($billing_subscription->fresh()); + } +} From 29922a24a0352987c0bb97623422294c5cc58862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Mon, 8 Mar 2021 15:19:23 +0100 Subject: [PATCH 09/30] Billing subscriptions: Event for created --- .../BillingSubscriptionWasCreated.php | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 app/Events/BillingSubscription/BillingSubscriptionWasCreated.php diff --git a/app/Events/BillingSubscription/BillingSubscriptionWasCreated.php b/app/Events/BillingSubscription/BillingSubscriptionWasCreated.php new file mode 100644 index 000000000000..73fc2d1f2f87 --- /dev/null +++ b/app/Events/BillingSubscription/BillingSubscriptionWasCreated.php @@ -0,0 +1,55 @@ +billing_subscription = $billing_subscription; + $this->company = $company; + $this->event_vars = $event_vars; + } + + /** + * Get the channels the event should broadcast on. + * + * @return \Illuminate\Broadcasting\Channel|array + */ + public function broadcastOn() + { + return new PrivateChannel('channel-name'); + } +} From 2660b8fa584db925a6b8f0572a176a57224aeb1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Mon, 8 Mar 2021 15:19:45 +0100 Subject: [PATCH 10/30] Billing subscriptions: Requests for crud endpoints --- .../CreateBillingSubscriptionRequest.php | 31 ++++++++++++ .../DestroyBillingSubscriptionRequest.php | 31 ++++++++++++ .../EditBillingSubscriptionRequest.php | 33 ++++++++++++ .../ShowBillingSubscriptionRequest.php | 32 ++++++++++++ .../StoreBillingSubscriptionRequest.php | 50 +++++++++++++++++++ .../UpdateBillingSubscriptionRequest.php | 33 ++++++++++++ 6 files changed, 210 insertions(+) create mode 100644 app/Http/Requests/BillingSubscription/CreateBillingSubscriptionRequest.php create mode 100644 app/Http/Requests/BillingSubscription/DestroyBillingSubscriptionRequest.php create mode 100644 app/Http/Requests/BillingSubscription/EditBillingSubscriptionRequest.php create mode 100644 app/Http/Requests/BillingSubscription/ShowBillingSubscriptionRequest.php create mode 100644 app/Http/Requests/BillingSubscription/StoreBillingSubscriptionRequest.php create mode 100644 app/Http/Requests/BillingSubscription/UpdateBillingSubscriptionRequest.php diff --git a/app/Http/Requests/BillingSubscription/CreateBillingSubscriptionRequest.php b/app/Http/Requests/BillingSubscription/CreateBillingSubscriptionRequest.php new file mode 100644 index 000000000000..f7f0a907343d --- /dev/null +++ b/app/Http/Requests/BillingSubscription/CreateBillingSubscriptionRequest.php @@ -0,0 +1,31 @@ +user()->can('create', BillingSubscription::class); // TODO + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/BillingSubscription/DestroyBillingSubscriptionRequest.php b/app/Http/Requests/BillingSubscription/DestroyBillingSubscriptionRequest.php new file mode 100644 index 000000000000..c263836d9442 --- /dev/null +++ b/app/Http/Requests/BillingSubscription/DestroyBillingSubscriptionRequest.php @@ -0,0 +1,31 @@ +user()->can('view', $this->billing_subscription); // TODO + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/BillingSubscription/ShowBillingSubscriptionRequest.php b/app/Http/Requests/BillingSubscription/ShowBillingSubscriptionRequest.php new file mode 100644 index 000000000000..f5d08bec9445 --- /dev/null +++ b/app/Http/Requests/BillingSubscription/ShowBillingSubscriptionRequest.php @@ -0,0 +1,32 @@ +user()->can('view', $this->billing_subscription); // TODO + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/BillingSubscription/StoreBillingSubscriptionRequest.php b/app/Http/Requests/BillingSubscription/StoreBillingSubscriptionRequest.php new file mode 100644 index 000000000000..c897edc5b299 --- /dev/null +++ b/app/Http/Requests/BillingSubscription/StoreBillingSubscriptionRequest.php @@ -0,0 +1,50 @@ + ['sometimes'], + 'product_id' => ['sometimes'], + 'assigned_user_id' => ['sometimes'], + 'company_id' => ['sometimes'], + 'is_recurring' => ['sometimes'], + 'frequency_id' => ['sometimes'], + 'auto_bill' => ['sometimes'], + 'promo_code' => ['sometimes'], + 'promo_discount' => ['sometimes'], + 'is_amount_discount' => ['sometimes'], + 'allow_cancellation' => ['sometimes'], + 'per_set_enabled' => ['sometimes'], + 'min_seats_limit' => ['sometimes'], + 'max_seats_limit' => ['sometimes'], + 'trial_enabled' => ['sometimes'], + 'trial_duration' => ['sometimes'], + 'allow_query_overrides' => ['sometimes'], + 'allow_plan_changes' => ['sometimes'], + 'plan_map' => ['sometimes'], + 'refund_period' => ['sometimes'], + 'webhook_configuration' => ['sometimes'], + ]; + } +} diff --git a/app/Http/Requests/BillingSubscription/UpdateBillingSubscriptionRequest.php b/app/Http/Requests/BillingSubscription/UpdateBillingSubscriptionRequest.php new file mode 100644 index 000000000000..a92d14ff0b33 --- /dev/null +++ b/app/Http/Requests/BillingSubscription/UpdateBillingSubscriptionRequest.php @@ -0,0 +1,33 @@ + Date: Tue, 9 Mar 2021 08:29:59 +1100 Subject: [PATCH 11/30] billing_subscriptions cleanup --- .../BillingSubscriptionController.php | 37 +++++++++++++++++++ .../OpenAPI/BillingSubscription.php | 31 ++++++++++++++++ app/Models/BillingSubscription.php | 9 +++++ .../BillingSubscriptionTransformer.php | 15 +++++--- ...729_create_billing_subscriptions_table.php | 2 +- routes/api.php | 2 +- 6 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 app/Http/Controllers/OpenAPI/BillingSubscription.php diff --git a/app/Http/Controllers/BillingSubscriptionController.php b/app/Http/Controllers/BillingSubscriptionController.php index 421e2e2768f4..0dff2f31c962 100644 --- a/app/Http/Controllers/BillingSubscriptionController.php +++ b/app/Http/Controllers/BillingSubscriptionController.php @@ -40,6 +40,43 @@ class BillingSubscriptionController extends BaseController $this->billing_subscription_repo = $billing_subscription_repo; } + /** + * Show the list of BillingSubscriptions. + * * + * @return Response + * + * @OA\Get( + * path="/api/v1/billing_subscriptions", + * operationId="getBillingSubscriptions", + * tags={"billing_subscriptions"}, + * summary="Gets a list of billing_subscriptions", + * description="Lists billing_subscriptions.", + * + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Response( + * response=200, + * description="A list of billing_subscriptions", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/BillingSubscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function index(): \Illuminate\Http\Response { $billing_subscriptions = BillingSubscription::query()->company(); diff --git a/app/Http/Controllers/OpenAPI/BillingSubscription.php b/app/Http/Controllers/OpenAPI/BillingSubscription.php new file mode 100644 index 000000000000..bde43b87c618 --- /dev/null +++ b/app/Http/Controllers/OpenAPI/BillingSubscription.php @@ -0,0 +1,31 @@ + 'boolean', + 'plan_map' => 'object', + 'webhook_configuration' => 'object', + 'updated_at' => 'timestamp', + 'created_at' => 'timestamp', + 'deleted_at' => 'timestamp', + ]; + public function company(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Company::class); diff --git a/app/Transformers/BillingSubscriptionTransformer.php b/app/Transformers/BillingSubscriptionTransformer.php index 4969f4dfa018..8b2f376f9762 100644 --- a/app/Transformers/BillingSubscriptionTransformer.php +++ b/app/Transformers/BillingSubscriptionTransformer.php @@ -29,7 +29,7 @@ class BillingSubscriptionTransformer extends EntityTransformer * @var array */ protected $availableIncludes = [ - 'products', + 'product', ]; public function transform(BillingSubscription $billing_subscription): array @@ -44,24 +44,27 @@ class BillingSubscriptionTransformer extends EntityTransformer 'frequency_id' => (string)$billing_subscription->frequency_id, 'auto_bill' => (string)$billing_subscription->auto_bill, 'promo_code' => (string)$billing_subscription->promo_code, - 'promo_discount' => (string)$billing_subscription->promo_discount, + 'promo_discount' => (float)$billing_subscription->promo_discount, 'is_amount_discount' => (bool)$billing_subscription->is_amount_discount, 'allow_cancellation' => (bool)$billing_subscription->allow_cancellation, - 'per_set_enabled' => (bool)$billing_subscription->per_set_enabled, + 'per_seat_enabled' => (bool)$billing_subscription->per_set_enabled, 'min_seats_limit' => (int)$billing_subscription->min_seats_limit, 'max_seats_limit' => (int)$billing_subscription->max_seats_limit, 'trial_enabled' => (bool)$billing_subscription->trial_enabled, - 'trial_duration' => (string)$billing_subscription->trial_duration, + 'trial_duration' => (int)$billing_subscription->trial_duration, 'allow_query_overrides' => (bool)$billing_subscription->allow_query_overrides, 'allow_plan_changes' => (bool)$billing_subscription->allow_plan_changes, 'plan_map' => (string)$billing_subscription->plan_map, - 'refund_period' => (string)$billing_subscription->refund_period, + 'refund_period' => (int)$billing_subscription->refund_period, 'webhook_configuration' => (string)$billing_subscription->webhook_configuration, 'is_deleted' => (bool)$billing_subscription->is_deleted, + 'created_at' => (int) $credit->created_at, + 'updated_at' => (int) $credit->updated_at, + 'archived_at' => (int) $credit->deleted_at, ]; } - public function includeProducts(BillingSubscription $billing_subscription): \League\Fractal\Resource\Item + public function includeProduct(BillingSubscription $billing_subscription): \League\Fractal\Resource\Item { $transformer = new ProductTransformer($this->serializer); diff --git a/database/migrations/2021_03_08_123729_create_billing_subscriptions_table.php b/database/migrations/2021_03_08_123729_create_billing_subscriptions_table.php index 640a21becc0c..27400a8fa679 100644 --- a/database/migrations/2021_03_08_123729_create_billing_subscriptions_table.php +++ b/database/migrations/2021_03_08_123729_create_billing_subscriptions_table.php @@ -26,7 +26,7 @@ class CreateBillingSubscriptionsTable extends Migration $table->float('promo_discount')->default(0); $table->boolean('is_amount_discount')->default(false); $table->boolean('allow_cancellation')->default(true); - $table->boolean('per_set_enabled')->default(false); + $table->boolean('per_seat_enabled')->default(false); $table->unsignedInteger('min_seats_limit'); $table->unsignedInteger('max_seats_limit'); $table->boolean('trial_enabled')->default(false); diff --git a/routes/api.php b/routes/api.php index a2122998f186..1af077d5fbeb 100644 --- a/routes/api.php +++ b/routes/api.php @@ -173,7 +173,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a // Route::post('hooks', 'SubscriptionController@subscribe')->name('hooks.subscribe'); // Route::delete('hooks/{subscription_id}', 'SubscriptionController@unsubscribe')->name('hooks.unsubscribe'); - Route::resource('billing/subscriptions', 'BillingSubscriptionController'); + Route::resource('billing_subscriptions', 'BillingSubscriptionController'); }); Route::match(['get', 'post'], 'payment_webhook/{company_key}/{company_gateway_id}', 'PaymentWebhookController') From 68f0e99f26d629591254c3cdde5a12c06c746c67 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 9 Mar 2021 09:53:04 +1100 Subject: [PATCH 12/30] Fixes for connected account --- app/Http/Controllers/ConnectedAccountController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Http/Controllers/ConnectedAccountController.php b/app/Http/Controllers/ConnectedAccountController.php index 456f519c2634..a6f011bde5f3 100644 --- a/app/Http/Controllers/ConnectedAccountController.php +++ b/app/Http/Controllers/ConnectedAccountController.php @@ -14,6 +14,7 @@ namespace App\Http\Controllers; use App\Libraries\MultiDB; use App\Libraries\OAuth\Providers\Google; use Illuminate\Http\Request; +use Google_Client; class ConnectedAccountController extends BaseController { From 67cedb834865338c1fa681db6ce7e423d349340e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 9 Mar 2021 17:03:22 +1100 Subject: [PATCH 13/30] minor fixes --- app/Http/Controllers/BillingSubscriptionController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/BillingSubscriptionController.php b/app/Http/Controllers/BillingSubscriptionController.php index 0dff2f31c962..c2f0892a6376 100644 --- a/app/Http/Controllers/BillingSubscriptionController.php +++ b/app/Http/Controllers/BillingSubscriptionController.php @@ -42,7 +42,7 @@ class BillingSubscriptionController extends BaseController /** * Show the list of BillingSubscriptions. - * * + * * @return Response * * @OA\Get( From 0a3b5e0665cbb09d12ac6714e4d001e5f72ba77f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 9 Mar 2021 17:06:03 +1100 Subject: [PATCH 14/30] minor fixes --- app/Factory/BillingSubscriptionFactory.php | 12 +++++- .../BillingSubscriptionController.php | 39 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/app/Factory/BillingSubscriptionFactory.php b/app/Factory/BillingSubscriptionFactory.php index 38708bd6ed79..f9f051293e9c 100644 --- a/app/Factory/BillingSubscriptionFactory.php +++ b/app/Factory/BillingSubscriptionFactory.php @@ -1,5 +1,13 @@ company_id = $company_id; + $billing_subscription->user_id = $user_id; return $billing_subscription; } diff --git a/app/Http/Controllers/BillingSubscriptionController.php b/app/Http/Controllers/BillingSubscriptionController.php index c2f0892a6376..348d0e89b2af 100644 --- a/app/Http/Controllers/BillingSubscriptionController.php +++ b/app/Http/Controllers/BillingSubscriptionController.php @@ -84,6 +84,45 @@ class BillingSubscriptionController extends BaseController return $this->listResponse($billing_subscriptions); } + /** + * Show the form for creating a new resource. + * + * @param CreateBillingSubscriptionRequest $request The request + * + * @return Response + * + * + * @OA\Get( + * path="/api/v1/billing_subscriptions/create", + * operationId="getBillingSubscriptionsCreate", + * tags={"billing_subscriptions"}, + * summary="Gets a new blank billing_subscriptions object", + * description="Returns a blank object with default values", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Response( + * response=200, + * description="A blank billing_subscriptions object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/BillingSubscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ public function create(CreateBillingSubscriptionRequest $request): \Illuminate\Http\Response { $billing_subscription = BillingSubscriptionFactory::create(auth()->user()->company()->id, auth()->user()->id); From cd48ae78f9cec2642e6a9c34db53aec030f8fc11 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 9 Mar 2021 21:30:34 +1100 Subject: [PATCH 15/30] Minor fixes --- .../BillingSubscriptionController.php | 241 ++++++++++++++++++ .../OpenAPI/BillingSubscription.php | 4 +- .../RecurringInvoiceController.php | 2 +- app/Http/Controllers/TwoFactorController.php | 2 +- app/Http/Middleware/PasswordProtection.php | 8 +- config/queue.php | 2 +- 6 files changed, 247 insertions(+), 12 deletions(-) diff --git a/app/Http/Controllers/BillingSubscriptionController.php b/app/Http/Controllers/BillingSubscriptionController.php index 348d0e89b2af..6c858bd1ed59 100644 --- a/app/Http/Controllers/BillingSubscriptionController.php +++ b/app/Http/Controllers/BillingSubscriptionController.php @@ -130,6 +130,45 @@ class BillingSubscriptionController extends BaseController return $this->itemResponse($billing_subscription); } + /** + * Store a newly created resource in storage. + * + * @param StoreBillingSubscriptionRequest $request The request + * + * @return Response + * + * + * @OA\Post( + * path="/api/v1/billing_subscriptions", + * operationId="storeBillingSubscription", + * tags={"billing_subscriptions"}, + * summary="Adds a billing_subscriptions", + * description="Adds an billing_subscriptions to the system", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Response( + * response=200, + * description="Returns the saved billing_subscriptions object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/BillingSubscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ public function store(StoreBillingSubscriptionRequest $request): \Illuminate\Http\Response { $billing_subscription = $this->billing_subscription_repo->save($request->all(), BillingSubscriptionFactory::create(auth()->user()->company()->id, auth()->user()->id)); @@ -139,16 +178,168 @@ class BillingSubscriptionController extends BaseController return $this->itemResponse($billing_subscription); } + /** + * Display the specified resource. + * + * @param ShowBillingSubscriptionRequest $request The request + * @param Invoice $billing_subscription The invoice + * + * @return Response + * + * + * @OA\Get( + * path="/api/v1/billing_subscriptions/{id}", + * operationId="showBillingSubscription", + * tags={"billing_subscriptions"}, + * summary="Shows an billing_subscriptions", + * description="Displays an billing_subscriptions by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The BillingSubscription Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the BillingSubscription object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/BillingSubscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ public function show(ShowBillingSubscriptionRequest $request, BillingSubscription $billing_subscription): \Illuminate\Http\Response { return $this->itemResponse($billing_subscription); } + /** + * Show the form for editing the specified resource. + * + * @param EditBillingSubscriptionRequest $request The request + * @param Invoice $billing_subscription The invoice + * + * @return Response + * + * @OA\Get( + * path="/api/v1/billing_subscriptions/{id}/edit", + * operationId="editBillingSubscription", + * tags={"billing_subscriptions"}, + * summary="Shows an billing_subscriptions for editting", + * description="Displays an billing_subscriptions by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The BillingSubscription Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the invoice object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/BillingSubscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ public function edit(EditBillingSubscriptionRequest $request, BillingSubscription $billing_subscription): \Illuminate\Http\Response { return $this->itemResponse($billing_subscription); } + /** + * Update the specified resource in storage. + * + * @param UpdateBillingSubscriptionRequest $request The request + * @param BillingSubscription $billing_subscription The invoice + * + * @return Response + * + * + * @OA\Put( + * path="/api/v1/billing_subscriptions/{id}", + * operationId="updateBillingSubscription", + * tags={"billing_subscriptions"}, + * summary="Updates an billing_subscriptions", + * description="Handles the updating of an billing_subscriptions by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The BillingSubscription Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the billing_subscriptions object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/BillingSubscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ public function update(UpdateBillingSubscriptionRequest $request, BillingSubscription $billing_subscription) { if ($request->entityIsDeleted($billing_subscription)) { @@ -160,6 +351,56 @@ class BillingSubscriptionController extends BaseController return $this->itemResponse($billing_subscription); } + /** + * Remove the specified resource from storage. + * + * @param DestroyBillingSubscriptionRequest $request + * @param BillingSubscription $invoice + * + * @return Response + * + * @throws \Exception + * @OA\Delete( + * path="/api/v1/billing_subscriptions/{id}", + * operationId="deleteBillingSubscription", + * tags={"billing_subscriptions"}, + * summary="Deletes a billing_subscriptions", + * description="Handles the deletion of an billing_subscriptions by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The BillingSubscription Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns a HTTP status", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ public function destroy(DestroyBillingSubscriptionRequest $request, BillingSubscription $billing_subscription): \Illuminate\Http\Response { $this->billing_subscription_repo->delete($billing_subscription); diff --git a/app/Http/Controllers/OpenAPI/BillingSubscription.php b/app/Http/Controllers/OpenAPI/BillingSubscription.php index bde43b87c618..848a318542e6 100644 --- a/app/Http/Controllers/OpenAPI/BillingSubscription.php +++ b/app/Http/Controllers/OpenAPI/BillingSubscription.php @@ -11,8 +11,8 @@ * @OA\Property(property="is_recurring", type="boolean", example="true", description="______"), * @OA\Property(property="frequency_id", type="string", example="1", description="integer const representation of the frequency"), * @OA\Property(property="auto_bill", type="string", example="always", description="enum setting"), - * @OA\Property(property="promo_code", type="string", example="100OFF", description="______"), - * @OA\Property(property="promo_discount", type="float", example=10, description="______"), + * @OA\Property(property="promo_code", type="string", example="PROMOCODE4U", description="______"), + * @OA\Property(property="promo_discount", type="number", example=10, description="______"), * @OA\Property(property="is_amount_discount", type="boolean", example="true", description="______"), * @OA\Property(property="allow_cancellation", type="boolean", example="true", description="______"), * @OA\Property(property="per_seat_enabled", type="boolean", example="true", description="______"), diff --git a/app/Http/Controllers/RecurringInvoiceController.php b/app/Http/Controllers/RecurringInvoiceController.php index e28a59c715df..e36a8f624924 100644 --- a/app/Http/Controllers/RecurringInvoiceController.php +++ b/app/Http/Controllers/RecurringInvoiceController.php @@ -439,7 +439,7 @@ class RecurringInvoiceController extends BaseController /** * @OA\Get( * path="/api/v1/recurring_invoice/{invitation_key}/download", - * operationId="downloadInvoice", + * operationId="downloadRecurringInvoice", * tags={"invoices"}, * summary="Download a specific invoice by invitation key", * description="Downloads a specific invoice", diff --git a/app/Http/Controllers/TwoFactorController.php b/app/Http/Controllers/TwoFactorController.php index cbee6da80188..8ffca0c5fc9e 100644 --- a/app/Http/Controllers/TwoFactorController.php +++ b/app/Http/Controllers/TwoFactorController.php @@ -38,7 +38,7 @@ class TwoFactorController extends BaseController $data = [ 'secret' => $secret, - 'qrCode' => $qrCode, + 'qrCode' => $qr_code, ]; return response()->json(['data' => $data], 200); diff --git a/app/Http/Middleware/PasswordProtection.php b/app/Http/Middleware/PasswordProtection.php index 3c0275d68b57..7dc70f9ee53d 100644 --- a/app/Http/Middleware/PasswordProtection.php +++ b/app/Http/Middleware/PasswordProtection.php @@ -31,18 +31,12 @@ class PasswordProtection */ public function handle($request, Closure $next) { - // {nlog($request->headers->all()); - // nlog($request->all()); $error = [ 'message' => 'Invalid Password', 'errors' => new stdClass, ]; - nlog(Cache::get(auth()->user()->hashed_id.'_logged_in')); - nlog($request->header('X-API-OAUTH-PASSWORD')); - - if (Cache::get(auth()->user()->hashed_id.'_logged_in')) { Cache::pull(auth()->user()->hashed_id.'_logged_in'); @@ -94,4 +88,4 @@ class PasswordProtection } -} +} \ No newline at end of file diff --git a/config/queue.php b/config/queue.php index 6fb44cc4f7a1..dc2d4665ea47 100644 --- a/config/queue.php +++ b/config/queue.php @@ -13,7 +13,7 @@ return [ | */ - 'default' => env('QUEUE_CONNECTION', 'database'), + 'default' => env('QUEUE_CONNECTION', 'sync'), /* |-------------------------------------------------------------------------- From 868b6efd8e05d50c3b9b7007118cf35cff7d35ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 9 Mar 2021 14:24:31 +0100 Subject: [PATCH 16/30] Add currency_id to billing_subscriptions table --- app/Models/BillingSubscription.php | 1 + ...ency_id_to_billing_subscriptions_table.php | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 database/migrations/2021_03_09_132242_add_currency_id_to_billing_subscriptions_table.php diff --git a/app/Models/BillingSubscription.php b/app/Models/BillingSubscription.php index 7e3da4135bba..486e3134d607 100644 --- a/app/Models/BillingSubscription.php +++ b/app/Models/BillingSubscription.php @@ -41,6 +41,7 @@ class BillingSubscription extends BaseModel 'plan_map', 'refund_period', 'webhook_configuration', + 'currency_id', ]; protected $casts = [ diff --git a/database/migrations/2021_03_09_132242_add_currency_id_to_billing_subscriptions_table.php b/database/migrations/2021_03_09_132242_add_currency_id_to_billing_subscriptions_table.php new file mode 100644 index 000000000000..bc687317a449 --- /dev/null +++ b/database/migrations/2021_03_09_132242_add_currency_id_to_billing_subscriptions_table.php @@ -0,0 +1,29 @@ +unsignedInteger('currency_id')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + } +} From 83b23956f6d34e9f9c7c937593b6bc9f363276a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 9 Mar 2021 14:29:57 +0100 Subject: [PATCH 17/30] Update authorize methods on billing subscription requests --- .../BillingSubscription/CreateBillingSubscriptionRequest.php | 4 ++-- .../BillingSubscription/DestroyBillingSubscriptionRequest.php | 2 +- .../BillingSubscription/EditBillingSubscriptionRequest.php | 4 +--- .../BillingSubscription/ShowBillingSubscriptionRequest.php | 3 +-- .../BillingSubscription/StoreBillingSubscriptionRequest.php | 3 ++- .../BillingSubscription/UpdateBillingSubscriptionRequest.php | 2 +- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/Http/Requests/BillingSubscription/CreateBillingSubscriptionRequest.php b/app/Http/Requests/BillingSubscription/CreateBillingSubscriptionRequest.php index f7f0a907343d..57bde4545018 100644 --- a/app/Http/Requests/BillingSubscription/CreateBillingSubscriptionRequest.php +++ b/app/Http/Requests/BillingSubscription/CreateBillingSubscriptionRequest.php @@ -3,6 +3,7 @@ namespace App\Http\Requests\BillingSubscription; use App\Http\Requests\Request; +use App\Models\BillingSubscription; class CreateBillingSubscriptionRequest extends Request { @@ -13,8 +14,7 @@ class CreateBillingSubscriptionRequest extends Request */ public function authorize(): bool { - return true; -// return auth()->user()->can('create', BillingSubscription::class); // TODO + return auth()->user()->can('create', BillingSubscription::class); } /** diff --git a/app/Http/Requests/BillingSubscription/DestroyBillingSubscriptionRequest.php b/app/Http/Requests/BillingSubscription/DestroyBillingSubscriptionRequest.php index c263836d9442..2abde3a9b43d 100644 --- a/app/Http/Requests/BillingSubscription/DestroyBillingSubscriptionRequest.php +++ b/app/Http/Requests/BillingSubscription/DestroyBillingSubscriptionRequest.php @@ -14,7 +14,7 @@ class DestroyBillingSubscriptionRequest extends Request */ public function authorize() { - return true; // TODO + return auth()->user()->can('edit', $this->billing_subscription); } /** diff --git a/app/Http/Requests/BillingSubscription/EditBillingSubscriptionRequest.php b/app/Http/Requests/BillingSubscription/EditBillingSubscriptionRequest.php index dcb3f966bcd5..74538a8a79f9 100644 --- a/app/Http/Requests/BillingSubscription/EditBillingSubscriptionRequest.php +++ b/app/Http/Requests/BillingSubscription/EditBillingSubscriptionRequest.php @@ -14,9 +14,7 @@ class EditBillingSubscriptionRequest extends Request */ public function authorize() { - return true; - - // return auth()->user()->can('view', $this->billing_subscription); // TODO + return auth()->user()->can('edit', $this->billing_subscription); } /** diff --git a/app/Http/Requests/BillingSubscription/ShowBillingSubscriptionRequest.php b/app/Http/Requests/BillingSubscription/ShowBillingSubscriptionRequest.php index f5d08bec9445..e5ee4828f3da 100644 --- a/app/Http/Requests/BillingSubscription/ShowBillingSubscriptionRequest.php +++ b/app/Http/Requests/BillingSubscription/ShowBillingSubscriptionRequest.php @@ -14,8 +14,7 @@ class ShowBillingSubscriptionRequest extends Request */ public function authorize() : bool { - return true; -// return auth()->user()->can('view', $this->billing_subscription); // TODO + return auth()->user()->can('view', $this->billing_subscription); } /** diff --git a/app/Http/Requests/BillingSubscription/StoreBillingSubscriptionRequest.php b/app/Http/Requests/BillingSubscription/StoreBillingSubscriptionRequest.php index c897edc5b299..58d99d06d1f5 100644 --- a/app/Http/Requests/BillingSubscription/StoreBillingSubscriptionRequest.php +++ b/app/Http/Requests/BillingSubscription/StoreBillingSubscriptionRequest.php @@ -3,6 +3,7 @@ namespace App\Http\Requests\BillingSubscription; use App\Http\Requests\Request; +use App\Models\BillingSubscription; class StoreBillingSubscriptionRequest extends Request { @@ -13,7 +14,7 @@ class StoreBillingSubscriptionRequest extends Request */ public function authorize() { - return true; // TODO + return auth()->user()->can('create', BillingSubscription::class); } /** diff --git a/app/Http/Requests/BillingSubscription/UpdateBillingSubscriptionRequest.php b/app/Http/Requests/BillingSubscription/UpdateBillingSubscriptionRequest.php index a92d14ff0b33..2436def991ed 100644 --- a/app/Http/Requests/BillingSubscription/UpdateBillingSubscriptionRequest.php +++ b/app/Http/Requests/BillingSubscription/UpdateBillingSubscriptionRequest.php @@ -16,7 +16,7 @@ class UpdateBillingSubscriptionRequest extends Request */ public function authorize() { - return true; // TODO + return auth()->user()->can('edit', $this->billing_subscription); } /** From 737020eeedcbed8a861d97f847eae52c867e96be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 9 Mar 2021 15:25:11 +0100 Subject: [PATCH 18/30] Fix wrong variable in the BillingSubscriptionTransformer.php --- app/Transformers/BillingSubscriptionTransformer.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Transformers/BillingSubscriptionTransformer.php b/app/Transformers/BillingSubscriptionTransformer.php index 8b2f376f9762..8adbd2568ac2 100644 --- a/app/Transformers/BillingSubscriptionTransformer.php +++ b/app/Transformers/BillingSubscriptionTransformer.php @@ -58,9 +58,9 @@ class BillingSubscriptionTransformer extends EntityTransformer 'refund_period' => (int)$billing_subscription->refund_period, 'webhook_configuration' => (string)$billing_subscription->webhook_configuration, 'is_deleted' => (bool)$billing_subscription->is_deleted, - 'created_at' => (int) $credit->created_at, - 'updated_at' => (int) $credit->updated_at, - 'archived_at' => (int) $credit->deleted_at, + 'created_at' => (int)$billing_subscription->created_at, + 'updated_at' => (int)$billing_subscription->updated_at, + 'archived_at' => (int)$billing_subscription->deleted_at, ]; } From fa5fa7d527276db842bc3253c08106ad31474fb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 9 Mar 2021 15:25:50 +0100 Subject: [PATCH 19/30] Feature tests for billing subscription API --- .../factories/BillingSubscriptionFactory.php | 38 ++++++ tests/Feature/BillingSubscriptionApiTest.php | 123 ++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 database/factories/BillingSubscriptionFactory.php create mode 100644 tests/Feature/BillingSubscriptionApiTest.php diff --git a/database/factories/BillingSubscriptionFactory.php b/database/factories/BillingSubscriptionFactory.php new file mode 100644 index 000000000000..2fdf320cd5f4 --- /dev/null +++ b/database/factories/BillingSubscriptionFactory.php @@ -0,0 +1,38 @@ +makeTestData(); + + Session::start(); + + $this->faker = \Faker\Factory::create(); + + Model::reguard(); + } + + public function testExpenseGet() + { + $product = Product::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + ]); + + $billing_subscription = BillingSubscription::factory()->create([ + 'product_id' => $product->id, + 'company_id' => $this->company->id, + ]); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/billing_subscriptions/' . $this->encodePrimaryKey($billing_subscription->id)); + + $response->assertStatus(200); + } + + public function testBillingSubscriptionsPost() + { + $product = Product::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + ]); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/billing_subscriptions', ['product_id' => $product->id, 'allow_cancellation' => true]); + + $response->assertStatus(200); + } + + public function testBillingSubscriptionPut() + { + $product = Product::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + ]); + + $response1 = $this + ->withHeaders(['X-API-SECRET' => config('ninja.api_secret'),'X-API-TOKEN' => $this->token]) + ->post('/api/v1/billing_subscriptions', ['product_id' => $product->id]) + ->assertStatus(200) + ->json(); + + $response2 = $this + ->withHeaders(['X-API-SECRET' => config('ninja.api_secret'),'X-API-TOKEN' => $this->token]) + ->put('/api/v1/billing_subscriptions/' . $response1['data']['id'], ['allow_cancellation' => true]) + ->assertStatus(200) + ->json(); + + $this->assertNotEquals($response1['data']['allow_cancellation'], $response2['data']['allow_cancellation']); + } + + /* + TypeError : Argument 1 passed to App\Transformers\BillingSubscriptionTransformer::transform() must be an instance of App\Models\BillingSubscription, bool given, called in /var/www/html/vendor/league/fractal/src/Scope.php on line 407 + /var/www/html/app/Transformers/BillingSubscriptionTransformer.php:35 + /var/www/html/vendor/league/fractal/src/Scope.php:407 + /var/www/html/vendor/league/fractal/src/Scope.php:349 + /var/www/html/vendor/league/fractal/src/Scope.php:235 + /var/www/html/app/Http/Controllers/BaseController.php:395 + /var/www/html/app/Http/Controllers/BillingSubscriptionController.php:408 + */ + public function testBillingSubscriptionDeleted() + { + $this->markTestSkipped(); + + $product = Product::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + ]); + + $billing_subscription = BillingSubscription::factory()->create([ + 'product_id' => $product->id, + 'company_id' => $this->company->id, + ]); + + $response = $this + ->withHeaders(['X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token]) + ->delete('/api/v1/billing_subscriptions/' . $this->encodePrimaryKey($billing_subscription->id)) + ->assertStatus(200) + ->json(); + + dd($response); + } +} From 90b3b25849333cc7d3e62b0a6c319f2306fa2931 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 10 Mar 2021 09:23:13 +1100 Subject: [PATCH 20/30] Fixes for tests - changed listResponse to itemResponse --- .../Controllers/BillingSubscriptionController.php | 2 +- tests/Feature/BillingSubscriptionApiTest.php | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/BillingSubscriptionController.php b/app/Http/Controllers/BillingSubscriptionController.php index 6c858bd1ed59..199d51005114 100644 --- a/app/Http/Controllers/BillingSubscriptionController.php +++ b/app/Http/Controllers/BillingSubscriptionController.php @@ -405,6 +405,6 @@ class BillingSubscriptionController extends BaseController { $this->billing_subscription_repo->delete($billing_subscription); - return $this->listResponse($billing_subscription->fresh()); + return $this->itemResponse($billing_subscription->fresh()); } } diff --git a/tests/Feature/BillingSubscriptionApiTest.php b/tests/Feature/BillingSubscriptionApiTest.php index c185968ba1e4..cd66f20f063a 100644 --- a/tests/Feature/BillingSubscriptionApiTest.php +++ b/tests/Feature/BillingSubscriptionApiTest.php @@ -1,4 +1,13 @@ markTestSkipped(); $product = Product::factory()->create([ 'company_id' => $this->company->id, @@ -118,6 +130,5 @@ class BillingSubscriptionApiTest extends TestCase ->assertStatus(200) ->json(); - dd($response); } } From e0b169b64b78aae59ca18e9d636c6a5fce0ce65e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 10 Mar 2021 09:56:59 +1100 Subject: [PATCH 21/30] Fixes for billing subscriptions --- app/Http/Controllers/ConnectedAccountController.php | 12 ++++++++++-- app/Http/Controllers/OpenAPI/BillingSubscription.php | 1 + app/Http/Controllers/SetupController.php | 1 + ..._08_123729_create_billing_subscriptions_table.php | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/ConnectedAccountController.php b/app/Http/Controllers/ConnectedAccountController.php index a6f011bde5f3..d51638fc3980 100644 --- a/app/Http/Controllers/ConnectedAccountController.php +++ b/app/Http/Controllers/ConnectedAccountController.php @@ -13,12 +13,18 @@ namespace App\Http\Controllers; use App\Libraries\MultiDB; use App\Libraries\OAuth\Providers\Google; -use Illuminate\Http\Request; +use App\Models\CompanyUser; +use App\Transformers\CompanyUserTransformer; use Google_Client; +use Illuminate\Http\Request; class ConnectedAccountController extends BaseController { + protected $entity_type = CompanyUser::class; + + protected $entity_transformer = CompanyUserTransformer::class; + public function __construct() { parent::__construct(); @@ -128,8 +134,10 @@ class ConnectedAccountController extends BaseController auth()->user()->save(); //$ct = CompanyUser::whereUserId(auth()->user()->id); + $ct = CompanyUser::whereUserId(auth()->user()->id); - return $this->listResponse(auth()->user()); + return $this->listResponse($ct); + // return $this->listResponse(auth()->user()); } return response() diff --git a/app/Http/Controllers/OpenAPI/BillingSubscription.php b/app/Http/Controllers/OpenAPI/BillingSubscription.php index 848a318542e6..c618f39c85a2 100644 --- a/app/Http/Controllers/OpenAPI/BillingSubscription.php +++ b/app/Http/Controllers/OpenAPI/BillingSubscription.php @@ -16,6 +16,7 @@ * @OA\Property(property="is_amount_discount", type="boolean", example="true", description="______"), * @OA\Property(property="allow_cancellation", type="boolean", example="true", description="______"), * @OA\Property(property="per_seat_enabled", type="boolean", example="true", description="______"), + * @OA\Property(property="currency_id", type="integer", example="1", description="______"), * @OA\Property(property="min_seats_limit", type="integer", example="1", description="______"), * @OA\Property(property="max_seats_limit", type="integer", example="100", description="______"), * @OA\Property(property="trial_enabled", type="boolean", example="true", description="______"), diff --git a/app/Http/Controllers/SetupController.php b/app/Http/Controllers/SetupController.php index 223d0943c216..d2bcc8708ce0 100644 --- a/app/Http/Controllers/SetupController.php +++ b/app/Http/Controllers/SetupController.php @@ -155,6 +155,7 @@ class SetupController extends Controller return redirect('/'); } catch (Exception $e) { + nlog($e->getMessage()); return redirect() diff --git a/database/migrations/2021_03_08_123729_create_billing_subscriptions_table.php b/database/migrations/2021_03_08_123729_create_billing_subscriptions_table.php index 27400a8fa679..6268b9638f48 100644 --- a/database/migrations/2021_03_08_123729_create_billing_subscriptions_table.php +++ b/database/migrations/2021_03_08_123729_create_billing_subscriptions_table.php @@ -39,7 +39,7 @@ class CreateBillingSubscriptionsTable extends Migration $table->softDeletes('deleted_at', 6); $table->boolean('is_deleted')->default(false); $table->timestamps(); - $table->foreign('product_id')->references('id')->on('products'); + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); $table->index(['company_id', 'deleted_at']); }); From 00eefcd7812285586fac6e59704cc618a0ca51c0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 10 Mar 2021 11:08:58 +1100 Subject: [PATCH 22/30] Client Subscription scaffolding --- .../ClientSubscriptionController.php | 410 ++++++++++++++++++ .../OpenAPI/BillingSubscription.php | 5 +- .../OpenAPI/ClientSubscription.php | 19 + .../CreateBillingSubscriptionRequest.php | 9 + .../DestroyBillingSubscriptionRequest.php | 9 + .../EditBillingSubscriptionRequest.php | 9 + .../ShowBillingSubscriptionRequest.php | 9 + .../StoreBillingSubscriptionRequest.php | 9 + .../UpdateBillingSubscriptionRequest.php | 9 + .../CreateClientSubscriptionRequest.php | 40 ++ .../DestroyClientSubscriptionRequest.php | 40 ++ .../EditClientSubscriptionRequest.php | 40 ++ .../ShowClientSubscriptionRequest.php | 40 ++ .../StoreClientSubscriptionRequest.php | 38 ++ .../UpdateClientSubscriptionRequest.php | 42 ++ app/Http/Requests/Request.php | 4 + app/Models/ClientSubscription.php | 40 +- app/Models/Company.php | 10 + app/Models/Credit.php | 1 + app/Models/Invoice.php | 1 + app/Models/Quote.php | 1 + app/Policies/ClientSubscriptionPolicy.php | 31 ++ app/Providers/AuthServiceProvider.php | 3 + .../ClientSubscriptionRepository.php | 28 ++ .../ClientSubscriptionTransformer.php | 78 ++++ app/Transformers/CompanyTransformer.php | 17 + app/Transformers/CreditTransformer.php | 1 + app/Transformers/InvoiceTransformer.php | 1 + app/Transformers/QuoteTransformer.php | 2 + ...729_create_billing_subscriptions_table.php | 5 + ...ency_id_to_billing_subscriptions_table.php | 12 + routes/api.php | 1 + 32 files changed, 960 insertions(+), 4 deletions(-) create mode 100644 app/Http/Controllers/ClientSubscriptionController.php create mode 100644 app/Http/Controllers/OpenAPI/ClientSubscription.php create mode 100644 app/Http/Requests/ClientSubscription/CreateClientSubscriptionRequest.php create mode 100644 app/Http/Requests/ClientSubscription/DestroyClientSubscriptionRequest.php create mode 100644 app/Http/Requests/ClientSubscription/EditClientSubscriptionRequest.php create mode 100644 app/Http/Requests/ClientSubscription/ShowClientSubscriptionRequest.php create mode 100644 app/Http/Requests/ClientSubscription/StoreClientSubscriptionRequest.php create mode 100644 app/Http/Requests/ClientSubscription/UpdateClientSubscriptionRequest.php create mode 100644 app/Policies/ClientSubscriptionPolicy.php create mode 100644 app/Repositories/ClientSubscriptionRepository.php create mode 100644 app/Transformers/ClientSubscriptionTransformer.php diff --git a/app/Http/Controllers/ClientSubscriptionController.php b/app/Http/Controllers/ClientSubscriptionController.php new file mode 100644 index 000000000000..bf0a9b39b167 --- /dev/null +++ b/app/Http/Controllers/ClientSubscriptionController.php @@ -0,0 +1,410 @@ +client_subscription_repo = $client_subscription_repo; + } + + /** + * Show the list of ClientSubscriptions. + * + * @return Response + * + * @OA\Get( + * path="/api/v1/client_subscriptions", + * operationId="getClientSubscriptions", + * tags={"client_subscriptions"}, + * summary="Gets a list of client_subscriptions", + * description="Lists client_subscriptions.", + * + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Response( + * response=200, + * description="A list of client_subscriptions", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/ClientSubscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + + public function index(): \Illuminate\Http\Response + { + $client_subscriptions = ClientSubscription::query()->company(); + + return $this->listResponse($client_subscriptions); + } + + /** + * Show the form for creating a new resource. + * + * @param CreateClientSubscriptionRequest $request The request + * + * @return Response + * + * + * @OA\Get( + * path="/api/v1/client_subscriptions/create", + * operationId="getClientSubscriptionsCreate", + * tags={"client_subscriptions"}, + * summary="Gets a new blank client_subscriptions object", + * description="Returns a blank object with default values", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Response( + * response=200, + * description="A blank client_subscriptions object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/ClientSubscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function create(CreateClientSubscriptionRequest $request): \Illuminate\Http\Response + { + $client_subscription = ClientSubscriptionFactory::create(auth()->user()->company()->id, auth()->user()->id); + + return $this->itemResponse($client_subscription); + } + + /** + * Store a newly created resource in storage. + * + * @param StoreClientSubscriptionRequest $request The request + * + * @return Response + * + * + * @OA\Post( + * path="/api/v1/client_subscriptions", + * operationId="storeClientSubscription", + * tags={"client_subscriptions"}, + * summary="Adds a client_subscriptions", + * description="Adds an client_subscriptions to the system", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Response( + * response=200, + * description="Returns the saved client_subscriptions object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/ClientSubscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function store(StoreClientSubscriptionRequest $request): \Illuminate\Http\Response + { + $client_subscription = $this->client_subscription_repo->save($request->all(), ClientSubscriptionFactory::create(auth()->user()->company()->id, auth()->user()->id)); + + event(new ClientsubscriptionWasCreated($client_subscription, $client_subscription->company, Ninja::eventVars())); + + return $this->itemResponse($client_subscription); + } + + /** + * Display the specified resource. + * + * @param ShowClientSubscriptionRequest $request The request + * @param Invoice $client_subscription The invoice + * + * @return Response + * + * + * @OA\Get( + * path="/api/v1/client_subscriptions/{id}", + * operationId="showClientSubscription", + * tags={"client_subscriptions"}, + * summary="Shows an client_subscriptions", + * description="Displays an client_subscriptions by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The ClientSubscription Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the ClientSubscription object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/ClientSubscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function show(ShowClientSubscriptionRequest $request, ClientSubscription $client_subscription): \Illuminate\Http\Response + { + return $this->itemResponse($client_subscription); + } + + /** + * Show the form for editing the specified resource. + * + * @param EditClientSubscriptionRequest $request The request + * @param Invoice $client_subscription The invoice + * + * @return Response + * + * @OA\Get( + * path="/api/v1/client_subscriptions/{id}/edit", + * operationId="editClientSubscription", + * tags={"client_subscriptions"}, + * summary="Shows an client_subscriptions for editting", + * description="Displays an client_subscriptions by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The ClientSubscription Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the invoice object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/ClientSubscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function edit(EditClientSubscriptionRequest $request, ClientSubscription $client_subscription): \Illuminate\Http\Response + { + return $this->itemResponse($client_subscription); + } + + /** + * Update the specified resource in storage. + * + * @param UpdateClientSubscriptionRequest $request The request + * @param ClientSubscription $client_subscription The invoice + * + * @return Response + * + * + * @OA\Put( + * path="/api/v1/client_subscriptions/{id}", + * operationId="updateClientSubscription", + * tags={"client_subscriptions"}, + * summary="Updates an client_subscriptions", + * description="Handles the updating of an client_subscriptions by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The ClientSubscription Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the client_subscriptions object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/ClientSubscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function update(UpdateClientSubscriptionRequest $request, ClientSubscription $client_subscription) + { + if ($request->entityIsDeleted($client_subscription)) { + return $request->disallowUpdate(); + } + + $client_subscription = $this->client_subscription_repo->save($request->all(), $client_subscription); + + return $this->itemResponse($client_subscription); + } + + /** + * Remove the specified resource from storage. + * + * @param DestroyClientSubscriptionRequest $request + * @param ClientSubscription $invoice + * + * @return Response + * + * @throws \Exception + * @OA\Delete( + * path="/api/v1/client_subscriptions/{id}", + * operationId="deleteClientSubscription", + * tags={"client_subscriptions"}, + * summary="Deletes a client_subscriptions", + * description="Handles the deletion of an client_subscriptions by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The ClientSubscription Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns a HTTP status", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function destroy(DestroyClientSubscriptionRequest $request, ClientSubscription $client_subscription): \Illuminate\Http\Response + { + $this->client_subscription_repo->delete($client_subscription); + + return $this->itemResponse($client_subscription->fresh()); + } +} diff --git a/app/Http/Controllers/OpenAPI/BillingSubscription.php b/app/Http/Controllers/OpenAPI/BillingSubscription.php index c618f39c85a2..fad9b1eac32f 100644 --- a/app/Http/Controllers/OpenAPI/BillingSubscription.php +++ b/app/Http/Controllers/OpenAPI/BillingSubscription.php @@ -26,7 +26,10 @@ * @OA\Property(property="plan_map", type="string", example="1", description="map describing the available upgrade/downgrade plans for this subscription"), * @OA\Property(property="refund_period", type="integer", example="2", description="______"), * @OA\Property(property="webhook_configuration", type="string", example="2", description="______"), - * @OA\Property(property="is_deleted", type="boolean", example="2", description="______"), + * @OA\Property(property="is_deleted", type="boolean", example="true", description="______"), + * @OA\Property(property="archived_at", type="number", format="integer", example="1434342123", description="Timestamp"), + * @OA\Property(property="created_at", type="number", format="integer", example="134341234234", description="Timestamp"), + * @OA\Property(property="updated_at", type="number", format="integer", example="134341234234", description="Timestamp"), * ) */ \ No newline at end of file diff --git a/app/Http/Controllers/OpenAPI/ClientSubscription.php b/app/Http/Controllers/OpenAPI/ClientSubscription.php new file mode 100644 index 000000000000..9ea5beae440d --- /dev/null +++ b/app/Http/Controllers/OpenAPI/ClientSubscription.php @@ -0,0 +1,19 @@ +user()->can('create', ClientSubscription::class); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/ClientSubscription/DestroyClientSubscriptionRequest.php b/app/Http/Requests/ClientSubscription/DestroyClientSubscriptionRequest.php new file mode 100644 index 000000000000..609b88bcf1e6 --- /dev/null +++ b/app/Http/Requests/ClientSubscription/DestroyClientSubscriptionRequest.php @@ -0,0 +1,40 @@ +user()->can('edit', $this->client_subscription); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/ClientSubscription/EditClientSubscriptionRequest.php b/app/Http/Requests/ClientSubscription/EditClientSubscriptionRequest.php new file mode 100644 index 000000000000..bf152757d735 --- /dev/null +++ b/app/Http/Requests/ClientSubscription/EditClientSubscriptionRequest.php @@ -0,0 +1,40 @@ +user()->can('edit', $this->client_subscription); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/ClientSubscription/ShowClientSubscriptionRequest.php b/app/Http/Requests/ClientSubscription/ShowClientSubscriptionRequest.php new file mode 100644 index 000000000000..bf2430e7ba5e --- /dev/null +++ b/app/Http/Requests/ClientSubscription/ShowClientSubscriptionRequest.php @@ -0,0 +1,40 @@ +user()->can('view', $this->client_subscription); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/ClientSubscription/StoreClientSubscriptionRequest.php b/app/Http/Requests/ClientSubscription/StoreClientSubscriptionRequest.php new file mode 100644 index 000000000000..5c29cd1a44a5 --- /dev/null +++ b/app/Http/Requests/ClientSubscription/StoreClientSubscriptionRequest.php @@ -0,0 +1,38 @@ +user()->can('create', ClientSubscription::class); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return []; + } +} diff --git a/app/Http/Requests/ClientSubscription/UpdateClientSubscriptionRequest.php b/app/Http/Requests/ClientSubscription/UpdateClientSubscriptionRequest.php new file mode 100644 index 000000000000..47f989631d8b --- /dev/null +++ b/app/Http/Requests/ClientSubscription/UpdateClientSubscriptionRequest.php @@ -0,0 +1,42 @@ +user()->can('edit', $this->client_subscription); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index df4edd1d94b3..a67b610c70d8 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -70,6 +70,10 @@ class Request extends FormRequest public function decodePrimaryKeys($input) { + if (array_key_exists('subscription_id', $input) && is_string($input['subscription_id'])) { + $input['subscription_id'] = $this->decodePrimaryKey($input['subscription_id']); + } + if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) { $input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']); } diff --git a/app/Models/ClientSubscription.php b/app/Models/ClientSubscription.php index ac7000cea2c0..bf38456dbc53 100644 --- a/app/Models/ClientSubscription.php +++ b/app/Models/ClientSubscription.php @@ -9,13 +9,47 @@ * @license https://opensource.org/licenses/AAL */ - namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Model; -class ClientSubscription extends Model +class ClientSubscription extends BaseModel { use HasFactory; + + protected $fillable = [ + // 'subscription_id', + // 'recurring_invoice_id', + // 'client_id', + // 'trial_started', + // 'trial_ends', + // 'is_deleted', + ]; + + protected $casts = [ + 'is_deleted' => 'boolean', + 'updated_at' => 'timestamp', + 'created_at' => 'timestamp', + 'deleted_at' => 'timestamp', + ]; + + public function company(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(Company::class); + } + + public function recurring_invoice(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(RecurringInvoice::class); + } + + public function client(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(Client::class); + } + + public function subscription(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(BillingSubscription::class); + } } diff --git a/app/Models/Company.php b/app/Models/Company.php index 16bc053a5a5a..3d1745444829 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -159,6 +159,16 @@ class Company extends BaseModel return $this->hasMany(ExpenseCategory::class)->withTrashed(); } + public function client_subscriptions() + { + return $this->hasMany(ClientSubscription::class)->withTrashed(); + } + + public function billing_subscriptions() + { + return $this->hasMany(BillingSubscription::class)->withTrashed(); + } + public function task_statuses() { return $this->hasMany(TaskStatus::class)->withTrashed(); diff --git a/app/Models/Credit.php b/app/Models/Credit.php index aecb446a18a5..8acf5a3dd9eb 100644 --- a/app/Models/Credit.php +++ b/app/Models/Credit.php @@ -77,6 +77,7 @@ class Credit extends BaseModel 'design_id', 'assigned_user_id', 'exchange_rate', + 'subscription_id', ]; protected $casts = [ diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index f4cd530696a8..22a9698a5bd4 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -91,6 +91,7 @@ class Invoice extends BaseModel 'design_id', 'assigned_user_id', 'exchange_rate', + 'subscription_id', ]; protected $casts = [ diff --git a/app/Models/Quote.php b/app/Models/Quote.php index 1f2b740ea81b..2f0c414ec75b 100644 --- a/app/Models/Quote.php +++ b/app/Models/Quote.php @@ -78,6 +78,7 @@ class Quote extends BaseModel 'design_id', 'assigned_user_id', 'exchange_rate', + 'subscription_id', ]; protected $casts = [ diff --git a/app/Policies/ClientSubscriptionPolicy.php b/app/Policies/ClientSubscriptionPolicy.php new file mode 100644 index 000000000000..e85df1028e4d --- /dev/null +++ b/app/Policies/ClientSubscriptionPolicy.php @@ -0,0 +1,31 @@ +isAdmin() || $user->hasPermission('create_client_subscription') || $user->hasPermission('create_all'); + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 60281db3b3fe..d71a5ae1435f 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -14,6 +14,7 @@ namespace App\Providers; use App\Models\Activity; use App\Models\BillingSubscription; use App\Models\Client; +use App\Models\ClientSubscription; use App\Models\Company; use App\Models\CompanyGateway; use App\Models\CompanyToken; @@ -40,6 +41,7 @@ use App\Models\Webhook; use App\Policies\ActivityPolicy; use App\Policies\BillingSubscriptionPolicy; use App\Policies\ClientPolicy; +use App\Policies\ClientSubscriptionPolicy; use App\Policies\CompanyGatewayPolicy; use App\Policies\CompanyPolicy; use App\Policies\CompanyTokenPolicy; @@ -77,6 +79,7 @@ class AuthServiceProvider extends ServiceProvider Activity::class => ActivityPolicy::class, BillingSubscription::class => BillingSubscriptionPolicy::class, Client::class => ClientPolicy::class, + ClientSubscription::class => ClientSubscriptionPolicy::class, Company::class => CompanyPolicy::class, CompanyToken::class => CompanyTokenPolicy::class, CompanyGateway::class => CompanyGatewayPolicy::class, diff --git a/app/Repositories/ClientSubscriptionRepository.php b/app/Repositories/ClientSubscriptionRepository.php new file mode 100644 index 000000000000..4cb88cb2ca53 --- /dev/null +++ b/app/Repositories/ClientSubscriptionRepository.php @@ -0,0 +1,28 @@ +fill($data) + ->save(); + + return $client_subscription; + } +} diff --git a/app/Transformers/ClientSubscriptionTransformer.php b/app/Transformers/ClientSubscriptionTransformer.php new file mode 100644 index 000000000000..5ed3c31d5539 --- /dev/null +++ b/app/Transformers/ClientSubscriptionTransformer.php @@ -0,0 +1,78 @@ + $this->encodePrimaryKey($client_subscription->id), + 'subscription_id' => $this->encodePrimaryKey($client_subscription->subscription_id), + 'company_id' => $this->encodePrimaryKey($client_subscription->company_id), + 'recurring_invoice_id' => $this->encodePrimaryKey($client_subscription->recurring_invoice_id), + 'client_id' => $this->encodePrimaryKey($client_subscription->client_id), + 'trial_started' => (string)$client_subscription->trial_started ?: '', + 'trial_ends' => (string)$client_subscription->trial_ends ?: '', + 'is_deleted' => (bool)$client_subscription->is_deleted, + 'created_at' => (int)$client_subscription->created_at, + 'updated_at' => (int)$client_subscription->updated_at, + 'archived_at' => (int)$client_subscription->deleted_at, + ]; + } + + public function includeClient(ClientSubscription $client_subscription): \League\Fractal\Resource\Item + { + $transformer = new ClientTransformer($this->serializer); + + return $this->includeItem($client_subscription->client, $transformer, Client::class); + } + + public function includeRecurringInvoice(ClientSubscription $client_subscription): \League\Fractal\Resource\Item + { + $transformer = new RecurringInvoiceTransformer($this->serializer); + + return $this->includeItem($client_subscription->recurring_invoice, $transformer, RecurringInvoice::class); + } + + public function includeSubscription(ClientSubscription $client_subscription): \League\Fractal\Resource\Item + { + $transformer = new BillingSubscriptionTransformer($this->serializer); + + return $this->includeItem($client_subscription->subscription, $transformer, BillingSubscription::class); + } +} + diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index 308cf6cde8b7..8620fefd7b2a 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -13,7 +13,9 @@ namespace App\Transformers; use App\Models\Account; use App\Models\Activity; +use App\Models\BillingSubscription; use App\Models\Client; +use App\Models\ClientSubscription; use App\Models\Company; use App\Models\CompanyGateway; use App\Models\CompanyLedger; @@ -90,6 +92,7 @@ class CompanyTransformer extends EntityTransformer 'system_logs', 'expense_categories', 'task_statuses', + 'client_subscriptions', ]; /** @@ -358,4 +361,18 @@ class CompanyTransformer extends EntityTransformer return $this->includeCollection($company->system_logs, $transformer, SystemLog::class); } + + public function includeClientSubscriptions(Company $company) + { + $transformer = new ClientSubscriptionTransformer($this->serializer); + + return $this->includeCollection($company->client_subscriptions, $transformer, ClientSubscription::class); + } + + public function includeBillingSubscriptions(Company $company) + { + $transformer = new BillingSubscriptionTransformer($this->serializer); + + return $this->includeCollection($company->billing_subscriptions, $transformer, BillingSubscription::class); + } } diff --git a/app/Transformers/CreditTransformer.php b/app/Transformers/CreditTransformer.php index 8d48f1b0b264..27d052e91cc4 100644 --- a/app/Transformers/CreditTransformer.php +++ b/app/Transformers/CreditTransformer.php @@ -137,6 +137,7 @@ class CreditTransformer extends EntityTransformer 'entity_type' => 'credit', 'exchange_rate' => (float) $credit->exchange_rate, 'paid_to_date' => (float) $credit->paid_to_date, + 'subscription_id' => $this->encodePrimaryKey($credit->subscription_id), ]; } } diff --git a/app/Transformers/InvoiceTransformer.php b/app/Transformers/InvoiceTransformer.php index 7a31a4b7760c..836c15e2c86d 100644 --- a/app/Transformers/InvoiceTransformer.php +++ b/app/Transformers/InvoiceTransformer.php @@ -141,6 +141,7 @@ class InvoiceTransformer extends EntityTransformer 'reminder3_sent' => $invoice->reminder3_sent ?: '', 'reminder_last_sent' => $invoice->reminder_last_sent ?: '', 'paid_to_date' => (float) $invoice->paid_to_date, + 'subscription_id' => $this->encodePrimaryKey($invoice->subscription_id), ]; } diff --git a/app/Transformers/QuoteTransformer.php b/app/Transformers/QuoteTransformer.php index ae8cd498c0b9..a3ecfc9938ac 100644 --- a/app/Transformers/QuoteTransformer.php +++ b/app/Transformers/QuoteTransformer.php @@ -139,6 +139,8 @@ class QuoteTransformer extends EntityTransformer 'exchange_rate' => (float) $quote->exchange_rate, 'paid_to_date' => (float) $quote->paid_to_date, 'project_id' => $this->encodePrimaryKey($quote->project_id), + 'subscription_id' => $this->encodePrimaryKey($quote->subscription_id), + ]; } } diff --git a/database/migrations/2021_03_08_123729_create_billing_subscriptions_table.php b/database/migrations/2021_03_08_123729_create_billing_subscriptions_table.php index 6268b9638f48..6f76050fb056 100644 --- a/database/migrations/2021_03_08_123729_create_billing_subscriptions_table.php +++ b/database/migrations/2021_03_08_123729_create_billing_subscriptions_table.php @@ -46,15 +46,20 @@ class CreateBillingSubscriptionsTable extends Migration Schema::create('client_subscriptions', function (Blueprint $table) { $table->increments('id'); + $table->unsignedInteger('company_id'); $table->unsignedInteger('subscription_id'); $table->unsignedInteger('recurring_invoice_id'); $table->unsignedInteger('client_id'); $table->unsignedInteger('trial_started')->nullable(); $table->unsignedInteger('trial_ends')->nullable(); + $table->boolean('is_deleted')->default(false); + $table->softDeletes('deleted_at', 6); $table->timestamps(); $table->foreign('subscription_id')->references('id')->on('billing_subscriptions'); $table->foreign('recurring_invoice_id')->references('id')->on('recurring_invoices'); $table->foreign('client_id')->references('id')->on('clients'); + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + $table->index(['company_id', 'deleted_at']); }); } diff --git a/database/migrations/2021_03_09_132242_add_currency_id_to_billing_subscriptions_table.php b/database/migrations/2021_03_09_132242_add_currency_id_to_billing_subscriptions_table.php index bc687317a449..ab3da33f51bd 100644 --- a/database/migrations/2021_03_09_132242_add_currency_id_to_billing_subscriptions_table.php +++ b/database/migrations/2021_03_09_132242_add_currency_id_to_billing_subscriptions_table.php @@ -16,6 +16,18 @@ class AddCurrencyIdToBillingSubscriptionsTable extends Migration Schema::table('billing_subscriptions', function (Blueprint $table) { $table->unsignedInteger('currency_id')->nullable(); }); + + Schema::table('invoices', function (Blueprint $table) { + $table->unsignedInteger('subscription_id')->nullable(); + }); + + Schema::table('quotes', function (Blueprint $table) { + $table->unsignedInteger('subscription_id')->nullable(); + }); + + Schema::table('credits', function (Blueprint $table) { + $table->unsignedInteger('subscription_id')->nullable(); + }); } /** diff --git a/routes/api.php b/routes/api.php index 1af077d5fbeb..6c3877a345b8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -174,6 +174,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a // Route::delete('hooks/{subscription_id}', 'SubscriptionController@unsubscribe')->name('hooks.unsubscribe'); Route::resource('billing_subscriptions', 'BillingSubscriptionController'); + Route::resource('cliente_subscriptions', 'ClientSubscriptionController'); }); Route::match(['get', 'post'], 'payment_webhook/{company_key}/{company_gateway_id}', 'PaymentWebhookController') From 11de15945838d77b3f5f824b1764c4407435c752 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 10 Mar 2021 11:11:05 +1100 Subject: [PATCH 23/30] Commit event for client subs --- .../ClientSubscriptionWasCreated.php | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 app/Events/ClientSubscription/ClientSubscriptionWasCreated.php diff --git a/app/Events/ClientSubscription/ClientSubscriptionWasCreated.php b/app/Events/ClientSubscription/ClientSubscriptionWasCreated.php new file mode 100644 index 000000000000..ce08f8793247 --- /dev/null +++ b/app/Events/ClientSubscription/ClientSubscriptionWasCreated.php @@ -0,0 +1,55 @@ +client_subscription = $client_subscription; + $this->company = $company; + $this->event_vars = $event_vars; + } + + /** + * Get the channels the event should broadcast on. + * + * @return \Illuminate\Broadcasting\Channel|array + */ + public function broadcastOn() + { + return new PrivateChannel('channel-name'); + } +} From 839a5c49d3fadb5d224484d1baf0dcfe8ae29cba Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 10 Mar 2021 11:14:05 +1100 Subject: [PATCH 24/30] Fixes for billing subs cron --- app/Jobs/Cron/BillingSubscriptionCron.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Jobs/Cron/BillingSubscriptionCron.php b/app/Jobs/Cron/BillingSubscriptionCron.php index 091cb10c5f83..3aef58c290cd 100644 --- a/app/Jobs/Cron/BillingSubscriptionCron.php +++ b/app/Jobs/Cron/BillingSubscriptionCron.php @@ -12,6 +12,7 @@ namespace App\Jobs\Cron; use App\Libraries\MultiDB; +use App\Models\ClientSubscription; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Support\Carbon; From 18e8934e7069b957149af771b2912e870a5df930 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 10 Mar 2021 12:28:12 +1100 Subject: [PATCH 25/30] Delete pdf in service rather than child job --- app/Services/Invoice/InvoiceService.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/Services/Invoice/InvoiceService.php b/app/Services/Invoice/InvoiceService.php index b45e5eb19b6e..66e5c176cad2 100644 --- a/app/Services/Invoice/InvoiceService.php +++ b/app/Services/Invoice/InvoiceService.php @@ -22,6 +22,7 @@ use App\Models\Task; use App\Services\Client\ClientService; use App\Utils\Traits\MakesHash; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Storage; class InvoiceService { @@ -273,8 +274,9 @@ class InvoiceService public function deletePdf() { - UnlinkFile::dispatchNow(config('filesystems.default'), $this->invoice->client->invoice_filepath() . $this->invoice->number.'.pdf'); - + //UnlinkFile::dispatchNow(config('filesystems.default'), $this->invoice->client->invoice_filepath() . $this->invoice->number.'.pdf'); + Storage::disk(config('filesystems.default'))->delete($this->invoice->client->invoice_filepath() . $this->invoice->number.'.pdf'); + return $this; } From e7e455667d775bc6fff79aa0de1b6dd2f6acaf40 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 10 Mar 2021 12:51:08 +1100 Subject: [PATCH 26/30] Fixes for company logo in emails --- app/Utils/Helpers.php | 5 +++-- resources/views/email/auth/password-reset.blade.php | 2 +- resources/views/email/template/dark.blade.php | 2 +- resources/views/email/template/light.blade.php | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/Utils/Helpers.php b/app/Utils/Helpers.php index d2aebc62d02e..7e522f426acb 100644 --- a/app/Utils/Helpers.php +++ b/app/Utils/Helpers.php @@ -26,7 +26,7 @@ class Helpers $elements['signature'] = ''; $elements['settings'] = new stdClass; $elements['whitelabel'] = true; - + $elements['company'] = ''; return $elements; } @@ -35,7 +35,8 @@ class Helpers $elements['signature'] = $_settings->email_signature; $elements['settings'] = $_settings; $elements['whitelabel'] = $client->user->account->isPaid() ? true : false; - + $elements['company'] = $client->company; + return $elements; } diff --git a/resources/views/email/auth/password-reset.blade.php b/resources/views/email/auth/password-reset.blade.php index ac5ebb54e274..de1673f452e8 100644 --- a/resources/views/email/auth/password-reset.blade.php +++ b/resources/views/email/auth/password-reset.blade.php @@ -1,4 +1,4 @@ -@component('email.template.master', ['design' => 'light', 'whitelabel' => false]) +@component('email.template.master', ['design' => 'light', 'whitelabel' => false, 'company' => $company]) @slot('header') @include('email.components.header', ['logo' => $logo]) diff --git a/resources/views/email/template/dark.blade.php b/resources/views/email/template/dark.blade.php index 5686fde210ba..00f04ff77bf8 100644 --- a/resources/views/email/template/dark.blade.php +++ b/resources/views/email/template/dark.blade.php @@ -1,7 +1,7 @@ @component('email.template.master', ['design' => 'dark', 'settings' => $settings, 'whitelabel' => $whitelabel]) @slot('header') - @include('email.components.header', ['logo' => (strlen($settings->company_logo) > 1) ? url('') . $settings->company_logo : 'https://www.invoiceninja.com/wp-content/uploads/2015/10/logo-white-horizontal-1.png']) + @include('email.components.header', ['logo' => $company->present()->logo($settings)]) @endslot {!! $body !!} diff --git a/resources/views/email/template/light.blade.php b/resources/views/email/template/light.blade.php index 071de7226d99..064cc9d357e6 100644 --- a/resources/views/email/template/light.blade.php +++ b/resources/views/email/template/light.blade.php @@ -1,7 +1,7 @@ @component('email.template.master', ['design' => 'light', 'settings' => $settings, 'whitelabel' => $whitelabel]) @slot('header') - @include('email.components.header', ['logo' => (strlen($settings->company_logo) > 1) ? url('') . $settings->company_logo : 'https://www.invoiceninja.com/wp-content/uploads/2015/10/logo-white-horizontal-1.png']) + @include('email.components.header', ['logo' => $company->present()->logo($settings)]) @endslot {!! $body !!} From 59d3cd8921578b33d0165db6ebef0829118749a3 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 10 Mar 2021 13:00:49 +1100 Subject: [PATCH 27/30] Minor fixes for company logo displaying in the client portal --- .../ninja2020/components/general/sidebar/desktop.blade.php | 2 +- .../ninja2020/components/general/sidebar/mobile.blade.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/portal/ninja2020/components/general/sidebar/desktop.blade.php b/resources/views/portal/ninja2020/components/general/sidebar/desktop.blade.php index d9863d7b84ea..deb6e40cc2b9 100644 --- a/resources/views/portal/ninja2020/components/general/sidebar/desktop.blade.php +++ b/resources/views/portal/ninja2020/components/general/sidebar/desktop.blade.php @@ -2,7 +2,7 @@
- {{ config('app.name') }} + {{ config('app.name') }}