Merge pull request #9403 from turbo124/v5-develop

v5.8.38
This commit is contained in:
David Bomba 2024-03-25 15:20:30 +11:00 committed by GitHub
commit f7094ecff7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 256 additions and 79 deletions

View File

@ -1 +1 @@
5.8.37 5.8.38

View File

@ -169,26 +169,21 @@ class CheckData extends Command
private function checkCompanyTokens() private function checkCompanyTokens()
{ {
// CompanyUser::whereDoesntHave('token', function ($query){
// return $query->where('is_system', 1);
// })->cursor()->each(function ($cu){
// if ($cu->user) {
// $this->logMessage("Creating missing company token for user # {$cu->user->id} for company id # {$cu->company->id}");
// (new CreateCompanyToken($cu->company, $cu->user, 'System'))->handle();
// } else {
// $this->logMessage("Dangling User ID # {$cu->id}");
// }
// });
CompanyUser::query()->cursor()->each(function ($cu) { CompanyUser::query()->cursor()->each(function ($cu) {
if (CompanyToken::where('user_id', $cu->user_id)->where('company_id', $cu->company_id)->where('is_system', 1)->doesntExist()) { if (CompanyToken::where('user_id', $cu->user_id)->where('company_id', $cu->company_id)->where('is_system', 1)->doesntExist()) {
$this->logMessage("Creating missing company token for user # {$cu->user_id} for company id # {$cu->company_id}");
if ($cu->company && $cu->user) { if ($cu->company && $cu->user) {
$this->logMessage("Creating missing company token for user # {$cu->user_id} for company id # {$cu->company_id}");
(new CreateCompanyToken($cu->company, $cu->user, 'System'))->handle(); (new CreateCompanyToken($cu->company, $cu->user, 'System'))->handle();
} else {
// $cu->forceDelete();
} }
if (!$cu->user) {
$this->logMessage("No user found for company user - removing company user");
$cu->forceDelete();
}
} }
}); });
} }
@ -482,6 +477,14 @@ class CheckData extends Command
} }
} else { } else {
$this->logMessage("No contact present, so cannot add invitation for {$entity_key} - {$entity->id}"); $this->logMessage("No contact present, so cannot add invitation for {$entity_key} - {$entity->id}");
try{
$entity->service()->createInvitations()->save();
}
catch(\Exception $e){
}
} }
try { try {

View File

@ -246,6 +246,8 @@ class InvoiceSum
if ($this->invoice->status_id != Invoice::STATUS_DRAFT) { if ($this->invoice->status_id != Invoice::STATUS_DRAFT) {
if ($this->invoice->amount != $this->invoice->balance) { if ($this->invoice->amount != $this->invoice->balance) {
// $paid_to_date = $this->invoice->amount - $this->invoice->balance;
$this->invoice->balance = Number::roundValue($this->getTotal(), $this->precision) - $this->invoice->paid_to_date; //21-02-2024 cannot use the calculated $paid_to_date here as it could send the balance backward. $this->invoice->balance = Number::roundValue($this->getTotal(), $this->precision) - $this->invoice->paid_to_date; //21-02-2024 cannot use the calculated $paid_to_date here as it could send the balance backward.
} else { } else {
$this->invoice->balance = Number::roundValue($this->getTotal(), $this->precision); $this->invoice->balance = Number::roundValue($this->getTotal(), $this->precision);

View File

@ -278,7 +278,7 @@ class InvitationController extends Controller
auth()->guard('contact')->loginUsingId($invitation->contact->id, true); auth()->guard('contact')->loginUsingId($invitation->contact->id, true);
$invoice = $invitation->invoice; $invoice = $invitation->invoice->service()->removeUnpaidGatewayFees()->save();
if ($invoice->partial > 0) { if ($invoice->partial > 0) {
$amount = round($invoice->partial, (int)$invoice->client->currency()->precision); $amount = round($invoice->partial, (int)$invoice->client->currency()->precision);

View File

@ -72,7 +72,7 @@ class InvoiceController extends Controller
$variables = ($invitation && auth()->guard('contact')->user()->client->getSetting('show_accept_invoice_terms')) ? (new HtmlEngine($invitation))->generateLabelsAndValues() : false; $variables = ($invitation && auth()->guard('contact')->user()->client->getSetting('show_accept_invoice_terms')) ? (new HtmlEngine($invitation))->generateLabelsAndValues() : false;
$data = [ $data = [
'invoice' => $invoice, 'invoice' => $invoice->service()->removeUnpaidGatewayFees()->save(),
'invitation' => $invitation ?: $invoice->invitations->first(), 'invitation' => $invitation ?: $invoice->invitations->first(),
'key' => $invitation ? $invitation->key : false, 'key' => $invitation ? $invitation->key : false,
'hash' => $hash, 'hash' => $hash,

View File

@ -14,6 +14,7 @@ namespace App\Http\Controllers;
use App\DataMapper\Analytics\LivePreview; use App\DataMapper\Analytics\LivePreview;
use App\Http\Requests\Preview\DesignPreviewRequest; use App\Http\Requests\Preview\DesignPreviewRequest;
use App\Http\Requests\Preview\PreviewInvoiceRequest; use App\Http\Requests\Preview\PreviewInvoiceRequest;
use App\Http\Requests\Preview\ShowPreviewRequest;
use App\Jobs\Util\PreviewPdf; use App\Jobs\Util\PreviewPdf;
use App\Models\Client; use App\Models\Client;
use App\Models\ClientContact; use App\Models\ClientContact;
@ -131,9 +132,9 @@ class PreviewController extends BaseController
* Used in the Custom Designer to preview design changes * Used in the Custom Designer to preview design changes
* @return mixed * @return mixed
*/ */
public function show() public function show(ShowPreviewRequest $request)
{ {
if(request()->has('template')) { if($request->input('design.is_template')) {
return $this->template(); return $this->template();
} }
@ -238,7 +239,6 @@ class PreviewController extends BaseController
private function liveTemplate(array $request_data) private function liveTemplate(array $request_data)
{ {
nlog($request_data['entity_type']);
/** @var \App\Models\User $user */ /** @var \App\Models\User $user */
$user = auth()->user(); $user = auth()->user();
@ -292,8 +292,6 @@ class PreviewController extends BaseController
->setTemplate($design_object) ->setTemplate($design_object)
->mock(); ->mock();
} catch(SyntaxError $e) { } catch(SyntaxError $e) {
// return response()->json(['message' => 'Twig syntax is invalid.', 'errors' => new \stdClass], 422);
} }
if (request()->query('html') == 'true') { if (request()->query('html') == 'true') {

View File

@ -0,0 +1,45 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Requests\Preview;
use App\Http\Requests\Request;
use App\Utils\Traits\MakesHash;
class ShowPreviewRequest extends Request
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
return true;
}
public function rules()
{
$rules = [
];
return $rules;
}
public function prepareForValidation()
{
$input = $this->all();
$this->replace($input);
}
}

View File

@ -11,10 +11,14 @@
namespace App\Http\Requests\Report; namespace App\Http\Requests\Report;
use App\Utils\Ninja;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use Illuminate\Auth\Access\AuthorizationException;
class GenericReportRequest extends Request class GenericReportRequest extends Request
{ {
private string $error_message = '';
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
* *
@ -22,11 +26,7 @@ class GenericReportRequest extends Request
*/ */
public function authorize(): bool public function authorize(): bool
{ {
/** @var \App\Models\User $user */ return $this->checkAuthority();
$user = auth()->user();
return $user->isAdmin() || $user->hasPermission('view_reports');
} }
public function rules() public function rules()
@ -70,4 +70,25 @@ class GenericReportRequest extends Request
$this->replace($input); $this->replace($input);
} }
private function checkAuthority()
{
$this->error_message = ctrans('texts.authorization_failure');
/** @var \App\Models\User $user */
$user = auth()->user();
if(Ninja::isHosted() && $user->account->isFreeHostedClient()){
$this->error_message = ctrans('texts.upgrade_to_view_reports');
return false;
}
return $user->isAdmin() || $user->hasPermission('view_reports');
}
protected function failedAuthorization()
{
throw new AuthorizationException($this->error_message);
}
} }

View File

@ -11,13 +11,17 @@
namespace App\Http\Requests\Report; namespace App\Http\Requests\Report;
use App\Utils\Ninja;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Auth\Access\AuthorizationException;
class ProductSalesReportRequest extends Request class ProductSalesReportRequest extends Request
{ {
use MakesHash; use MakesHash;
private string $error_message = '';
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
* *
@ -25,18 +29,22 @@ class ProductSalesReportRequest extends Request
*/ */
public function authorize(): bool public function authorize(): bool
{ {
return auth()->user()->isAdmin(); return $this->checkAuthority();
} }
public function rules() public function rules()
{ {
/** @var \App\Models\User $user */
$user = auth()->user();
return [ return [
'date_range' => 'bail|required|string', 'date_range' => 'bail|required|string',
'end_date' => 'bail|required_if:date_range,custom|nullable|date', 'end_date' => 'bail|required_if:date_range,custom|nullable|date',
'start_date' => 'bail|required_if:date_range,custom|nullable|date', 'start_date' => 'bail|required_if:date_range,custom|nullable|date',
'report_keys' => 'bail|present|array', 'report_keys' => 'bail|present|array',
'send_email' => 'bail|required|bool', 'send_email' => 'bail|required|bool',
'client_id' => 'bail|nullable|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id.',is_deleted,0', 'client_id' => 'bail|nullable|sometimes|exists:clients,id,company_id,'.$user->company()->id.',is_deleted,0',
]; ];
} }
@ -67,4 +75,26 @@ class ProductSalesReportRequest extends Request
$this->replace($input); $this->replace($input);
} }
private function checkAuthority()
{
$this->error_message = ctrans('texts.authorization_failure');
/** @var \App\Models\User $user */
$user = auth()->user();
if(Ninja::isHosted() && $user->account->isFreeHostedClient()){
$this->error_message = ctrans('texts.upgrade_to_view_reports');
return false;
}
return $user->isAdmin() || $user->hasPermission('view_reports');
}
protected function failedAuthorization()
{
throw new AuthorizationException($this->error_message);
}
} }

View File

@ -11,10 +11,15 @@
namespace App\Http\Requests\Report; namespace App\Http\Requests\Report;
use App\Utils\Ninja;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use Illuminate\Auth\Access\AuthorizationException;
class ProfitLossRequest extends Request class ProfitLossRequest extends Request
{ {
private string $error_message = '';
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
* *
@ -22,10 +27,7 @@ class ProfitLossRequest extends Request
*/ */
public function authorize(): bool public function authorize(): bool
{ {
/** @var \App\Models\User $user */ return $this->checkAuthority();
$user = auth()->user();
return $user->isAdmin();
} }
public function rules() public function rules()
@ -51,4 +53,26 @@ class ProfitLossRequest extends Request
$this->replace($input); $this->replace($input);
} }
private function checkAuthority()
{
$this->error_message = ctrans('texts.authorization_failure');
/** @var \App\Models\User $user */
$user = auth()->user();
if(Ninja::isHosted() && $user->account->isFreeHostedClient()){
$this->error_message = ctrans('texts.upgrade_to_view_reports');
return false;
}
return $user->isAdmin() || $user->hasPermission('view_reports');
}
protected function failedAuthorization()
{
throw new AuthorizationException($this->error_message);
}
} }

View File

@ -11,10 +11,14 @@
namespace App\Http\Requests\Report; namespace App\Http\Requests\Report;
use App\Utils\Ninja;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use Illuminate\Auth\Access\AuthorizationException;
class ReportPreviewRequest extends Request class ReportPreviewRequest extends Request
{ {
private string $error_message = '';
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
* *
@ -22,11 +26,7 @@ class ReportPreviewRequest extends Request
*/ */
public function authorize(): bool public function authorize(): bool
{ {
/** @var \App\Models\User $user */ return $this->checkAuthority();
$user = auth()->user();
return $user->isAdmin() || $user->hasPermission('view_reports');
} }
public function rules() public function rules()
@ -38,4 +38,26 @@ class ReportPreviewRequest extends Request
public function prepareForValidation() public function prepareForValidation()
{ {
} }
private function checkAuthority()
{
$this->error_message = ctrans('texts.authorization_failure');
/** @var \App\Models\User $user */
$user = auth()->user();
if(Ninja::isHosted() && $user->account->isFreeHostedClient()){
$this->error_message = ctrans('texts.upgrade_to_view_reports');
return false;
}
return $user->isAdmin() || $user->hasPermission('view_reports');
}
protected function failedAuthorization()
{
throw new AuthorizationException($this->error_message);
}
} }

View File

@ -59,15 +59,12 @@ class CleanStaleInvoiceOrder implements ShouldQueue
Invoice::query() Invoice::query()
->withTrashed() ->withTrashed()
->where('status_id', Invoice::STATUS_SENT) ->where('status_id', Invoice::STATUS_SENT)
->whereBetween('created_at', [now()->subHours(1), now()->subMinutes(30)]) ->where('created_at', '<', now()->subMinutes(30))
->where('balance', '>', 0) ->where('balance', '>', 0)
->whereJsonContains('line_items', ['type_id' => '3'])
->cursor() ->cursor()
->each(function ($invoice) { ->each(function ($invoice) {
$invoice->service()->removeUnpaidGatewayFees();
if (collect($invoice->line_items)->contains('type_id', 3)) {
$invoice->service()->removeUnpaidGatewayFees();
}
}); });
return; return;
@ -86,6 +83,18 @@ class CleanStaleInvoiceOrder implements ShouldQueue
$invoice->is_proforma = false; $invoice->is_proforma = false;
$repo->delete($invoice); $repo->delete($invoice);
}); });
Invoice::query()
->withTrashed()
->where('status_id', Invoice::STATUS_SENT)
->where('created_at', '<', now()->subMinutes(30))
->where('balance', '>', 0)
->whereJsonContains('line_items', ['type_id' => '3'])
->cursor()
->each(function ($invoice) {
$invoice->service()->removeUnpaidGatewayFees();
});
} }
} }

