mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Merge branch 'v5-develop' into preview
This commit is contained in:
commit
e83cad0f47
2
.github/workflows/phpunit.yml
vendored
2
.github/workflows/phpunit.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
operating-system: ['ubuntu-20.04', 'ubuntu-22.04']
|
||||
php-versions: ['8.1.11']
|
||||
php-versions: ['8.1']
|
||||
phpunit-versions: ['latest']
|
||||
|
||||
env:
|
||||
|
@ -1 +1 @@
|
||||
5.5.45
|
||||
5.5.49
|
@ -118,6 +118,7 @@ class CheckData extends Command
|
||||
$this->checkBalanceVsPaidStatus();
|
||||
$this->checkDuplicateRecurringInvoices();
|
||||
$this->checkOauthSanity();
|
||||
$this->checkVendorSettings();
|
||||
|
||||
if(Ninja::isHosted()){
|
||||
$this->checkAccountStatuses();
|
||||
@ -984,6 +985,27 @@ class CheckData extends Command
|
||||
|
||||
}
|
||||
|
||||
public function checkVendorSettings()
|
||||
{
|
||||
|
||||
if ($this->option('fix') == 'true')
|
||||
{
|
||||
|
||||
Vendor::query()->whereNull('currency_id')->orWhere('currency_id', '')->cursor()->each(function ($vendor){
|
||||
|
||||
$vendor->currency_id = $vendor->company->settings->currency_id;
|
||||
$vendor->save();
|
||||
|
||||
$this->logMessage("Fixing vendor currency for # {$vendor->id}");
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function checkBalanceVsPaidStatus()
|
||||
{
|
||||
$this->wrong_paid_status = 0;
|
||||
|
@ -27,7 +27,7 @@ class TranslationsExport extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'ninja:translations';
|
||||
protected $signature = 'ninja:translations {--type=} {--path=}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@ -36,8 +36,11 @@ class TranslationsExport extends Command
|
||||
*/
|
||||
protected $description = 'Transform translations to json';
|
||||
|
||||
protected $log = '';
|
||||
|
||||
private array $langs = [
|
||||
'ar',
|
||||
'bg',
|
||||
'ca',
|
||||
'cs',
|
||||
'da',
|
||||
@ -47,10 +50,12 @@ class TranslationsExport extends Command
|
||||
'en_GB',
|
||||
'es',
|
||||
'es_ES',
|
||||
'et',
|
||||
'fa',
|
||||
'fi',
|
||||
'fr',
|
||||
'fr_CA',
|
||||
'he',
|
||||
'hr',
|
||||
'it',
|
||||
'ja',
|
||||
@ -65,7 +70,9 @@ class TranslationsExport extends Command
|
||||
'ro',
|
||||
'ru_RU',
|
||||
'sl',
|
||||
'sk',
|
||||
'sq',
|
||||
'sr',
|
||||
'sv',
|
||||
'th',
|
||||
'tr_TR',
|
||||
@ -89,14 +96,65 @@ class TranslationsExport extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
Storage::makeDirectory(storage_path('lang'));
|
||||
$type =$this->option('type') ?? 'export';
|
||||
|
||||
if($type == 'import')
|
||||
$this->import();
|
||||
|
||||
if($type == 'export')
|
||||
$this->export();
|
||||
|
||||
}
|
||||
|
||||
private function import()
|
||||
{
|
||||
//loop and
|
||||
|
||||
foreach($this->langs as $lang)
|
||||
{
|
||||
|
||||
$import_file = "textsphp_{$lang}.php";
|
||||
$dir = $this->option('path') ?? storage_path('lang_import/');
|
||||
$path = $dir.$import_file;
|
||||
|
||||
if(file_exists($path)){
|
||||
$this->logMessage($path);
|
||||
|
||||
$trans = file_get_contents($path);
|
||||
|
||||
file_put_contents(lang_path("/{$lang}/texts.php"), $trans);
|
||||
|
||||
}
|
||||
else{
|
||||
|
||||
$this->logMessage("Could not open file");
|
||||
$this->logMessage($path);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function export()
|
||||
{
|
||||
Storage::disk('local')->makeDirectory('lang');
|
||||
|
||||
foreach ($this->langs as $lang) {
|
||||
Storage::makeDirectory(storage_path("lang/{$lang}"));
|
||||
Storage::disk('local')->makeDirectory("lang/{$lang}");
|
||||
|
||||
$translations = Lang::getLoader()->load($lang, 'texts');
|
||||
|
||||
Storage::put(storage_path("lang/{$lang}/{$lang}.json"), json_encode(Arr::dot($translations), JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
Storage::disk('local')->put("lang/{$lang}/{$lang}.json", json_encode(Arr::dot($translations), JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
|
||||
private function logMessage($str)
|
||||
{
|
||||
$str = date('Y-m-d h:i:s').' '.$str;
|
||||
$this->info($str);
|
||||
$this->log .= $str."\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -152,7 +152,22 @@ class BankTransactionFilters extends QueryFilters
|
||||
public function sort(string $sort) : Builder
|
||||
{
|
||||
$sort_col = explode('|', $sort);
|
||||
|
||||
if(!is_array($sort_col))
|
||||
return $this->builder;
|
||||
|
||||
if($sort_col[0] == 'deposit')
|
||||
return $this->builder->where('base_type', 'CREDIT')->orderBy('amount', $sort_col[1]);
|
||||
|
||||
if($sort_col[0] == 'withdrawal')
|
||||
return $this->builder->where('base_type', 'DEBIT')->orderBy('amount', $sort_col[1]);
|
||||
|
||||
if($sort_col[0] == 'status')
|
||||
$sort_col[0] = 'status_id';
|
||||
|
||||
if(in_array($sort_col[0],['invoices','expense']))
|
||||
return $this->builder;
|
||||
|
||||
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
|
||||
}
|
||||
|
||||
|
124
app/Filters/ExpenseCategoryFilters.php
Normal file
124
app/Filters/ExpenseCategoryFilters.php
Normal file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use App\Models\Expense;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
/**
|
||||
* ExpenseCategoryFilters.
|
||||
*/
|
||||
class ExpenseCategoryFilters extends QueryFilters
|
||||
{
|
||||
/**
|
||||
* Filter based on search text.
|
||||
*
|
||||
* @param string query filter
|
||||
* @return Builder
|
||||
* @deprecated
|
||||
*/
|
||||
public function filter(string $filter = '') : Builder
|
||||
{
|
||||
if (strlen($filter) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
return $this->builder->where('expense_categories.name', 'like', '%'.$filter.'%');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the list based on the status
|
||||
* archived, active, deleted.
|
||||
*
|
||||
* @param string filter
|
||||
* @return Builder
|
||||
*/
|
||||
public function status(string $filter = '') : Builder
|
||||
{
|
||||
if (strlen($filter) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
$table = 'expense_categories';
|
||||
$filters = explode(',', $filter);
|
||||
|
||||
return $this->builder->where(function ($query) use ($filters, $table) {
|
||||
$query->whereNull($table.'.id');
|
||||
|
||||
if (in_array(parent::STATUS_ACTIVE, $filters)) {
|
||||
$query->orWhereNull($table.'.deleted_at');
|
||||
}
|
||||
|
||||
if (in_array(parent::STATUS_ARCHIVED, $filters)) {
|
||||
$query->orWhere(function ($query) use ($table) {
|
||||
$query->whereNotNull($table.'.deleted_at');
|
||||
|
||||
if (! in_array($table, ['users'])) {
|
||||
$query->where($table.'.is_deleted', '=', 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (in_array(parent::STATUS_DELETED, $filters)) {
|
||||
$query->orWhere($table.'.is_deleted', '=', 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the list based on $sort.
|
||||
*
|
||||
* @param string sort formatted as column|asc
|
||||
* @return Builder
|
||||
*/
|
||||
public function sort(string $sort) : Builder
|
||||
{
|
||||
$sort_col = explode('|', $sort);
|
||||
|
||||
if (is_array($sort_col) && in_array($sort_col[1], ['asc', 'desc']) && in_array($sort_col[0], ['name'])) {
|
||||
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
|
||||
}
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base query.
|
||||
*
|
||||
* @param int company_id
|
||||
* @param User $user
|
||||
* @return Builder
|
||||
* @deprecated
|
||||
*/
|
||||
public function baseQuery(int $company_id, User $user) : Builder
|
||||
{
|
||||
|
||||
return $this->builder;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the query by the users company ID.
|
||||
*
|
||||
* @return Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function entityFilter()
|
||||
{
|
||||
|
||||
//return $this->builder->whereCompanyId(auth()->user()->company()->id);
|
||||
return $this->builder->company();
|
||||
}
|
||||
}
|
@ -92,6 +92,20 @@ class ExpenseFilters extends QueryFilters
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of expenses that can be matched to bank transactions
|
||||
*/
|
||||
public function match_transactions($value = '')
|
||||
{
|
||||
|
||||
if($value == 'true')
|
||||
{
|
||||
return $this->builder->where('is_deleted',0)->whereNull('transaction_id');
|
||||
}
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filters the list based on the status
|
||||
|
@ -16,6 +16,8 @@ use App\Models\User;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* InvoiceFilters.
|
||||
@ -136,6 +138,10 @@ class InvoiceFilters extends QueryFilters
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Builder
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function without_deleted_clients()
|
||||
{
|
||||
|
||||
@ -144,6 +150,10 @@ class InvoiceFilters extends QueryFilters
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Builder
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function upcoming()
|
||||
{
|
||||
return $this->builder
|
||||
@ -154,6 +164,10 @@ class InvoiceFilters extends QueryFilters
|
||||
->orderBy('due_date', 'ASC');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function overdue()
|
||||
{
|
||||
$this->builder->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
@ -165,6 +179,11 @@ class InvoiceFilters extends QueryFilters
|
||||
->orderBy('due_date', 'ASC');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $client_id
|
||||
* @return Builder
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function payable(string $client_id = '')
|
||||
{
|
||||
if (strlen($client_id) == 0) {
|
||||
@ -222,8 +241,20 @@ class InvoiceFilters extends QueryFilters
|
||||
} else {
|
||||
return $this->builder->company()->with(['invitations.company'], ['documents.company']);
|
||||
}
|
||||
}
|
||||
|
||||
// return $this->builder->whereCompanyId(auth()->user()->company()->id);
|
||||
/**
|
||||
* @param string $filter
|
||||
* @return Builder
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function private_notes($filter = '') :Builder
|
||||
{
|
||||
if (strlen($filter) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
return $this->builder->where('private_notes', 'LIKE', '%'.$filter.'%');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -81,6 +81,25 @@ class PaymentFilters extends QueryFilters
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of payments that can be matched to bank transactions
|
||||
*/
|
||||
public function match_transactions($value = 'true') :Builder
|
||||
{
|
||||
|
||||
if($value == 'true'){
|
||||
return $this->builder
|
||||
->where('is_deleted',0)
|
||||
->where(function ($query){
|
||||
$query->whereNull('transaction_id')
|
||||
->orWhere("transaction_id","");
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the list based on $sort.
|
||||
*
|
||||
|
@ -226,12 +226,6 @@ abstract class QueryFilters
|
||||
return $this->builder->where('is_deleted', 0);
|
||||
}
|
||||
|
||||
// if($value == 'true'){
|
||||
|
||||
// $this->builder->withTrashed();
|
||||
|
||||
// }
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,13 @@ class QuoteFilters extends QueryFilters
|
||||
|
||||
if (in_array('expired', $status_parameters)) {
|
||||
$this->builder->where('status_id', Quote::STATUS_SENT)
|
||||
->where('due_date', '<=', now()->toDateString());
|
||||
->where('due_date', '>=', now()->toDateString());
|
||||
}
|
||||
|
||||
if (in_array('upcoming', $status_parameters)) {
|
||||
$this->builder->where('status_id', Quote::STATUS_SENT)
|
||||
->where('due_date', '<=', now()->toDateString())
|
||||
->orderBy('due_date', 'DESC');
|
||||
}
|
||||
|
||||
return $this->builder;
|
||||
|
@ -49,7 +49,7 @@ class InvoiceSum
|
||||
/**
|
||||
* Constructs the object with Invoice and Settings object.
|
||||
*
|
||||
* @param Invoice $invoice The invoice
|
||||
* @param \App\Models\RecurringInvoice|\App\Models\Quote|\App\Models\Credit|\App\Models\PurchaseOrder|\App\Models\Invoice $invoice The entity
|
||||
*/
|
||||
public function __construct($invoice)
|
||||
{
|
||||
|
@ -46,7 +46,7 @@ class InvoiceSumInclusive
|
||||
/**
|
||||
* Constructs the object with Invoice and Settings object.
|
||||
*
|
||||
* @param Invoice $invoice The invoice
|
||||
* @param \App\Models\RecurringInvoice|\App\Models\Quote|\App\Models\Credit|\App\Models\PurchaseOrder|\App\Models\Invoice $invoice The entity
|
||||
*/
|
||||
public function __construct($invoice)
|
||||
{
|
||||
|
@ -413,8 +413,17 @@ class LoginController extends BaseController
|
||||
|
||||
return $this->timeConstrainedResponse($cu);
|
||||
}
|
||||
|
||||
nlog("socialite");
|
||||
nlog($user);
|
||||
|
||||
$name = OAuth::splitName($user->name);
|
||||
|
||||
if($provider == 'apple') {
|
||||
$name[0] = request()->has('first_name') ? request()->input('first_name') : $name[0];
|
||||
$name[1] = request()->has('last_name') ? request()->input('last_name') : $name[1];
|
||||
}
|
||||
|
||||
$new_account = [
|
||||
'first_name' => $name[0],
|
||||
'last_name' => $name[1],
|
||||
|
@ -687,7 +687,7 @@ class BankIntegrationController extends BaseController
|
||||
|
||||
auth()->user()->account->bank_integrations->each(function ($bank_integration) {
|
||||
|
||||
ProcessBankTransactions::dispatchSync(auth()->user()->account->bank_integration_account_id, $bank_integration);
|
||||
(new ProcessBankTransactions(auth()->user()->account->bank_integration_account_id, $bank_integration))->handle();
|
||||
|
||||
});
|
||||
|
||||
|
@ -12,12 +12,14 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Factory\ExpenseCategoryFactory;
|
||||
use App\Filters\ExpenseCategoryFilters;
|
||||
use App\Http\Requests\ExpenseCategory\CreateExpenseCategoryRequest;
|
||||
use App\Http\Requests\ExpenseCategory\DestroyExpenseCategoryRequest;
|
||||
use App\Http\Requests\ExpenseCategory\EditExpenseCategoryRequest;
|
||||
use App\Http\Requests\ExpenseCategory\ShowExpenseCategoryRequest;
|
||||
use App\Http\Requests\ExpenseCategory\StoreExpenseCategoryRequest;
|
||||
use App\Http\Requests\ExpenseCategory\UpdateExpenseCategoryRequest;
|
||||
use App\Models\Expense;
|
||||
use App\Models\ExpenseCategory;
|
||||
use App\Repositories\BaseRepository;
|
||||
use App\Transformers\ExpenseCategoryTransformer;
|
||||
@ -79,13 +81,15 @@ class ExpenseCategoryController extends BaseController
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function index()
|
||||
public function index(ExpenseCategoryFilters $filters)
|
||||
{
|
||||
$expense_categories = ExpenseCategory::scope();
|
||||
$expense_categories = ExpenseCategory::filter($filters);
|
||||
|
||||
return $this->listResponse($expense_categories);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
|
@ -733,7 +733,7 @@ class InvoiceController extends BaseController
|
||||
}
|
||||
break;
|
||||
case 'mark_sent':
|
||||
$invoice->service()->markSent()->save();
|
||||
$invoice->service()->markSent(true)->save();
|
||||
|
||||
if (! $bulk) {
|
||||
return $this->itemResponse($invoice);
|
||||
@ -781,7 +781,8 @@ class InvoiceController extends BaseController
|
||||
case 'email':
|
||||
//check query parameter for email_type and set the template else use calculateTemplate
|
||||
|
||||
if (request()->has('email_type') && in_array(request()->input('email_type'), ['reminder1', 'reminder2', 'reminder3', 'reminder_endless', 'custom1', 'custom2', 'custom3'])) {
|
||||
// if (request()->has('email_type') && in_array(request()->input('email_type'), ['reminder1', 'reminder2', 'reminder3', 'reminder_endless', 'custom1', 'custom2', 'custom3'])) {
|
||||
if (request()->has('email_type') && property_exists($invoice->company->settings, request()->input('email_type'))) {
|
||||
$this->reminder_template = $invoice->client->getSetting(request()->input('email_type'));
|
||||
} else {
|
||||
$this->reminder_template = $invoice->calculateTemplate('invoice');
|
||||
|
@ -11,23 +11,30 @@
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use App\DataMapper\ClientSettings;
|
||||
use App\Factory\ClientFactory;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Mail\ContactPasswordlessLogin;
|
||||
use App\Mail\Subscription\OtpCode;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\Subscription;
|
||||
use App\Repositories\ClientContactRepository;
|
||||
use App\Repositories\ClientRepository;
|
||||
use App\Utils\Number;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
use App\DataMapper\ClientSettings;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Laracasts\Presenter\Exceptions\PresenterException;
|
||||
use InvalidArgumentException;
|
||||
use Livewire\Component;
|
||||
|
||||
class BillingPortalPurchasev2 extends Component
|
||||
@ -46,13 +53,6 @@ class BillingPortalPurchasev2 extends Component
|
||||
*/
|
||||
public $email;
|
||||
|
||||
/**
|
||||
* Password model for user input.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $password;
|
||||
|
||||
/**
|
||||
* Instance of subscription.
|
||||
*
|
||||
@ -67,19 +67,6 @@ class BillingPortalPurchasev2 extends Component
|
||||
*/
|
||||
public $contact;
|
||||
|
||||
/**
|
||||
* Rules for validating the form.
|
||||
*
|
||||
* @var \string[][]
|
||||
*/
|
||||
// protected $rules = [
|
||||
// 'email' => ['required', 'email'],
|
||||
// 'data' => ['required', 'array'],
|
||||
// 'data.*.recurring_qty' => ['required', 'between:100,1000'],
|
||||
// 'data.*.optional_recurring_qty' => ['required', 'between:100,1000'],
|
||||
// 'data.*.optional_qty' => ['required', 'between:100,1000'],
|
||||
// ];
|
||||
|
||||
/**
|
||||
* Id for CompanyGateway record.
|
||||
*
|
||||
@ -94,28 +81,10 @@ class BillingPortalPurchasev2 extends Component
|
||||
*/
|
||||
public $payment_method_id;
|
||||
|
||||
private $user_coupon;
|
||||
|
||||
/**
|
||||
* List of steps that frontend form follows.
|
||||
*
|
||||
* @var array
|
||||
* Array of front end variables for
|
||||
* the subscription
|
||||
*/
|
||||
public $steps = [
|
||||
'passed_email' => false,
|
||||
'existing_user' => false,
|
||||
'fetched_payment_methods' => false,
|
||||
'fetched_client' => false,
|
||||
'show_start_trial' => false,
|
||||
'passwordless_login_sent' => false,
|
||||
'started_payment' => false,
|
||||
'discount_applied' => false,
|
||||
'show_loading_bar' => false,
|
||||
'not_eligible' => null,
|
||||
'not_eligible_message' => null,
|
||||
'payment_required' => true,
|
||||
];
|
||||
|
||||
public $data = [];
|
||||
|
||||
/**
|
||||
@ -153,18 +122,6 @@ class BillingPortalPurchasev2 extends Component
|
||||
*/
|
||||
public $request_data;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $price;
|
||||
|
||||
/**
|
||||
* Disabled state of passwordless login button.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $passwordless_login_btn = false;
|
||||
|
||||
/**
|
||||
* Instance of company.
|
||||
*
|
||||
@ -179,97 +136,488 @@ class BillingPortalPurchasev2 extends Component
|
||||
*/
|
||||
public $campaign;
|
||||
|
||||
public $bundle;
|
||||
public $recurring_products;
|
||||
public $products;
|
||||
public $optional_recurring_products;
|
||||
public $optional_products;
|
||||
public $total;
|
||||
public $discount;
|
||||
public $sub_total;
|
||||
public $authenticated = false;
|
||||
public $login;
|
||||
public $float_amount_total;
|
||||
public $payment_started = false;
|
||||
public $valid_coupon = false;
|
||||
public $payable_invoices = [];
|
||||
public $payment_confirmed = false;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$this->quantity = 1;
|
||||
$this->discount = 0;
|
||||
$this->sub_total = 0;
|
||||
$this->float_amount_total = 0;
|
||||
|
||||
$this->data = [];
|
||||
|
||||
$this->price = $this->subscription->price;
|
||||
$this->price = $this->subscription->price; // ?
|
||||
|
||||
if (request()->query('coupon')) {
|
||||
$this->recurring_products = $this->subscription->service()->recurring_products();
|
||||
$this->products = $this->subscription->service()->products();
|
||||
$this->optional_recurring_products = $this->subscription->service()->optional_recurring_products();
|
||||
$this->optional_products = $this->subscription->service()->optional_products();
|
||||
|
||||
$this->bundle = collect();
|
||||
|
||||
//every thing below is redundant
|
||||
|
||||
if (request()->query('coupon')) {
|
||||
$this->coupon = request()->query('coupon');
|
||||
$this->handleCoupon();
|
||||
}
|
||||
elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0){
|
||||
$this->price = $this->subscription->promo_price;
|
||||
$this->price = $this->subscription->promo_price;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function updatingData()
|
||||
public function loginValidation()
|
||||
{
|
||||
nlog('updating');
|
||||
// nlog($this->data);
|
||||
$this->resetErrorBag('login');
|
||||
$this->resetValidation('login');
|
||||
}
|
||||
|
||||
public function updatedData()
|
||||
public function handleLogin($user_code)
|
||||
{
|
||||
nlog('updated');
|
||||
nlog($this->data);
|
||||
$validatedData = $this->validate();
|
||||
nlog( $validatedData );
|
||||
|
||||
$this->resetErrorBag('login');
|
||||
$this->resetValidation('login');
|
||||
|
||||
$code = Cache::get("subscriptions:otp:{$this->email}");
|
||||
|
||||
if($user_code != $code){
|
||||
$errors = $this->getErrorBag();
|
||||
$errors->add('login', ctrans('texts.invalid_code'));
|
||||
return $this;
|
||||
}
|
||||
|
||||
$contact = ClientContact::where('email', $this->email)
|
||||
->where('company_id', $this->subscription->company_id)
|
||||
->first();
|
||||
|
||||
if($contact){
|
||||
Auth::guard('contact')->loginUsingId($contact->id, true);
|
||||
$this->contact = $contact;
|
||||
}
|
||||
else {
|
||||
$this->createClientContact();
|
||||
}
|
||||
|
||||
$this->getPaymentMethods();
|
||||
|
||||
$this->authenticated = true;
|
||||
$this->payment_started = true;
|
||||
|
||||
}
|
||||
|
||||
public function updated($propertyName)
|
||||
public function resetEmail()
|
||||
{
|
||||
nlog("validating {$propertyName}");
|
||||
$this->errors = $this->validateOnly($propertyName);
|
||||
$this->email = null;
|
||||
}
|
||||
|
||||
nlog($this->errors);
|
||||
$validatedData = $this->validate();
|
||||
nlog( $validatedData );
|
||||
public function handleEmail()
|
||||
{
|
||||
$this->validateOnly('email', ['email' => 'required|bail|email:rfc']);
|
||||
|
||||
$rand = rand(100000,999999);
|
||||
|
||||
$email_hash = "subscriptions:otp:{$this->email}";
|
||||
|
||||
Cache::put($email_hash, $rand, 120);
|
||||
|
||||
$this->emailOtpCode($rand);
|
||||
}
|
||||
|
||||
private function emailOtpCode($code)
|
||||
{
|
||||
|
||||
$cc = new ClientContact();
|
||||
$cc->email = $this->email;
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new OtpCode($this->subscription->company, $this->contact, $code);
|
||||
$nmo->company = $this->subscription->company;
|
||||
$nmo->settings = $this->subscription->company->settings;
|
||||
$nmo->to_user = $cc;
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a coupon being entered into the checkout
|
||||
*/
|
||||
public function handleCoupon()
|
||||
{
|
||||
|
||||
if($this->coupon == $this->subscription->promo_code) {
|
||||
$this->valid_coupon = true;
|
||||
$this->buildBundle();
|
||||
}
|
||||
else{
|
||||
$this->discount = 0;
|
||||
$this->valid_coupon = false;
|
||||
$this->buildBundle();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the bundle in the checkout
|
||||
*/
|
||||
public function buildBundle()
|
||||
{
|
||||
$this->bundle = collect();
|
||||
|
||||
$data = $this->data;
|
||||
|
||||
/* Recurring products can have a variable quantity */
|
||||
foreach($this->recurring_products as $key => $p)
|
||||
{
|
||||
|
||||
$qty = isset($data[$key]['recurring_qty']) ? $data[$key]['recurring_qty'] : 1;
|
||||
$total = $p->price * $qty;
|
||||
|
||||
$this->bundle->push([
|
||||
'description' => $p->notes,
|
||||
'product_key' => $p->product_key,
|
||||
'unit_cost' => $p->price,
|
||||
'product' => nl2br(substr($p->notes, 0, 50)),
|
||||
'price' => Number::formatMoney($total, $this->subscription->company).' / '. RecurringInvoice::frequencyForKey($this->subscription->frequency_id),
|
||||
'total' => $total,
|
||||
'qty' => $qty,
|
||||
'is_recurring' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/* One time products can only have a single quantity */
|
||||
foreach($this->products as $key => $p)
|
||||
{
|
||||
|
||||
$qty = 1;
|
||||
$total = $p->price * $qty;
|
||||
|
||||
$this->bundle->push([
|
||||
'description' => $p->notes,
|
||||
'product_key' => $p->product_key,
|
||||
'unit_cost' => $p->price,
|
||||
'product' => nl2br(substr($p->notes, 0, 50)),
|
||||
'price' => Number::formatMoney($total, $this->subscription->company),
|
||||
'total' => $total,
|
||||
'qty' => $qty,
|
||||
'is_recurring' => false
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
foreach($this->data as $key => $value)
|
||||
{
|
||||
|
||||
/* Optional recurring products can have a variable quantity */
|
||||
if(isset($this->data[$key]['optional_recurring_qty']))
|
||||
{
|
||||
$p = $this->optional_recurring_products->first(function ($v,$k) use($key){
|
||||
return $k == $key;
|
||||
});
|
||||
|
||||
$qty = isset($this->data[$key]['optional_recurring_qty']) ? $this->data[$key]['optional_recurring_qty'] : false;
|
||||
$total = $p->price * $qty;
|
||||
|
||||
if($qty)
|
||||
{
|
||||
|
||||
$this->bundle->push([
|
||||
'description' => $p->notes,
|
||||
'product_key' => $p->product_key,
|
||||
'unit_cost' => $p->price,
|
||||
'product' => nl2br(substr($p->notes, 0, 50)),
|
||||
'price' => Number::formatMoney($total, $this->subscription->company).' / '. RecurringInvoice::frequencyForKey($this->subscription->frequency_id),
|
||||
'total' => $total,
|
||||
'qty' => $qty,
|
||||
'is_recurring' => true
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/* Optional products can have a variable quantity */
|
||||
if(isset($this->data[$key]['optional_qty']))
|
||||
{
|
||||
$p = $this->optional_products->first(function ($v,$k) use($key){
|
||||
return $k == $key;
|
||||
});
|
||||
|
||||
$qty = isset($this->data[$key]['optional_qty']) ? $this->data[$key]['optional_qty'] : false;
|
||||
$total = $p->price * $qty;
|
||||
|
||||
if($qty)
|
||||
{
|
||||
$this->bundle->push([
|
||||
'description' => $p->notes,
|
||||
'product_key' => $p->product_key,
|
||||
'unit_cost' => $p->price,
|
||||
'product' => nl2br(substr($p->notes, 0, 50)),
|
||||
'price' => Number::formatMoney($total, $this->subscription->company),
|
||||
'total' => $total,
|
||||
'qty' => $qty,
|
||||
'is_recurring' => false
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->sub_total = Number::formatMoney($this->bundle->sum('total'), $this->subscription->company);
|
||||
$this->total = $this->sub_total;
|
||||
|
||||
if($this->valid_coupon)
|
||||
{
|
||||
|
||||
if($this->subscription->is_amount_discount)
|
||||
$discount = $this->subscription->promo_discount;
|
||||
else
|
||||
$discount = round($this->bundle->sum('total') * ($this->subscription->promo_discount / 100), 2);
|
||||
|
||||
$this->discount = Number::formatMoney($discount, $this->subscription->company);
|
||||
|
||||
$this->total = Number::formatMoney(($this->bundle->sum('total') - $discount), $this->subscription->company);
|
||||
|
||||
$this->float_amount_total = ($this->bundle->sum('total') - $discount);
|
||||
}
|
||||
else {
|
||||
$this->float_amount_total = $this->bundle->sum('total');
|
||||
$this->total = Number::formatMoney($this->float_amount_total, $this->subscription->company);
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
* @throws PresenterException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function createClientContact()
|
||||
{
|
||||
|
||||
$company = $this->subscription->company;
|
||||
$user = $this->subscription->user;
|
||||
$user->setCompany($company);
|
||||
|
||||
$client_repo = new ClientRepository(new ClientContactRepository());
|
||||
$data = [
|
||||
'name' => '',
|
||||
'contacts' => [
|
||||
['email' => $this->email],
|
||||
],
|
||||
'client_hash' => Str::random(40),
|
||||
'settings' => ClientSettings::defaults(),
|
||||
];
|
||||
|
||||
$client = $client_repo->save($data, ClientFactory::create($company->id, $user->id));
|
||||
|
||||
$this->contact = $client->fresh()->contacts()->first();
|
||||
|
||||
Auth::guard('contact')->loginUsingId($this->contact->id, true);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed $propertyName
|
||||
*
|
||||
* @return BillingPortalPurchasev2
|
||||
*/
|
||||
public function updated($propertyName) :self
|
||||
{
|
||||
if(in_array($propertyName, ['login','email']))
|
||||
return $this;
|
||||
|
||||
$this->buildBundle();
|
||||
|
||||
nlog($this->bundle);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetching payment methods from the client.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function getPaymentMethods() :self
|
||||
{
|
||||
if($this->contact)
|
||||
$this->methods = $this->contact->client->service()->getPaymentMethods($this->float_amount_total);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Middle method between selecting payment method &
|
||||
* submitting the from to the backend.
|
||||
*
|
||||
* @param $company_gateway_id
|
||||
* @param $gateway_type_id
|
||||
*/
|
||||
public function handleMethodSelectingEvent($company_gateway_id, $gateway_type_id)
|
||||
{
|
||||
$this->payment_confirmed = true;
|
||||
|
||||
$this->company_gateway_id = $company_gateway_id;
|
||||
$this->payment_method_id = $gateway_type_id;
|
||||
|
||||
$this->handleBeforePaymentEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to handle events before payments.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handleBeforePaymentEvents() :void
|
||||
{
|
||||
|
||||
$data = [
|
||||
'client_id' => $this->contact->client->id,
|
||||
'date' => now()->format('Y-m-d'),
|
||||
'invitations' => [[
|
||||
'key' => '',
|
||||
'client_contact_id' => $this->contact->hashed_id,
|
||||
]],
|
||||
'user_input_promo_code' => $this->coupon,
|
||||
'coupon' => empty($this->subscription->promo_code) ? '' : $this->coupon,
|
||||
// 'quantity' => $this->quantity,
|
||||
];
|
||||
|
||||
$is_eligible = $this->subscription->service()->isEligible($this->contact);
|
||||
|
||||
// if (is_array($is_eligible) && $is_eligible['message'] != 'Success') {
|
||||
// $this->steps['not_eligible'] = true;
|
||||
// $this->steps['not_eligible_message'] = $is_eligible['message'];
|
||||
// $this->steps['show_loading_bar'] = false;
|
||||
|
||||
// return;
|
||||
// }
|
||||
|
||||
$this->invoice = $this->subscription
|
||||
->service()
|
||||
->createInvoiceV2($this->bundle, $this->contact->client_id, $this->valid_coupon)
|
||||
->service()
|
||||
->markSent()
|
||||
->fillDefaults()
|
||||
->adjustInventory()
|
||||
->save();
|
||||
|
||||
Cache::put($this->hash, [
|
||||
'subscription_id' => $this->subscription->id,
|
||||
'email' => $this->email ?? $this->contact->email,
|
||||
'client_id' => $this->contact->client->id,
|
||||
'invoice_id' => $this->invoice->id,
|
||||
'context' => 'purchase',
|
||||
'campaign' => $this->campaign,
|
||||
'bundle' => $this->bundle,
|
||||
], now()->addMinutes(60));
|
||||
|
||||
$this->emit('beforePaymentEventsCompleted');
|
||||
}
|
||||
|
||||
public function handleTrial()
|
||||
{
|
||||
return $this->subscription->service()->startTrial([
|
||||
'email' => $this->email ?? $this->contact->email,
|
||||
'quantity' => $this->quantity,
|
||||
'contact_id' => $this->contact->id,
|
||||
'client_id' => $this->contact->client->id,
|
||||
'bundle' => $this->bundle,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public function rules()
|
||||
{
|
||||
$rules = [
|
||||
'email' => ['required', 'email'],
|
||||
'data' => ['required', 'array'],
|
||||
'data.*.recurring_qty' => ['required', 'between:100,1000'],
|
||||
'data.*.optional_recurring_qty' => ['required', 'between:100,1000'],
|
||||
'data.*.optional_qty' => ['required', 'between:100,1000'],
|
||||
$rules = [
|
||||
];
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle user authentication
|
||||
*
|
||||
* @return $this|bool|void
|
||||
*/
|
||||
public function authenticate()
|
||||
public function attributes()
|
||||
{
|
||||
$this->validate();
|
||||
$attributes = [
|
||||
];
|
||||
|
||||
$contact = ClientContact::where('email', $this->email)
|
||||
->where('company_id', $this->subscription->company_id)
|
||||
->first();
|
||||
|
||||
if ($contact && $this->steps['existing_user'] === false) {
|
||||
return $this->steps['existing_user'] = true;
|
||||
}
|
||||
|
||||
if ($contact && $this->steps['existing_user']) {
|
||||
$attempt = Auth::guard('contact')->attempt(['email' => $this->email, 'password' => $this->password, 'company_id' => $this->subscription->company_id]);
|
||||
|
||||
return $attempt
|
||||
? $this->getPaymentMethods($contact)
|
||||
: session()->flash('message', 'These credentials do not match our records.');
|
||||
}
|
||||
|
||||
$this->steps['existing_user'] = false;
|
||||
|
||||
$contact = $this->createBlankClient();
|
||||
|
||||
if ($contact && $contact instanceof ClientContact) {
|
||||
$this->getPaymentMethods($contact);
|
||||
}
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Handle user authentication
|
||||
// *
|
||||
// * @return $this|bool|void
|
||||
// */
|
||||
// public function authenticate()
|
||||
// {
|
||||
// $this->validate();
|
||||
|
||||
// $contact = ClientContact::where('email', $this->email)
|
||||
// ->where('company_id', $this->subscription->company_id)
|
||||
// ->first();
|
||||
|
||||
// if ($contact && $this->steps['existing_user'] === false) {
|
||||
// return $this->steps['existing_user'] = true;
|
||||
// }
|
||||
|
||||
// if ($contact && $this->steps['existing_user']) {
|
||||
// $attempt = Auth::guard('contact')->attempt(['email' => $this->email, 'password' => $this->password, 'company_id' => $this->subscription->company_id]);
|
||||
|
||||
// return $attempt
|
||||
// ? $this->getPaymentMethods($contact)
|
||||
// : session()->flash('message', 'These credentials do not match our records.');
|
||||
// }
|
||||
|
||||
// $this->steps['existing_user'] = false;
|
||||
|
||||
// $contact = $this->createBlankClient();
|
||||
|
||||
// if ($contact && $contact instanceof ClientContact) {
|
||||
// $this->getPaymentMethods($contact);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a blank client. Used for new customers purchasing.
|
||||
*
|
||||
@ -341,212 +689,13 @@ class BillingPortalPurchasev2 extends Component
|
||||
return $client->fresh()->contacts->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetching payment methods from the client.
|
||||
*
|
||||
* @param ClientContact $contact
|
||||
* @return $this
|
||||
*/
|
||||
protected function getPaymentMethods(ClientContact $contact): self
|
||||
{
|
||||
Auth::guard('contact')->loginUsingId($contact->id, true);
|
||||
|
||||
$this->contact = $contact;
|
||||
|
||||
if ($this->subscription->trial_enabled) {
|
||||
$this->heading_text = ctrans('texts.plan_trial');
|
||||
$this->steps['show_start_trial'] = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ((int)$this->price == 0)
|
||||
$this->steps['payment_required'] = false;
|
||||
else
|
||||
$this->steps['fetched_payment_methods'] = true;
|
||||
|
||||
$this->methods = $contact->client->service()->getPaymentMethods($this->price);
|
||||
|
||||
$this->heading_text = ctrans('texts.payment_methods');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Middle method between selecting payment method &
|
||||
* submitting the from to the backend.
|
||||
*
|
||||
* @param $company_gateway_id
|
||||
* @param $gateway_type_id
|
||||
*/
|
||||
public function handleMethodSelectingEvent($company_gateway_id, $gateway_type_id)
|
||||
{
|
||||
$this->company_gateway_id = $company_gateway_id;
|
||||
$this->payment_method_id = $gateway_type_id;
|
||||
|
||||
$this->handleBeforePaymentEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to handle events before payments.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handleBeforePaymentEvents()
|
||||
{
|
||||
$this->steps['started_payment'] = true;
|
||||
$this->steps['show_loading_bar'] = true;
|
||||
|
||||
$data = [
|
||||
'client_id' => $this->contact->client->id,
|
||||
'date' => now()->format('Y-m-d'),
|
||||
'invitations' => [[
|
||||
'key' => '',
|
||||
'client_contact_id' => $this->contact->hashed_id,
|
||||
]],
|
||||
'user_input_promo_code' => $this->coupon,
|
||||
'coupon' => empty($this->subscription->promo_code) ? '' : $this->coupon,
|
||||
'quantity' => $this->quantity,
|
||||
];
|
||||
|
||||
$is_eligible = $this->subscription->service()->isEligible($this->contact);
|
||||
|
||||
if (is_array($is_eligible) && $is_eligible['message'] != 'Success') {
|
||||
$this->steps['not_eligible'] = true;
|
||||
$this->steps['not_eligible_message'] = $is_eligible['message'];
|
||||
$this->steps['show_loading_bar'] = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->invoice = $this->subscription
|
||||
->service()
|
||||
->createInvoice($data, $this->quantity)
|
||||
->service()
|
||||
->markSent()
|
||||
->fillDefaults()
|
||||
->adjustInventory()
|
||||
->save();
|
||||
|
||||
Cache::put($this->hash, [
|
||||
'subscription_id' => $this->subscription->id,
|
||||
'email' => $this->email ?? $this->contact->email,
|
||||
'client_id' => $this->contact->client->id,
|
||||
'invoice_id' => $this->invoice->id,
|
||||
'context' => 'purchase',
|
||||
'campaign' => $this->campaign,
|
||||
], now()->addMinutes(60));
|
||||
|
||||
$this->emit('beforePaymentEventsCompleted');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Proxy method for starting the trial.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function handleTrial()
|
||||
{
|
||||
return $this->subscription->service()->startTrial([
|
||||
'email' => $this->email ?? $this->contact->email,
|
||||
'quantity' => $this->quantity,
|
||||
'contact_id' => $this->contact->id,
|
||||
'client_id' => $this->contact->client->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function handlePaymentNotRequired()
|
||||
{
|
||||
|
||||
$is_eligible = $this->subscription->service()->isEligible($this->contact);
|
||||
|
||||
if ($is_eligible['status_code'] != 200) {
|
||||
$this->steps['not_eligible'] = true;
|
||||
$this->steps['not_eligible_message'] = $is_eligible['message'];
|
||||
$this->steps['show_loading_bar'] = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
return $this->subscription->service()->handleNoPaymentRequired([
|
||||
'email' => $this->email ?? $this->contact->email,
|
||||
'quantity' => $this->quantity,
|
||||
'contact_id' => $this->contact->id,
|
||||
'client_id' => $this->contact->client->id,
|
||||
'coupon' => $this->coupon,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update quantity property.
|
||||
*
|
||||
* @param string $option
|
||||
* @return int
|
||||
*/
|
||||
public function updateQuantity(string $option): int
|
||||
{
|
||||
$this->handleCoupon();
|
||||
|
||||
if ($this->quantity == 1 && $option == 'decrement') {
|
||||
$this->price = $this->price * 1;
|
||||
return $this->quantity;
|
||||
}
|
||||
|
||||
if ($this->quantity > $this->subscription->max_seats_limit && $option == 'increment') {
|
||||
$this->price = $this->price * $this->subscription->max_seats_limit;
|
||||
return $this->quantity;
|
||||
}
|
||||
|
||||
if ($option == 'increment') {
|
||||
$this->quantity++;
|
||||
$this->price = $this->price * $this->quantity;
|
||||
return $this->quantity;
|
||||
}
|
||||
|
||||
$this->quantity--;
|
||||
$this->price = $this->price * $this->quantity;
|
||||
|
||||
return $this->quantity;
|
||||
}
|
||||
|
||||
public function handleCoupon()
|
||||
{
|
||||
|
||||
if($this->steps['discount_applied']){
|
||||
$this->price = $this->subscription->promo_price;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->coupon == $this->subscription->promo_code) {
|
||||
$this->price = $this->subscription->promo_price;
|
||||
$this->quantity = 1;
|
||||
$this->steps['discount_applied'] = true;
|
||||
}
|
||||
else
|
||||
$this->price = $this->subscription->price;
|
||||
}
|
||||
|
||||
public function passwordlessLogin()
|
||||
{
|
||||
$this->passwordless_login_btn = true;
|
||||
|
||||
$contact = ClientContact::query()
|
||||
->where('email', $this->email)
|
||||
->where('company_id', $this->subscription->company_id)
|
||||
->first();
|
||||
|
||||
$mailer = new NinjaMailerObject();
|
||||
$mailer->mailable = new ContactPasswordlessLogin($this->email, $this->subscription->company, (string)route('client.subscription.purchase', $this->subscription->hashed_id) . '?coupon=' . $this->coupon);
|
||||
$mailer->company = $this->subscription->company;
|
||||
$mailer->settings = $this->subscription->company->settings;
|
||||
$mailer->to_user = $contact;
|
||||
|
||||
NinjaMailerJob::dispatch($mailer);
|
||||
|
||||
$this->steps['passwordless_login_sent'] = true;
|
||||
$this->passwordless_login_btn = false;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
@ -555,16 +704,10 @@ class BillingPortalPurchasev2 extends Component
|
||||
}
|
||||
|
||||
if ($this->contact instanceof ClientContact) {
|
||||
$this->getPaymentMethods($this->contact);
|
||||
$this->getPaymentMethods();
|
||||
}
|
||||
|
||||
return render('components.livewire.billing-portal-purchasev2');
|
||||
}
|
||||
|
||||
public function changeData()
|
||||
{
|
||||
|
||||
nlog($this->data);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class PaymentsTable extends Component
|
||||
->where('company_id', $this->company->id)
|
||||
->where('client_id', auth()->guard('contact')->user()->client_id)
|
||||
->whereIn('status_id', [Payment::STATUS_FAILED, Payment::STATUS_COMPLETED, Payment::STATUS_PENDING, Payment::STATUS_REFUNDED, Payment::STATUS_PARTIALLY_REFUNDED])
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'desc' : 'asc')
|
||||
->withTrashed()
|
||||
->paginate($this->per_page);
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
namespace App\Http\Livewire\RecurringInvoices;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use Livewire\Component;
|
||||
|
||||
class UpdateAutoBilling extends Component
|
||||
@ -24,6 +25,12 @@ class UpdateAutoBilling extends Component
|
||||
if ($this->invoice->auto_bill == 'optin' || $this->invoice->auto_bill == 'optout') {
|
||||
$this->invoice->auto_bill_enabled = ! $this->invoice->auto_bill_enabled;
|
||||
$this->invoice->saveQuietly();
|
||||
|
||||
Invoice::where('recurring_id', $this->invoice->id)
|
||||
->whereIn('status_id', [2,3])
|
||||
->where('is_deleted',0)
|
||||
->where('balance', '>', 0)
|
||||
->update(['auto_bill_enabled' => $this->invoice->auto_bill_enabled]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,21 +64,22 @@ class MatchBankTransactionRequest extends Request
|
||||
|
||||
if(array_key_exists('payment_id', $inputs['transactions'][$key]) && strlen($inputs['transactions'][$key]['payment_id']) >= 1){
|
||||
$inputs['transactions'][$key]['payment_id'] = $this->decodePrimaryKey($inputs['transactions'][$key]['payment_id']);
|
||||
$p = Payment::withTrashed()->find($inputs['transactions'][$key]['payment_id']);
|
||||
$p = Payment::withTrashed()->where('company_id', auth()->user()->company()->id)->where('id', $inputs['transactions'][$key]['payment_id'])->first();
|
||||
|
||||
/*Ensure we don't relink an existing payment*/
|
||||
if(!$p || $p->transaction_id)
|
||||
$inputs['transactions'][$key]['payment_id'] = null;
|
||||
if(!$p || is_numeric($p->transaction_id)){
|
||||
unset($inputs['transactions'][$key]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(array_key_exists('expense_id', $inputs['transactions'][$key]) && strlen($inputs['transactions'][$key]['expense_id']) >= 1){
|
||||
$inputs['transactions'][$key]['expense_id'] = $this->decodePrimaryKey($inputs['transactions'][$key]['expense_id']);
|
||||
$e = Expense::withTrashed()->find($inputs['transactions'][$key]['expense_id']);
|
||||
$e = Expense::withTrashed()->where('company_id', auth()->user()->company()->id)->where('id', $inputs['transactions'][$key]['expense_id'])->first();
|
||||
|
||||
/*Ensure we don't relink an existing expense*/
|
||||
if(!$e || $e->transaction_id)
|
||||
$inputs['transactions'][$key]['expense_id'] = null;
|
||||
if(!$e || is_numeric($e->transaction_id))
|
||||
unset($inputs['transactions'][$key]['expense_id']);
|
||||
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,8 @@ class UpdateCompanyRequest extends Request
|
||||
$rules['size_id'] = 'integer|nullable';
|
||||
$rules['country_id'] = 'integer|nullable';
|
||||
$rules['work_email'] = 'email|nullable';
|
||||
$rules['matomo_id'] = 'nullable|integer';
|
||||
|
||||
// $rules['client_registration_fields'] = 'array';
|
||||
|
||||
if (isset($input['portal_mode']) && ($input['portal_mode'] == 'domain' || $input['portal_mode'] == 'iframe')) {
|
||||
|
@ -28,7 +28,6 @@ class GenericReportRequest extends Request
|
||||
|
||||
public function rules()
|
||||
{
|
||||
nlog($this->date_range);
|
||||
|
||||
return [
|
||||
'date_range' => 'bail|required|string',
|
||||
|
@ -28,8 +28,8 @@ class ProfitLossRequest extends Request
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'start_date' => 'required_if:date_range,custom|string|date',
|
||||
'end_date' => 'required_if:date_range,custom|string|date',
|
||||
'start_date' => 'bail|nullable|required_if:date_range,custom|string|date',
|
||||
'end_date' => 'bail|nullable|required_if:date_range,custom|string|date',
|
||||
'is_income_billed' => 'required|bail|bool',
|
||||
'is_expense_billed' => 'bool',
|
||||
'include_tax' => 'required|bail|bool',
|
||||
|
32
app/Http/Requests/Vendor/StoreVendorRequest.php
vendored
32
app/Http/Requests/Vendor/StoreVendorRequest.php
vendored
@ -25,37 +25,43 @@ class StoreVendorRequest extends Request
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
* @method static \Illuminate\Contracts\Auth\Authenticatable|null user()
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('create', Vendor::class);
|
||||
/** @var \App\User|null $user */
|
||||
$user = auth()->user();
|
||||
|
||||
return $user->can('create', Vendor::class);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
|
||||
/* Ensure we have a client name, and that all emails are unique*/
|
||||
//$rules['name'] = 'required|min:1';
|
||||
// $rules['id_number'] = 'unique:vendors,id_number,'.$this->id.',id,company_id,'.auth()->user()->company()->id;
|
||||
//$rules['settings'] = new ValidVendorGroupSettingsRule();
|
||||
/** @var \App\User|null $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$rules['contacts.*.email'] = 'bail|nullable|distinct|sometimes|email';
|
||||
|
||||
if (isset($this->number)) {
|
||||
$rules['number'] = Rule::unique('vendors')->where('company_id', auth()->user()->company()->id);
|
||||
}
|
||||
if (isset($this->number))
|
||||
$rules['number'] = Rule::unique('vendors')->where('company_id', $user->company()->id);
|
||||
|
||||
$rules['currency_id'] = 'bail|required|exists:currencies,id';
|
||||
|
||||
// if (isset($this->id_number)) {
|
||||
// $rules['id_number'] = Rule::unique('vendors')->where('company_id', auth()->user()->company()->id);
|
||||
// }
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
/** @var \App\User|null $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$input = $this->all();
|
||||
|
||||
if(!array_key_exists('currency_id', $input) || empty($input['currency_id'])){
|
||||
$input['currency_id'] = $user->company()->settings->currency_id;
|
||||
}
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
$this->replace($input);
|
||||
@ -64,8 +70,6 @@ class StoreVendorRequest extends Request
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
// 'unique' => ctrans('validation.unique', ['attribute' => 'email']),
|
||||
//'required' => trans('validation.required', ['attribute' => 'email']),
|
||||
'contacts.*.email.required' => ctrans('validation.email', ['attribute' => 'email']),
|
||||
];
|
||||
}
|
||||
|
12
app/Http/Requests/Vendor/UpdateVendorRequest.php
vendored
12
app/Http/Requests/Vendor/UpdateVendorRequest.php
vendored
@ -35,17 +35,14 @@ class UpdateVendorRequest extends Request
|
||||
{
|
||||
/* Ensure we have a client name, and that all emails are unique*/
|
||||
|
||||
$rules['country_id'] = 'integer|nullable';
|
||||
$rules['country_id'] = 'integer';
|
||||
|
||||
if ($this->number) {
|
||||
$rules['number'] = Rule::unique('vendors')->where('company_id', auth()->user()->company()->id)->ignore($this->vendor->id);
|
||||
}
|
||||
|
||||
// if($this->id_number)
|
||||
// $rules['id_number'] = Rule::unique('vendors')->where('company_id', auth()->user()->company()->id)->ignore($this->vendor->id);
|
||||
|
||||
|
||||
$rules['contacts.*.email'] = 'nullable|distinct';
|
||||
// $rules['id_number'] = 'unique:vendors,id_number,'.$this->id.',id,company_id,'.auth()->user()->company()->id;
|
||||
$rules['currency_id'] = 'bail|sometimes|exists:currencies,id';
|
||||
|
||||
return $rules;
|
||||
}
|
||||
@ -67,6 +64,9 @@ class UpdateVendorRequest extends Request
|
||||
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
|
||||
}
|
||||
|
||||
if(array_key_exists('country_id', $input) && is_null($input['country_id']))
|
||||
unset($input['country_id']);
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
$this->replace($input);
|
||||
|
@ -107,8 +107,10 @@ class Csv extends BaseImport implements ImportInterface
|
||||
$this->transformer = new BankTransformer($this->company);
|
||||
$bank_transaction_count = $this->ingest($data, $entity_type);
|
||||
$this->entity_count['bank_transactions'] = $bank_transaction_count;
|
||||
|
||||
nlog("bank matching co id = {$this->company->id}");
|
||||
|
||||
BankMatchingService::dispatchSync($this->company->id, $this->company->db);
|
||||
(new BankMatchingService($this->company->id, $this->company->db))->handle();
|
||||
|
||||
}
|
||||
|
||||
|
@ -111,13 +111,15 @@ class MatchBankTransactions implements ShouldQueue
|
||||
|
||||
foreach($this->input as $input)
|
||||
{
|
||||
if(array_key_exists('invoice_ids', $input) && strlen($input['invoice_ids']) > 1)
|
||||
nlog($input);
|
||||
|
||||
if(array_key_exists('invoice_ids', $input) && strlen($input['invoice_ids']) >= 1)
|
||||
$this->matchInvoicePayment($input);
|
||||
elseif(array_key_exists('payment_id', $input) && strlen($input['payment_id']) > 1)
|
||||
elseif(array_key_exists('payment_id', $input) && strlen($input['payment_id']) >= 1)
|
||||
$this->linkPayment($input);
|
||||
elseif(array_key_exists('expense_id', $input) && strlen($input['expense_id']) > 1)
|
||||
elseif(array_key_exists('expense_id', $input) && strlen($input['expense_id']) >= 1)
|
||||
$this->linkExpense($input);
|
||||
else
|
||||
elseif((array_key_exists('vendor_id', $input) && strlen($input['vendor_id']) >= 1) || (array_key_exists('ninja_category_id', $input) && strlen($input['ninja_category_id']) >= 1))
|
||||
$this->matchExpense($input);
|
||||
}
|
||||
|
||||
@ -182,8 +184,12 @@ class MatchBankTransactions implements ShouldQueue
|
||||
$this->bt->ninja_category_id = $expense->category_id;
|
||||
$this->bt->save();
|
||||
|
||||
$this->bts->push($this->bt->id);
|
||||
|
||||
}
|
||||
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
private function linkPayment($input)
|
||||
@ -206,8 +212,10 @@ class MatchBankTransactions implements ShouldQueue
|
||||
$this->bt->invoice_ids = collect($payment->invoices)->pluck('hashed_id')->implode(',');
|
||||
$this->bt->save();
|
||||
|
||||
$this->bts->push($this->bt->id);
|
||||
}
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function matchInvoicePayment($input) :self
|
||||
@ -225,9 +233,9 @@ class MatchBankTransactions implements ShouldQueue
|
||||
|
||||
$this->createPayment($_invoices, $amount);
|
||||
|
||||
}
|
||||
$this->bts->push($this->bt->id);
|
||||
|
||||
$this->bts->push($this->bt->id);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -240,7 +248,6 @@ class MatchBankTransactions implements ShouldQueue
|
||||
if(!$this->bt || $this->bt->status_id == BankTransaction::STATUS_CONVERTED)
|
||||
return $this;
|
||||
|
||||
|
||||
$expense = ExpenseFactory::create($this->bt->company_id, $this->bt->user_id);
|
||||
$expense->category_id = $this->resolveCategory($input);
|
||||
$expense->amount = $this->bt->amount;
|
||||
@ -383,6 +390,7 @@ class MatchBankTransactions implements ShouldQueue
|
||||
|
||||
$this->bt->invoice_ids = collect($invoices)->pluck('hashed_id')->implode(',');
|
||||
$this->bt->status_id = BankTransaction::STATUS_CONVERTED;
|
||||
$this->bt->payment_id = $payment->id;
|
||||
$this->bt->save();
|
||||
}
|
||||
|
||||
|
@ -426,14 +426,14 @@ class NinjaMailerJob implements ShouldQueue
|
||||
private function logMailError($errors, $recipient_object)
|
||||
{
|
||||
|
||||
SystemLogger::dispatchSync(
|
||||
(new SystemLogger(
|
||||
$errors,
|
||||
SystemLog::CATEGORY_MAIL,
|
||||
SystemLog::EVENT_MAIL_SEND,
|
||||
SystemLog::TYPE_FAILURE,
|
||||
$recipient_object,
|
||||
$this->nmo->company
|
||||
);
|
||||
))->handle();
|
||||
|
||||
$job_failure = new EmailFailure($this->nmo->company->company_key);
|
||||
$job_failure->string_metric5 = 'failed_email';
|
||||
|
@ -60,7 +60,7 @@ class BankTransactionSync implements ShouldQueue
|
||||
|
||||
$account->bank_integrations()->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account){
|
||||
|
||||
ProcessBankTransactions::dispatchSync($account->bank_integration_account_id, $bank_integration);
|
||||
(new ProcessBankTransactions($account->bank_integration_account_id, $bank_integration))->handle();
|
||||
|
||||
});
|
||||
|
||||
|
@ -142,13 +142,13 @@ class ProcessPostmarkWebhook implements ShouldQueue
|
||||
$this->invitation->opened_date = now();
|
||||
$this->invitation->save();
|
||||
|
||||
SystemLogger::dispatchSync($this->request,
|
||||
(new SystemLogger($this->request,
|
||||
SystemLog::CATEGORY_MAIL,
|
||||
SystemLog::EVENT_MAIL_OPENED,
|
||||
SystemLog::TYPE_WEBHOOK_RESPONSE,
|
||||
$this->invitation->contact->client,
|
||||
$this->invitation->company
|
||||
);
|
||||
))->handle();
|
||||
|
||||
}
|
||||
|
||||
@ -171,13 +171,13 @@ class ProcessPostmarkWebhook implements ShouldQueue
|
||||
$this->invitation->email_status = 'delivered';
|
||||
$this->invitation->save();
|
||||
|
||||
SystemLogger::dispatchSync($this->request,
|
||||
(new SystemLogger($this->request,
|
||||
SystemLog::CATEGORY_MAIL,
|
||||
SystemLog::EVENT_MAIL_DELIVERY,
|
||||
SystemLog::TYPE_WEBHOOK_RESPONSE,
|
||||
$this->invitation->contact->client,
|
||||
$this->invitation->company
|
||||
);
|
||||
))->handle();
|
||||
}
|
||||
|
||||
// {
|
||||
@ -219,7 +219,7 @@ class ProcessPostmarkWebhook implements ShouldQueue
|
||||
|
||||
LightLogs::create($bounce)->send();
|
||||
|
||||
SystemLogger::dispatchSync($this->request, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_BOUNCED, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company);
|
||||
(new SystemLogger($this->request, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_BOUNCED, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company))->handle();
|
||||
|
||||
// if(config('ninja.notification.slack'))
|
||||
// $this->invitation->company->notification(new EmailBounceNotification($this->invitation->company->account))->ninja();
|
||||
@ -265,7 +265,7 @@ class ProcessPostmarkWebhook implements ShouldQueue
|
||||
|
||||
LightLogs::create($spam)->send();
|
||||
|
||||
SystemLogger::dispatchSync($this->request, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_SPAM_COMPLAINT, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company);
|
||||
(new SystemLogger($this->request, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_SPAM_COMPLAINT, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company))->handle();
|
||||
|
||||
if(config('ninja.notification.slack'))
|
||||
$this->invitation->company->notification(new EmailSpamNotification($this->invitation->company->account))->ninja();
|
||||
|
@ -112,10 +112,19 @@ class UpdateOrCreateProduct implements ShouldQueue
|
||||
$product->tax_rate2 = isset($item->tax_rate2) ? $item->tax_rate2 : 0;
|
||||
$product->tax_name3 = isset($item->tax_name3) ? $item->tax_name3 : '';
|
||||
$product->tax_rate3 = isset($item->tax_rate3) ? $item->tax_rate3 : 0;
|
||||
$product->custom_value1 = isset($item->custom_value1) ? $item->custom_value1 : '';
|
||||
$product->custom_value2 = isset($item->custom_value2) ? $item->custom_value2 : '';
|
||||
$product->custom_value3 = isset($item->custom_value3) ? $item->custom_value3 : '';
|
||||
$product->custom_value4 = isset($item->custom_value4) ? $item->custom_value4 : '';
|
||||
|
||||
if(isset($item->custom_value1) && strlen($item->custom_value1) >=1)
|
||||
$product->custom_value1 = $item->custom_value1;
|
||||
|
||||
if(isset($item->custom_value2) && strlen($item->custom_value1) >=1)
|
||||
$product->custom_value2 = $item->custom_value2;
|
||||
|
||||
if(isset($item->custom_value3) && strlen($item->custom_value1) >=1)
|
||||
$product->custom_value3 = $item->custom_value3;
|
||||
|
||||
if(isset($item->custom_value4) && strlen($item->custom_value1) >=1)
|
||||
$product->custom_value4 = $item->custom_value4;
|
||||
|
||||
$product->user_id = $this->invoice->user_id;
|
||||
$product->company_id = $this->invoice->company_id;
|
||||
$product->project_id = $this->invoice->project_id;
|
||||
|
@ -99,7 +99,6 @@ class SendRecurring implements ShouldQueue
|
||||
/* 09-01-2022 ensure we create the PDFs at this point in time! */
|
||||
$invoice->service()->touchPdf(true);
|
||||
|
||||
//nlog('updating recurring invoice dates');
|
||||
/* Set next date here to prevent a recurring loop forming */
|
||||
$this->recurring_invoice->next_send_date = $this->recurring_invoice->nextSendDate();
|
||||
$this->recurring_invoice->next_send_date_client = $this->recurring_invoice->nextSendDateClient();
|
||||
@ -111,10 +110,6 @@ class SendRecurring implements ShouldQueue
|
||||
$this->recurring_invoice->setCompleted();
|
||||
}
|
||||
|
||||
//nlog('next send date = '.$this->recurring_invoice->next_send_date);
|
||||
// nlog('remaining cycles = '.$this->recurring_invoice->remaining_cycles);
|
||||
//nlog('last send date = '.$this->recurring_invoice->last_sent_date);
|
||||
|
||||
$this->recurring_invoice->save();
|
||||
|
||||
event('eloquent.created: App\Models\Invoice', $invoice);
|
||||
@ -125,8 +120,6 @@ class SendRecurring implements ShouldQueue
|
||||
$invoice->entityEmailEvent($invoice->invitations->first(), 'invoice', 'email_template_invoice');
|
||||
}
|
||||
|
||||
nlog("Invoice {$invoice->number} created");
|
||||
|
||||
$invoice->invitations->each(function ($invitation) use ($invoice) {
|
||||
if ($invitation->contact && ! $invitation->contact->trashed() && strlen($invitation->contact->email) >= 1 && $invoice->client->getSetting('auto_email_invoice')) {
|
||||
try {
|
||||
@ -140,15 +133,14 @@ class SendRecurring implements ShouldQueue
|
||||
});
|
||||
}
|
||||
|
||||
if ($invoice->client->getSetting('auto_bill_date') == 'on_send_date' && $invoice->auto_bill_enabled) {
|
||||
//auto bill, BUT NOT DRAFTS!!
|
||||
if ($invoice->auto_bill_enabled && $invoice->client->getSetting('auto_bill_date') == 'on_send_date' && $invoice->client->getSetting('auto_email_invoice')) {
|
||||
nlog("attempting to autobill {$invoice->number}");
|
||||
// $invoice->service()->autoBill();
|
||||
AutoBill::dispatch($invoice->id, $this->db)->delay(rand(1,2));
|
||||
|
||||
} elseif ($invoice->client->getSetting('auto_bill_date') == 'on_due_date' && $invoice->auto_bill_enabled) {
|
||||
} elseif ($invoice->auto_bill_enabled && $invoice->client->getSetting('auto_bill_date') == 'on_due_date' && $invoice->client->getSetting('auto_email_invoice')) {
|
||||
if ($invoice->due_date && Carbon::parse($invoice->due_date)->startOfDay()->lte(now()->startOfDay())) {
|
||||
nlog("attempting to autobill {$invoice->number}");
|
||||
// $invoice->service()->autoBill();
|
||||
AutoBill::dispatch($invoice->id, $this->db)->delay(rand(1,2));
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +167,7 @@ class Import implements ShouldQueue
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public $timeout = 0;
|
||||
public $timeout = 10000000;
|
||||
|
||||
// public $backoff = 86430;
|
||||
|
||||
@ -188,10 +188,10 @@ class Import implements ShouldQueue
|
||||
$this->resources = $resources;
|
||||
}
|
||||
|
||||
public function middleware()
|
||||
{
|
||||
return [new WithoutOverlapping("only_one_migration_at_a_time_ever")];
|
||||
}
|
||||
// public function middleware()
|
||||
// {
|
||||
// return [new WithoutOverlapping("only_one_migration_at_a_time_ever")];
|
||||
// }
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
|
@ -17,6 +17,7 @@ use App\Models\Client as ClientModel;
|
||||
use App\Models\SystemLog;
|
||||
use App\Models\Webhook;
|
||||
use App\Transformers\ArraySerializer;
|
||||
use App\Utils\Ninja;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
use Illuminate\Bus\Queueable;
|
||||
@ -65,11 +66,12 @@ class WebhookHandler implements ShouldQueue
|
||||
* @return bool
|
||||
*/
|
||||
public function handle()
|
||||
{//todo set multidb here
|
||||
{
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
if (! $this->company || $this->company->is_disabled) {
|
||||
//If the company is disabled, or if on hosted, the user is not a paid hosted user return early
|
||||
if (! $this->company || $this->company->is_disabled || (Ninja::isHosted() && !$this->company->account->isPaidHostedClient())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ class QuoteViewedActivity implements ShouldQueue
|
||||
$fields->user_id = $event->invitation->quote->user_id;
|
||||
$fields->company_id = $event->invitation->company_id;
|
||||
$fields->activity_type_id = Activity::VIEW_QUOTE;
|
||||
$fields->client_id = $event->invitation->client_id;
|
||||
$fields->client_id = $event->invitation->quote->client_id;
|
||||
$fields->client_contact_id = $event->invitation->client_contact_id;
|
||||
$fields->invitation_id = $event->invitation->id;
|
||||
$fields->quote_id = $event->invitation->quote_id;
|
||||
|
@ -138,11 +138,11 @@ class InvoiceEmailEngine extends BaseEmailEngine
|
||||
|
||||
// Storage::url
|
||||
foreach ($this->invoice->documents as $document) {
|
||||
$this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => NULL, ]]);
|
||||
$this->setAttachments([['file' => base64_encode($document->getFile()), 'path' => $document->filePath(), 'name' => $document->name, 'mime' => NULL, ]]);
|
||||
}
|
||||
|
||||
foreach ($this->invoice->company->documents as $document) {
|
||||
$this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => NULL, ]]);
|
||||
$this->setAttachments([['file' => base64_encode($document->getFile()), 'path' => $document->filePath(), 'name' => $document->name, 'mime' => NULL, ]]);
|
||||
}
|
||||
|
||||
$line_items = $this->invoice->line_items;
|
||||
|
@ -89,17 +89,24 @@ class PaymentEmailEngine extends BaseEmailEngine
|
||||
->setViewText('');
|
||||
|
||||
if ($this->client->getSetting('pdf_email_attachment') !== false && $this->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
|
||||
|
||||
$this->payment->invoices->each(function ($invoice) {
|
||||
// if (Ninja::isHosted()) {
|
||||
// $this->setAttachments([$invoice->pdf_file_path($invoice->invitations->first(), 'url', true)]);
|
||||
// } else {
|
||||
// $this->setAttachments([$invoice->pdf_file_path($invoice->invitations->first())]);
|
||||
// }
|
||||
$pdf = ((new CreateRawPdf($invoice->invitations->first(), $invoice->company->db))->handle());
|
||||
|
||||
$this->setAttachments([['file' => base64_encode($pdf), 'name' => $invoice->numberFormatter().'.pdf']]);
|
||||
$pdf = ((new CreateRawPdf($invoice->invitations->first(), $invoice->company->db))->handle());
|
||||
|
||||
$this->setAttachments([['file' => base64_encode($pdf), 'name' => $invoice->numberFormatter().'.pdf']]);
|
||||
|
||||
//attach invoice documents also to payments
|
||||
if ($this->client->getSetting('document_email_attachment') !== false)
|
||||
{
|
||||
foreach ($invoice->documents as $document) {
|
||||
$this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => NULL, ]]);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -117,11 +117,6 @@ class QuoteEmailEngine extends BaseEmailEngine
|
||||
->setTextBody($text_body);
|
||||
|
||||
if ($this->client->getSetting('pdf_email_attachment') !== false && $this->quote->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
|
||||
// if (Ninja::isHosted()) {
|
||||
// $this->setAttachments([$this->quote->pdf_file_path($this->invitation, 'url', true)]);
|
||||
// } else {
|
||||
// $this->setAttachments([$this->quote->pdf_file_path($this->invitation)]);
|
||||
// }
|
||||
|
||||
$pdf = ((new CreateRawPdf($this->invitation, $this->invitation->company->db))->handle());
|
||||
|
||||
@ -133,11 +128,11 @@ class QuoteEmailEngine extends BaseEmailEngine
|
||||
|
||||
// Storage::url
|
||||
foreach ($this->quote->documents as $document) {
|
||||
$this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => NULL, ]]);
|
||||
$this->setAttachments([['file' => base64_encode($document->getFile()), 'path' => $document->filePath(), 'name' => $document->name, 'mime' => NULL, ]]);
|
||||
}
|
||||
|
||||
foreach ($this->quote->company->documents as $document) {
|
||||
$this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => NULL, ]]);
|
||||
$this->setAttachments([['file' => base64_encode($document->getFile()), 'path' => $document->filePath(), 'name' => $document->name, 'mime' => NULL, ]]);
|
||||
}
|
||||
}
|
||||
|
||||
|
63
app/Mail/Subscription/OtpCode.php
Normal file
63
app/Mail/Subscription/OtpCode.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Mail\Subscription;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class OtpCode extends Mailable
|
||||
{
|
||||
// use Queueable, SerializesModels;
|
||||
|
||||
public $company;
|
||||
|
||||
public $contact;
|
||||
|
||||
public $code;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($company, $contact, $code)
|
||||
{
|
||||
$this->company = $company;
|
||||
$this->contact = $contact;
|
||||
$this->code = $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
App::setLocale($this->company->locale());
|
||||
|
||||
return $this->from(config('mail.from.address'), config('mail.from.name'))
|
||||
->subject(ctrans('texts.otp_code_subject'))
|
||||
->text('email.admin.generic_text')
|
||||
->view('email.admin.generic')
|
||||
->with([
|
||||
'settings' => $this->company->settings,
|
||||
'logo' => $this->company->present()->logo(),
|
||||
'title' => ctrans('texts.otp_code_subject'),
|
||||
'content' => ctrans('texts.otp_code_body', ['code' => $this->code]),
|
||||
'whitelabel' => $this->company->account->isPaid(),
|
||||
]);
|
||||
}
|
||||
}
|
@ -118,14 +118,11 @@ class TemplateEmail extends Mailable
|
||||
'logo' => $this->company->present()->logo($settings),
|
||||
]);
|
||||
|
||||
|
||||
//22-10-2022 - Performance - To improve the performance/reliability of sending emails, attaching as Data is much better, stubs in place
|
||||
foreach ($this->build_email->getAttachments() as $file) {
|
||||
if(array_key_exists('file', $file))
|
||||
$this->attachData(base64_decode($file['file']), $file['name']);
|
||||
else
|
||||
$this->attach($file['path'], ['as' => $file['name'], 'mime' => null]);
|
||||
|
||||
}
|
||||
|
||||
if ($this->invitation && $this->invitation->invoice && $settings->ubl_email_attachment && $this->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
|
||||
|
@ -193,12 +193,9 @@ class BaseModel extends Model
|
||||
$number = strlen($this->number) >= 1 ? $this->number : class_basename($this) . "_" . Str::random(5);
|
||||
|
||||
$formatted_number = mb_ereg_replace("([^\w\s\d\-_~,;\[\]\(\).])", '', $number);
|
||||
// Remove any runs of periods (thanks falstro!)
|
||||
$formatted_number = mb_ereg_replace("([\.]{2,})", '', $formatted_number);
|
||||
|
||||
// $formatted_number = str_replace(" ", "_", $formatted_number);
|
||||
|
||||
//11-01-2021 fixes for multiple spaces
|
||||
$formatted_number = mb_ereg_replace("([\.]{2,})", '', $formatted_number);
|
||||
|
||||
$formatted_number = preg_replace('/\s+/', '_', $formatted_number);
|
||||
|
||||
return $formatted_number;
|
||||
|
@ -97,6 +97,8 @@ class Company extends BaseModel
|
||||
'first_month_of_year',
|
||||
'slack_webhook_url',
|
||||
'google_analytics_key',
|
||||
'matomo_url',
|
||||
'matomo_id',
|
||||
'client_can_register',
|
||||
'enable_shop_api',
|
||||
'invoice_task_timelog',
|
||||
@ -125,7 +127,8 @@ class Company extends BaseModel
|
||||
'invoice_task_project',
|
||||
'report_include_deleted',
|
||||
'invoice_task_lock',
|
||||
'use_vendor_currency',
|
||||
'convert_payment_currency',
|
||||
'convert_expense_currency',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
@ -440,7 +443,7 @@ class Company extends BaseModel
|
||||
return $item->id == $this->settings->language_id;
|
||||
})->first();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function getLocale()
|
||||
|
@ -35,4 +35,8 @@ class Country extends StaticModel
|
||||
{
|
||||
return trans('texts.country_'.$this->name);
|
||||
}
|
||||
public function getID() :string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
|
@ -11,13 +11,15 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Filterable;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class ExpenseCategory extends BaseModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
use Filterable;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'color',
|
||||
|
@ -115,6 +115,7 @@ class Gateway extends StaticModel
|
||||
GatewayType::BECS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
|
||||
GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
|
||||
GatewayType::ACSS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
|
||||
GatewayType::KLARNA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::FPX => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], ];
|
||||
case 39:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']]]; //Checkout
|
||||
@ -134,13 +135,14 @@ class Gateway extends StaticModel
|
||||
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']],
|
||||
];
|
||||
break;
|
||||
case 56:
|
||||
case 56: //Stripe
|
||||
return [
|
||||
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => ['payment_intent.succeeded']],
|
||||
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
|
||||
GatewayType::ALIPAY => ['refund' => false, 'token_billing' => false],
|
||||
GatewayType::APPLE_PAY => ['refund' => false, 'token_billing' => false],
|
||||
GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']], //Stripe
|
||||
GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::KLARNA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::SEPA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::PRZELEWY24 => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::GIROPAY => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
|
@ -11,8 +11,6 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use function Symfony\Component\String\s;
|
||||
|
||||
class GatewayType extends StaticModel
|
||||
{
|
||||
public $timestamps = false;
|
||||
@ -61,6 +59,8 @@ class GatewayType extends StaticModel
|
||||
|
||||
const FPX = 22;
|
||||
|
||||
const KLARNA = 23;
|
||||
|
||||
public function gateway()
|
||||
{
|
||||
return $this->belongsTo(Gateway::class);
|
||||
@ -116,6 +116,8 @@ class GatewayType extends StaticModel
|
||||
return ctrans('texts.payment_type_instant_bank_pay');
|
||||
case self::FPX:
|
||||
return ctrans('texts.fpx');
|
||||
case self::KLARNA:
|
||||
return ctrans('texts.klarna');
|
||||
default:
|
||||
return ' ';
|
||||
break;
|
||||
|
@ -55,6 +55,8 @@ class PaymentType extends StaticModel
|
||||
const ACSS = 44;
|
||||
const INSTANT_BANK_PAY = 45;
|
||||
const FPX = 46;
|
||||
const KLARNA = 47;
|
||||
const Interac_E_Transfer = 48;
|
||||
|
||||
public static function parseCardType($cardName)
|
||||
{
|
||||
|
@ -239,7 +239,8 @@ class PurchaseOrder extends BaseModel
|
||||
return $this->belongsTo(Invoice::class);
|
||||
}
|
||||
|
||||
public function service()
|
||||
/** @return PurchaseOrderService */
|
||||
public function service() :PurchaseOrderService
|
||||
{
|
||||
return new PurchaseOrderService($this);
|
||||
}
|
||||
|
@ -212,7 +212,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
/**
|
||||
* Returns the current company.
|
||||
*
|
||||
* @return Collection
|
||||
* @return App\Models\Company $company
|
||||
*/
|
||||
public function company()
|
||||
{
|
||||
|
@ -52,6 +52,8 @@ class CreditCard
|
||||
{
|
||||
$amount = $this->mollie->convertToMollieAmount((float) $this->mollie->payment_hash->data->amount_with_fee);
|
||||
|
||||
$description = sprintf('%s: %s', ctrans('texts.invoices'), \implode(', ', collect($this->mollie->payment_hash->invoices())->pluck('invoice_number')->toArray()));
|
||||
|
||||
$this->mollie->payment_hash
|
||||
->withData('gateway_type_id', GatewayType::CREDIT_CARD)
|
||||
->withData('client_id', $this->mollie->client->id);
|
||||
@ -68,8 +70,9 @@ class CreditCard
|
||||
'mandateId' => $request->token,
|
||||
'customerId' => $cgt->gateway_customer_reference,
|
||||
'sequenceType' => 'recurring',
|
||||
'description' => \sprintf('Hash: %s', $this->mollie->payment_hash->hash),
|
||||
'description' => $description,
|
||||
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
|
||||
'idempotencyKey' => uniqid("st",true),
|
||||
'metadata' => [
|
||||
'client_id' => $this->mollie->client->hashed_id,
|
||||
'hash' => $this->mollie->payment_hash->hash,
|
||||
@ -90,7 +93,11 @@ class CreditCard
|
||||
if ($payment->status === 'open') {
|
||||
$this->mollie->payment_hash->withData('payment_id', $payment->id);
|
||||
|
||||
return redirect($payment->getCheckoutUrl());
|
||||
if(!$payment->getCheckoutUrl())
|
||||
return render('gateways.mollie.mollie_placeholder');
|
||||
else
|
||||
return redirect()->away($payment->getCheckoutUrl());
|
||||
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return $this->processUnsuccessfulPayment($e);
|
||||
@ -103,7 +110,7 @@ class CreditCard
|
||||
'currency' => $this->mollie->client->currency()->code,
|
||||
'value' => $amount,
|
||||
],
|
||||
'description' => \sprintf('Hash: %s', $this->mollie->payment_hash->hash),
|
||||
'description' => $description,
|
||||
'redirectUrl' => route('mollie.3ds_redirect', [
|
||||
'company_key' => $this->mollie->client->company->company_key,
|
||||
'company_gateway_id' => $this->mollie->company_gateway->hashed_id,
|
||||
@ -150,7 +157,13 @@ class CreditCard
|
||||
if ($payment->status === 'open') {
|
||||
$this->mollie->payment_hash->withData('payment_id', $payment->id);
|
||||
|
||||
return redirect($payment->getCheckoutUrl());
|
||||
nlog("Mollie");
|
||||
nlog($payment);
|
||||
|
||||
if(!$payment->getCheckoutUrl())
|
||||
return render('gateways.mollie.mollie_placeholder');
|
||||
else
|
||||
return redirect()->away($payment->getCheckoutUrl());
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->processUnsuccessfulPayment($e);
|
||||
|
@ -351,7 +351,7 @@ class MolliePaymentDriver extends BaseDriver
|
||||
|
||||
if ($record) {
|
||||
if (in_array($payment->status, ['canceled', 'expired', 'failed'])) {
|
||||
$record->service()->deletePayment();
|
||||
$record->service()->deletePayment(false); //sometimes mollie does not return but we still decrement the paid to date, this is incorrect.
|
||||
}
|
||||
|
||||
$record->status_id = $codes[$payment->status];
|
||||
|
@ -69,7 +69,7 @@ class ACH
|
||||
$customer = $this->stripe->findOrCreateCustomer();
|
||||
|
||||
try {
|
||||
$source = Customer::createSource($customer->id, ['source' => $stripe_response->token->id], $this->stripe->stripe_connect_auth);
|
||||
$source = Customer::createSource($customer->id, ['source' => $stripe_response->token->id], array_merge($this->stripe->stripe_connect_auth, ['idempotency_key' => uniqid("st",true)]));
|
||||
} catch (InvalidRequestException $e) {
|
||||
throw new PaymentFailed($e->getMessage(), $e->getCode());
|
||||
}
|
||||
@ -173,9 +173,9 @@ class ACH
|
||||
->first();
|
||||
|
||||
if ($invoice) {
|
||||
$description = ctrans('texts.stripe_paymenttext', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
} else {
|
||||
$description = ctrans('texts.stripe_paymenttext_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
}
|
||||
|
||||
|
||||
@ -211,9 +211,9 @@ class ACH
|
||||
->first();
|
||||
|
||||
if ($invoice) {
|
||||
$description = ctrans('texts.stripe_paymenttext', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
} else {
|
||||
$description = ctrans('texts.stripe_paymenttext_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
}
|
||||
|
||||
if (substr($cgt->token, 0, 2) === 'pm') {
|
||||
@ -455,9 +455,9 @@ class ACH
|
||||
->first();
|
||||
|
||||
if ($invoice) {
|
||||
$description = ctrans('texts.stripe_paymenttext', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
} else {
|
||||
$description = ctrans('texts.stripe_paymenttext_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
}
|
||||
|
||||
if (substr($source->token, 0, 2) === 'pm') {
|
||||
|
@ -55,7 +55,7 @@ class ACSS
|
||||
$customer = $this->stripe->findOrCreateCustomer();
|
||||
|
||||
try {
|
||||
$source = Customer::createSource($customer->id, ['source' => $stripe_response->token->id], $this->stripe->stripe_connect_auth);
|
||||
$source = Customer::createSource($customer->id, ['source' => $stripe_response->token->id], array_merge($this->stripe->stripe_connect_auth, ['idempotency_key' => uniqid("st",true)]));
|
||||
} catch (InvalidRequestException $e) {
|
||||
throw new PaymentFailed($e->getMessage(), $e->getCode());
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ class BECS
|
||||
'payment_hash' => $this->stripe->payment_hash->hash,
|
||||
'gateway_type_id' => GatewayType::BECS,
|
||||
],
|
||||
], $this->stripe->stripe_connect_auth);
|
||||
], array_merge($this->stripe->stripe_connect_auth, ['idempotency_key' => uniqid("st",true)]));
|
||||
|
||||
$data['pi_client_secret'] = $intent->client_secret;
|
||||
|
||||
|
@ -57,7 +57,7 @@ class Bancontact
|
||||
'gateway_type_id' => GatewayType::BANCONTACT,
|
||||
],
|
||||
|
||||
], $this->stripe->stripe_connect_auth);
|
||||
], array_merge($this->stripe->stripe_connect_auth, ['idempotency_key' => uniqid("st",true)]));
|
||||
|
||||
$data['pi_client_secret'] = $intent->client_secret;
|
||||
|
||||
|
@ -229,6 +229,6 @@ class BrowserPay implements MethodInterface
|
||||
$domain = config('ninja.app_url');
|
||||
}
|
||||
|
||||
return str_replace('https://', '', $domain);
|
||||
return str_replace(['https://', '/public'], '', $domain);
|
||||
}
|
||||
}
|
||||
|
@ -63,9 +63,9 @@ class Charge
|
||||
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->withTrashed()->first();
|
||||
|
||||
if ($invoice) {
|
||||
$description = ctrans('texts.stripe_paymenttext', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
} else {
|
||||
$description = ctrans('texts.stripe_paymenttext_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
}
|
||||
|
||||
$this->stripe->init();
|
||||
@ -79,7 +79,6 @@ class Charge
|
||||
'payment_method' => $cgt->token,
|
||||
'customer' => $cgt->gateway_customer_reference,
|
||||
'confirm' => true,
|
||||
// 'off_session' => true,
|
||||
'description' => $description,
|
||||
'metadata' => [
|
||||
'payment_hash' => $payment_hash->hash,
|
||||
@ -96,7 +95,7 @@ class Charge
|
||||
$data['off_session'] = true;
|
||||
}
|
||||
|
||||
$response = $this->stripe->createPaymentIntent($data, $this->stripe->stripe_connect_auth);
|
||||
$response = $this->stripe->createPaymentIntent($data, array_merge($this->stripe->stripe_connect_auth, ['idempotency_key' => uniqid("st",true)]));
|
||||
|
||||
SystemLogger::dispatch($response, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_STRIPE, $this->stripe->client, $this->stripe->client->company);
|
||||
} catch (\Exception $e) {
|
||||
|
@ -63,7 +63,7 @@ class CreditCard
|
||||
|
||||
// $description = $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')) . " for client {$this->stripe->client->present()->name()}";
|
||||
$invoice_numbers = collect($data['invoices'])->pluck('invoice_number')->implode(',');
|
||||
$description = ctrans('texts.stripe_paymenttext', ['invoicenumber' => $invoice_numbers, 'amount' => Number::formatMoney($data['total']['amount_with_fee'], $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice_numbers, 'amount' => Number::formatMoney($data['total']['amount_with_fee'], $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
|
||||
$payment_intent_data = [
|
||||
'amount' => $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
|
||||
@ -112,7 +112,7 @@ class CreditCard
|
||||
$state['store_card'] = false;
|
||||
}
|
||||
|
||||
$state['payment_intent'] = PaymentIntent::retrieve($state['server_response']->id, $this->stripe->stripe_connect_auth);
|
||||
$state['payment_intent'] = PaymentIntent::retrieve($state['server_response']->id, array_merge($this->stripe->stripe_connect_auth, ['idempotency_key' => uniqid("st",true)]));
|
||||
$state['customer'] = $state['payment_intent']->customer;
|
||||
|
||||
$this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, $state);
|
||||
|
@ -56,7 +56,7 @@ class EPS
|
||||
'payment_hash' => $this->stripe->payment_hash->hash,
|
||||
'gateway_type_id' => GatewayType::EPS,
|
||||
],
|
||||
], $this->stripe->stripe_connect_auth);
|
||||
], array_merge($this->stripe->stripe_connect_auth, ['idempotency_key' => uniqid("st",true)]));
|
||||
|
||||
$data['pi_client_secret'] = $intent->client_secret;
|
||||
|
||||
|
@ -57,7 +57,7 @@ class FPX
|
||||
'payment_hash' => $this->stripe->payment_hash->hash,
|
||||
'gateway_type_id' => GatewayType::FPX,
|
||||
],
|
||||
], $this->stripe->stripe_connect_auth);
|
||||
], array_merge($this->stripe->stripe_connect_auth, ['idempotency_key' => uniqid("st",true)]));
|
||||
|
||||
$data['pi_client_secret'] = $intent->client_secret;
|
||||
|
||||
|
@ -56,7 +56,7 @@ class GIROPAY
|
||||
'payment_hash' => $this->stripe->payment_hash->hash,
|
||||
'gateway_type_id' => GatewayType::GIROPAY,
|
||||
],
|
||||
], $this->stripe->stripe_connect_auth);
|
||||
], array_merge($this->stripe->stripe_connect_auth, ['idempotency_key' => uniqid("st",true)]));
|
||||
|
||||
$data['pi_client_secret'] = $intent->client_secret;
|
||||
|
||||
|
@ -154,31 +154,6 @@ class PaymentIntentWebhook implements ShouldQueue
|
||||
'card_details' => isset($charge['payment_method_details']['card']['brand']) ? $charge['payment_method_details']['card']['brand'] : PaymentType::CREDIT_CARD_OTHER
|
||||
];
|
||||
|
||||
if(isset($pi['allowed_source_types']) && in_array('card', $pi['allowed_source_types']))
|
||||
{
|
||||
|
||||
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
|
||||
$client = $invoice->client;
|
||||
|
||||
$this->updateCreditCardPayment($payment_hash, $client, $meta);
|
||||
}
|
||||
elseif(isset($pi['payment_method_types']) && in_array('card', $pi['payment_method_types']))
|
||||
{
|
||||
|
||||
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
|
||||
$client = $invoice->client;
|
||||
|
||||
$this->updateCreditCardPayment($payment_hash, $client, $meta);
|
||||
}
|
||||
elseif(isset($pi['payment_method_types']) && in_array('us_bank_account', $pi['payment_method_types']))
|
||||
{
|
||||
|
||||
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
|
||||
$client = $invoice->client;
|
||||
|
||||
$this->updateAchPayment($payment_hash, $client, $meta);
|
||||
}
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => $this->stripe_request, 'data' => []],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
@ -188,6 +163,39 @@ class PaymentIntentWebhook implements ShouldQueue
|
||||
$company,
|
||||
);
|
||||
|
||||
if(isset($pi['allowed_source_types']) && in_array('card', $pi['allowed_source_types']))
|
||||
{
|
||||
|
||||
$invoice = Invoice::with('client')->withTrashed()->find($payment_hash->fee_invoice_id);
|
||||
$client = $invoice->client;
|
||||
|
||||
if($invoice->is_deleted)
|
||||
return;
|
||||
|
||||
$this->updateCreditCardPayment($payment_hash, $client, $meta);
|
||||
}
|
||||
elseif(isset($pi['payment_method_types']) && in_array('card', $pi['payment_method_types']))
|
||||
{
|
||||
|
||||
$invoice = Invoice::with('client')->withTrashed()->find($payment_hash->fee_invoice_id);
|
||||
$client = $invoice->client;
|
||||
|
||||
if($invoice->is_deleted)
|
||||
return;
|
||||
|
||||
$this->updateCreditCardPayment($payment_hash, $client, $meta);
|
||||
}
|
||||
elseif(isset($pi['payment_method_types']) && in_array('us_bank_account', $pi['payment_method_types']))
|
||||
{
|
||||
|
||||
$invoice = Invoice::with('client')->withTrashed()->find($payment_hash->fee_invoice_id);
|
||||
$client = $invoice->client;
|
||||
|
||||
if($invoice->is_deleted)
|
||||
return;
|
||||
|
||||
$this->updateAchPayment($payment_hash, $client, $meta);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
156
app/PaymentDrivers/Stripe/Klarna.php
Normal file
156
app/PaymentDrivers/Stripe/Klarna.php
Normal file
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers\Stripe;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\StripePaymentDriver;
|
||||
use App\Utils\Number;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class Klarna
|
||||
{
|
||||
/** @var StripePaymentDriver */
|
||||
public StripePaymentDriver $stripe;
|
||||
|
||||
public function __construct(StripePaymentDriver $stripe)
|
||||
{
|
||||
$this->stripe = $stripe;
|
||||
}
|
||||
|
||||
public function authorizeView($data)
|
||||
{
|
||||
return render('gateways.stripe.klarna.authorize', $data);
|
||||
}
|
||||
|
||||
public function paymentView(array $data)
|
||||
{
|
||||
$this->stripe->init();
|
||||
|
||||
$data['gateway'] = $this->stripe;
|
||||
$data['return_url'] = $this->buildReturnUrl();
|
||||
$data['stripe_amount'] = $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency());
|
||||
$data['client'] = $this->stripe->client;
|
||||
$data['customer'] = $this->stripe->findOrCreateCustomer()->id;
|
||||
$data['country'] = $this->stripe->client->country->iso_3166_2;
|
||||
|
||||
$amount = $data['total']['amount_with_fee'];
|
||||
|
||||
$invoice_numbers = collect($data['invoices'])->pluck('invoice_number');
|
||||
|
||||
if ($invoice_numbers->count() > 0) {
|
||||
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice_numbers->implode(', '), 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
} else {
|
||||
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
}
|
||||
|
||||
$intent = \Stripe\PaymentIntent::create([
|
||||
'amount' => $data['stripe_amount'],
|
||||
'currency' => $this->stripe->client->getCurrencyCode(),
|
||||
'payment_method_types' => ['klarna'],
|
||||
'customer' => $this->stripe->findOrCreateCustomer(),
|
||||
'description' => $description,
|
||||
'metadata' => [
|
||||
'payment_hash' => $this->stripe->payment_hash->hash,
|
||||
'gateway_type_id' => GatewayType::KLARNA,
|
||||
],
|
||||
], array_merge($this->stripe->stripe_connect_auth, ['idempotency_key' => uniqid("st",true)]));
|
||||
|
||||
$data['pi_client_secret'] = $intent->client_secret;
|
||||
|
||||
$this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, ['stripe_amount' => $data['stripe_amount']]);
|
||||
$this->stripe->payment_hash->save();
|
||||
|
||||
return render('gateways.stripe.klarna.pay', $data);
|
||||
}
|
||||
|
||||
private function buildReturnUrl(): string
|
||||
{
|
||||
return route('client.payments.response', [
|
||||
'company_gateway_id' => $this->stripe->company_gateway->id,
|
||||
'payment_hash' => $this->stripe->payment_hash->hash,
|
||||
'payment_method_id' => GatewayType::KLARNA,
|
||||
]);
|
||||
}
|
||||
|
||||
public function paymentResponse($request)
|
||||
{
|
||||
$this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, $request->all());
|
||||
$this->stripe->payment_hash->save();
|
||||
|
||||
if (in_array($request->redirect_status, ['succeeded','pending'])) {
|
||||
return $this->processSuccessfulPayment($request->payment_intent);
|
||||
}
|
||||
|
||||
return $this->processUnsuccessfulPayment();
|
||||
}
|
||||
|
||||
public function processSuccessfulPayment(string $payment_intent)
|
||||
{
|
||||
|
||||
$this->stripe->init();
|
||||
|
||||
//catch duplicate submissions.
|
||||
if (Payment::where('transaction_reference', $payment_intent)->exists()) {
|
||||
return redirect()->route('client.payments.index');
|
||||
}
|
||||
|
||||
$data = [
|
||||
'payment_method' => $payment_intent,
|
||||
'payment_type' => PaymentType::KLARNA,
|
||||
'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
|
||||
'transaction_reference' => $payment_intent,
|
||||
'gateway_type_id' => GatewayType::KLARNA,
|
||||
];
|
||||
|
||||
$this->stripe->createPayment($data, Payment::STATUS_PENDING);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => $this->stripe->payment_hash->data, 'data' => $data],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_STRIPE,
|
||||
$this->stripe->client,
|
||||
$this->stripe->client->company,
|
||||
);
|
||||
|
||||
return redirect()->route('client.payments.index');
|
||||
}
|
||||
|
||||
public function processUnsuccessfulPayment()
|
||||
{
|
||||
$server_response = $this->stripe->payment_hash->data;
|
||||
|
||||
$this->stripe->sendFailureMail($server_response);
|
||||
|
||||
$message = [
|
||||
'server_response' => $server_response,
|
||||
'data' => $this->stripe->payment_hash->data,
|
||||
];
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$message,
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_STRIPE,
|
||||
$this->stripe->client,
|
||||
$this->stripe->client->company,
|
||||
);
|
||||
|
||||
throw new PaymentFailed(ctrans('texts.gateway_error'), 500);
|
||||
}
|
||||
}
|
@ -56,7 +56,7 @@ class PRZELEWY24
|
||||
'payment_hash' => $this->stripe->payment_hash->hash,
|
||||
'gateway_type_id' => GatewayType::PRZELEWY24,
|
||||
],
|
||||
], $this->stripe->stripe_connect_auth);
|
||||
], array_merge($this->stripe->stripe_connect_auth, ['idempotency_key' => uniqid("st",true)]));
|
||||
|
||||
$data['pi_client_secret'] = $intent->client_secret;
|
||||
|
||||
|
@ -67,7 +67,7 @@ class SEPA
|
||||
],
|
||||
];
|
||||
|
||||
$intent = \Stripe\PaymentIntent::create($intent_data, $this->stripe->stripe_connect_auth);
|
||||
$intent = \Stripe\PaymentIntent::create($intent_data, array_merge($this->stripe->stripe_connect_auth, ['idempotency_key' => uniqid("st",true)]));
|
||||
|
||||
$data['pi_client_secret'] = $intent->client_secret;
|
||||
|
||||
|
@ -56,7 +56,7 @@ class SOFORT
|
||||
'payment_hash' => $this->stripe->payment_hash->hash,
|
||||
'gateway_type_id' => GatewayType::SOFORT,
|
||||
],
|
||||
], $this->stripe->stripe_connect_auth);
|
||||
], array_merge($this->stripe->stripe_connect_auth, ['idempotency_key' => uniqid("st",true)]));
|
||||
|
||||
$data['pi_client_secret'] = $intent->client_secret;
|
||||
|
||||
|
@ -74,7 +74,7 @@ class UpdatePaymentMethods
|
||||
{
|
||||
$sources = $customer->sources;
|
||||
|
||||
if(!$customer || !property_exists($sources, 'data'))
|
||||
if(!$customer || is_null($sources) || !property_exists($sources, 'data'))
|
||||
return;
|
||||
|
||||
foreach ($sources->data as $method) {
|
||||
|
@ -56,7 +56,7 @@ class iDeal
|
||||
'payment_hash' => $this->stripe->payment_hash->hash,
|
||||
'gateway_type_id' => GatewayType::IDEAL,
|
||||
],
|
||||
], $this->stripe->stripe_connect_auth);
|
||||
], array_merge($this->stripe->stripe_connect_auth, ['idempotency_key' => uniqid("st",true)]));
|
||||
|
||||
$data['pi_client_secret'] = $intent->client_secret;
|
||||
|
||||
|
@ -20,6 +20,7 @@ use App\Http\Requests\Request;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Country;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
@ -37,6 +38,7 @@ use App\PaymentDrivers\Stripe\CreditCard;
|
||||
use App\PaymentDrivers\Stripe\EPS;
|
||||
use App\PaymentDrivers\Stripe\FPX;
|
||||
use App\PaymentDrivers\Stripe\GIROPAY;
|
||||
use App\PaymentDrivers\Stripe\Klarna;
|
||||
use App\PaymentDrivers\Stripe\iDeal;
|
||||
use App\PaymentDrivers\Stripe\ImportCustomers;
|
||||
use App\PaymentDrivers\Stripe\Jobs\PaymentIntentFailureWebhook;
|
||||
@ -97,6 +99,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
GatewayType::BECS => BECS::class,
|
||||
GatewayType::ACSS => ACSS::class,
|
||||
GatewayType::FPX => FPX::class,
|
||||
GatewayType::KLARNA => Klarna::class,
|
||||
];
|
||||
|
||||
const SYSTEM_LOG_TYPE = SystemLog::TYPE_STRIPE;
|
||||
@ -116,13 +119,13 @@ class StripePaymentDriver extends BaseDriver
|
||||
throw new StripeConnectFailure('Stripe Connect has not been configured');
|
||||
}
|
||||
} else {
|
||||
|
||||
|
||||
$this->stripe = new StripeClient(
|
||||
$this->company_gateway->getConfigField('apiKey')
|
||||
);
|
||||
|
||||
Stripe::setApiKey($this->company_gateway->getConfigField('apiKey'));
|
||||
// Stripe::setApiVersion('2022-11-15');
|
||||
Stripe::setApiVersion('2022-11-15');
|
||||
|
||||
}
|
||||
|
||||
@ -236,7 +239,21 @@ class StripePaymentDriver extends BaseDriver
|
||||
&& in_array($this->client->country->iso_3166_3, ['CAN', 'USA'])) {
|
||||
$types[] = GatewayType::ACSS;
|
||||
}
|
||||
|
||||
if ($this->client
|
||||
&& $this->client->currency()
|
||||
&& in_array($this->client->currency()->code, ['EUR', 'DKK', 'GBP', 'NOK', 'SEK', 'AUD', 'NZD', 'CAD', 'PLN', 'CHF'])
|
||||
&& isset($this->client->country)
|
||||
&& in_array($this->client->country->iso_3166_3, ['AUT','BEL','DNK','FIN','FRA','DEU','IRL','ITA','NLD','NOR','ESP','SWE','GBR'])) {
|
||||
$types[] = GatewayType::KLARNA;
|
||||
}
|
||||
if ($this->client
|
||||
&& $this->client->currency()
|
||||
&& in_array($this->client->currency()->code, ['EUR', 'DKK', 'GBP', 'NOK', 'SEK', 'AUD', 'NZD', 'CAD', 'PLN', 'CHF', 'USD'])
|
||||
&& isset($this->client->country)
|
||||
&& in_array($this->client->company->country()->getID(), ['840'])
|
||||
&& in_array($this->client->country->iso_3166_3, ['AUT','BEL','DNK','FIN','FRA','DEU','IRL','ITA','NLD','NOR','ESP','SWE','GBR','USA'])) {
|
||||
$types[] = GatewayType::KLARNA;
|
||||
}
|
||||
if (
|
||||
$this->client
|
||||
&& isset($this->client->country)
|
||||
@ -274,6 +291,9 @@ class StripePaymentDriver extends BaseDriver
|
||||
case GatewayType::GIROPAY:
|
||||
return 'gateways.stripe.giropay';
|
||||
break;
|
||||
case GatewayType::KLARNA:
|
||||
return 'gateways.stripe.klarna';
|
||||
break;
|
||||
case GatewayType::IDEAL:
|
||||
return 'gateways.stripe.ideal';
|
||||
case GatewayType::EPS:
|
||||
@ -387,7 +407,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
|
||||
$meta = $this->stripe_connect_auth;
|
||||
|
||||
return PaymentIntent::create($data, $meta);
|
||||
return PaymentIntent::create($data, array_merge($meta, ['idempotency_key' => uniqid("st",true)]));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -404,7 +424,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
$params = ['usage' => 'off_session'];
|
||||
$meta = $this->stripe_connect_auth;
|
||||
|
||||
return SetupIntent::create($params, $meta);
|
||||
return SetupIntent::create($params, array_merge($meta, ['idempotency_key' => uniqid("st",true)]));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -481,7 +501,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
$data['address']['state'] = $this->client->state;
|
||||
$data['address']['country'] = $this->client->country ? $this->client->country->iso_3166_2 : '';
|
||||
|
||||
$customer = Customer::create($data, $this->stripe_connect_auth);
|
||||
$customer = Customer::create($data, array_merge($this->stripe_connect_auth, ['idempotency_key' => uniqid("st",true)]));
|
||||
|
||||
if (! $customer) {
|
||||
throw new Exception('Unable to create gateway customer');
|
||||
|
@ -292,9 +292,6 @@ class BaseRepository
|
||||
|
||||
if (($state['finished_amount'] != $state['starting_amount']) && ($model->status_id != Invoice::STATUS_DRAFT)) {
|
||||
|
||||
//14-09-2022 log when we make changes to the invoice balance.
|
||||
nlog("Adjustment - {$model->number} - " .$state['finished_amount']. " - " . $state['starting_amount']);
|
||||
|
||||
$model->service()->updateStatus()->save();
|
||||
$model->client->service()->updateBalance(($state['finished_amount'] - $state['starting_amount']))->save();
|
||||
$model->ledger()->updateInvoiceBalance(($state['finished_amount'] - $state['starting_amount']), "Update adjustment for invoice {$model->number}");
|
||||
|
@ -16,6 +16,7 @@ use App\Factory\ExpenseFactory;
|
||||
use App\Libraries\Currency\Conversion\CurrencyApi;
|
||||
use App\Models\Expense;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use Carbon\Exceptions\InvalidFormatException;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Database\QueryException;
|
||||
|
||||
@ -31,12 +32,12 @@ class ExpenseRepository extends BaseRepository
|
||||
/**
|
||||
* Saves the expense and its contacts.
|
||||
*
|
||||
* @param array $data The data
|
||||
* @param \App\Models\Expense $expense The expense
|
||||
* @param array $data The data
|
||||
* @param \App\Models\Expense $expense The expense
|
||||
*
|
||||
* @return \App\Models\Expense|null expense Object
|
||||
* @return \App\Models\Expense
|
||||
*/
|
||||
public function save(array $data, Expense $expense): ?Expense
|
||||
public function save(array $data, Expense $expense): Expense
|
||||
{
|
||||
$expense->fill($data);
|
||||
|
||||
@ -71,6 +72,12 @@ class ExpenseRepository extends BaseRepository
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $data
|
||||
* @param mixed $expense
|
||||
* @return Expense
|
||||
* @throws InvalidFormatException
|
||||
*/
|
||||
public function processExchangeRates($data, $expense): Expense
|
||||
{
|
||||
if (array_key_exists('exchange_rate', $data) && isset($data['exchange_rate']) && $data['exchange_rate'] != 1) {
|
||||
|
@ -118,6 +118,36 @@ class SubscriptionRepository extends BaseRepository
|
||||
return $line_items;
|
||||
}
|
||||
|
||||
public function generateBundleLineItems($bundle, $is_recurring = false, $is_credit = false)
|
||||
{
|
||||
$multiplier = $is_credit ? -1 : 1;
|
||||
|
||||
$line_items = [];
|
||||
|
||||
$line_items = collect($bundle)->filter(function ($item){
|
||||
|
||||
return $item->is_recurring;
|
||||
|
||||
})->map(function ($item){
|
||||
|
||||
$line_item = new InvoiceItem;
|
||||
$line_item->product_key = $item->product_key;
|
||||
$line_item->quantity = (float)$item->qty;
|
||||
$line_item->cost = (float)$item->unit_cost;
|
||||
$line_item->notes = $item->description;
|
||||
|
||||
return $line_item;
|
||||
|
||||
|
||||
})->toArray();
|
||||
|
||||
|
||||
$line_items = $this->cleanItems($line_items);
|
||||
|
||||
return $line_items;
|
||||
}
|
||||
|
||||
|
||||
private function makeLineItem($product, $multiplier)
|
||||
{
|
||||
$item = new InvoiceItem;
|
||||
|
@ -59,8 +59,10 @@ class UserRepository extends BaseRepository
|
||||
// if(array_key_exists('oauth_provider_id', $details))
|
||||
// unset($details['oauth_provider_id']);
|
||||
|
||||
if (request()->has('validated_phone'))
|
||||
if (request()->has('validated_phone')){
|
||||
$details['phone'] = request()->input('validated_phone');
|
||||
$user->verified_phone_number = false;
|
||||
}
|
||||
|
||||
$user->fill($details);
|
||||
|
||||
|
@ -33,7 +33,14 @@ class BankMatchingService implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(protected int $company_id, protected string $db){
|
||||
protected $company_id;
|
||||
|
||||
protected $db;
|
||||
|
||||
protected $middleware_key;
|
||||
|
||||
public function __construct($company_id, $db)
|
||||
{
|
||||
$this->company_id = $company_id;
|
||||
$this->db = $db;
|
||||
$this->middleware_key = "bank_match_rate:{$this->company_id}";
|
||||
|
@ -44,8 +44,6 @@ class ProcessBankRules extends AbstractService
|
||||
private function matchCredit()
|
||||
{
|
||||
|
||||
$this->credit_rules = $this->bank_transaction->company->credit_rules();
|
||||
|
||||
$this->invoices = Invoice::where('company_id', $this->bank_transaction->company_id)
|
||||
->whereIn('status_id', [1,2,3])
|
||||
->where('is_deleted', 0)
|
||||
@ -65,6 +63,8 @@ class ProcessBankRules extends AbstractService
|
||||
return;
|
||||
}
|
||||
|
||||
$this->credit_rules = $this->bank_transaction->company->credit_rules();
|
||||
|
||||
//stub for credit rules
|
||||
foreach($this->credit_rules as $rule)
|
||||
{
|
||||
@ -81,11 +81,16 @@ class ProcessBankRules extends AbstractService
|
||||
|
||||
$this->categories = collect(Cache::get('bank_categories'));
|
||||
|
||||
|
||||
|
||||
foreach($this->debit_rules as $bank_transaction_rule)
|
||||
{
|
||||
|
||||
$matches = 0;
|
||||
|
||||
if(!is_array($bank_transaction_rule['rules']))
|
||||
continue;
|
||||
|
||||
foreach($bank_transaction_rule['rules'] as $rule)
|
||||
{
|
||||
$rule_count = count($bank_transaction_rule['rules']);
|
||||
|
@ -175,9 +175,9 @@ class InvoiceService
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function markSent()
|
||||
public function markSent($fire_event = false)
|
||||
{
|
||||
$this->invoice = (new MarkSent($this->invoice->client, $this->invoice))->run();
|
||||
$this->invoice = (new MarkSent($this->invoice->client, $this->invoice))->run($fire_event);
|
||||
|
||||
$this->setExchangeRate();
|
||||
|
||||
|
@ -30,7 +30,7 @@ class MarkSent extends AbstractService
|
||||
$this->invoice = $invoice;
|
||||
}
|
||||
|
||||
public function run()
|
||||
public function run($fire_webhook = false)
|
||||
{
|
||||
|
||||
/* Return immediately if status is not draft or invoice has been deleted */
|
||||
@ -68,6 +68,10 @@ class MarkSent extends AbstractService
|
||||
|
||||
event(new InvoiceWasUpdated($this->invoice, $this->invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
if($fire_webhook)
|
||||
event('eloquent.updated: App\Models\Invoice', $this->invoice);
|
||||
|
||||
|
||||
return $this->invoice->fresh();
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ class TriggeredActions extends AbstractService
|
||||
|
||||
if ($this->request->has('mark_sent') && $this->request->input('mark_sent') == 'true' && $this->invoice->status_id == Invoice::STATUS_DRAFT) {
|
||||
$this->invoice = $this->invoice->service()->markSent()->save(); //update notification NOT sent
|
||||
$this->updated = true;
|
||||
$this->updated = false;
|
||||
}
|
||||
|
||||
if ($this->request->has('amount_paid') && is_numeric($this->request->input('amount_paid'))) {
|
||||
|
@ -145,8 +145,6 @@ class UpdateReminder extends AbstractService
|
||||
$reminder_date = $this->addTimeInterval($this->invoice->last_sent_date, (int) $this->settings->endless_reminder_frequency_id);
|
||||
|
||||
if ($reminder_date) {
|
||||
// $reminder_date->addSeconds($offset);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) {
|
||||
$date_collection->push($reminder_date);
|
||||
}
|
||||
|
@ -17,24 +17,26 @@ use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\TransactionEvent;
|
||||
use App\Repositories\ActivityRepository;
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
|
||||
class DeletePayment
|
||||
{
|
||||
public $payment;
|
||||
private float $_paid_to_date_deleted = 0;
|
||||
|
||||
private $activity_repository;
|
||||
|
||||
public function __construct($payment)
|
||||
{
|
||||
$this->payment = $payment;
|
||||
|
||||
$this->activity_repository = new ActivityRepository();
|
||||
}
|
||||
/**
|
||||
* @param mixed $payment
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(public Payment $payment, private bool $update_client_paid_to_date) {}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
|
||||
\DB::connection(config('database.default'))->transaction(function () {
|
||||
\DB::connection(config('database.default'))->transaction(function () {
|
||||
|
||||
|
||||
if ($this->payment->is_deleted) {
|
||||
@ -46,7 +48,6 @@ class DeletePayment
|
||||
$this->setStatus(Payment::STATUS_CANCELLED) //sets status of payment
|
||||
->updateCreditables() //return the credits first
|
||||
->adjustInvoices()
|
||||
->updateClient()
|
||||
->deletePaymentables()
|
||||
->cleanupPayment()
|
||||
->save();
|
||||
@ -58,6 +59,7 @@ class DeletePayment
|
||||
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
private function cleanupPayment()
|
||||
{
|
||||
$this->payment->is_deleted = true;
|
||||
@ -66,6 +68,7 @@ class DeletePayment
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
private function deletePaymentables()
|
||||
{
|
||||
$this->payment->paymentables()->update(['deleted_at' => now()]);
|
||||
@ -73,20 +76,16 @@ class DeletePayment
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function updateClient()
|
||||
{
|
||||
//$this->payment->client->service()->updatePaidToDate(-1 * $this->payment->amount)->save();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
private function adjustInvoices()
|
||||
{
|
||||
$this->_paid_to_date_deleted = 0;
|
||||
|
||||
if ($this->payment->invoices()->exists()) {
|
||||
$this->payment->invoices()->each(function ($paymentable_invoice) {
|
||||
$net_deletable = $paymentable_invoice->pivot->amount - $paymentable_invoice->pivot->refunded;
|
||||
|
||||
$client = $this->payment->client->fresh();
|
||||
$this->_paid_to_date_deleted += $net_deletable;
|
||||
|
||||
nlog("net deletable amount - refunded = {$net_deletable}");
|
||||
|
||||
@ -105,7 +104,7 @@ class DeletePayment
|
||||
$client = $this->payment
|
||||
->client
|
||||
->service()
|
||||
->updateBalance($net_deletable)
|
||||
->updateBalanceAndPaidToDate($net_deletable, $net_deletable*-1)
|
||||
->save();
|
||||
|
||||
if ($paymentable_invoice->balance == $paymentable_invoice->amount) {
|
||||
@ -114,46 +113,30 @@ class DeletePayment
|
||||
$paymentable_invoice->service()->setStatus(Invoice::STATUS_PARTIAL)->save();
|
||||
}
|
||||
} else {
|
||||
$paymentable_invoice->restore();
|
||||
|
||||
//If the invoice is deleted we only update the meta data on the invoice
|
||||
//and reduce the clients paid to date
|
||||
$paymentable_invoice->restore();
|
||||
$paymentable_invoice->service()
|
||||
->updatePaidToDate($net_deletable * -1)
|
||||
->save();
|
||||
}
|
||||
|
||||
$transaction = [
|
||||
'invoice' => $paymentable_invoice->transaction_event(),
|
||||
'payment' => $this->payment->transaction_event(),
|
||||
'client' => $client->transaction_event(),
|
||||
'credit' => [],
|
||||
'metadata' => [],
|
||||
];
|
||||
|
||||
// TransactionLog::dispatch(TransactionEvent::PAYMENT_DELETED, $transaction, $paymentable_invoice->company->db);
|
||||
});
|
||||
}
|
||||
|
||||
$this->payment
|
||||
->client
|
||||
->service()
|
||||
->updatePaidToDate(($this->payment->amount - $this->payment->refunded) * -1)
|
||||
->save();
|
||||
|
||||
$transaction = [
|
||||
'invoice' => [],
|
||||
'payment' => [],
|
||||
'client' => $this->payment->client->transaction_event(),
|
||||
'credit' => [],
|
||||
'metadata' => [],
|
||||
];
|
||||
|
||||
// TransactionLog::dispatch(TransactionEvent::CLIENT_STATUS, $transaction, $this->payment->company->db);
|
||||
|
||||
//sometimes the payment is NOT created properly, this catches the payment and prevents the paid to date reducing inappropriately.
|
||||
if($this->update_client_paid_to_date)
|
||||
{
|
||||
$this->payment
|
||||
->client
|
||||
->service()
|
||||
->updatePaidToDate(min(0, ($this->payment->amount - $this->payment->refunded - $this->_paid_to_date_deleted) * -1))
|
||||
->save();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
private function updateCreditables()
|
||||
{
|
||||
if ($this->payment->credits()->exists()) {
|
||||
@ -183,6 +166,10 @@ class DeletePayment
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $status
|
||||
* @return $this
|
||||
*/
|
||||
private function setStatus($status)
|
||||
{
|
||||
$this->payment->status_id = Payment::STATUS_CANCELLED;
|
||||
|
@ -87,9 +87,9 @@ class PaymentService
|
||||
return ((new RefundPayment($this->payment, $data)))->run();
|
||||
}
|
||||
|
||||
public function deletePayment() :?Payment
|
||||
public function deletePayment($update_client_paid_to_date = true) :?Payment
|
||||
{
|
||||
return (new DeletePayment($this->payment))->run();
|
||||
return (new DeletePayment($this->payment, $update_client_paid_to_date))->run();
|
||||
}
|
||||
|
||||
public function updateInvoicePayment(PaymentHash $payment_hash) :?Payment
|
||||
|
@ -127,7 +127,7 @@ class RecurringService
|
||||
|
||||
if($this->recurring_entity instanceof RecurringInvoice && $this->recurring_entity->status_id == RecurringInvoice::STATUS_DRAFT){
|
||||
$this->start()->save();
|
||||
SendRecurring::dispatchSync($this->recurring_entity, $this->recurring_entity->company->db);
|
||||
(new SendRecurring($this->recurring_entity, $this->recurring_entity->company->db))->handle();
|
||||
}
|
||||
|
||||
$this->recurring_entity = $this->recurring_entity->fresh();
|
||||
|
@ -79,7 +79,11 @@ class SubscriptionService
|
||||
// if we have a recurring product - then generate a recurring invoice
|
||||
if(strlen($this->subscription->recurring_product_ids) >=1){
|
||||
|
||||
$recurring_invoice = $this->convertInvoiceToRecurring($payment_hash->payment->client_id);
|
||||
if(isset($payment_hash->data->billing_context->bundle))
|
||||
$recurring_invoice = $this->convertInvoiceToRecurringBundle($payment_hash->payment->client_id, $payment_hash->data->billing_context->bundle);
|
||||
else
|
||||
$recurring_invoice = $this->convertInvoiceToRecurring($payment_hash->payment->client_id);
|
||||
|
||||
$recurring_invoice_repo = new RecurringInvoiceRepository();
|
||||
|
||||
$recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice);
|
||||
@ -162,7 +166,11 @@ class SubscriptionService
|
||||
//create recurring invoice with start date = trial_duration + 1 day
|
||||
$recurring_invoice_repo = new RecurringInvoiceRepository();
|
||||
|
||||
$recurring_invoice = $this->convertInvoiceToRecurring($client_contact->client_id);
|
||||
if(isset($data['bundle']))
|
||||
$recurring_invoice = $this->convertInvoiceToRecurringBundle($client_contact->client_id, $data['bundle']->map(function ($bundle){ return (object) $bundle;}));
|
||||
else
|
||||
$recurring_invoice = $this->convertInvoiceToRecurring($client_contact->client_id);
|
||||
|
||||
$recurring_invoice->next_send_date = now()->addSeconds($this->subscription->trial_duration);
|
||||
$recurring_invoice->next_send_date_client = now()->addSeconds($this->subscription->trial_duration);
|
||||
$recurring_invoice->backup = 'is_trial';
|
||||
@ -177,7 +185,6 @@ class SubscriptionService
|
||||
$recurring_invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
||||
}
|
||||
|
||||
|
||||
$recurring_invoice = $recurring_invoice_repo->save($data, $recurring_invoice);
|
||||
|
||||
/* Start the recurring service */
|
||||
@ -704,6 +711,39 @@ class SubscriptionService
|
||||
|
||||
}
|
||||
|
||||
public function createInvoiceV2($bundle, $client_id, $valid_coupon = false)
|
||||
{
|
||||
|
||||
$invoice_repo = new InvoiceRepository();
|
||||
$subscription_repo = new SubscriptionRepository();
|
||||
|
||||
$invoice = InvoiceFactory::create($this->subscription->company_id, $this->subscription->user_id);
|
||||
$invoice->subscription_id = $this->subscription->id;
|
||||
$invoice->client_id = $client_id;
|
||||
|
||||
$line_items = $bundle->map(function ($item){
|
||||
|
||||
$line_item = new InvoiceItem;
|
||||
$line_item->product_key = $item['product_key'];
|
||||
$line_item->quantity = (float)$item['qty'];
|
||||
$line_item->cost = (float)$item['unit_cost'];
|
||||
$line_item->notes = $item['description'];
|
||||
|
||||
return $line_item;
|
||||
|
||||
})->toArray();
|
||||
|
||||
$invoice->line_items = $line_items;
|
||||
|
||||
if($valid_coupon){
|
||||
$invoice->discount = $this->subscription->promo_discount;
|
||||
$invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
||||
}
|
||||
|
||||
return $invoice_repo->save([], $invoice);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the first invoice when a subscription is purchased
|
||||
*
|
||||
@ -768,6 +808,41 @@ class SubscriptionService
|
||||
return $recurring_invoice;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates a recurring invoice based on
|
||||
* the specifications of the subscription USING BUNDLE
|
||||
*
|
||||
* @param int $client_id The Client Id
|
||||
* @return RecurringInvoice
|
||||
*/
|
||||
public function convertInvoiceToRecurringBundle($client_id, $bundle) :RecurringInvoice
|
||||
{
|
||||
MultiDB::setDb($this->subscription->company->db);
|
||||
|
||||
$client = Client::withTrashed()->find($client_id);
|
||||
|
||||
$subscription_repo = new SubscriptionRepository();
|
||||
|
||||
$recurring_invoice = RecurringInvoiceFactory::create($this->subscription->company_id, $this->subscription->user_id);
|
||||
$recurring_invoice->client_id = $client_id;
|
||||
$recurring_invoice->line_items = $subscription_repo->generateBundleLineItems($bundle, true, false);
|
||||
$recurring_invoice->subscription_id = $this->subscription->id;
|
||||
$recurring_invoice->frequency_id = $this->subscription->frequency_id ?: RecurringInvoice::FREQUENCY_MONTHLY;
|
||||
$recurring_invoice->date = now();
|
||||
$recurring_invoice->remaining_cycles = -1;
|
||||
$recurring_invoice->auto_bill = $client->getSetting('auto_bill');
|
||||
$recurring_invoice->auto_bill_enabled = $this->setAutoBillFlag($recurring_invoice->auto_bill);
|
||||
$recurring_invoice->due_date_days = 'terms';
|
||||
$recurring_invoice->next_send_date = now()->format('Y-m-d');
|
||||
$recurring_invoice->next_send_date_client = now()->format('Y-m-d');
|
||||
$recurring_invoice->next_send_date = $recurring_invoice->nextSendDate();
|
||||
$recurring_invoice->next_send_date_client = $recurring_invoice->nextSendDateClient();
|
||||
|
||||
return $recurring_invoice;
|
||||
}
|
||||
|
||||
|
||||
private function setAutoBillFlag($auto_bill)
|
||||
{
|
||||
if ($auto_bill == 'always' || $auto_bill == 'optout') {
|
||||
|
@ -149,6 +149,8 @@ class CompanyTransformer extends EntityTransformer
|
||||
'slack_webhook_url' => (string) $company->slack_webhook_url,
|
||||
'google_analytics_url' => (string) $company->google_analytics_key, //@deprecate 1-2-2021
|
||||
'google_analytics_key' => (string) $company->google_analytics_key,
|
||||
'matomo_url' => (string) $company->matomo_url,
|
||||
'matomo_id' => (string) $company->matomo_id ?: '',
|
||||
'enabled_item_tax_rates' => (int) $company->enabled_item_tax_rates,
|
||||
'client_can_register' => (bool) $company->client_can_register,
|
||||
'is_large' => (bool) $company->is_large,
|
||||
@ -189,7 +191,8 @@ class CompanyTransformer extends EntityTransformer
|
||||
'invoice_task_project' => (bool) $company->invoice_task_project,
|
||||
'report_include_deleted' => (bool) $company->report_include_deleted,
|
||||
'invoice_task_lock' => (bool) $company->invoice_task_lock,
|
||||
'use_vendor_currency' => (bool) $company->use_vendor_currency,
|
||||
'convert_payment_currency' => (bool) $company->convert_payment_currency,
|
||||
'convert_expense_currency' => (bool) $company->convert_expense_currency,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -285,6 +285,9 @@ class HtmlEngine
|
||||
$data['$amount_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.amount')];
|
||||
}
|
||||
|
||||
// $data['$amount_in_words'] = ['value' => (new \NumberFormatter($this->client->locale(), \NumberFormatter::SPELLOUT))->format($this->entity->amount), 'label' => ''];
|
||||
// $data['$balance_in_words'] = ['value' => (new \NumberFormatter($this->client->locale(), \NumberFormatter::SPELLOUT))->format($this->entity->balance), 'label' => ''];
|
||||
|
||||
// $data['$balance_due'] = $data['$balance_due'];
|
||||
$data['$outstanding'] = &$data['$balance_due'];
|
||||
$data['$partial_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: ' ', 'label' => ctrans('texts.partial_due')];
|
||||
|
@ -60,7 +60,7 @@ trait GeneratesCounter
|
||||
|
||||
$counter_entity = $client;
|
||||
} elseif ((strpos($pattern, 'groupCounter') !== false) || (strpos($pattern, 'group_counter') !== false)) {
|
||||
if (property_exists($client->group_settings, $counter_string)) {
|
||||
if (property_exists($client, 'group_settings') && property_exists($client->group_settings, $counter_string)) {
|
||||
$counter = $client->group_settings->{$counter_string};
|
||||
} else {
|
||||
$counter = 1;
|
||||
@ -751,7 +751,7 @@ trait GeneratesCounter
|
||||
$replace[] = $client->number;
|
||||
|
||||
$search[] = '{$client_id_number}';
|
||||
$replace[] = $client->id_number;
|
||||
$replace[] = $client->id_number ?: $client->number;
|
||||
|
||||
$search[] = '{$clientIdNumber}';
|
||||
$replace[] = $client->id_number ?: $client->number;
|
||||
|
@ -264,7 +264,7 @@ trait MakesInvoiceValues
|
||||
public function transformLineItems($items, $table_type = '$product') :array
|
||||
{ //$start = microtime(true);
|
||||
|
||||
$entity = $this->client ? $this->client : $this->company;
|
||||
$entity = $this->client ? $this->client : $this->vendor;
|
||||
|
||||
$data = [];
|
||||
|
||||
|
@ -77,10 +77,6 @@ trait MakesReminders
|
||||
|
||||
private function checkEndlessReminder($last_sent_date, $endless_reminder_frequency_id) :bool
|
||||
{
|
||||
// nlog("endless date match = ".$this->addTimeInterval($last_sent_date, $endless_reminder_frequency_id));
|
||||
// nlog("Endless reminder bool = ");
|
||||
// nlog(Carbon::now()->startOfDay()->eq($this->addTimeInterval($last_sent_date, $endless_reminder_frequency_id)));
|
||||
|
||||
if (Carbon::now()->startOfDay()->eq($this->addTimeInterval($last_sent_date, $endless_reminder_frequency_id))) {
|
||||
return true;
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ trait NumberFormatter
|
||||
|
||||
return number_format($this->parseFloat(rtrim(sprintf('%f', $value), '0')), $precision, '.', '');
|
||||
|
||||
// return number_format($this->parseFloat($value), $precision, '.', '');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -21,9 +21,11 @@ class PDF extends FPDI
|
||||
{
|
||||
$this->SetXY(0, -5);
|
||||
$this->SetFont('Arial', 'I', 9);
|
||||
|
||||
$this->SetTextColor(135, 135, 135);
|
||||
|
||||
$trans = ctrans('texts.pdf_page_info', ['current' => $this->PageNo(), 'total' => '{nb}']);
|
||||
$trans = iconv('UTF-8', 'ISO-8859-7', $trans);
|
||||
$this->Cell(0, 5, $trans, 0, 0, $this->text_alignment);
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ use Illuminate\Support\Facades\Cache;
|
||||
|
||||
/**
|
||||
* Note the premise used here is that any currencies will be formatted back to the company currency and not
|
||||
* user the vendor currency, if we continue to extend on vendor, we will need to relook at this
|
||||
* use the vendor currency, if we continue to extend on vendor, we will need to relook at this
|
||||
*/
|
||||
|
||||
class VendorHtmlEngine
|
||||
@ -169,16 +169,16 @@ class VendorHtmlEngine
|
||||
|
||||
$data['$entity_number'] = &$data['$number'];
|
||||
$data['$discount'] = ['value' => $this->entity->discount, 'label' => ctrans('texts.discount')];
|
||||
$data['$subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal(), $this->company) ?: ' ', 'label' => ctrans('texts.subtotal')];
|
||||
$data['$gross_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getGrossSubTotal(), $this->company) ?: ' ', 'label' => ctrans('texts.subtotal')];
|
||||
$data['$subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal(), $this->vendor) ?: ' ', 'label' => ctrans('texts.subtotal')];
|
||||
$data['$gross_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getGrossSubTotal(), $this->vendor) ?: ' ', 'label' => ctrans('texts.subtotal')];
|
||||
|
||||
if($this->entity->uses_inclusive_taxes)
|
||||
$data['$net_subtotal'] = ['value' => Number::formatMoney(($this->entity_calc->getSubTotal() - $this->entity->total_taxes - $this->entity_calc->getTotalDiscount()), $this->company) ?: ' ', 'label' => ctrans('texts.net_subtotal')];
|
||||
$data['$net_subtotal'] = ['value' => Number::formatMoney(($this->entity_calc->getSubTotal() - $this->entity->total_taxes - $this->entity_calc->getTotalDiscount()), $this->vendor) ?: ' ', 'label' => ctrans('texts.net_subtotal')];
|
||||
else
|
||||
$data['$net_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal() - $this->entity_calc->getTotalDiscount(), $this->company) ?: ' ', 'label' => ctrans('texts.net_subtotal')];
|
||||
$data['$net_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal() - $this->entity_calc->getTotalDiscount(), $this->vendor) ?: ' ', 'label' => ctrans('texts.net_subtotal')];
|
||||
|
||||
if ($this->entity->partial > 0) {
|
||||
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->company) ?: ' ', 'label' => ctrans('texts.partial_due')];
|
||||
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->vendor) ?: ' ', 'label' => ctrans('texts.partial_due')];
|
||||
$data['$balance_due_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.partial_due')];
|
||||
$data['$amount_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.partial_due')];
|
||||
$data['$due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')];
|
||||
@ -186,12 +186,12 @@ class VendorHtmlEngine
|
||||
} else {
|
||||
|
||||
if($this->entity->status_id == 1){
|
||||
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->amount, $this->company) ?: ' ', 'label' => ctrans('texts.balance_due')];
|
||||
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->amount, $this->vendor) ?: ' ', 'label' => ctrans('texts.balance_due')];
|
||||
$data['$balance_due_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.balance_due')];
|
||||
$data['$amount_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.amount')];
|
||||
}
|
||||
else{
|
||||
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->balance, $this->company) ?: ' ', 'label' => ctrans('texts.balance_due')];
|
||||
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->balance, $this->vendor) ?: ' ', 'label' => ctrans('texts.balance_due')];
|
||||
$data['$balance_due_raw'] = ['value' => $this->entity->balance, 'label' => ctrans('texts.balance_due')];
|
||||
$data['$amount_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.amount')];
|
||||
}
|
||||
@ -199,18 +199,18 @@ class VendorHtmlEngine
|
||||
|
||||
// $data['$balance_due'] = $data['$balance_due'];
|
||||
$data['$outstanding'] = &$data['$balance_due'];
|
||||
$data['$partial_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->company) ?: ' ', 'label' => ctrans('texts.partial_due')];
|
||||
$data['$partial_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->vendor) ?: ' ', 'label' => ctrans('texts.partial_due')];
|
||||
$data['$partial'] = &$data['$partial_due'];
|
||||
|
||||
$data['$total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->company) ?: ' ', 'label' => ctrans('texts.total')];
|
||||
$data['$total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->vendor) ?: ' ', 'label' => ctrans('texts.total')];
|
||||
|
||||
$data['$purchase_order.total'] = &$data['$total'];
|
||||
|
||||
$data['$amount'] = &$data['$total'];
|
||||
$data['$amount_due'] = ['value' => &$data['$total']['value'], 'label' => ctrans('texts.amount_due')];
|
||||
$data['$balance'] = ['value' => Number::formatMoney($this->entity_calc->getBalance(), $this->company) ?: ' ', 'label' => ctrans('texts.balance')];
|
||||
$data['$balance'] = ['value' => Number::formatMoney($this->entity_calc->getBalance(), $this->vendor) ?: ' ', 'label' => ctrans('texts.balance')];
|
||||
|
||||
$data['$taxes'] = ['value' => Number::formatMoney($this->entity_calc->getItemTotalTaxes(), $this->company) ?: ' ', 'label' => ctrans('texts.taxes')];
|
||||
$data['$taxes'] = ['value' => Number::formatMoney($this->entity_calc->getItemTotalTaxes(), $this->vendor) ?: ' ', 'label' => ctrans('texts.taxes')];
|
||||
|
||||
$data['$user.name'] = ['value' => $this->entity->user->present()->name(), 'label' => ctrans('texts.name')];
|
||||
$data['$user.first_name'] = ['value' => $this->entity->user->first_name, 'label' => ctrans('texts.first_name')];
|
||||
@ -282,7 +282,7 @@ class VendorHtmlEngine
|
||||
|
||||
$data['$vendor.currency'] = ['value' => $this->vendor->currency()->code, 'label' => ''];
|
||||
|
||||
$data['$paid_to_date'] = ['value' => Number::formatMoney($this->entity->paid_to_date, $this->company), 'label' => ctrans('texts.paid_to_date')];
|
||||
$data['$paid_to_date'] = ['value' => Number::formatMoney($this->entity->paid_to_date, $this->vendor), 'label' => ctrans('texts.paid_to_date')];
|
||||
|
||||
$data['$contact.full_name'] = ['value' => $this->contact->present()->name(), 'label' => ctrans('texts.name')];
|
||||
$data['$contact'] = &$data['$contact.full_name'];
|
||||
@ -337,10 +337,10 @@ class VendorHtmlEngine
|
||||
$data['$company.custom3'] = &$data['$company3'];
|
||||
$data['$company.custom4'] = &$data['$company4'];
|
||||
|
||||
$data['$custom_surcharge1'] = ['value' => Number::formatMoney($this->entity->custom_surcharge1, $this->company) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge1')];
|
||||
$data['$custom_surcharge2'] = ['value' => Number::formatMoney($this->entity->custom_surcharge2, $this->company) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge2')];
|
||||
$data['$custom_surcharge3'] = ['value' => Number::formatMoney($this->entity->custom_surcharge3, $this->company) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge3')];
|
||||
$data['$custom_surcharge4'] = ['value' => Number::formatMoney($this->entity->custom_surcharge4, $this->company) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge4')];
|
||||
$data['$custom_surcharge1'] = ['value' => Number::formatMoney($this->entity->custom_surcharge1, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge1')];
|
||||
$data['$custom_surcharge2'] = ['value' => Number::formatMoney($this->entity->custom_surcharge2, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge2')];
|
||||
$data['$custom_surcharge3'] = ['value' => Number::formatMoney($this->entity->custom_surcharge3, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge3')];
|
||||
$data['$custom_surcharge4'] = ['value' => Number::formatMoney($this->entity->custom_surcharge4, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge4')];
|
||||
|
||||
$data['$product.item'] = ['value' => '', 'label' => ctrans('texts.item')];
|
||||
$data['$product.date'] = ['value' => '', 'label' => ctrans('texts.date')];
|
||||
|
12
composer.lock
generated
12
composer.lock
generated
@ -2164,16 +2164,16 @@
|
||||
},
|
||||
{
|
||||
"name": "google/apiclient-services",
|
||||
"version": "v0.276.0",
|
||||
"version": "v0.277.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/googleapis/google-api-php-client-services.git",
|
||||
"reference": "9a0bab5e4472d46bf979e06208da4bd03a5e6103"
|
||||
"reference": "72e0eacbd51131c954da05edf110d9774f0f5af0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/9a0bab5e4472d46bf979e06208da4bd03a5e6103",
|
||||
"reference": "9a0bab5e4472d46bf979e06208da4bd03a5e6103",
|
||||
"url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/72e0eacbd51131c954da05edf110d9774f0f5af0",
|
||||
"reference": "72e0eacbd51131c954da05edf110d9774f0f5af0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2202,9 +2202,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/googleapis/google-api-php-client-services/issues",
|
||||
"source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.276.0"
|
||||
"source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.277.0"
|
||||
},
|
||||
"time": "2022-11-25T01:16:27+00:00"
|
||||
"time": "2022-12-05T01:04:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "google/auth",
|
||||
|
@ -14,8 +14,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => '5.5.45',
|
||||
'app_tag' => '5.5.45',
|
||||
'app_version' => '5.5.49',
|
||||
'app_tag' => '5.5.49',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
50
database/migrations/2022_05_12_56879_add_stripe_klarna.php
Normal file
50
database/migrations/2022_05_12_56879_add_stripe_klarna.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\PaymentType;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$pt = PaymentType::find(47);
|
||||
|
||||
if(!$pt)
|
||||
{
|
||||
$type = new PaymentType();
|
||||
$type->id = 47;
|
||||
$type->name = 'Klarna';
|
||||
$type->gateway_type_id = GatewayType::KLARNA;
|
||||
$type->save();
|
||||
}
|
||||
|
||||
|
||||
$pt = PaymentType::find(48);
|
||||
|
||||
if(!$pt)
|
||||
{
|
||||
$type = new PaymentType();
|
||||
$type->id = 48;
|
||||
$type->name = 'Interac E-Transfer';
|
||||
$type->save();
|
||||
}
|
||||
|
||||
$gt = GatewayType::find(23);
|
||||
|
||||
if(!$gt)
|
||||
{
|
||||
$type = new GatewayType();
|
||||
$type->id = 23;
|
||||
$type->alias = 'klarna';
|
||||
$type->name = 'Klarna';
|
||||
$type->save();
|
||||
}
|
||||
}
|
||||
};
|
22
database/migrations/2022_07_12_45766_add_matomo.php
Normal file
22
database/migrations/2022_07_12_45766_add_matomo.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('companies', function (Blueprint $table) {
|
||||
$table->string('matomo_url',191)->nullable();
|
||||
$table->bigInteger('matomo_id')->nullable();
|
||||
});
|
||||
|
||||
}
|
||||
};
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('companies', function (Blueprint $table) {
|
||||
$table->boolean('convert_payment_currency')->default(false);
|
||||
$table->boolean('convert_expense_currency')->default(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
}
|
||||
};
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Vendor;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
|
||||
Schema::table('companies', function (Blueprint $table)
|
||||
{
|
||||
$table->dropColumn('use_vendor_currency');
|
||||
});
|
||||
|
||||
Vendor::query()->whereNull('currency_id')->orWhere('currency_id', '')->cursor()->each(function ($vendor){
|
||||
|
||||
$vendor->currency_id = $vendor->company->settings->currency_id;
|
||||
$vendor->save();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
@ -130,7 +130,7 @@ class CurrenciesSeeder extends Seeder
|
||||
['id' => 105, 'name' => 'Gambia Dalasi', 'code' => 'GMD', 'symbol' => 'D', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 106, 'name' => 'Paraguayan Guarani', 'code' => 'PYG', 'symbol' => '₲', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 107, 'name' => 'Malawi Kwacha', 'code' => 'MWK', 'symbol' => 'MK', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 108, 'name' => 'Zimbabwean Dollar', 'code' => 'ZWL', 'symbol' => 'Z$', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 108, 'name' => 'Zimbabwean Dollar', 'code' => 'ZWL', 'symbol' => 'Z$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 109, 'name' => 'Cambodian Riel', 'code' => 'KHR', 'symbol' => '៛', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 110, 'name' => 'Vanuatu Vatu', 'code' => 'VUV', 'symbol' => 'VT', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 111, 'name' => 'Cuban Peso', 'code' => 'CUP', 'symbol' => '₱', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
|
@ -153,12 +153,12 @@ $LANG = array(
|
||||
'enter_credit' => 'أدخل الائتمان',
|
||||
'last_logged_in' => 'آخر تسجيل دخول',
|
||||
'details' => 'تفاصيل',
|
||||
'standing' => 'Standing',
|
||||
'standing' => 'احتياط',
|
||||
'credit' => 'دائن',
|
||||
'activity' => 'نشاط',
|
||||
'date' => 'تاريخ',
|
||||
'message' => 'رسالة',
|
||||
'adjustment' => 'Adjustment',
|
||||
'adjustment' => 'تعديل سعر شراء',
|
||||
'are_you_sure' => 'هل أنت متأكد؟',
|
||||
'payment_type_id' => 'نوع الدفعة',
|
||||
'amount' => 'القيمة',
|
||||
@ -195,7 +195,8 @@ $LANG = array(
|
||||
'csv_file' => 'ملف CSV',
|
||||
'export_clients' => 'تصدير معلومات العميل',
|
||||
'created_client' => 'تم إنشاء العميل بنجاح',
|
||||
'created_clients' => 'Successfully created :count client(s)',
|
||||
'created_clients' => 'تم الإنشاء بنجاح: حساب العميل (العملاء)
|
||||
',
|
||||
'updated_settings' => 'تم تعديل الإعدادات بنجاح',
|
||||
'removed_logo' => 'تمت إزالة الشعار بنجاح',
|
||||
'sent_message' => 'تم إرسال الرسالة بنجاح',
|
||||
@ -203,7 +204,8 @@ $LANG = array(
|
||||
'limit_clients' => 'نعتذر, ستتجاوز الحد المسموح به من العملاء المقدر ب :count . الرجاء الترقيه الى النسخه المدفوعه.',
|
||||
'payment_error' => 'كان هناك خطأ في معالجة الدفع الخاص بك. الرجاء معاودة المحاولة في وقت لاحق',
|
||||
'registration_required' => 'يرجى التسجيل لإرسال فاتورة بالبريد الإلكتروني',
|
||||
'confirmation_required' => 'Please confirm your email address, :link to resend the confirmation email.',
|
||||
'confirmation_required' => 'يرجى تأكيد عنوان بريدك الإلكتروني: رابط لإعادة إرسال رسالة التأكيد عبر البريد الإلكتروني.
|
||||
',
|
||||
'updated_client' => 'تم تحديث العميل بنجاح',
|
||||
'archived_client' => 'تمت أرشفة العميل بنجاح',
|
||||
'archived_clients' => 'تمت أرشفته :count عملاء بنجاح',
|
||||
@ -213,13 +215,13 @@ $LANG = array(
|
||||
'created_invoice' => 'تم انشاء الفاتورة بنجاح',
|
||||
'cloned_invoice' => 'تم نسخ الفاتورة بنجاح',
|
||||
'emailed_invoice' => 'تم ارسال الفاتورة الى البريد بنجاح',
|
||||
'and_created_client' => 'and created client',
|
||||
'and_created_client' => 'وانشاء عميل',
|
||||
'archived_invoice' => 'تمت أرشفة الفاتورة بنجاح',
|
||||
'archived_invoices' => 'تم ارشفة :count فواتير بنجاح',
|
||||
'deleted_invoice' => 'تم حذف الفاتورة بنجاح',
|
||||
'deleted_invoices' => 'تم حذف :count فواتير بنجاح',
|
||||
'created_payment' => 'تم إنشاء الدفع بنجاح',
|
||||
'created_payments' => 'تم انشاء :count عملية دفع (مدفوعات) بنجاح',
|
||||
'created_payments' => 'تم انشاء عملية دفع (مدفوعات) بنجاح',
|
||||
'archived_payment' => 'تمت أرشفة الدفع بنجاح',
|
||||
'archived_payments' => 'تمت ارشفة :count مدفوعات بنجاح',
|
||||
'deleted_payment' => 'تم حذف الدفع بنجاح',
|
||||
@ -254,6 +256,8 @@ $LANG = array(
|
||||
'notification_invoice_paid' => 'A payment of :amount was made by client :client towards Invoice :invoice.',
|
||||
'notification_invoice_sent' => 'The following client :client was emailed Invoice :invoice for :amount.',
|
||||
'notification_invoice_viewed' => 'The following client :client viewed Invoice :invoice for :amount.',
|
||||
'stripe_payment_text' => 'Invoice :invoicenumber for :amount for client :client',
|
||||
'stripe_payment_text_without_invoice' => 'Payment with no invoice for amount :amount for client :client',
|
||||
'reset_password' => 'You can reset your account password by clicking the following button:',
|
||||
'secure_payment' => 'دفع امن',
|
||||
'card_number' => 'رقم البطاقة',
|
||||
@ -262,15 +266,15 @@ $LANG = array(
|
||||
'cvv' => 'CVV',
|
||||
'logout' => 'تسجيل الخروج',
|
||||
'sign_up_to_save' => 'سجل دخول لحفظ عملك',
|
||||
'agree_to_terms' => 'أنا أوافق على :terms',
|
||||
'agree_to_terms' => 'أنا أوافق على :الشروط',
|
||||
'terms_of_service' => 'شروط الخدمة',
|
||||
'email_taken' => 'عنوان البريد الإلكتروني مسجل بالفعل',
|
||||
'working' => 'Working',
|
||||
'working' => 'عمل',
|
||||
'success' => 'نجاح',
|
||||
'success_message' => 'لقد قمت بالتسجيل بنجاح! يرجى زيارة الرابط في البريد الإلكتروني لتأكيد الحساب للتحقق من عنوان بريدك الإلكتروني.',
|
||||
'erase_data' => 'حسابك غير مسجل ، سيؤدي ذلك إلى محو بياناتك بشكل دائم.',
|
||||
'password' => 'كلمة السر',
|
||||
'pro_plan_product' => 'خطة Pro',
|
||||
'pro_plan_product' => 'خطة حساب',
|
||||
'pro_plan_success' => 'Thanks for choosing Invoice Ninja\'s Pro plan!<p/> <br/>
|
||||
<b>Next Steps</b><p/>A payable invoice has been sent to the email
|
||||
address associated with your account. To unlock all of the awesome
|
||||
@ -542,7 +546,7 @@ $LANG = array(
|
||||
'recurring' => 'Recurring',
|
||||
'last_invoice_sent' => 'Last invoice sent :date',
|
||||
'processed_updates' => 'Successfully completed update',
|
||||
'tasks' => 'Tasks',
|
||||
'tasks' => 'المهام',
|
||||
'new_task' => 'New Task',
|
||||
'start_time' => 'Start Time',
|
||||
'created_task' => 'Successfully created task',
|
||||
@ -574,12 +578,12 @@ $LANG = array(
|
||||
'invoiced' => 'Invoiced',
|
||||
'logged' => 'Logged',
|
||||
'running' => 'Running',
|
||||
'task_error_multiple_clients' => 'The tasks can\'t belong to different clients',
|
||||
'task_error_running' => 'Please stop running tasks first',
|
||||
'task_error_invoiced' => 'Tasks have already been invoiced',
|
||||
'task_error_multiple_clients' => 'لا يمكن أن تنتمي المهام إلى عملاء مختلفين',
|
||||
'task_error_running' => 'من فضلك توقف عن تشغيل المهام أولا',
|
||||
'task_error_invoiced' => 'تم بالفعل إصدار فاتورة بالمهام',
|
||||
'restored_task' => 'Successfully restored task',
|
||||
'archived_task' => 'Successfully archived task',
|
||||
'archived_tasks' => 'Successfully archived :count tasks',
|
||||
'archived_tasks' => 'تمت أرشفته بنجاح: عد المهام',
|
||||
'deleted_task' => 'Successfully deleted task',
|
||||
'deleted_tasks' => 'Successfully deleted :count tasks',
|
||||
'create_task' => 'Create Task',
|
||||
@ -1123,18 +1127,18 @@ $LANG = array(
|
||||
'december' => 'December',
|
||||
|
||||
// Documents
|
||||
'documents_header' => 'Documents:',
|
||||
'email_documents_header' => 'Documents:',
|
||||
'documents_header' => 'الملفات',
|
||||
'email_documents_header' => 'الملفات',
|
||||
'email_documents_example_1' => 'Widgets Receipt.pdf',
|
||||
'email_documents_example_2' => 'Final Deliverable.zip',
|
||||
'quote_documents' => 'Quote Documents',
|
||||
'invoice_documents' => 'Invoice Documents',
|
||||
'quote_documents' => 'اقتباس ملفات',
|
||||
'invoice_documents' => 'فاتورة ملفات',
|
||||
'expense_documents' => 'Expense Documents',
|
||||
'invoice_embed_documents' => 'Embed Documents',
|
||||
'invoice_embed_documents_help' => 'Include attached images in the invoice.',
|
||||
'document_email_attachment' => 'Attach Documents',
|
||||
'document_email_attachment' => 'ارفاق المستندات',
|
||||
'ubl_email_attachment' => 'Attach UBL',
|
||||
'download_documents' => 'Download Documents (:size)',
|
||||
'download_documents' => 'تنزيل الملفات (:حجم)',
|
||||
'documents_from_expenses' => 'From Expenses:',
|
||||
'dropzone_default_message' => 'Drop files or click to upload',
|
||||
'dropzone_default_message_disabled' => 'Uploads disabled',
|
||||
@ -1146,7 +1150,7 @@ $LANG = array(
|
||||
'dropzone_cancel_upload' => 'Cancel upload',
|
||||
'dropzone_cancel_upload_confirmation' => 'Are you sure you want to cancel this upload?',
|
||||
'dropzone_remove_file' => 'Remove file',
|
||||
'documents' => 'Documents',
|
||||
'documents' => 'المستندات',
|
||||
'document_date' => 'Document Date',
|
||||
'document_size' => 'Size',
|
||||
|
||||
@ -1269,7 +1273,7 @@ $LANG = array(
|
||||
'company_account' => 'Company Account',
|
||||
'account_holder_name' => 'Account Holder Name',
|
||||
'add_account' => 'Add Account',
|
||||
'payment_methods' => 'Payment Methods',
|
||||
'payment_methods' => 'طرق الدفع',
|
||||
'complete_verification' => 'Complete Verification',
|
||||
'verification_amount1' => 'Amount 1',
|
||||
'verification_amount2' => 'Amount 2',
|
||||
@ -2105,9 +2109,9 @@ $LANG = array(
|
||||
'group_when_sorted' => 'Group Sort',
|
||||
'group_dates_by' => 'Group Dates By',
|
||||
'year' => 'Year',
|
||||
'view_statement' => 'View Statement',
|
||||
'statement' => 'Statement',
|
||||
'statement_date' => 'Statement Date',
|
||||
'view_statement' => 'عرض كشف حساب',
|
||||
'statement' => 'كشف حساب',
|
||||
'statement_date' => 'تاريخ كشف الحساب',
|
||||
'mark_active' => 'Mark Active',
|
||||
'send_automatically' => 'Send Automatically',
|
||||
'initial_email' => 'Initial Email',
|
||||
@ -2142,9 +2146,9 @@ $LANG = array(
|
||||
'profile' => 'Profile',
|
||||
'payment_type_help' => 'Sets the default <b>manual payment type</b>.',
|
||||
'industry_Construction' => 'Construction',
|
||||
'your_statement' => 'Your Statement',
|
||||
'your_statement' => 'كشف حساباتك',
|
||||
'statement_issued_to' => 'Statement issued to',
|
||||
'statement_to' => 'Statement to',
|
||||
'statement_to' => 'كشف حساب لـ',
|
||||
'customize_options' => 'Customize options',
|
||||
'created_payment_term' => 'Successfully created payment term',
|
||||
'updated_payment_term' => 'Successfully updated payment term',
|
||||
@ -2332,7 +2336,7 @@ $LANG = array(
|
||||
'downloaded_invoices' => 'An email will be sent with the invoice PDFs',
|
||||
'downloaded_quotes' => 'An email will be sent with the quote PDFs',
|
||||
'clone_expense' => 'Clone Expense',
|
||||
'default_documents' => 'Default Documents',
|
||||
'default_documents' => 'المستندات الافتراضية',
|
||||
'send_email_to_client' => 'Send email to the client',
|
||||
'refund_subject' => 'Refund Processed',
|
||||
'refund_body' => 'You have been processed a refund of :amount for invoice :invoice_number.',
|
||||
@ -2506,6 +2510,7 @@ $LANG = array(
|
||||
'alipay' => 'Alipay',
|
||||
'sofort' => 'Sofort',
|
||||
'sepa' => 'SEPA Direct Debit',
|
||||
'name_without_special_characters' => 'Please enter a name with only the letters a-z and whitespaces',
|
||||
'enable_alipay' => 'Accept Alipay',
|
||||
'enable_sofort' => 'Accept EU bank transfers',
|
||||
'stripe_alipay_help' => 'These gateways also need to be activated in :link.',
|
||||
@ -2619,7 +2624,7 @@ $LANG = array(
|
||||
'verification_file_missing' => 'The verification file is needed to accept payments.',
|
||||
'apple_pay_domain' => 'Use <code>:domain</code> as the domain in :link.',
|
||||
'apple_pay_not_supported' => 'Sorry, Apple/Google Pay isn\'t supported by your browser',
|
||||
'optional_payment_methods' => 'Optional Payment Methods',
|
||||
'optional_payment_methods' => 'اعدادات طرق الدفع',
|
||||
'add_subscription' => 'Add Subscription',
|
||||
'target_url' => 'Target',
|
||||
'target_url_help' => 'When the selected event occurs the app will post the entity to the target URL.',
|
||||
@ -2645,7 +2650,7 @@ $LANG = array(
|
||||
'subscription_event_19' => 'Updated Task',
|
||||
'subscription_event_20' => 'Deleted Task',
|
||||
'subscription_event_21' => 'Approved Quote',
|
||||
'subscriptions' => 'Subscriptions',
|
||||
'subscriptions' => 'الاشتراكات',
|
||||
'updated_subscription' => 'Successfully updated subscription',
|
||||
'created_subscription' => 'Successfully created subscription',
|
||||
'edit_subscription' => 'Edit Subscription',
|
||||
@ -2841,7 +2846,7 @@ $LANG = array(
|
||||
'client_must_be_active' => 'Error: the client must be active',
|
||||
'purge_client' => 'Purge Client',
|
||||
'purged_client' => 'Successfully purged client',
|
||||
'purge_client_warning' => 'All related records (invoices, tasks, expenses, documents, etc) will also be deleted.',
|
||||
'purge_client_warning' => 'سيتم أيضًا حذف جميع السجلات ذات الصلة (الفواتير والمهام والمصروفات والمستندات وما إلى ذلك).',
|
||||
'clone_product' => 'Clone Product',
|
||||
'item_details' => 'Item Details',
|
||||
'send_item_details_help' => 'Send line item details to the payment gateway.',
|
||||
@ -3018,7 +3023,7 @@ $LANG = array(
|
||||
'from_name_placeholder' => 'Support Center',
|
||||
'attachments' => 'Attachments',
|
||||
'client_upload' => 'Client uploads',
|
||||
'enable_client_upload_help' => 'Allow clients to upload documents/attachments',
|
||||
'enable_client_upload_help' => 'السماح للعملاء بتحميل المستندات / المرفقات',
|
||||
'max_file_size_help' => 'Maximum file size (KB) is limited by your post_max_size and upload_max_filesize variables as set in your PHP.INI',
|
||||
'max_file_size' => 'Maximum file size',
|
||||
'mime_types' => 'Mime types',
|
||||
@ -3196,7 +3201,7 @@ $LANG = array(
|
||||
'custom_javascript' => 'Custom JavaScript',
|
||||
'portal_mode' => 'Portal Mode',
|
||||
'attach_pdf' => 'Attach PDF',
|
||||
'attach_documents' => 'Attach Documents',
|
||||
'attach_documents' => 'ارفاق مستندات',
|
||||
'attach_ubl' => 'Attach UBL',
|
||||
'email_style' => 'Email Style',
|
||||
'processed' => 'Processed',
|
||||
@ -3693,7 +3698,7 @@ $LANG = array(
|
||||
'force_update_help' => 'You are running the latest version but there may be pending fixes available.',
|
||||
'mark_paid_help' => 'Track the expense has been paid',
|
||||
'mark_invoiceable_help' => 'تفعيل النفقات لتتم فوترتها',
|
||||
'add_documents_to_invoice_help' => 'Make the documents visible to client',
|
||||
'add_documents_to_invoice_help' => 'اجعل المستندات مرئية للعميل',
|
||||
'convert_currency_help' => 'Set an exchange rate',
|
||||
'expense_settings' => 'Expense Settings',
|
||||
'clone_to_recurring' => 'Clone to Recurring',
|
||||
@ -3847,9 +3852,9 @@ $LANG = array(
|
||||
'archived_groups' => 'Successfully archived :value groups',
|
||||
'deleted_groups' => 'Successfully deleted :value groups',
|
||||
'restored_groups' => 'Successfully restored :value groups',
|
||||
'archived_documents' => 'Successfully archived :value documents',
|
||||
'deleted_documents' => 'Successfully deleted :value documents',
|
||||
'restored_documents' => 'Successfully restored :value documents',
|
||||
'archived_documents' => 'تمت أرشفته بنجاح: مستندات القيمة',
|
||||
'deleted_documents' => 'تمت عملية الحذف بنجاح: مستندات القيمة',
|
||||
'restored_documents' => 'تمت الاستعادة بنجاح: مستندات القيمة',
|
||||
'restored_vendors' => 'Successfully restored :value vendors',
|
||||
'restored_expenses' => 'Successfully restored :value expenses',
|
||||
'restored_tasks' => 'Successfully restored :value tasks',
|
||||
@ -3886,7 +3891,7 @@ $LANG = array(
|
||||
'converted_balance' => 'Converted Balance',
|
||||
'is_sent' => 'Is Sent',
|
||||
'document_upload' => 'Document Upload',
|
||||
'document_upload_help' => 'Enable clients to upload documents',
|
||||
'document_upload_help' => 'تمكين العملاء من تحميل المستندات',
|
||||
'expense_total' => 'إجمالي المصروف',
|
||||
'enter_taxes' => 'أدخل الضرائب',
|
||||
'by_rate' => 'بالنسبة',
|
||||
@ -3962,7 +3967,7 @@ $LANG = array(
|
||||
'list_of_payments' => 'List of payments',
|
||||
'payment_details' => 'Details of the payment',
|
||||
'list_of_payment_invoices' => 'List of invoices affected by the payment',
|
||||
'list_of_payment_methods' => 'List of payment methods',
|
||||
'list_of_payment_methods' => 'قائمة طرق الدفع',
|
||||
'payment_method_details' => 'Details of payment method',
|
||||
'permanently_remove_payment_method' => 'Permanently remove this payment method.',
|
||||
'warning_action_cannot_be_reversed' => 'Warning! This action can not be reversed!',
|
||||
@ -4068,7 +4073,7 @@ $LANG = array(
|
||||
'save_payment_method_details' => 'Save payment method details',
|
||||
'new_card' => 'New card',
|
||||
'new_bank_account' => 'New bank account',
|
||||
'company_limit_reached' => 'Limit of 10 companies per account.',
|
||||
'company_limit_reached' => 'Limit of :limit companies per account.',
|
||||
'credits_applied_validation' => 'Total credits applied cannot be MORE than total of invoices',
|
||||
'credit_number_taken' => 'Credit number already taken',
|
||||
'credit_not_found' => 'Credit not found',
|
||||
@ -4106,7 +4111,7 @@ $LANG = array(
|
||||
'company_user_not_found' => 'Company User record not found',
|
||||
'no_credits_found' => 'No credits found.',
|
||||
'action_unavailable' => 'The requested action :action is not available.',
|
||||
'no_documents_found' => 'No Documents Found',
|
||||
'no_documents_found' => 'لم يتم العثور على مستندات',
|
||||
'no_group_settings_found' => 'No group settings found',
|
||||
'access_denied' => 'Insufficient privileges to access/modify this resource',
|
||||
'invoice_cannot_be_marked_paid' => 'Invoice cannot be marked as paid',
|
||||
@ -4141,7 +4146,7 @@ $LANG = array(
|
||||
'recurring_cancellation_request' => 'Request for recurring invoice cancellation from :contact',
|
||||
'recurring_cancellation_request_body' => ':contact from Client :client requested to cancel Recurring Invoice :invoice',
|
||||
'hello' => 'أهلاً',
|
||||
'group_documents' => 'Group documents',
|
||||
'group_documents' => 'وثائق المجموعة',
|
||||
'quote_approval_confirmation_label' => 'Are you sure you want to approve this quote?',
|
||||
'migration_select_company_label' => 'Select companies to migrate',
|
||||
'force_migration' => 'Force migration',
|
||||
@ -4197,7 +4202,7 @@ $LANG = array(
|
||||
'removed_subscription' => 'Successfully removed subscription',
|
||||
'restored_subscription' => 'Successfully restored subscription',
|
||||
'search_subscription' => 'Search 1 Subscription',
|
||||
'search_subscriptions' => 'Search :count Subscriptions',
|
||||
'search_subscriptions' => 'بحث: حساب الاشتراكات',
|
||||
'subdomain_is_not_available' => 'Subdomain is not available',
|
||||
'connect_gmail' => 'Connect Gmail',
|
||||
'disconnect_gmail' => 'Disconnect Gmail',
|
||||
@ -4208,7 +4213,6 @@ $LANG = array(
|
||||
'count_minutes' => ':count Minutes',
|
||||
'password_timeout' => 'Password Timeout',
|
||||
'shared_invoice_credit_counter' => 'Share Invoice/Credit Counter',
|
||||
|
||||
'activity_80' => ':user created subscription :subscription',
|
||||
'activity_81' => ':user updated subscription :subscription',
|
||||
'activity_82' => ':user archived subscription :subscription',
|
||||
@ -4216,7 +4220,6 @@ $LANG = array(
|
||||
'activity_84' => ':user restored subscription :subscription',
|
||||
'amount_greater_than_balance_v5' => 'The amount is greater than the invoice balance. You cannot overpay an invoice.',
|
||||
'click_to_continue' => 'Click to continue',
|
||||
|
||||
'notification_invoice_created_body' => 'الفاتورة التالية: تم إنشاء الفاتورة للعميل: العميل مقابل: المبلغ.
|
||||
',
|
||||
'notification_invoice_created_subject' => 'الفاتورة: فاتورة تم إنشاؤها من أجل: العميل
|
||||
@ -4254,7 +4257,7 @@ $LANG = array(
|
||||
'user_duplicate_error' => 'Cannot add the same user to the same company',
|
||||
'user_cross_linked_error' => 'User exists but cannot be crossed linked to multiple accounts',
|
||||
'ach_verification_notification_label' => 'ACH verification',
|
||||
'ach_verification_notification' => 'Connecting bank accounts require verification. Payment gateway will automatically send two small deposits for this purpose. These deposits take 1-2 business days to appear on the customer\'s online statement.',
|
||||
'ach_verification_notification' => 'يتطلب ربط الحسابات المصرفية التحقق. سترسل بوابة الدفع تلقائيًا إيداعين صغيرين لهذا الغرض. تستغرق هذه الإيداعات من يوم إلى يومي عمل لتظهر في كشف حساب العميل عبر الإنترنت',
|
||||
'login_link_requested_label' => 'Login link requested',
|
||||
'login_link_requested' => 'There was a request to login using link. If you did not request this, it\'s safe to ignore it.',
|
||||
'invoices_backup_subject' => 'الفاتوره جاهزه للحفظ',
|
||||
@ -4301,7 +4304,7 @@ $LANG = array(
|
||||
'savings' => 'Savings',
|
||||
'unable_to_verify_payment_method' => 'Unable to verify payment method.',
|
||||
'generic_gateway_error' => 'Gateway configuration error. Please check your credentials.',
|
||||
'my_documents' => 'My documents',
|
||||
'my_documents' => 'مستنداتي',
|
||||
'payment_method_cannot_be_preauthorized' => 'This payment method cannot be preauthorized.',
|
||||
'kbc_cbc' => 'KBC/CBC',
|
||||
'bancontact' => 'Bancontact',
|
||||
@ -4313,6 +4316,7 @@ $LANG = array(
|
||||
'przelewy24_accept' => 'I declare that I have familiarized myself with the regulations and information obligation of the Przelewy24 service.',
|
||||
'giropay' => 'GiroPay',
|
||||
'giropay_law' => 'By entering your Customer information (such as name, sort code and account number) you (the Customer) agree that this information is given voluntarily.',
|
||||
'klarna' => 'Klarna',
|
||||
'eps' => 'EPS',
|
||||
'becs' => 'BECS Direct Debit',
|
||||
'becs_mandate' => 'By providing your bank account details, you agree to this <a class="underline" href="https://stripe.com/au-becs-dd-service-agreement/legal">Direct Debit Request and the Direct Debit Request service agreement</a>, and authorise Stripe Payments Australia Pty Ltd ACN 160 180 343 Direct Debit User ID number 507156 (“Stripe”) to debit your account through the Bulk Electronic Clearing System (BECS) on behalf of :company (the “Merchant”) for any amounts separately communicated to you by the Merchant. You certify that you are either an account holder or an authorised signatory on the account listed above.',
|
||||
@ -4555,7 +4559,7 @@ $LANG = array(
|
||||
'small' => 'Small',
|
||||
'quotes_backup_subject' => 'Your quotes are ready for download',
|
||||
'credits_backup_subject' => 'Your credits are ready for download',
|
||||
'document_download_subject' => 'Your documents are ready for download',
|
||||
'document_download_subject' => 'مستنداتك جاهزة للتحميل',
|
||||
'reminder_message' => 'Reminder for invoice :number for :balance',
|
||||
'gmail_credentials_invalid_subject' => 'Send with GMail invalid credentials',
|
||||
'gmail_credentials_invalid_body' => 'Your GMail credentials are not correct, please log into the administrator portal and navigate to Settings > User Details and disconnect and reconnect your GMail account. We will send you this notification daily until this issue is resolved',
|
||||
@ -4583,9 +4587,9 @@ $LANG = array(
|
||||
'invalid_time' => 'Invalid Time',
|
||||
'signed_in_as' => 'Signed in as',
|
||||
'total_results' => 'Total results',
|
||||
'restore_company_gateway' => 'Restore payment gateway',
|
||||
'archive_company_gateway' => 'Archive payment gateway',
|
||||
'delete_company_gateway' => 'Delete payment gateway',
|
||||
'restore_company_gateway' => 'Restore gateway',
|
||||
'archive_company_gateway' => 'Archive gateway',
|
||||
'delete_company_gateway' => 'Delete gateway',
|
||||
'exchange_currency' => 'Exchange currency',
|
||||
'tax_amount1' => 'Tax Amount 1',
|
||||
'tax_amount2' => 'Tax Amount 2',
|
||||
@ -4660,7 +4664,7 @@ $LANG = array(
|
||||
'added_purchase_orders_to_inventory' => 'Successfully added purchase orders to inventory',
|
||||
'client_document_upload' => 'Client Document Upload',
|
||||
'vendor_document_upload' => 'Vendor Document Upload',
|
||||
'vendor_document_upload_help' => 'Enable vendors to upload documents',
|
||||
'vendor_document_upload_help' => 'تمكن البائعين من تحميل المستندات',
|
||||
'are_you_enjoying_the_app' => 'Are you enjoying the app?',
|
||||
'yes_its_great' => 'Yes, it"s great!',
|
||||
'not_so_much' => 'Not so much',
|
||||
@ -4796,8 +4800,134 @@ $LANG = array(
|
||||
'invoice_task_project' => 'مشروع مهمة الفاتورة',
|
||||
'invoice_task_project_help' => 'أضف المشروع إلى بنود سطر الفاتورة',
|
||||
'bulk_action' => 'أمر جماعي',
|
||||
'phone_validation_error' => 'This phone number is not valid, please enter in E.164 format',
|
||||
'transaction' => 'Transaction',
|
||||
'phone_validation_error' => 'This mobile (cell) phone number is not valid, please enter in E.164 format',
|
||||
'transaction' => 'Transaction',
|
||||
'disable_2fa' => 'Disable 2FA',
|
||||
'change_number' => 'Change Number',
|
||||
'resend_code' => 'Resend Code',
|
||||
'base_type' => 'Base Type',
|
||||
'category_type' => 'Category Type',
|
||||
'bank_transaction' => 'Transaction',
|
||||
'bulk_print' => 'Print PDF',
|
||||
'vendor_postal_code' => 'Vendor Postal Code',
|
||||
'preview_location' => 'Preview Location',
|
||||
'bottom' => 'Bottom',
|
||||
'side' => 'Side',
|
||||
'pdf_preview' => 'PDF Preview',
|
||||
'long_press_to_select' => 'Long Press to Select',
|
||||
'purchase_order_item' => 'Purchase Order Item',
|
||||
'would_you_rate_the_app' => 'Would you like to rate the app?',
|
||||
'include_deleted' => 'Include Deleted',
|
||||
'include_deleted_help' => 'Include deleted records in reports',
|
||||
'due_on' => 'Due On',
|
||||
'browser_pdf_viewer' => 'Use Browser PDF Viewer',
|
||||
'browser_pdf_viewer_help' => 'Warning: Prevents interacting with app over the PDF',
|
||||
'converted_transactions' => 'Successfully converted transactions',
|
||||
'default_category' => 'Default Category',
|
||||
'connect_accounts' => 'Connect Accounts',
|
||||
'manage_rules' => 'Manage Rules',
|
||||
'search_category' => 'Search 1 Category',
|
||||
'search_categories' => 'Search :count Categories',
|
||||
'min_amount' => 'Min Amount',
|
||||
'max_amount' => 'Max Amount',
|
||||
'converted_transaction' => 'Successfully converted transaction',
|
||||
'convert_to_payment' => 'Convert to Payment',
|
||||
'deposit' => 'Deposit',
|
||||
'withdrawal' => 'Withdrawal',
|
||||
'deposits' => 'Deposits',
|
||||
'withdrawals' => 'Withdrawals',
|
||||
'matched' => 'Matched',
|
||||
'unmatched' => 'Unmatched',
|
||||
'create_credit' => 'Create Credit',
|
||||
'transactions' => 'Transactions',
|
||||
'new_transaction' => 'New Transaction',
|
||||
'edit_transaction' => 'Edit Transaction',
|
||||
'created_transaction' => 'Successfully created transaction',
|
||||
'updated_transaction' => 'Successfully updated transaction',
|
||||
'archived_transaction' => 'Successfully archived transaction',
|
||||
'deleted_transaction' => 'Successfully deleted transaction',
|
||||
'removed_transaction' => 'Successfully removed transaction',
|
||||
'restored_transaction' => 'Successfully restored transaction',
|
||||
'search_transaction' => 'Search Transaction',
|
||||
'search_transactions' => 'Search :count Transactions',
|
||||
'deleted_bank_account' => 'Successfully deleted bank account',
|
||||
'removed_bank_account' => 'Successfully removed bank account',
|
||||
'restored_bank_account' => 'Successfully restored bank account',
|
||||
'search_bank_account' => 'Search Bank Account',
|
||||
'search_bank_accounts' => 'Search :count Bank Accounts',
|
||||
'code_was_sent_to' => 'A code has been sent via SMS to :number',
|
||||
'verify_phone_number_2fa_help' => 'Please verify your phone number for 2FA backup',
|
||||
'enable_applying_payments_later' => 'Enable Applying Payments Later',
|
||||
'line_item_tax_rates' => 'Line Item Tax Rates',
|
||||
'show_tasks_in_client_portal' => 'Show Tasks in Client Portal',
|
||||
'notification_quote_expired_subject' => 'Quote :invoice has expired for :client',
|
||||
'notification_quote_expired' => 'The following Quote :invoice for client :client and :amount has now expired.',
|
||||
'auto_sync' => 'Auto Sync',
|
||||
'refresh_accounts' => 'Refresh Accounts',
|
||||
'upgrade_to_connect_bank_account' => 'Upgrade to Enterprise to connect your bank account',
|
||||
'click_here_to_connect_bank_account' => 'Click here to connect your bank account',
|
||||
'include_tax' => 'Include tax',
|
||||
'email_template_change' => 'E-mail template body can be changed on',
|
||||
'task_update_authorization_error' => 'Insufficient permissions, or task may be locked',
|
||||
'cash_vs_accrual' => 'Accrual accounting',
|
||||
'cash_vs_accrual_help' => 'Turn on for accrual reporting, turn off for cash basis reporting.',
|
||||
'expense_paid_report' => 'Expensed reporting',
|
||||
'expense_paid_report_help' => 'Turn on for reporting all expenses, turn off for reporting only paid expenses',
|
||||
'payment_type_Klarna' => 'Klarna',
|
||||
'online_payment_email_help' => 'Send an email when an online payment is made',
|
||||
'manual_payment_email_help' => 'Send an email when manually entering a payment',
|
||||
'mark_paid_payment_email_help' => 'Send an email when marking an invoice as paid',
|
||||
'linked_transaction' => 'Successfully linked transaction',
|
||||
'link_payment' => 'Link Payment',
|
||||
'link_expense' => 'Link Expense',
|
||||
'lock_invoiced_tasks' => 'Lock Invoiced Tasks',
|
||||
'lock_invoiced_tasks_help' => 'Prevent tasks from being edited once invoiced',
|
||||
'registration_required_help' => 'Require clients to register',
|
||||
'use_inventory_management' => 'Use Inventory Management',
|
||||
'use_inventory_management_help' => 'Require products to be in stock',
|
||||
'optional_products' => 'Optional Products',
|
||||
'optional_recurring_products' => 'Optional Recurring Products',
|
||||
'convert_matched' => 'Convert',
|
||||
'auto_billed_invoice' => 'Successfully queued invoice to be auto-billed',
|
||||
'auto_billed_invoices' => 'Successfully queued invoices to be auto-billed',
|
||||
'operator' => 'Operator',
|
||||
'value' => 'Value',
|
||||
'is' => 'Is',
|
||||
'contains' => 'Contains',
|
||||
'starts_with' => 'Starts with',
|
||||
'is_empty' => 'Is empty',
|
||||
'add_rule' => 'Add Rule',
|
||||
'match_all_rules' => 'Match All Rules',
|
||||
'match_all_rules_help' => 'All criteria needs to match for the rule to be applied',
|
||||
'auto_convert_help' => 'Automatically convert matched transactions to expenses',
|
||||
'rules' => 'Rules',
|
||||
'transaction_rule' => 'Transaction Rule',
|
||||
'transaction_rules' => 'Transaction Rules',
|
||||
'new_transaction_rule' => 'New Transaction Rule',
|
||||
'edit_transaction_rule' => 'Edit Transaction Rule',
|
||||
'created_transaction_rule' => 'Successfully created rule',
|
||||
'updated_transaction_rule' => 'Successfully updated transaction rule',
|
||||
'archived_transaction_rule' => 'Successfully archived transaction rule',
|
||||
'deleted_transaction_rule' => 'Successfully deleted transaction rule',
|
||||
'removed_transaction_rule' => 'Successfully removed transaction rule',
|
||||
'restored_transaction_rule' => 'Successfully restored transaction rule',
|
||||
'search_transaction_rule' => 'Search Transaction Rule',
|
||||
'search_transaction_rules' => 'Search Transaction Rules',
|
||||
'payment_type_Interac E-Transfer' => 'Interac E-Transfer',
|
||||
'delete_bank_account' => 'Delete Bank Account',
|
||||
'archive_transaction' => 'Archive Transaction',
|
||||
'delete_transaction' => 'Delete Transaction',
|
||||
'otp_code_message' => 'Enter the code emailed.',
|
||||
'otp_code_subject' => 'Your one time passcode code',
|
||||
'otp_code_body' => 'Your one time passcode is :code',
|
||||
'delete_tax_rate' => 'Delete Tax Rate',
|
||||
'restore_tax_rate' => 'Restore Tax Rate',
|
||||
'company_backup_file' => 'Select company backup file',
|
||||
'company_backup_file_help' => 'Please upload the .zip file used to create this backup.',
|
||||
'backup_restore' => 'Backup | Restore',
|
||||
'export_company' => 'Create company backup',
|
||||
'backup' => 'Backup',
|
||||
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user