View File

@ -126,14 +126,9 @@ class BaseDriver extends AbstractPaymentDriver
$fields[] = ['name' => 'client_name', 'label' => ctrans('texts.client_name'), 'type' => 'text', 'validation' => 'required']; $fields[] = ['name' => 'client_name', 'label' => ctrans('texts.client_name'), 'type' => 'text', 'validation' => 'required'];
} }
// if ($this->company_gateway->require_contact_name) { $fields[] = ['name' => 'contact_first_name', 'label' => ctrans('texts.first_name'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'contact_first_name', 'label' => ctrans('texts.first_name'), 'type' => 'text', 'validation' => 'required']; $fields[] = ['name' => 'contact_last_name', 'label' => ctrans('texts.last_name'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'contact_last_name', 'label' => ctrans('texts.last_name'), 'type' => 'text', 'validation' => 'required']; $fields[] = ['name' => 'contact_email', 'label' => ctrans('texts.email'), 'type' => 'text', 'validation' => 'required,email:rfc'];
// }
// if ($this->company_gateway->require_contact_email) {
$fields[] = ['name' => 'contact_email', 'label' => ctrans('texts.email'), 'type' => 'text', 'validation' => 'required,email:rfc'];
// }
if ($this->company_gateway->require_client_phone) { if ($this->company_gateway->require_client_phone) {
$fields[] = ['name' => 'client_phone', 'label' => ctrans('texts.client_phone'), 'type' => 'tel', 'validation' => 'required']; $fields[] = ['name' => 'client_phone', 'label' => ctrans('texts.client_phone'), 'type' => 'tel', 'validation' => 'required'];
@ -166,12 +161,10 @@ class BaseDriver extends AbstractPaymentDriver
$fields[] = ['name' => 'client_custom_value2', 'label' => $this->helpers->makeCustomField($this->client->company->custom_fields, 'client2'), 'type' => 'text', 'validation' => 'required']; $fields[] = ['name' => 'client_custom_value2', 'label' => $this->helpers->makeCustomField($this->client->company->custom_fields, 'client2'), 'type' => 'text', 'validation' => 'required'];
} }
if ($this->company_gateway->require_custom_value3) { if ($this->company_gateway->require_custom_value3) {
$fields[] = ['name' => 'client_custom_value3', 'label' => $this->helpers->makeCustomField($this->client->company->custom_fields, 'client3'), 'type' => 'text', 'validation' => 'required']; $fields[] = ['name' => 'client_custom_value3', 'label' => $this->helpers->makeCustomField($this->client->company->custom_fields, 'client3'), 'type' => 'text', 'validation' => 'required'];
} }
if ($this->company_gateway->require_custom_value4) { if ($this->company_gateway->require_custom_value4) {
$fields[] = ['name' => 'client_custom_value4', 'label' => $this->helpers->makeCustomField($this->client->company->custom_fields, 'client4'), 'type' => 'text', 'validation' => 'required']; $fields[] = ['name' => 'client_custom_value4', 'label' => $this->helpers->makeCustomField($this->client->company->custom_fields, 'client4'), 'type' => 'text', 'validation' => 'required'];
} }

View File

@ -287,6 +287,27 @@ class BraintreePaymentDriver extends BaseDriver
} }
} }
/**
* Required fields for client to fill, to proceed with gateway actions.
*
* @return array[]
*/
public function getClientRequiredFields(): array
{
$fields = [];
$fields[] = ['name' => 'contact_first_name', 'label' => ctrans('texts.first_name'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'contact_last_name', 'label' => ctrans('texts.last_name'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'contact_email', 'label' => ctrans('texts.email'), 'type' => 'text', 'validation' => 'required,email:rfc'];
$fields[] = ['name' => 'client_address_line_1', 'label' => ctrans('texts.address1'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'client_city', 'label' => ctrans('texts.city'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'client_state', 'label' => ctrans('texts.state'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'text', 'validation' => 'required'];
return $fields;
}
public function processWebhookRequest($request) public function processWebhookRequest($request)
{ {
$validator = Validator::make($request->all(), [ $validator = Validator::make($request->all(), [

View File

@ -61,9 +61,9 @@ class CreditCard
'State' => $this->eway_driver->client->state, 'State' => $this->eway_driver->client->state,
'PostalCode' => $this->eway_driver->client->postal_code, 'PostalCode' => $this->eway_driver->client->postal_code,
'Country' => $this->eway_driver->client->country->iso_3166_2, 'Country' => $this->eway_driver->client->country->iso_3166_2,
'Phone' => $this->eway_driver->client->phone, 'Phone' => $this->eway_driver->client->phone ?? '',
'Email' => $this->eway_driver->client->contacts()->first()->email, 'Email' => $this->eway_driver->client->contacts()->first()->email ?? '',
'Url' => $this->eway_driver->client->website, 'Url' => $this->eway_driver->client->website ?? '',
'Method' => \Eway\Rapid\Enum\PaymentMethod::CREATE_TOKEN_CUSTOMER, 'Method' => \Eway\Rapid\Enum\PaymentMethod::CREATE_TOKEN_CUSTOMER,
'SecuredCardData' => $securefieldcode, 'SecuredCardData' => $securefieldcode,
]; ];

View File

@ -453,12 +453,6 @@ class InvoiceService
if ((int) $pre_count != (int) $post_count) { if ((int) $pre_count != (int) $post_count) {
$adjustment = $balance - $new_balance; $adjustment = $balance - $new_balance;
// $this->invoice
// ->client
// ->service()
// ->updateBalance($adjustment * -1)
// ->save();
$this->invoice $this->invoice
->ledger() ->ledger()
->updateInvoiceBalance($adjustment * -1, 'Adjustment for removing gateway fee'); ->updateInvoiceBalance($adjustment * -1, 'Adjustment for removing gateway fee');

View File

@ -82,16 +82,27 @@ class PdfMaker
$ts = new TemplateService(); $ts = new TemplateService();
if(isset($this->data['template']['entity'])) { if(isset($this->options['client'])) {
$client = $this->options['client'];
try { try {
$entity = $this->data['template']['entity']; $ts->setCompany($client->company);
$ts->setCompany($entity->company); $ts->addGlobal(['currency_code' => $client->company->currency()->code]);
} catch(\Exception $e) { } catch(\Exception $e) {
nlog($e->getMessage());
} }
} }
$data = $ts->processData($this->options)->getData(); if(isset($this->options['vendor'])) {
$vendor = $this->options['vendor'];
try {
$ts->setCompany($vendor->company);
$ts->addGlobal(['currency_code' => $vendor->company->currency()->code]);
} catch(\Exception $e) {
nlog($e->getMessage());
}
}
$data = $ts->processData($this->options)->setGlobals()->getData();
$twig = $ts->twig; $twig = $ts->twig;
foreach ($contents as $content) { foreach ($contents as $content) {

View File

@ -79,7 +79,7 @@ class TemplateAction implements ShouldQueue
*/ */
public function handle() public function handle()
{ {
// nlog("inside template action"); nlog("inside template action");
MultiDB::setDb($this->db); MultiDB::setDb($this->db);
@ -108,7 +108,14 @@ class TemplateAction implements ShouldQueue
->where('company_id', $this->company->id) ->where('company_id', $this->company->id)
->get(); ->get();
// nlog($result->toArray()); /** Set a global currency_code */
$first_entity = $result->first();
if($first_entity->client)
$currency_code = $first_entity->client->currency()->code;
elseif($first_entity instanceof Client)
$currency_code = $first_entity->currency()->code;
else
$currency_code = $this->company->currency()->code;
if($result->count() <= 1) { if($result->count() <= 1) {
$data[$key] = collect($result); $data[$key] = collect($result);
@ -118,10 +125,9 @@ class TemplateAction implements ShouldQueue
$ts = $template_service $ts = $template_service
->setCompany($this->company) ->setCompany($this->company)
->addGlobal(['currency_code' => $currency_code])
->build($data); ->build($data);
// nlog($ts->getHtml());
if($this->send_email) { if($this->send_email) {
$pdf = $ts->getPdf(); $pdf = $ts->getPdf();
$this->sendEmail($pdf, $template); $this->sendEmail($pdf, $template);

View File

@ -157,7 +157,7 @@ class TemplateService
return $this; return $this;
} }
private function setGlobals(): self public function setGlobals(): self
{ {
foreach($this->global_vars as $key => $value) { foreach($this->global_vars as $key => $value) {
@ -241,8 +241,6 @@ class TemplateService
public function getPdf(): string public function getPdf(): string
{ {
// nlog($this->getHtml());
if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') {
$pdf = (new NinjaPdf())->build($this->compiled_html); $pdf = (new NinjaPdf())->build($this->compiled_html);
} else { } else {

View File

@ -17,8 +17,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true), 'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => env('APP_VERSION', '5.8.37'), 'app_version' => env('APP_VERSION', '5.8.38'),
'app_tag' => env('APP_TAG', '5.8.37'), 'app_tag' => env('APP_TAG', '5.8.38'),
'minimum_client_version' => '5.0.16', 'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', false), 'api_secret' => env('API_SECRET', false),