mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
merging v5-dev
This commit is contained in:
commit
620ad57d50
18
.env.example
18
.env.example
@ -5,20 +5,14 @@ APP_DEBUG=false
|
||||
|
||||
APP_URL=http://localhost
|
||||
|
||||
DB_CONNECTION=db-ninja-01
|
||||
DB_CONNECTION=mysql
|
||||
MULTI_DB_ENABLED=false
|
||||
|
||||
DB_HOST1=localhost
|
||||
DB_DATABASE1=ninja
|
||||
DB_USERNAME1=ninja
|
||||
DB_PASSWORD1=ninja
|
||||
DB_PORT1=3306
|
||||
|
||||
DB_HOST2=localhost
|
||||
DB_DATABASE2=ninja2
|
||||
DB_USERNAME2=ninja
|
||||
DB_PASSWORD2=ninja
|
||||
DB_PORT2=3306
|
||||
DB_HOST=localhost
|
||||
DB_DATABASE=ninja
|
||||
DB_USERNAME=ninja
|
||||
DB_PASSWORD=ninja
|
||||
DB_PORT=3306
|
||||
|
||||
DEMO_MODE=false
|
||||
|
||||
|
@ -1 +1 @@
|
||||
5.2.6
|
||||
5.2.10
|
@ -16,19 +16,21 @@ use App\Factory\ClientContactFactory;
|
||||
use App\Models\Account;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyLedger;
|
||||
use App\Models\Contact;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Paymentable;
|
||||
use App\Utils\Ninja;
|
||||
use DB;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Str;
|
||||
use Mail;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/*
|
||||
|
||||
@ -84,6 +86,8 @@ class CheckData extends Command
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$time_start = microtime(true);
|
||||
|
||||
$database_connection = $this->option('database') ? $this->option('database') : 'Connected to Default DB';
|
||||
$fix_status = $this->option('fix') ? "Fixing Issues" : "Just checking issues ";
|
||||
|
||||
@ -96,6 +100,7 @@ class CheckData extends Command
|
||||
$this->checkInvoiceBalances();
|
||||
$this->checkInvoicePayments();
|
||||
$this->checkPaidToDates();
|
||||
// $this->checkPaidToCompanyDates();
|
||||
$this->checkClientBalances();
|
||||
$this->checkContacts();
|
||||
$this->checkCompanyData();
|
||||
@ -107,6 +112,8 @@ class CheckData extends Command
|
||||
}
|
||||
|
||||
$this->logMessage('Done: '.strtoupper($this->isValid ? Account::RESULT_SUCCESS : Account::RESULT_FAILURE));
|
||||
$this->logMessage('Total execution time in seconds: ' . (microtime(true) - $time_start));
|
||||
|
||||
$errorEmail = config('ninja.error_email');
|
||||
|
||||
if ($errorEmail) {
|
||||
@ -227,38 +234,38 @@ class CheckData extends Command
|
||||
}
|
||||
}
|
||||
|
||||
// check for more than one primary contact
|
||||
$clients = DB::table('clients')
|
||||
->leftJoin('client_contacts', function ($join) {
|
||||
$join->on('client_contacts.client_id', '=', 'clients.id')
|
||||
->where('client_contacts.is_primary', '=', true)
|
||||
->whereNull('client_contacts.deleted_at');
|
||||
})
|
||||
->groupBy('clients.id')
|
||||
->havingRaw('count(client_contacts.id) != 1');
|
||||
// // check for more than one primary contact
|
||||
// $clients = DB::table('clients')
|
||||
// ->leftJoin('client_contacts', function ($join) {
|
||||
// $join->on('client_contacts.client_id', '=', 'clients.id')
|
||||
// ->where('client_contacts.is_primary', '=', true)
|
||||
// ->whereNull('client_contacts.deleted_at');
|
||||
// })
|
||||
// ->groupBy('clients.id')
|
||||
// ->havingRaw('count(client_contacts.id) != 1');
|
||||
|
||||
if ($this->option('client_id')) {
|
||||
$clients->where('clients.id', '=', $this->option('client_id'));
|
||||
}
|
||||
// if ($this->option('client_id')) {
|
||||
// $clients->where('clients.id', '=', $this->option('client_id'));
|
||||
// }
|
||||
|
||||
$clients = $clients->get(['clients.id', 'clients.user_id', 'clients.company_id']);
|
||||
$this->logMessage($clients->count().' clients without a single primary contact');
|
||||
// $clients = $clients->get(['clients.id', 'clients.user_id', 'clients.company_id']);
|
||||
// // $this->logMessage($clients->count().' clients without a single primary contact');
|
||||
|
||||
if ($this->option('fix') == 'true') {
|
||||
foreach ($clients as $client) {
|
||||
$this->logMessage("Fixing missing primary contacts #{$client->id}");
|
||||
// // if ($this->option('fix') == 'true') {
|
||||
// // foreach ($clients as $client) {
|
||||
// // $this->logMessage("Fixing missing primary contacts #{$client->id}");
|
||||
|
||||
$new_contact = ClientContactFactory::create($client->company_id, $client->user_id);
|
||||
$new_contact->client_id = $client->id;
|
||||
$new_contact->contact_key = Str::random(40);
|
||||
$new_contact->is_primary = true;
|
||||
$new_contact->save();
|
||||
}
|
||||
}
|
||||
// // $new_contact = ClientContactFactory::create($client->company_id, $client->user_id);
|
||||
// // $new_contact->client_id = $client->id;
|
||||
// // $new_contact->contact_key = Str::random(40);
|
||||
// // $new_contact->is_primary = true;
|
||||
// // $new_contact->save();
|
||||
// // }
|
||||
// // }
|
||||
|
||||
if ($clients->count() > 0) {
|
||||
$this->isValid = false;
|
||||
}
|
||||
// if ($clients->count() > 0) {
|
||||
// $this->isValid = false;
|
||||
// }
|
||||
}
|
||||
|
||||
private function checkFailedJobs()
|
||||
@ -307,32 +314,93 @@ class CheckData extends Command
|
||||
}
|
||||
}
|
||||
|
||||
// private function checkPaidToCompanyDates()
|
||||
// {
|
||||
// Company::cursor()->each(function ($company){
|
||||
|
||||
// $payments = Payment::where('is_deleted', 0)
|
||||
// ->where('company_id', $company->id)
|
||||
// ->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])
|
||||
// ->pluck('id');
|
||||
|
||||
// $unapplied = Payment::where('is_deleted', 0)
|
||||
// ->where('company_id', $company->id)
|
||||
// ->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
|
||||
// ->sum(\DB::Raw('amount - applied'));
|
||||
|
||||
// $paymentables = Paymentable::whereIn('payment_id', $payments)->sum(\DB::Raw('amount - refunded'));
|
||||
|
||||
// $client_paid_to_date = Client::where('company_id', $company->id)->where('is_deleted', 0)->withTrashed()->sum('paid_to_date');
|
||||
|
||||
// $total_payments = $paymentables + $unapplied;
|
||||
|
||||
// if (round($total_payments, 2) != round($client_paid_to_date, 2)) {
|
||||
// $this->wrong_paid_to_dates++;
|
||||
|
||||
// $this->logMessage($company->present()->name.' id = # '.$company->id." - Paid to date does not match Client Paid To Date = {$client_paid_to_date} - Invoice Payments = {$total_payments}");
|
||||
// }
|
||||
|
||||
// });
|
||||
|
||||
// }
|
||||
|
||||
private function checkPaidToDates()
|
||||
{
|
||||
$this->wrong_paid_to_dates = 0;
|
||||
$credit_total_applied = 0;
|
||||
|
||||
Client::withTrashed()->where('is_deleted', 0)->cursor()->each(function ($client) use ($credit_total_applied) {
|
||||
|
||||
$clients = DB::table('clients')
|
||||
->leftJoin('payments', function($join) {
|
||||
$join->on('payments.client_id', '=', 'clients.id')
|
||||
->where('payments.is_deleted', 0)
|
||||
->whereIn('payments.status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED]);
|
||||
})
|
||||
->where('clients.is_deleted',0)
|
||||
->where('clients.updated_at', '>', now()->subDays(2))
|
||||
->groupBy('clients.id')
|
||||
->havingRaw('clients.paid_to_date != sum(coalesce(payments.amount - payments.refunded, 0))')
|
||||
->get(['clients.id', 'clients.paid_to_date', DB::raw('sum(coalesce(payments.amount - payments.refunded, 0)) as amount')]);
|
||||
|
||||
/* Due to accounting differences we need to perform a second loop here to ensure there actually is an issue */
|
||||
$clients->each(function ($client_record) use ($credit_total_applied) {
|
||||
|
||||
$client = Client::find($client_record->id);
|
||||
|
||||
$total_invoice_payments = 0;
|
||||
|
||||
foreach ($client->invoices()->where('is_deleted', false)->where('status_id', '>', 1)->get() as $invoice) {
|
||||
|
||||
$total_amount = $invoice->payments()->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->get()->sum('pivot.amount');
|
||||
$total_refund = $invoice->payments()->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->get()->sum('pivot.refunded');
|
||||
$total_invoice_payments += $invoice->payments()
|
||||
->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
|
||||
->selectRaw('sum(paymentables.amount - paymentables.refunded) as p')
|
||||
->pluck('p')
|
||||
->first();
|
||||
|
||||
$total_invoice_payments += ($total_amount - $total_refund);
|
||||
}
|
||||
|
||||
//commented IN 27/06/2021 - sums ALL client payments AND the unapplied amounts to match the client paid to date
|
||||
$p = Payment::where('client_id', $client->id)
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
|
||||
->sum(DB::Raw('amount - applied'));
|
||||
|
||||
$total_invoice_payments += $p;
|
||||
|
||||
// 10/02/21
|
||||
foreach ($client->payments as $payment) {
|
||||
$credit_total_applied += $payment->paymentables()->where('paymentable_type', App\Models\Credit::class)->get()->sum(DB::raw('amount'));
|
||||
|
||||
$credit_total_applied += $payment->paymentables()
|
||||
->where('paymentable_type', App\Models\Credit::class)
|
||||
->selectRaw('sum(paymentables.amount - paymentables.refunded) as p')
|
||||
->pluck('p')
|
||||
->first();
|
||||
}
|
||||
|
||||
if ($credit_total_applied < 0) {
|
||||
$total_invoice_payments += $credit_total_applied;
|
||||
}
|
||||
|
||||
|
||||
if (round($total_invoice_payments, 2) != round($client->paid_to_date, 2)) {
|
||||
$this->wrong_paid_to_dates++;
|
||||
|
||||
@ -355,20 +423,26 @@ class CheckData extends Command
|
||||
{
|
||||
$this->wrong_balances = 0;
|
||||
|
||||
Client::cursor()->where('is_deleted', 0)->each(function ($client) {
|
||||
Client::cursor()->where('is_deleted', 0)->where('clients.updated_at', '>', now()->subDays(2))->each(function ($client) {
|
||||
|
||||
$client->invoices->where('is_deleted', false)->whereIn('status_id', '!=', Invoice::STATUS_DRAFT)->each(function ($invoice) use ($client) {
|
||||
$total_amount = $invoice->payments()->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->get()->sum('pivot.amount');
|
||||
$total_refund = $invoice->payments()->get()->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->sum('pivot.refunded');
|
||||
|
||||
$total_paid = $invoice->payments()
|
||||
->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
|
||||
->selectRaw('sum(paymentables.amount - paymentables.refunded) as p')
|
||||
->pluck('p')
|
||||
->first();
|
||||
|
||||
// $total_paid = $total_amount - $total_refund;
|
||||
|
||||
$total_credit = $invoice->credits()->get()->sum('amount');
|
||||
|
||||
$total_paid = $total_amount - $total_refund;
|
||||
$calculated_paid_amount = $invoice->amount - $invoice->balance - $total_credit;
|
||||
|
||||
if ((string)$total_paid != (string)($invoice->amount - $invoice->balance - $total_credit)) {
|
||||
$this->wrong_balances++;
|
||||
|
||||
$this->logMessage($client->present()->name.' - '.$client->id." - Total Amount = {$total_amount} != Calculated Total = {$calculated_paid_amount} - Total Refund = {$total_refund} Total credit = {$total_credit}");
|
||||
$this->logMessage($client->present()->name.' - '.$client->id." - Total Paid = {$total_paid} != Calculated Total = {$calculated_paid_amount}");
|
||||
|
||||
$this->isValid = false;
|
||||
}
|
||||
@ -379,12 +453,44 @@ class CheckData extends Command
|
||||
$this->logMessage("{$this->wrong_balances} clients with incorrect invoice balances");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// $clients = DB::table('clients')
|
||||
// ->leftJoin('invoices', function($join){
|
||||
// $join->on('invoices.client_id', '=', 'clients.id')
|
||||
// ->where('invoices.is_deleted',0)
|
||||
// ->where('invoices.status_id', '>', 1);
|
||||
// })
|
||||
// ->leftJoin('credits', function($join){
|
||||
// $join->on('credits.client_id', '=', 'clients.id')
|
||||
// ->where('credits.is_deleted',0)
|
||||
// ->where('credits.status_id', '>', 1);
|
||||
// })
|
||||
// ->leftJoin('payments', function($join) {
|
||||
// $join->on('payments.client_id', '=', 'clients.id')
|
||||
// ->where('payments.is_deleted', 0)
|
||||
// ->whereIn('payments.status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED]);
|
||||
// })
|
||||
// ->where('clients.is_deleted',0)
|
||||
// //->where('clients.updated_at', '>', now()->subDays(2))
|
||||
// ->groupBy('clients.id')
|
||||
// ->havingRaw('sum(coalesce(invoices.amount - invoices.balance - credits.amount)) != sum(coalesce(payments.amount - payments.refunded, 0))')
|
||||
// ->get(['clients.id', DB::raw('sum(coalesce(invoices.amount - invoices.balance - credits.amount)) as invoice_amount'), DB::raw('sum(coalesce(payments.amount - payments.refunded, 0)) as payment_amount')]);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private function checkClientBalances()
|
||||
{
|
||||
$this->wrong_balances = 0;
|
||||
$this->wrong_paid_to_dates = 0;
|
||||
|
||||
foreach (Client::cursor()->where('is_deleted', 0) as $client) {
|
||||
foreach (Client::cursor()->where('is_deleted', 0)->where('clients.updated_at', '>', now()->subDays(2)) as $client) {
|
||||
//$invoice_balance = $client->invoices->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance');
|
||||
$invoice_balance = Invoice::where('client_id', $client->id)->where('is_deleted', false)->where('status_id', '>', 1)->withTrashed()->sum('balance');
|
||||
$credit_balance = Credit::where('client_id', $client->id)->where('is_deleted', false)->withTrashed()->sum('balance');
|
||||
@ -418,12 +524,9 @@ class CheckData extends Command
|
||||
$this->wrong_balances = 0;
|
||||
$this->wrong_paid_to_dates = 0;
|
||||
|
||||
foreach (Client::where('is_deleted', 0)->cursor() as $client) {
|
||||
$invoice_balance = $client->invoices()->where('is_deleted', false)->where('status_id', '>', 1)->get()->sum('balance');
|
||||
$credit_balance = $client->credits()->where('is_deleted', false)->get()->sum('balance');
|
||||
|
||||
// if($client->balance != $invoice_balance)
|
||||
// $invoice_balance -= $credit_balance;//doesn't make sense to remove the credit amount
|
||||
foreach (Client::where('is_deleted', 0)->where('clients.updated_at', '>', now()->subDays(2))->cursor() as $client) {
|
||||
$invoice_balance = $client->invoices()->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance');
|
||||
$credit_balance = $client->credits()->where('is_deleted', false)->sum('balance');
|
||||
|
||||
$ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first();
|
||||
|
||||
|
@ -60,36 +60,36 @@ class SubdomainFill extends Command
|
||||
});
|
||||
|
||||
|
||||
$db1 = Company::on('db-ninja-01')->get();
|
||||
// $db1 = Company::on('db-ninja-01')->get();
|
||||
|
||||
$db1->each(function ($company){
|
||||
// $db1->each(function ($company){
|
||||
|
||||
$db2 = Company::on('db-ninja-02a')->find($company->id);
|
||||
// $db2 = Company::on('db-ninja-02a')->find($company->id);
|
||||
|
||||
if($db2)
|
||||
{
|
||||
$db2->subdomain = $company->subdomain;
|
||||
$db2->save();
|
||||
}
|
||||
// if($db2)
|
||||
// {
|
||||
// $db2->subdomain = $company->subdomain;
|
||||
// $db2->save();
|
||||
// }
|
||||
|
||||
});
|
||||
// });
|
||||
|
||||
|
||||
$db1 = null;
|
||||
$db2 = null;
|
||||
// $db1 = null;
|
||||
// $db2 = null;
|
||||
|
||||
$db2 = Company::on('db-ninja-02')->get();
|
||||
// $db2 = Company::on('db-ninja-02')->get();
|
||||
|
||||
$db2->each(function ($company){
|
||||
// $db2->each(function ($company){
|
||||
|
||||
$db1 = Company::on('db-ninja-01a')->find($company->id);
|
||||
// $db1 = Company::on('db-ninja-01a')->find($company->id);
|
||||
|
||||
if($db1)
|
||||
{
|
||||
$db1->subdomain = $company->subdomain;
|
||||
$db1->save();
|
||||
}
|
||||
});
|
||||
// if($db1)
|
||||
// {
|
||||
// $db1->subdomain = $company->subdomain;
|
||||
// $db1->save();
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ class Handler extends ExceptionHandler
|
||||
return;
|
||||
}
|
||||
|
||||
if(Ninja::isHosted()){
|
||||
if(Ninja::isHosted() && !($exception instanceof ValidationException)){
|
||||
|
||||
app('sentry')->configureScope(function (Scope $scope): void {
|
||||
|
||||
|
@ -35,8 +35,12 @@ class ContactLoginController extends Controller
|
||||
|
||||
public function showLoginForm(Request $request)
|
||||
{
|
||||
if ($request->subdomain) {
|
||||
$company = Company::where('subdomain', $request->subdomain)->first();
|
||||
//if we are on the root domain invoicing.co do not show any company logos
|
||||
if(Ninja::isHosted() && count(explode('.', request()->getHost())) == 2){
|
||||
$company = null;
|
||||
}elseif (strpos($request->getHost(), 'invoicing.co') !== false) {
|
||||
$subdomain = explode('.', $request->getHost())[0];
|
||||
$company = Company::where('subdomain', $subdomain)->first();
|
||||
} elseif (Ninja::isSelfHost()) {
|
||||
$company = Account::first()->default_company;
|
||||
} else {
|
||||
|
@ -201,6 +201,14 @@ class LoginController extends BaseController
|
||||
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||
}
|
||||
|
||||
/* If for some reason we lose state on the default company ie. a company is deleted - always make sure we can default to a company*/
|
||||
if(!$user->account->default_company){
|
||||
$account = $user->account;
|
||||
$account->default_company_id = $user->companies->first()->id;
|
||||
$account->save();
|
||||
$user = $user->fresh();
|
||||
}
|
||||
|
||||
$user->setCompany($user->account->default_company);
|
||||
|
||||
$this->setLoginCache($user);
|
||||
|
@ -319,7 +319,8 @@ class PaymentController extends Controller
|
||||
* @return Response The response view
|
||||
*/
|
||||
public function credit_response(Request $request)
|
||||
{
|
||||
{
|
||||
|
||||
$payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$request->input('payment_hash')])->first();
|
||||
|
||||
/* Hydrate the $payment */
|
||||
|
@ -14,7 +14,7 @@ namespace App\Http\Controllers\ClientPortal;
|
||||
|
||||
use App\Events\Payment\Methods\MethodDeleted;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ClientPortal\CreatePaymentMethodRequest;
|
||||
use App\Http\Requests\ClientPortal\PaymentMethod\CreatePaymentMethodRequest;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
@ -52,7 +52,7 @@ class PaymentMethodController extends Controller
|
||||
|
||||
$data['gateway'] = $gateway;
|
||||
$data['client'] = auth()->user()->client;
|
||||
|
||||
|
||||
return $gateway
|
||||
->driver(auth()->user()->client)
|
||||
->setPaymentMethod($request->query('method'))
|
||||
@ -93,7 +93,7 @@ class PaymentMethodController extends Controller
|
||||
public function verify(ClientGatewayToken $payment_method)
|
||||
{
|
||||
// $gateway = $this->getClientGateway();
|
||||
|
||||
|
||||
return $payment_method->gateway
|
||||
->driver(auth()->user()->client)
|
||||
->setPaymentMethod(request()->query('method'))
|
||||
|
@ -119,7 +119,7 @@ class QuoteController extends Controller
|
||||
|
||||
if ($process) {
|
||||
foreach ($quotes as $quote) {
|
||||
$quote->service()->approve(auth()->user())->save();
|
||||
$quote->service()->approve(auth('contact')->user())->save();
|
||||
event(new QuoteWasApproved(auth('contact')->user(), $quote, $quote->company, Ninja::eventVars()));
|
||||
|
||||
if (request()->has('signature') && !is_null(request()->signature) && !empty(request()->signature)) {
|
||||
|
@ -476,8 +476,10 @@ class CompanyController extends BaseController
|
||||
{
|
||||
$company_count = $company->account->companies->count();
|
||||
$account = $company->account;
|
||||
$account_key = $account->key;
|
||||
|
||||
if ($company_count == 1) {
|
||||
|
||||
$company->company_users->each(function ($company_user) {
|
||||
$company_user->user->forceDelete();
|
||||
$company_user->forceDelete();
|
||||
@ -485,9 +487,13 @@ class CompanyController extends BaseController
|
||||
|
||||
$account->delete();
|
||||
|
||||
if(Ninja::isHosted())
|
||||
\Modules\Admin\Jobs\Account\NinjaDeletedAccount::dispatch($account_key);
|
||||
|
||||
LightLogs::create(new AccountDeleted())
|
||||
->increment()
|
||||
->batch();
|
||||
|
||||
} else {
|
||||
$company_id = $company->id;
|
||||
|
||||
|
@ -11,14 +11,17 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Exceptions\NonExistingMigrationFile;
|
||||
use App\Http\Requests\Import\ImportJsonRequest;
|
||||
use App\Jobs\Company\CompanyExport;
|
||||
use App\Jobs\Company\CompanyImport;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
use ZipArchive;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ImportJsonController extends BaseController
|
||||
{
|
||||
@ -60,40 +63,19 @@ class ImportJsonController extends BaseController
|
||||
public function import(ImportJsonRequest $request)
|
||||
{
|
||||
|
||||
$import_file = $request->file('files');
|
||||
$file_location = $request->file('files')
|
||||
->storeAs(
|
||||
'migrations',
|
||||
$request->file('files')->getClientOriginalName()
|
||||
);
|
||||
|
||||
$contents = $this->unzipFile($import_file->getPathname());
|
||||
|
||||
$hash = Str::random(32);
|
||||
|
||||
nlog($hash);
|
||||
|
||||
Cache::put( $hash, base64_encode( $contents ), 3600 );
|
||||
|
||||
CompanyImport::dispatch(auth()->user()->getCompany(), auth()->user(), $hash, $request->except('files'))->delay(now()->addMinutes(1));
|
||||
if(Ninja::isHosted())
|
||||
CompanyImport::dispatch(auth()->user()->getCompany(), auth()->user(), $file_location, $request->except('files'))->onQueue('migration');
|
||||
else
|
||||
CompanyImport::dispatch(auth()->user()->getCompany(), auth()->user(), $file_location, $request->except('files'));
|
||||
|
||||
return response()->json(['message' => 'Processing'], 200);
|
||||
|
||||
}
|
||||
|
||||
private function unzipFile($file_contents)
|
||||
{
|
||||
$zip = new ZipArchive();
|
||||
$archive = $zip->open($file_contents);
|
||||
|
||||
$filename = pathinfo($file_contents, PATHINFO_FILENAME);
|
||||
$zip->extractTo(public_path("storage/backups/{$filename}"));
|
||||
$zip->close();
|
||||
$file_location = public_path("storage/backups/$filename/backup.json");
|
||||
|
||||
if (! file_exists($file_location))
|
||||
throw new NonExistingMigrationFile('Backup file does not exist, or is corrupted.');
|
||||
|
||||
$data = file_get_contents($file_location);
|
||||
|
||||
unlink($file_contents);
|
||||
unlink($file_location);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
@ -387,7 +387,6 @@ class MigrationController extends BaseController
|
||||
else
|
||||
StartMigration::dispatch($migration_file, $user, $fresh_company);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -208,9 +208,6 @@ class PaymentController extends BaseController
|
||||
{
|
||||
$payment = $this->payment_repo->save($request->all(), PaymentFactory::create(auth()->user()->company()->id, auth()->user()->id));
|
||||
|
||||
if($request->has('email_receipt') && $request->input('email_receipt') == 'true' && !$payment->client->getSetting('client_manual_payment_notification'))
|
||||
$payment->service()->sendEmail();
|
||||
|
||||
return $this->itemResponse($payment);
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,6 @@ class PaymentWebhookController extends Controller
|
||||
if(!$client)
|
||||
return response()->json(['message' => 'Client record not found.'], 400);
|
||||
|
||||
|
||||
return $request->getCompanyGateway()
|
||||
->driver($client)
|
||||
->processWebhookRequest($request, $payment);
|
||||
|
@ -155,7 +155,7 @@ class PreviewController extends BaseController
|
||||
$t = app('translator');
|
||||
$t->replace(Ninja::transformTranslations(auth()->user()->company()->settings));
|
||||
|
||||
DB::beginTransaction();
|
||||
DB::connection(config('database.default'))->beginTransaction();
|
||||
|
||||
$client = Client::factory()->create([
|
||||
'user_id' => auth()->user()->id,
|
||||
@ -230,7 +230,7 @@ class PreviewController extends BaseController
|
||||
|
||||
$file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), auth()->user()->company());
|
||||
|
||||
DB::rollBack();
|
||||
DB::connection(config('database.default'))->rollBack();
|
||||
|
||||
$response = Response::make($file_path, 200);
|
||||
$response->header('Content-Type', 'application/pdf');
|
||||
|
@ -109,11 +109,11 @@ class SetupController extends Controller
|
||||
'REQUIRE_HTTPS' => $request->input('https') ? 'true' : 'false',
|
||||
'APP_DEBUG' => 'false',
|
||||
|
||||
'DB_HOST1' => $request->input('db_host'),
|
||||
'DB_PORT1' => $request->input('db_port'),
|
||||
'DB_DATABASE1' => $request->input('db_database'),
|
||||
'DB_USERNAME1' => $request->input('db_username'),
|
||||
'DB_PASSWORD1' => $request->input('db_password'),
|
||||
'DB_HOST' => $request->input('db_host'),
|
||||
'DB_PORT' => $request->input('db_port'),
|
||||
'DB_DATABASE' => $request->input('db_database'),
|
||||
'DB_USERNAME' => $request->input('db_username'),
|
||||
'DB_PASSWORD' => $request->input('db_password'),
|
||||
|
||||
'MAIL_MAILER' => $mail_driver,
|
||||
'MAIL_PORT' => $request->input('mail_port'),
|
||||
@ -125,6 +125,7 @@ class SetupController extends Controller
|
||||
'MAIL_PASSWORD' => $request->input('mail_password'),
|
||||
|
||||
'NINJA_ENVIRONMENT' => 'selfhost',
|
||||
'DB_CONNECTION' => 'mysql',
|
||||
];
|
||||
|
||||
if (config('ninja.db.multi_db_enabled')) {
|
||||
@ -133,11 +134,11 @@ class SetupController extends Controller
|
||||
|
||||
if (config('ninja.preconfigured_install')) {
|
||||
// Database connection was already configured. Don't let the user override it.
|
||||
unset($env_values['DB_HOST1']);
|
||||
unset($env_values['DB_PORT1']);
|
||||
unset($env_values['DB_DATABASE1']);
|
||||
unset($env_values['DB_USERNAME1']);
|
||||
unset($env_values['DB_PASSWORD1']);
|
||||
unset($env_values['DB_HOST']);
|
||||
unset($env_values['DB_PORT']);
|
||||
unset($env_values['DB_DATABASE']);
|
||||
unset($env_values['DB_USERNAME']);
|
||||
unset($env_values['DB_PASSWORD']);
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -30,6 +30,7 @@ use App\Http\Middleware\QueryLogging;
|
||||
use App\Http\Middleware\RedirectIfAuthenticated;
|
||||
use App\Http\Middleware\SetDb;
|
||||
use App\Http\Middleware\SetDbByCompanyKey;
|
||||
use App\Http\Middleware\SetDocumentDb;
|
||||
use App\Http\Middleware\SetDomainNameDb;
|
||||
use App\Http\Middleware\SetEmailDb;
|
||||
use App\Http\Middleware\SetInviteDb;
|
||||
@ -158,6 +159,7 @@ class Kernel extends HttpKernel
|
||||
'contact_key_login' => ContactKeyLogin::class,
|
||||
'check_client_existence' => CheckClientExistence::class,
|
||||
'user_verified' => UserVerified::class,
|
||||
'document_db' => SetDocumentDb::class,
|
||||
];
|
||||
|
||||
|
||||
|
@ -237,7 +237,7 @@ class BillingPortalPurchase extends Component
|
||||
$client_repo = new ClientRepository(new ClientContactRepository());
|
||||
|
||||
$data = [
|
||||
'name' => 'Client Name',
|
||||
'name' => '',
|
||||
'contacts' => [
|
||||
['email' => $this->email],
|
||||
],
|
||||
|
@ -26,7 +26,7 @@ class CreditsTable extends Component
|
||||
public $per_page = 10;
|
||||
|
||||
public $company;
|
||||
|
||||
|
||||
public function mount()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
@ -34,9 +34,15 @@ class CreditsTable extends Component
|
||||
|
||||
public function render()
|
||||
{
|
||||
|
||||
$query = Credit::query()
|
||||
->where('client_id', auth('contact')->user()->client->id)
|
||||
->where('company_id', $this->company->id)
|
||||
->where('status_id', '<>', Credit::STATUS_DRAFT)
|
||||
->where(function ($query){
|
||||
$query->whereDate('due_date', '<=', now())
|
||||
->orWhereNull('due_date');
|
||||
})
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||
->paginate($this->per_page);
|
||||
|
||||
|
@ -44,6 +44,7 @@ class InvoicesTable extends Component
|
||||
|
||||
$query = Invoice::query()
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', false);
|
||||
|
||||
if (in_array('paid', $this->status)) {
|
||||
|
56
app/Http/Livewire/PaymentMethods/UpdateDefaultMethod.php
Normal file
56
app/Http/Livewire/PaymentMethods/UpdateDefaultMethod.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Livewire\PaymentMethods;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use Livewire\Component;
|
||||
|
||||
class UpdateDefaultMethod extends Component
|
||||
{
|
||||
/** @var \App\Models\Company */
|
||||
public $company;
|
||||
|
||||
/** @var \App\Models\ClientGatewayToken */
|
||||
public $token;
|
||||
|
||||
/** @var \App\Models\Client */
|
||||
public $client;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->company = $this->client->company;
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$this->is_disabled = $this->token->is_default;
|
||||
}
|
||||
|
||||
public function makeDefault(): void
|
||||
{
|
||||
if ($this->token->is_default) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->client->gateway_tokens()->update(['is_default' => 0]);
|
||||
|
||||
$this->token->is_default = 1;
|
||||
$this->token->save();
|
||||
|
||||
$this->emit('UpdateDefaultMethod::method-updated');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return render('components.livewire.update-default-payment-method');
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ class PaymentMethodsTable extends Component
|
||||
{
|
||||
$query = ClientGatewayToken::query()
|
||||
->with('gateway_type')
|
||||
->where('company_id', $this->company->id)
|
||||
->where('client_id', $this->client->id)
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||
->paginate($this->per_page);
|
||||
|
@ -41,6 +41,7 @@ class PaymentsTable extends Component
|
||||
{
|
||||
$query = Payment::query()
|
||||
->with('type', 'client')
|
||||
->where('company_id', $this->company->id)
|
||||
->where('client_id', auth('contact')->user()->client->id)
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||
->paginate($this->per_page);
|
||||
|
@ -45,6 +45,7 @@ class QuotesTable extends Component
|
||||
}
|
||||
|
||||
$query = $query
|
||||
->where('company_id', $this->company->id)
|
||||
->where('client_id', auth('contact')->user()->client->id)
|
||||
->where('status_id', '<>', Quote::STATUS_DRAFT)
|
||||
->paginate($this->per_page);
|
||||
|
34
app/Http/Livewire/RecurringInvoices/UpdateAutoBilling.php
Normal file
34
app/Http/Livewire/RecurringInvoices/UpdateAutoBilling.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Livewire\RecurringInvoices;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class UpdateAutoBilling extends Component
|
||||
{
|
||||
/** @var \App\Models\RecurringInvoice */
|
||||
public $invoice;
|
||||
|
||||
public function updateAutoBilling(): void
|
||||
{
|
||||
if ($this->invoice->auto_bill === 'optin' || $this->invoice->auto_bill === 'optout') {
|
||||
$this->invoice->auto_bill_enabled = !$this->invoice->auto_bill_enabled;
|
||||
$this->invoice->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return render('components.livewire.recurring-invoices-switch-autobilling');
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\Traits\WithSorting;
|
||||
use Livewire\Component;
|
||||
@ -23,8 +24,12 @@ class RecurringInvoicesTable extends Component
|
||||
|
||||
public $per_page = 10;
|
||||
|
||||
public $company;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$this->sort_asc = false;
|
||||
|
||||
$this->sort_field = 'date';
|
||||
@ -36,6 +41,7 @@ class RecurringInvoicesTable extends Component
|
||||
|
||||
$query = $query
|
||||
->where('client_id', auth('contact')->user()->client->id)
|
||||
->where('company_id', $this->company->id)
|
||||
->whereIn('status_id', [RecurringInvoice::STATUS_PENDING, RecurringInvoice::STATUS_ACTIVE, RecurringInvoice::STATUS_PAUSED,RecurringInvoice::STATUS_COMPLETED])
|
||||
->orderBy('status_id', 'asc')
|
||||
->with('client')
|
||||
|
@ -90,24 +90,40 @@ class SubscriptionPlanSwitch extends Component
|
||||
|
||||
$this->state['show_loading_bar'] = true;
|
||||
|
||||
$this->state['invoice'] = $this->target->service()->createChangePlanInvoice([
|
||||
$payment_required = $this->target->service()->changePlanPaymentCheck([
|
||||
'recurring_invoice' => $this->recurring_invoice,
|
||||
'subscription' => $this->subscription,
|
||||
'target' => $this->target,
|
||||
'hash' => $this->hash,
|
||||
]);
|
||||
|
||||
Cache::put($this->hash, [
|
||||
'subscription_id' => $this->target->id,
|
||||
'target_id' => $this->target->id,
|
||||
'recurring_invoice' => $this->recurring_invoice->id,
|
||||
'client_id' => $this->recurring_invoice->client->id,
|
||||
'invoice_id' => $this->state['invoice']->id,
|
||||
'context' => 'change_plan',
|
||||
now()->addMinutes(60)]
|
||||
);
|
||||
if($payment_required)
|
||||
{
|
||||
|
||||
$this->state['invoice'] = $this->target->service()->createChangePlanInvoice([
|
||||
'recurring_invoice' => $this->recurring_invoice,
|
||||
'subscription' => $this->subscription,
|
||||
'target' => $this->target,
|
||||
'hash' => $this->hash,
|
||||
]);
|
||||
|
||||
Cache::put($this->hash, [
|
||||
'subscription_id' => $this->target->id,
|
||||
'target_id' => $this->target->id,
|
||||
'recurring_invoice' => $this->recurring_invoice->id,
|
||||
'client_id' => $this->recurring_invoice->client->id,
|
||||
'invoice_id' => $this->state['invoice']->id,
|
||||
'context' => 'change_plan',
|
||||
now()->addMinutes(60)]
|
||||
);
|
||||
|
||||
$this->state['payment_initialised'] = true;
|
||||
|
||||
}
|
||||
else
|
||||
$this->handlePaymentNotRequired();
|
||||
|
||||
|
||||
$this->state['payment_initialised'] = true;
|
||||
|
||||
$this->emit('beforePaymentEventsCompleted');
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ class SubscriptionRecurringInvoicesTable extends Component
|
||||
{
|
||||
$query = RecurringInvoice::query()
|
||||
->where('client_id', auth('contact')->user()->client->id)
|
||||
->where('company_id', $this->company->id)
|
||||
->whereNotNull('subscription_id')
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||
->paginate($this->per_page);
|
||||
|
@ -26,17 +26,27 @@ class TasksTable extends Component
|
||||
public $per_page = 10;
|
||||
|
||||
public $company;
|
||||
|
||||
|
||||
public function mount()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
}
|
||||
|
||||
|
||||
public function render()
|
||||
{
|
||||
$query = Task::query()
|
||||
->where('client_id', auth('contact')->user()->client->id)
|
||||
->whereNotNull('invoice_id')
|
||||
->where('company_id', $this->company->id)
|
||||
->where('client_id', auth('contact')->user()->client->id);
|
||||
|
||||
if ($this->company->getSetting('show_all_tasks_client_portal') === 'invoiced') {
|
||||
$query = $query->whereNotNull('invoice_id');
|
||||
}
|
||||
|
||||
if ($this->company->getSetting('show_all_tasks_client_portal') === 'uninvoiced') {
|
||||
$query = $query->whereNull('invoice_id');
|
||||
}
|
||||
|
||||
$query = $query
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||
->paginate($this->per_page);
|
||||
|
||||
|
@ -29,6 +29,7 @@ class CheckClientExistence
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$multiple_contacts = ClientContact::query()
|
||||
->with('company','client')
|
||||
->where('email', auth('contact')->user()->email)
|
||||
->whereNotNull('email')
|
||||
->where('email', '<>', '')
|
||||
|
@ -33,7 +33,8 @@ class ContactRegister
|
||||
|
||||
if($company)
|
||||
{
|
||||
abort_unless($company->client_can_register, 404);
|
||||
if(! $company->client_can_register)
|
||||
abort(400, 'Registration disabled');
|
||||
|
||||
$request->merge(['key' => $company->company_key]);
|
||||
|
||||
@ -49,7 +50,9 @@ class ContactRegister
|
||||
|
||||
if($company = Company::where($query)->first())
|
||||
{
|
||||
abort_unless($company->client_can_register, 404);
|
||||
|
||||
if(! $company->client_can_register)
|
||||
abort(400, 'Registration disabled');
|
||||
|
||||
$request->merge(['key' => $company->company_key]);
|
||||
|
||||
@ -62,7 +65,10 @@ class ContactRegister
|
||||
if ($request->route()->parameter('company_key') && Ninja::isSelfHost()) {
|
||||
$company = Company::where('company_key', $request->company_key)->firstOrFail();
|
||||
|
||||
abort_unless($company->client_can_register, 404);
|
||||
if(! (bool)$company->client_can_register);
|
||||
abort(400, 'Registration disabled');
|
||||
|
||||
$request->merge(['key' => $company->company_key]);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@ -72,7 +78,8 @@ class ContactRegister
|
||||
if (!$request->route()->parameter('company_key') && Ninja::isSelfHost()) {
|
||||
$company = Account::first()->default_company;
|
||||
|
||||
abort_unless($company->client_can_register, 404);
|
||||
if(! $company->client_can_register)
|
||||
abort(400, 'Registration disabled');
|
||||
|
||||
$request->merge(['key' => $company->company_key]);
|
||||
|
||||
|
44
app/Http/Middleware/SetDocumentDb.php
Normal file
44
app/Http/Middleware/SetDocumentDb.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use stdClass;
|
||||
|
||||
class SetDocumentDb
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$error = [
|
||||
'message' => 'Document not set or not found',
|
||||
'errors' => new stdClass,
|
||||
];
|
||||
|
||||
if (config('ninja.db.multi_db_enabled')) {
|
||||
|
||||
if (! MultiDB::documentFindAndSetDb($request->segment(2)))
|
||||
return response()->json($error, 400);
|
||||
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
@ -37,8 +37,9 @@ class StoreClientGatewayTokenRequest extends Request
|
||||
|
||||
public function rules()
|
||||
{
|
||||
//ensure client is present
|
||||
$rules = [
|
||||
'client_id' => 'required',
|
||||
'client_id' => 'required|exists:clients,id,company_id,'.auth()->user()->company()->id,
|
||||
'company_gateway_id' => 'required',
|
||||
'gateway_type_id' => 'required|integer',
|
||||
'meta' => 'required',
|
||||
|
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\ClientPortal;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CreatePaymentMethodRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return auth()->user()->client->getCreditCardGateway() ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\ClientPortal\PaymentMethod;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\Client;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use function auth;
|
||||
use function collect;
|
||||
|
||||
class CreatePaymentMethodRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
/** @var Client $client */
|
||||
$client = auth()->user()->client;
|
||||
|
||||
$available_methods = [];
|
||||
|
||||
collect($client->service()->getPaymentMethods(1))
|
||||
->filter(function ($method) use (&$available_methods) {
|
||||
$available_methods[] = $method['gateway_type_id'];
|
||||
});
|
||||
|
||||
if (in_array($this->query('method'), $available_methods)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Requests\ClientPortal;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Account;
|
||||
use App\Models\Company;
|
||||
use App\Utils\Ninja;
|
||||
@ -26,20 +27,27 @@ class RegisterRequest extends FormRequest
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
$rules = [
|
||||
'first_name' => ['required', 'string', 'max:255'],
|
||||
'last_name' => ['required', 'string', 'max:255'],
|
||||
'phone' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'email:rfc,dns', 'max:255'],
|
||||
'password' => ['required', 'string', 'min:6', 'confirmed'],
|
||||
];
|
||||
|
||||
if ($this->company()->settings->client_portal_terms || $this->company()->settings->client_portal_privacy_policy) {
|
||||
$rules['terms'] = ['required'];
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
if ($this->subdomain) {
|
||||
return Company::where('subdomain', $this->subdomain)->firstOrFail();
|
||||
}
|
||||
|
||||
//this should be all we need, the rest SHOULD be redundant because of our Middleware
|
||||
if ($this->key)
|
||||
return Company::where('company_key', $this->key)->first();
|
||||
|
||||
if ($this->company_key) {
|
||||
return Company::where('company_key', $this->company_key)->firstOrFail();
|
||||
@ -48,11 +56,34 @@ class RegisterRequest extends FormRequest
|
||||
if (!$this->route()->parameter('company_key') && Ninja::isSelfHost()) {
|
||||
$company = Account::first()->default_company;
|
||||
|
||||
abort_unless($company->client_can_register, 404);
|
||||
if(!$company->client_can_register)
|
||||
abort(403, "This page is restricted");
|
||||
|
||||
return $company;
|
||||
}
|
||||
|
||||
abort(404, 'Register request not found.');
|
||||
if (Ninja::isHosted()) {
|
||||
|
||||
$subdomain = explode('.', $this->getHost())[0];
|
||||
|
||||
$query = [
|
||||
'subdomain' => $subdomain,
|
||||
'portal_mode' => 'subdomain',
|
||||
];
|
||||
|
||||
if($company = MultiDB::findAndSetDbByDomain($query))
|
||||
return $company;
|
||||
|
||||
$query = [
|
||||
'portal_domain' => $this->getSchemeAndHttpHost(),
|
||||
'portal_mode' => 'domain',
|
||||
];
|
||||
|
||||
if($company = MultiDB::findAndSetDbByDomain($query))
|
||||
return $company;
|
||||
|
||||
}
|
||||
|
||||
abort(400, 'Register request not found.');
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ use App\Models\Account;
|
||||
use App\Models\Timezone;
|
||||
use App\Notifications\Ninja\NewAccountCreated;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\User\LoginCache;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@ -39,6 +40,7 @@ use Turbo124\Beacon\Facades\LightLogs;
|
||||
class CreateAccount
|
||||
{
|
||||
use Dispatchable;
|
||||
use LoginCache;
|
||||
|
||||
protected $request;
|
||||
|
||||
@ -92,6 +94,8 @@ class CreateAccount
|
||||
}
|
||||
|
||||
$spaa9f78->setCompany($sp035a66);
|
||||
$this->setLoginCache($spaa9f78);
|
||||
|
||||
$spafe62e = isset($this->request['token_name']) ? $this->request['token_name'] : request()->server('HTTP_USER_AGENT');
|
||||
$sp2d97e8 = CreateCompanyToken::dispatchNow($sp035a66, $spaa9f78, $spafe62e);
|
||||
|
||||
|
@ -20,6 +20,7 @@ use App\Libraries\MultiDB;
|
||||
use App\Mail\DownloadBackup;
|
||||
use App\Mail\DownloadInvoices;
|
||||
use App\Mail\Import\CompanyImportFailure;
|
||||
use App\Mail\Import\ImportCompleted;
|
||||
use App\Models\Activity;
|
||||
use App\Models\Backup;
|
||||
use App\Models\Client;
|
||||
@ -56,6 +57,7 @@ use App\Models\Vendor;
|
||||
use App\Models\VendorContact;
|
||||
use App\Models\Webhook;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\TempFile;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
@ -73,6 +75,10 @@ class CompanyImport implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public $timeout = 0;
|
||||
|
||||
protected $current_app_version;
|
||||
|
||||
private $account;
|
||||
@ -81,7 +87,7 @@ class CompanyImport implements ShouldQueue
|
||||
|
||||
public $user;
|
||||
|
||||
private $hash;
|
||||
private $file_location;
|
||||
|
||||
public $backup_file;
|
||||
|
||||
@ -142,11 +148,11 @@ class CompanyImport implements ShouldQueue
|
||||
* @param string $hash - the cache hash of the import data.
|
||||
* @param array $request->all()
|
||||
*/
|
||||
public function __construct(Company $company, User $user, string $hash, array $request_array)
|
||||
public function __construct(Company $company, User $user, string $file_location, array $request_array)
|
||||
{
|
||||
$this->company = $company;
|
||||
$this->user = $user;
|
||||
$this->hash = $hash;
|
||||
$this->file_location = $file_location;
|
||||
$this->request_array = $request_array;
|
||||
$this->current_app_version = config('ninja.app_version');
|
||||
}
|
||||
@ -160,14 +166,17 @@ class CompanyImport implements ShouldQueue
|
||||
$this->company_owner = $this->company->owner();
|
||||
|
||||
nlog("Company ID = {$this->company->id}");
|
||||
nlog("Hash ID = {$this->hash}");
|
||||
nlog("file_location ID = {$this->file_location}");
|
||||
|
||||
$this->backup_file = Cache::get($this->hash);
|
||||
// $this->backup_file = Cache::get($this->hash);
|
||||
|
||||
if ( empty( $this->backup_file ) )
|
||||
if ( empty( $this->file_location ) )
|
||||
throw new \Exception('No import data found, has the cache expired?');
|
||||
|
||||
$this->backup_file = json_decode(base64_decode($this->backup_file));
|
||||
// $this->backup_file = json_decode(file_get_contents($this->file_location));
|
||||
$tmp_file = $this->unzipFile();
|
||||
|
||||
$this->backup_file = json_decode(file_get_contents($tmp_file));
|
||||
|
||||
// nlog($this->backup_file);
|
||||
$this->checkUserCount();
|
||||
@ -185,6 +194,17 @@ class CompanyImport implements ShouldQueue
|
||||
->purgeCompanyData()
|
||||
->importData();
|
||||
|
||||
$data = [
|
||||
'errors' => []
|
||||
];
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new ImportCompleted($this->company, $data);
|
||||
$nmo->company = $this->company;
|
||||
$nmo->settings = $this->company->settings;
|
||||
$nmo->to_user = $this->company->owner();
|
||||
NinjaMailerJob::dispatchNow($nmo);
|
||||
|
||||
}
|
||||
catch(\Exception $e){
|
||||
|
||||
@ -194,8 +214,35 @@ class CompanyImport implements ShouldQueue
|
||||
|
||||
}
|
||||
|
||||
unlink($tmp_file);
|
||||
|
||||
}
|
||||
|
||||
private function unzipFile()
|
||||
{
|
||||
|
||||
if(mime_content_type(Storage::path($this->file_location)) == 'text/plain')
|
||||
return Storage::path($this->file_location);
|
||||
|
||||
$path = TempFile::filePath(Storage::get($this->file_location), basename($this->file_location));
|
||||
|
||||
$zip = new ZipArchive();
|
||||
$archive = $zip->open($path);
|
||||
|
||||
$file_path = sys_get_temp_dir().'/'.sha1(microtime());
|
||||
|
||||
$zip->extractTo($file_path);
|
||||
$zip->close();
|
||||
$file_location = "{$file_path}/backup.json";
|
||||
|
||||
if (! file_exists($file_location))
|
||||
throw new NonExistingMigrationFile('Backup file does not exist, or is corrupted.');
|
||||
|
||||
return $file_location;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* On the hosted platform we cannot allow the
|
||||
* import to start if there are users > plan number
|
||||
@ -293,14 +340,9 @@ class CompanyImport implements ShouldQueue
|
||||
|
||||
if($this->pre_flight_checks_pass === false)
|
||||
{
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new CompanyImportFailure($this->company, $this->message);
|
||||
$nmo->company = $this->company;
|
||||
$nmo->settings = $this->company->settings;
|
||||
$nmo->to_user = $this->company->owner();
|
||||
NinjaMailerJob::dispatchNow($nmo);
|
||||
|
||||
nlog($this->message);
|
||||
$this->sendImportMail($this->message);
|
||||
|
||||
throw new \Exception($this->message);
|
||||
}
|
||||
|
||||
@ -362,7 +404,7 @@ class CompanyImport implements ShouldQueue
|
||||
|
||||
}
|
||||
|
||||
nlog("finished importing company data");
|
||||
nlog("finished importing company data");
|
||||
|
||||
return $this;
|
||||
|
||||
@ -1116,6 +1158,10 @@ class CompanyImport implements ShouldQueue
|
||||
|
||||
foreach($this->backup_file->{$object_property} as $obj)
|
||||
{
|
||||
|
||||
if(is_null($obj))
|
||||
continue;
|
||||
|
||||
/* Remove unwanted keys*/
|
||||
$obj_array = (array)$obj;
|
||||
foreach($unset as $un){
|
||||
@ -1236,12 +1282,22 @@ class CompanyImport implements ShouldQueue
|
||||
|
||||
if (! array_key_exists($resource, $this->ids)) {
|
||||
// nlog($this->ids);
|
||||
|
||||
$this->sendImportMail("The Import failed due to missing data in the import file. Resource {$resource} not available.");
|
||||
throw new \Exception("Resource {$resource} not available.");
|
||||
}
|
||||
|
||||
if (! array_key_exists("{$old}", $this->ids[$resource])) {
|
||||
// nlog($this->ids[$resource]);
|
||||
nlog("searching for {$old} in {$resource}");
|
||||
|
||||
nlog("If we are missing a user - default to the company owner");
|
||||
|
||||
if($resource == 'users')
|
||||
return $this->company_owner->id;
|
||||
|
||||
$this->sendImportMail("The Import failed due to missing data in the import file. Resource {$resource} not available.");
|
||||
|
||||
throw new \Exception("Missing {$resource} key: {$old}");
|
||||
}
|
||||
|
||||
@ -1249,4 +1305,15 @@ class CompanyImport implements ShouldQueue
|
||||
}
|
||||
|
||||
|
||||
private function sendImportMail($message){
|
||||
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new CompanyImportFailure($this->company, $message);
|
||||
$nmo->company = $this->company;
|
||||
$nmo->settings = $this->company->settings;
|
||||
$nmo->to_user = $this->company->owner();
|
||||
NinjaMailerJob::dispatchNow($nmo);
|
||||
|
||||
}
|
||||
}
|
@ -45,8 +45,13 @@ class RecurringInvoicesCron
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
$recurring_invoices = RecurringInvoice::where('next_send_date', '<=', now()->toDateTimeString())
|
||||
->whereNotNull('next_send_date')
|
||||
->whereNull('deleted_at')
|
||||
->where('status_id', RecurringInvoice::STATUS_ACTIVE)
|
||||
->where('remaining_cycles', '!=', '0')
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted',0)
|
||||
->where('deleted_at', NULL);
|
||||
})
|
||||
->with('company')
|
||||
->cursor();
|
||||
|
||||
@ -66,15 +71,20 @@ class RecurringInvoicesCron
|
||||
|
||||
$recurring_invoices = RecurringInvoice::where('next_send_date', '<=', now()->toDateTimeString())
|
||||
->whereNotNull('next_send_date')
|
||||
->whereNull('deleted_at')
|
||||
->where('status_id', RecurringInvoice::STATUS_ACTIVE)
|
||||
->where('remaining_cycles', '!=', '0')
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted',0)
|
||||
->where('deleted_at', NULL);
|
||||
})
|
||||
->with('company')
|
||||
->cursor();
|
||||
|
||||
nlog(now()->format('Y-m-d') . ' Sending Recurring Invoices. Count = '.$recurring_invoices->count().' On Database # '.$db);
|
||||
|
||||
$recurring_invoices->each(function ($recurring_invoice, $key) {
|
||||
nlog("Current date = " . now()->format("Y-m-d") . " Recurring date = " .$recurring_invoice->next_send_date);
|
||||
nlog("Current date = " . now()->format("Y-m-d") . " Recurring date = " .$recurring_invoice->next_send_date ." Recurring #id = ". $recurring_invoice->id);
|
||||
|
||||
if (!$recurring_invoice->company->is_disabled) {
|
||||
SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db);
|
||||
|
@ -62,6 +62,7 @@ class SubscriptionCron
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('balance', '>', 0)
|
||||
->whereDate('due_date', '<=', now()->addDay()->startOfDay())
|
||||
->whereNull('deleted_at')
|
||||
->whereNotNull('subscription_id')
|
||||
->cursor();
|
||||
|
||||
|
@ -27,6 +27,7 @@ use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use ZipStream\Option\Archive;
|
||||
use ZipStream\ZipStream;
|
||||
use ZipArchive;
|
||||
|
||||
class ZipInvoices implements ShouldQueue
|
||||
{
|
||||
@ -62,48 +63,49 @@ class ZipInvoices implements ShouldQueue
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
* @throws \ZipStream\Exception\FileNotFoundException
|
||||
* @throws \ZipStream\Exception\FileNotReadableException
|
||||
* @throws \ZipStream\Exception\OverflowException
|
||||
*/
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$tempStream = fopen('php://memory', 'w+');
|
||||
# create new zip object
|
||||
$zip = new ZipArchive();
|
||||
|
||||
$options = new Archive();
|
||||
$options->setOutputStream($tempStream);
|
||||
|
||||
// create a new zipstream object
|
||||
$invitation = $this->invoices->first()->invitations->first();
|
||||
$path = $this->invoices->first()->client->invoice_filepath($invitation);
|
||||
$file_name = date('Y-m-d').'_'.str_replace(' ', '_', trans('texts.invoices')).'.zip';
|
||||
|
||||
$tmp_file = @tempnam('.', '');
|
||||
$zip->open($tmp_file , ZipArchive::OVERWRITE);
|
||||
|
||||
$invoice = $this->invoices->first();
|
||||
$invitation = $invoice->invitations->first();
|
||||
|
||||
$path = $invoice->client->invoice_filepath($invitation);
|
||||
|
||||
$zip = new ZipStream($file_name, $options);
|
||||
|
||||
# loop through each file
|
||||
foreach ($this->invoices as $invoice) {
|
||||
//$zip->addFileFromPath(basename($invoice->pdf_file_path()), TempFile::path($invoice->pdf_file_path()));
|
||||
$zip->addFileFromPath(basename($invoice->pdf_file_path($invitation)), $invoice->pdf_file_path());
|
||||
|
||||
$inv = $invoice->invitations->first();
|
||||
|
||||
# download file
|
||||
$download_file = file_get_contents($invoice->pdf_file_path($inv, 'url', true));
|
||||
|
||||
#add it to the zip
|
||||
$zip->addFromString(basename($invoice->pdf_file_path($inv)), $download_file);
|
||||
}
|
||||
|
||||
$zip->finish();
|
||||
|
||||
Storage::disk('public')->put($path.$file_name, $tempStream);
|
||||
|
||||
fclose($tempStream);
|
||||
# close zip
|
||||
$zip->close();
|
||||
|
||||
Storage::put($path.$file_name, file_get_contents($tmp_file));
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new DownloadInvoices(Storage::disk('public')->url($path.$file_name), $this->company);
|
||||
$nmo->mailable = new DownloadInvoices(Storage::url($path.$file_name), $this->company);
|
||||
$nmo->to_user = $this->user;
|
||||
$nmo->settings = $this->settings;
|
||||
$nmo->company = $this->company;
|
||||
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
|
||||
UnlinkFile::dispatch('public', $path.$file_name)->delay(now()->addHours(1));
|
||||
UnlinkFile::dispatch(config('filesystems.default'), $path.$file_name)->delay(now()->addHours(1));
|
||||
}
|
||||
}
|
||||
|
@ -71,10 +71,10 @@ class NinjaMailerJob implements ShouldQueue
|
||||
|
||||
public function handle()
|
||||
{
|
||||
/*If we are migrating data we don't want to fire any emails*/
|
||||
if ($this->nmo->company->is_disabled && !$this->override)
|
||||
return true;
|
||||
|
||||
if($this->preFlightChecksFail())
|
||||
return;
|
||||
|
||||
/*Set the correct database*/
|
||||
MultiDB::setDb($this->nmo->company->db);
|
||||
|
||||
@ -215,6 +215,19 @@ class NinjaMailerJob implements ShouldQueue
|
||||
|
||||
}
|
||||
|
||||
private function preFlightChecksFail()
|
||||
{
|
||||
/* If we are migrating data we don't want to fire any emails */
|
||||
if ($this->nmo->company->is_disabled && !$this->override)
|
||||
return true;
|
||||
|
||||
/* On the hosted platform we set default contacts a @example.com email address - we shouldn't send emails to these types of addresses */
|
||||
if(Ninja::isHosted() && strpos($this->nmo->to_user->email, '@example.com') !== false)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function logMailError($errors, $recipient_object)
|
||||
{
|
||||
SystemLogger::dispatch(
|
||||
|
@ -56,7 +56,7 @@ class CompanySizeCheck implements ShouldQueue
|
||||
{
|
||||
Company::cursor()->each(function ($company) {
|
||||
|
||||
if ($company->invoices()->count() > 1000 || $company->products()->count() > 1000 || $company->clients()->count() > 1000) {
|
||||
if ($company->invoices()->count() > 500 || $company->products()->count() > 500 || $company->clients()->count() > 500) {
|
||||
|
||||
nlog("Marking company {$company->id} as large");
|
||||
|
||||
|
@ -29,6 +29,7 @@ use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
//@DEPRECATED
|
||||
class SendReminders implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesDates, MakesReminders;
|
||||
|
@ -214,7 +214,7 @@ class Import implements ShouldQueue
|
||||
// if(Ninja::isHosted() && array_key_exists('ninja_tokens', $data))
|
||||
$this->processNinjaTokens($data['ninja_tokens']);
|
||||
|
||||
$this->fixData();
|
||||
// $this->fixData();
|
||||
|
||||
$this->setInitialCompanyLedgerBalances();
|
||||
|
||||
@ -337,6 +337,10 @@ class Import implements ShouldQueue
|
||||
|
||||
if(!MultiDB::checkDomainAvailable($data['subdomain']))
|
||||
$data['subdomain'] = MultiDB::randomSubdomainGenerator();
|
||||
|
||||
if(strlen($data['subdomain']) == 0)
|
||||
$data['subdomain'] = MultiDB::randomSubdomainGenerator();
|
||||
|
||||
}
|
||||
|
||||
$rules = (new UpdateCompanyRequest())->rules();
|
||||
@ -389,6 +393,10 @@ class Import implements ShouldQueue
|
||||
foreach ($data['settings'] as $key => $value) {
|
||||
if ($key == 'invoice_design_id' || $key == 'quote_design_id' || $key == 'credit_design_id') {
|
||||
$value = $this->encodePrimaryKey($value);
|
||||
|
||||
if(!$value)
|
||||
$value = $this->encodePrimaryKey(1);
|
||||
|
||||
}
|
||||
|
||||
if ($key == 'payment_terms' && $key = '') {
|
||||
|
@ -11,11 +11,13 @@
|
||||
|
||||
namespace App\Jobs\Util;
|
||||
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\Events\Invoice\InvoiceWasEmailed;
|
||||
use App\Jobs\Entity\EmailEntity;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Invoice;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\Traits\MakesReminders;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
@ -26,7 +28,7 @@ use Illuminate\Support\Carbon;
|
||||
|
||||
class ReminderJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesReminders;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesReminders, MakesDates;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@ -56,14 +58,21 @@ class ReminderJob implements ShouldQueue
|
||||
nlog("Sending invoice reminders " . now()->format('Y-m-d h:i:s'));
|
||||
|
||||
Invoice::where('next_send_date', '<=', now()->toDateTimeString())
|
||||
->whereNull('deleted_at')
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('balance', '>', 0)
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted',0)
|
||||
->where('deleted_at', NULL);
|
||||
})
|
||||
->with('invitations')->cursor()->each(function ($invoice) {
|
||||
|
||||
if ($invoice->isPayable()) {
|
||||
$reminder_template = $invoice->calculateTemplate('invoice');
|
||||
$invoice->service()->touchReminder($reminder_template)->save();
|
||||
|
||||
$invoice = $this->calcLateFee($invoice, $reminder_template);
|
||||
|
||||
$invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) {
|
||||
EmailEntity::dispatch($invitation, $invitation->company, $reminder_template);
|
||||
@ -83,4 +92,89 @@ class ReminderJob implements ShouldQueue
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the late if - if any - and rebuilds the invoice
|
||||
*
|
||||
* @param Invoice $invoice
|
||||
* @param string $template
|
||||
* @return Invoice
|
||||
*/
|
||||
private function calcLateFee($invoice, $template) :Invoice
|
||||
{
|
||||
$late_fee_amount = 0;
|
||||
$late_fee_percent = 0;
|
||||
|
||||
switch ($template) {
|
||||
case 'reminder1':
|
||||
$late_fee_amount = $invoice->client->getSetting('late_fee_amount1');
|
||||
$late_fee_percent = $invoice->client->getSetting('late_fee_percent1');
|
||||
break;
|
||||
case 'reminder2':
|
||||
$late_fee_amount = $invoice->client->getSetting('late_fee_amount2');
|
||||
$late_fee_percent = $invoice->client->getSetting('late_fee_percent2');
|
||||
break;
|
||||
case 'reminder3':
|
||||
$late_fee_amount = $invoice->client->getSetting('late_fee_amount3');
|
||||
$late_fee_percent = $invoice->client->getSetting('late_fee_percent3');
|
||||
break;
|
||||
case 'endless_reminder':
|
||||
$late_fee_amount = $invoice->client->getSetting('late_fee_endless_amount');
|
||||
$late_fee_percent = $invoice->client->getSetting('late_fee_endless_percent');
|
||||
break;
|
||||
default:
|
||||
$late_fee_amount = 0;
|
||||
$late_fee_percent = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->setLateFee($invoice, $late_fee_amount, $late_fee_percent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the late fee to the invoice line items
|
||||
*
|
||||
* @param Invoice $invoice
|
||||
* @param float $amount The fee amount
|
||||
* @param float $percent The fee percentage amount
|
||||
*
|
||||
* @return Invoice
|
||||
*/
|
||||
private function setLateFee($invoice, $amount, $percent) :Invoice
|
||||
{
|
||||
$temp_invoice_balance = $invoice->balance;
|
||||
|
||||
if ($amount <= 0 && $percent <= 0) {
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
$fee = $amount;
|
||||
|
||||
if ($invoice->partial > 0) {
|
||||
$fee += round($invoice->partial * $percent / 100, 2);
|
||||
} else {
|
||||
$fee += round($invoice->balance * $percent / 100, 2);
|
||||
}
|
||||
|
||||
$invoice_item = new InvoiceItem;
|
||||
$invoice_item->type_id = '5';
|
||||
$invoice_item->product_key = trans('texts.fee');
|
||||
$invoice_item->notes = ctrans('texts.late_fee_added', ['date' => $this->translateDate(now()->startOfDay(), $invoice->client->date_format(), $invoice->client->locale())]);
|
||||
$invoice_item->quantity = 1;
|
||||
$invoice_item->cost = $fee;
|
||||
|
||||
$invoice_items = $invoice->line_items;
|
||||
$invoice_items[] = $invoice_item;
|
||||
|
||||
$invoice->line_items = $invoice_items;
|
||||
|
||||
/**Refresh Invoice values*/
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
|
||||
$invoice->client->service()->updateBalance($this->invoice->balance - $temp_invoice_balance)->save();
|
||||
$invoice->ledger()->updateInvoiceBalance($this->invoice->balance - $temp_invoice_balance, "Late Fee Adjustment for invoice {$this->invoice->number}");
|
||||
|
||||
return $invoice;
|
||||
}
|
||||
}
|
||||
|
@ -92,16 +92,6 @@ class StartMigration implements ShouldQueue
|
||||
$archive = $zip->open(public_path("storage/{$this->filepath}"));
|
||||
$filename = pathinfo($this->filepath, PATHINFO_FILENAME);
|
||||
|
||||
// if($this->company->id == $this->company->account->default_company_id)
|
||||
// {
|
||||
// $new_default_company = $this->company->account->companies->first();
|
||||
|
||||
// if ($new_default_company) {
|
||||
// $this->company->account->default_company_id = $new_default_company->id;
|
||||
// $this->company->account->save();
|
||||
// }
|
||||
// }
|
||||
|
||||
$update_product_flag = $this->company->update_products;
|
||||
|
||||
$this->company->update_products = false;
|
||||
@ -129,9 +119,6 @@ class StartMigration implements ShouldQueue
|
||||
|
||||
Storage::deleteDirectory(public_path("storage/migrations/{$filename}"));
|
||||
|
||||
// $this->company->account->default_company_id = $this->company->id;
|
||||
// $this->company->account->save();
|
||||
|
||||
$this->company->update_products = $update_product_flag;
|
||||
$this->company->save();
|
||||
|
||||
|
@ -15,6 +15,7 @@ use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyToken;
|
||||
use App\Models\Document;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
@ -206,6 +207,24 @@ class MultiDB
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function documentFindAndSetDb($hash) : bool
|
||||
{
|
||||
$current_db = config('database.default');
|
||||
|
||||
//multi-db active
|
||||
foreach (self::$dbs as $db) {
|
||||
|
||||
if (Document::on($db)->where('hash', $hash)->count() >= 1){
|
||||
self::setDb($db);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
self::setDB($current_db);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function findAndSetDb($token) :bool
|
||||
{
|
||||
$current_db = config('database.default');
|
||||
|
@ -50,7 +50,7 @@ $user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars[
|
||||
$fields->subscription_id = $subscription->id;
|
||||
$fields->user_id = $user_id;
|
||||
$fields->company_id = $subscription->company_id;
|
||||
$fields->activity_type_id = Activity::ARCHIVE_SUBSCRIPTIOn;
|
||||
$fields->activity_type_id = Activity::ARCHIVE_SUBSCRIPTION;
|
||||
|
||||
$this->activity_repo->save($fields, $subscription, $event->event_vars);
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class RestoredUserActivity implements ShouldQueue
|
||||
|
||||
$fields = new stdClass;
|
||||
|
||||
$fields->user_id = $creating_user->user->id;
|
||||
$fields->user_id = $event->user->id;
|
||||
$fields->notes = $event->creating_user->present()->name() . " Restored user " . $event->user->present()->name();
|
||||
|
||||
$fields->company_id = $event->company->id;
|
||||
|
@ -11,6 +11,9 @@
|
||||
|
||||
namespace App\Mail\Admin;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class AccountCreatedObject
|
||||
{
|
||||
|
||||
@ -30,6 +33,14 @@ class AccountCreatedObject
|
||||
public function build()
|
||||
{
|
||||
|
||||
App::forgetInstance('translator');
|
||||
/* Init a new copy of the translator*/
|
||||
$t = app('translator');
|
||||
/* Set the locale*/
|
||||
App::setLocale($this->company->getLocale());
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
$data = [
|
||||
'title' => ctrans('texts.new_signup'),
|
||||
'message' => ctrans('texts.new_signup_text', ['user' => $this->user->present()->name(), 'email' => $this->user->email, 'ip' => $this->user->ip]),
|
||||
|
@ -13,8 +13,10 @@ namespace App\Mail\Admin;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use stdClass;
|
||||
|
||||
class AutoBillingFailureObject
|
||||
@ -55,6 +57,15 @@ class AutoBillingFailureObject
|
||||
|
||||
public function build()
|
||||
{
|
||||
|
||||
App::forgetInstance('translator');
|
||||
/* Init a new copy of the translator*/
|
||||
$t = app('translator');
|
||||
/* Set the locale*/
|
||||
App::setLocale($this->company->getLocale());
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
$this->$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->get();
|
||||
|
||||
$mail_obj = new stdClass;
|
||||
|
@ -13,8 +13,10 @@ namespace App\Mail\Admin;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use stdClass;
|
||||
|
||||
class ClientPaymentFailureObject
|
||||
@ -56,6 +58,14 @@ class ClientPaymentFailureObject
|
||||
public function build()
|
||||
{
|
||||
|
||||
App::forgetInstance('translator');
|
||||
/* Init a new copy of the translator*/
|
||||
$t = app('translator');
|
||||
/* Set the locale*/
|
||||
App::setLocale($this->company->getLocale());
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
$this->invoices = Invoice::whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->get();
|
||||
|
||||
$mail_obj = new stdClass;
|
||||
|
@ -11,8 +11,10 @@
|
||||
|
||||
namespace App\Mail\Admin;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use stdClass;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class EntityCreatedObject
|
||||
{
|
||||
@ -39,6 +41,13 @@ class EntityCreatedObject
|
||||
|
||||
public function build()
|
||||
{
|
||||
App::forgetInstance('translator');
|
||||
/* Init a new copy of the translator*/
|
||||
$t = app('translator');
|
||||
/* Set the locale*/
|
||||
App::setLocale($this->entity->company->getLocale());
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->entity->company->settings));
|
||||
|
||||
$this->contact = $this->entity->invitations()->first()->contact;
|
||||
$this->company = $this->entity->company;
|
||||
|
@ -12,8 +12,10 @@
|
||||
namespace App\Mail\Admin;
|
||||
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use stdClass;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class EntityFailedSendObject
|
||||
{
|
||||
@ -50,6 +52,15 @@ class EntityFailedSendObject
|
||||
|
||||
public function build()
|
||||
{
|
||||
|
||||
App::forgetInstance('translator');
|
||||
/* Init a new copy of the translator*/
|
||||
$t = app('translator');
|
||||
/* Set the locale*/
|
||||
App::setLocale($this->company->getLocale());
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
$this->setTemplate();
|
||||
|
||||
$mail_obj = new stdClass;
|
||||
|
@ -12,7 +12,9 @@
|
||||
namespace App\Mail\Admin;
|
||||
|
||||
use App\Mail\Engine\PaymentEmailEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use stdClass;
|
||||
|
||||
class EntityPaidObject
|
||||
@ -35,6 +37,15 @@ class EntityPaidObject
|
||||
|
||||
public function build()
|
||||
{
|
||||
|
||||
App::forgetInstance('translator');
|
||||
/* Init a new copy of the translator*/
|
||||
$t = app('translator');
|
||||
/* Set the locale*/
|
||||
App::setLocale($this->company->getLocale());
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
$mail_obj = new stdClass;
|
||||
$mail_obj->amount = $this->getAmount();
|
||||
$mail_obj->subject = $this->getSubject();
|
||||
|
@ -11,8 +11,10 @@
|
||||
|
||||
namespace App\Mail\Admin;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use stdClass;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class EntitySentObject
|
||||
{
|
||||
@ -46,6 +48,15 @@ class EntitySentObject
|
||||
|
||||
public function build()
|
||||
{
|
||||
|
||||
App::forgetInstance('translator');
|
||||
/* Init a new copy of the translator*/
|
||||
$t = app('translator');
|
||||
/* Set the locale*/
|
||||
App::setLocale($this->company->getLocale());
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
$this->setTemplate();
|
||||
|
||||
$mail_obj = new stdClass;
|
||||
|
@ -11,8 +11,10 @@
|
||||
|
||||
namespace App\Mail\Admin;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use stdClass;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class EntityViewedObject
|
||||
{
|
||||
@ -39,6 +41,15 @@ class EntityViewedObject
|
||||
|
||||
public function build()
|
||||
{
|
||||
|
||||
App::forgetInstance('translator');
|
||||
/* Init a new copy of the translator*/
|
||||
$t = app('translator');
|
||||
/* Set the locale*/
|
||||
App::setLocale($this->company->getLocale());
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
$mail_obj = new stdClass;
|
||||
$mail_obj->amount = $this->getAmount();
|
||||
$mail_obj->subject = $this->getSubject();
|
||||
|
@ -12,9 +12,11 @@
|
||||
namespace App\Mail\Admin;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use stdClass;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class PaymentFailureObject
|
||||
{
|
||||
@ -55,6 +57,14 @@ class PaymentFailureObject
|
||||
public function build()
|
||||
{
|
||||
|
||||
App::forgetInstance('translator');
|
||||
/* Init a new copy of the translator*/
|
||||
$t = app('translator');
|
||||
/* Set the locale*/
|
||||
App::setLocale($this->company->getLocale());
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
// $this->invoices = Invoice::whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->get();
|
||||
|
||||
$mail_obj = new stdClass;
|
||||
|
@ -11,6 +11,9 @@
|
||||
|
||||
namespace App\Mail\Admin;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class ResetPasswordObject
|
||||
{
|
||||
|
||||
@ -32,6 +35,14 @@ class ResetPasswordObject
|
||||
public function build()
|
||||
{
|
||||
|
||||
App::forgetInstance('translator');
|
||||
/* Init a new copy of the translator*/
|
||||
$t = app('translator');
|
||||
/* Set the locale*/
|
||||
App::setLocale($this->company->getLocale());
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
$data = [
|
||||
'title' => ctrans('texts.your_password_reset_link'),
|
||||
'message' => ctrans('texts.reset_password'),
|
||||
|
@ -11,7 +11,9 @@
|
||||
|
||||
namespace App\Mail\Admin;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class VerifyUserObject
|
||||
{
|
||||
@ -33,6 +35,15 @@ class VerifyUserObject
|
||||
|
||||
public function build()
|
||||
{
|
||||
|
||||
App::forgetInstance('translator');
|
||||
/* Init a new copy of the translator*/
|
||||
$t = app('translator');
|
||||
/* Set the locale*/
|
||||
App::setLocale($this->company->getLocale());
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
$this->user->confirmation_code = $this->createDbHash($this->company->db);
|
||||
$this->user->save();
|
||||
|
||||
|
@ -11,6 +11,9 @@
|
||||
|
||||
namespace App\Mail\ClientContact;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class ClientContactResetPasswordObject
|
||||
{
|
||||
|
||||
@ -32,6 +35,12 @@ class ClientContactResetPasswordObject
|
||||
public function build()
|
||||
{
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
App::setLocale($this->client_contact->preferredLocale());
|
||||
$t->replace(Ninja::transformTranslations($this->client_contact->client->getMergedSettings()));
|
||||
|
||||
|
||||
$data = [
|
||||
'title' => ctrans('texts.your_password_reset_link'),
|
||||
'content' => ctrans('texts.reset_password'),
|
||||
|
@ -17,6 +17,7 @@ use App\Utils\Helpers;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class PaymentEmailEngine extends BaseEmailEngine
|
||||
{
|
||||
@ -49,6 +50,11 @@ class PaymentEmailEngine extends BaseEmailEngine
|
||||
|
||||
public function build()
|
||||
{
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
App::setLocale($this->contact->preferredLocale());
|
||||
$t->replace(Ninja::transformTranslations($this->client->getMergedSettings()));
|
||||
|
||||
if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) {
|
||||
$body_template = $this->template_data['body'];
|
||||
} elseif (strlen($this->client->getSetting('email_template_payment')) > 0) {
|
||||
|
@ -25,14 +25,21 @@ class ACHVerificationNotification extends Mailable
|
||||
*/
|
||||
public $company;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $url;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Company $company)
|
||||
public function __construct(Company $company, string $url)
|
||||
{
|
||||
$this->company = $company;
|
||||
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -36,7 +36,7 @@ class MigrationCompleted extends Mailable
|
||||
$data['settings'] = $this->company->settings;
|
||||
$data['company'] = $this->company->fresh();
|
||||
$data['whitelabel'] = $this->company->account->isPaid() ? true : false;
|
||||
$data['check_data'] = $this->check_data;
|
||||
$data['check_data'] = $this->check_data ?: '';
|
||||
$data['logo'] = $this->company->present()->logo();
|
||||
|
||||
$result = $this->from(config('mail.from.address'), config('mail.from.name'))
|
||||
|
@ -11,6 +11,9 @@
|
||||
|
||||
namespace App\Mail\RecurringInvoice;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class ClientContactRequestCancellationObject
|
||||
{
|
||||
|
||||
@ -33,6 +36,10 @@ class ClientContactRequestCancellationObject
|
||||
public function build()
|
||||
{
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
$data = [
|
||||
'title' => ctrans('texts.recurring_cancellation_request', ['contact' => $this->client_contact->present()->name()]),
|
||||
'content' => ctrans('texts.recurring_cancellation_request_body', ['contact' => $this->client_contact->present()->name(), 'client' => $this->client_contact->client->present()->name(), 'invoice' => $this->recurring_invoice->number]),
|
||||
|
@ -52,15 +52,15 @@ class SupportMessageSent extends Mailable
|
||||
|
||||
$account = auth()->user()->account;
|
||||
|
||||
$plan = $account->plan ?: 'Free Self Hosted';
|
||||
$plan = $account->plan ?: 'Forever Free';
|
||||
|
||||
$company = auth()->user()->company();
|
||||
$user = auth()->user();
|
||||
|
||||
if(Ninja::isHosted())
|
||||
$subject = "Hosted {$user->present()->name} - [{$plan} - DB:{$company->db}]";
|
||||
$subject = "Hosted {$user->present()->name} - [{$plan} - {$company->db}]";
|
||||
else
|
||||
$subject = "Self Host {$user->present()->name} - [{$plan} - DB:{$company->db}]";
|
||||
$subject = "Self Host {$user->present()->name} - [{$plan} - {$company->db}]";
|
||||
|
||||
return $this->from(config('mail.from.address'), config('mail.from.name'))
|
||||
->replyTo($user->email, $user->present()->name())
|
||||
|
@ -227,6 +227,21 @@ class Account extends BaseModel
|
||||
return $plan_details && $plan_details['trial'];
|
||||
}
|
||||
|
||||
public function startTrial($plan)
|
||||
{
|
||||
if (! Ninja::isNinja()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->trial_started && $this->trial_started != '0000-00-00') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->trial_plan = $plan;
|
||||
$this->trial_started = now();
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function getPlanDetails($include_inactive = false, $include_trial = true)
|
||||
{
|
||||
$plan = $this->plan;
|
||||
|
@ -13,6 +13,7 @@ namespace App\Models;
|
||||
|
||||
use App\DataMapper\ClientSettings;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\Presenters\ClientPresenter;
|
||||
use App\Services\Client\ClientService;
|
||||
use App\Utils\Traits\AppSetup;
|
||||
@ -215,6 +216,11 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
return $this->hasMany(Invoice::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function recurring_invoices()
|
||||
{
|
||||
return $this->hasMany(RecurringInvoice::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function shipping_country()
|
||||
{
|
||||
return $this->belongsTo(Country::class, 'shipping_country_id', 'id');
|
||||
@ -407,7 +413,7 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
}
|
||||
|
||||
foreach ($gateways as $gateway) {
|
||||
if (in_array(GatewayType::CREDIT_CARD, $gateway->driver($this)->gatewayTypes())) {
|
||||
if (in_array(GatewayType::CREDIT_CARD, $gateway->driver($this)->gatewayTypeEnabled(GatewayType::CREDIT_CARD))) {
|
||||
return $gateway;
|
||||
}
|
||||
}
|
||||
@ -417,35 +423,57 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
|
||||
public function getBankTransferGateway() :?CompanyGateway
|
||||
{
|
||||
$company_gateways = $this->getSetting('company_gateway_ids');
|
||||
$pms = $this->service()->getPaymentMethods(0);
|
||||
|
||||
if($this->currency()->code == 'USD' && in_array(GatewayType::BANK_TRANSFER, array_column($pms, 'gateway_type_id'))){
|
||||
|
||||
foreach($pms as $pm){
|
||||
if($pm['gateway_type_id'] == GatewayType::BANK_TRANSFER)
|
||||
return CompanyGateway::find($pm['company_gateway_id']);
|
||||
}
|
||||
|
||||
if (strlen($company_gateways) >= 1) {
|
||||
$transformed_ids = $this->transformKeys(explode(',', $company_gateways));
|
||||
$gateways = $this->company
|
||||
->company_gateways
|
||||
->whereIn('id', $transformed_ids)
|
||||
->sortby(function ($model) use ($transformed_ids) {
|
||||
return array_search($model->id, $transformed_ids);
|
||||
});
|
||||
} else {
|
||||
$gateways = $this->company->company_gateways;
|
||||
}
|
||||
|
||||
foreach ($gateways as $gateway) {
|
||||
if ($this->currency()->code == 'USD' && in_array(GatewayType::BANK_TRANSFER, $gateway->driver($this)->gatewayTypes())) {
|
||||
return $gateway;
|
||||
if($this->currency()->code == 'EUR' && in_array(GatewayType::BANK_TRANSFER, array_column($pms, 'gateway_type_id'))){
|
||||
|
||||
foreach($pms as $pm){
|
||||
if($pm['gateway_type_id'] == GatewayType::SEPA)
|
||||
return CompanyGateway::find($pm['company_gateway_id']);
|
||||
}
|
||||
|
||||
if ($this->currency()->code == 'EUR' && in_array(GatewayType::SEPA, $gateway->driver($this)->gatewayTypes())) {
|
||||
return $gateway;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
// $company_gateways = $this->getSetting('company_gateway_ids');
|
||||
|
||||
// if (strlen($company_gateways) >= 1) {
|
||||
// $transformed_ids = $this->transformKeys(explode(',', $company_gateways));
|
||||
// $gateways = $this->company
|
||||
// ->company_gateways
|
||||
// ->whereIn('id', $transformed_ids)
|
||||
// ->sortby(function ($model) use ($transformed_ids) {
|
||||
// return array_search($model->id, $transformed_ids);
|
||||
// });
|
||||
// } else {
|
||||
// $gateways = $this->company->company_gateways;
|
||||
// }
|
||||
|
||||
// foreach ($gateways as $gateway) {
|
||||
// if ($this->currency()->code == 'USD' && in_array(GatewayType::BANK_TRANSFER, $gateway->driver($this)->gatewayTypeEnabled(GatewayType::BANK_TRANSFER))) {
|
||||
// return $gateway;
|
||||
// }
|
||||
|
||||
// if ($this->currency()->code == 'EUR' && in_array(GatewayType::SEPA, $gateway->driver($this)->gatewayTypeEnabled(GatewayType::SEPA))) {
|
||||
// return $gateway;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return null;
|
||||
}
|
||||
|
||||
public function getBankTransferMethodType()
|
||||
{
|
||||
|
||||
if ($this->currency()->code == 'USD') {
|
||||
return GatewayType::BANK_TRANSFER;
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ class ClientGatewayToken extends BaseModel
|
||||
'gateway_customer_reference',
|
||||
'gateway_type_id',
|
||||
'meta',
|
||||
'client_id',
|
||||
];
|
||||
|
||||
public function getEntityType()
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Language;
|
||||
use App\Models\Presenters\CompanyPresenter;
|
||||
use App\Models\User;
|
||||
use App\Services\Notification\NotificationService;
|
||||
|
@ -59,6 +59,17 @@ class CompanyGateway extends BaseModel
|
||||
16 => ['card' => 'images/credit_cards/Test-Discover-Icon.png', 'text' => 'Discover'],
|
||||
];
|
||||
|
||||
|
||||
// const TYPE_PAYPAL = 300;
|
||||
// const TYPE_STRIPE = 301;
|
||||
// const TYPE_LEDGER = 302;
|
||||
// const TYPE_FAILURE = 303;
|
||||
// const TYPE_CHECKOUT = 304;
|
||||
// const TYPE_AUTHORIZE = 305;
|
||||
// const TYPE_CUSTOM = 306;
|
||||
// const TYPE_BRAINTREE = 307;
|
||||
// const TYPE_WEPAY = 309;
|
||||
|
||||
public $gateway_consts = [
|
||||
'38f2c48af60c7dd69e04248cbb24c36e' => 300,
|
||||
'd14dd26a37cecc30fdd65700bfb55b23' => 301,
|
||||
@ -66,6 +77,8 @@ class CompanyGateway extends BaseModel
|
||||
'3b6621f970ab18887c4f6dca78d3f8bb' => 305,
|
||||
'54faab2ab6e3223dbe848b1686490baa' => 306,
|
||||
'd14dd26a47cecc30fdd65700bfb67b34' => 301,
|
||||
'8fdeed552015b3c7b44ed6c8ebd9e992' => 309,
|
||||
'f7ec488676d310683fb51802d076d713' => 307,
|
||||
];
|
||||
|
||||
protected $touches = [];
|
||||
|
@ -224,6 +224,13 @@ class RecurringInvoice extends BaseModel
|
||||
|
||||
$offset = $this->client->timezone_offset();
|
||||
|
||||
/*
|
||||
As we are firing at UTC+0 if our offset is negative it is technically firing the day before so we always need
|
||||
to add ON a day - a day = 86400 seconds
|
||||
*/
|
||||
if($offset < 0)
|
||||
$offset += 86400;
|
||||
|
||||
switch ($this->frequency_id) {
|
||||
case self::FREQUENCY_DAILY:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addDay()->addSeconds($offset);
|
||||
@ -428,17 +435,14 @@ class RecurringInvoice extends BaseModel
|
||||
'due_date' => $next_due_date_string
|
||||
];
|
||||
|
||||
$next_send_date = $this->nextDateByFrequency($next_send_date->format('Y-m-d'));
|
||||
/* Fixes the timeshift in case the offset is negative which cause a infinite loop due to UTC +0*/
|
||||
if($this->client->timezone_offset() < 0){
|
||||
$next_send_date = $this->nextDateByFrequency($next_send_date->addDay()->format('Y-m-d'));
|
||||
}
|
||||
else
|
||||
$next_send_date = $this->nextDateByFrequency($next_send_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
/*If no due date is set - unset the due_date value */
|
||||
// if(!$this->due_date_days || $this->due_date_days == 0){
|
||||
|
||||
// foreach($data as $key => $value)
|
||||
// $data[$key]['due_date'] = '';
|
||||
|
||||
// }
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laracasts\Presenter\PresentableTrait;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class User extends Authenticatable implements MustVerifyEmail
|
||||
{
|
||||
|
@ -119,7 +119,7 @@ class AuthorizeCreditCard
|
||||
'data' => $this->formatGatewayResponse($data, $vars),
|
||||
];
|
||||
|
||||
SystemLogger::dispatch($logger_message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_AUTHORIZE, $this->authorize->client);
|
||||
SystemLogger::dispatch($logger_message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_AUTHORIZE, $this->authorize->client, $this->authorize->client->company);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
|
@ -54,11 +54,11 @@ class AuthorizePaymentDriver extends BaseDriver
|
||||
/**
|
||||
* Returns the gateway types.
|
||||
*/
|
||||
public function gatewayTypes() :array
|
||||
public function gatewayTypes(): array
|
||||
{
|
||||
$types = [
|
||||
GatewayType::CREDIT_CARD,
|
||||
];
|
||||
$types = [];
|
||||
|
||||
$types[] = GatewayType::CREDIT_CARD;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
@ -554,5 +555,24 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
'company_gateway_id' => $this->company_gateway->id,
|
||||
'client' => $this->client->id,
|
||||
]);
|
||||
|
||||
/* Performs an extra iterate on the gatewayTypes() array and passes back only the enabled gateways*/
|
||||
public function gatewayTypeEnabled($type)
|
||||
{
|
||||
$types = [];
|
||||
|
||||
// if($type == GatewayType::BANK_TRANSFER && $this->company_gateway->fees_and_limits->{GatewayType::BANK_TRANSFER}->is_enabled)
|
||||
// {
|
||||
// $types[] = $type;
|
||||
// }
|
||||
// elseif($type == GatewayType::CREDIT_CARD && $this->company_gateway->fees_and_limits->{GatewayType::CREDIT_CARD}->is_enabled)
|
||||
// {
|
||||
// $types[] = $type;
|
||||
// }
|
||||
|
||||
$types[] = GatewayType::CREDIT_CARD;
|
||||
$types[] = GatewayType::BANK_TRANSFER;
|
||||
|
||||
return $types;
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\Braintree\CreditCard;
|
||||
use App\PaymentDrivers\Braintree\PayPal;
|
||||
use Braintree\Gateway;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class BraintreePaymentDriver extends BaseDriver
|
||||
@ -36,7 +38,7 @@ class BraintreePaymentDriver extends BaseDriver
|
||||
public $can_authorise_credit_card = true;
|
||||
|
||||
/**
|
||||
* @var \Braintree\Gateway;
|
||||
* @var Gateway;
|
||||
*/
|
||||
public $gateway;
|
||||
|
||||
@ -49,7 +51,7 @@ class BraintreePaymentDriver extends BaseDriver
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
$this->gateway = new \Braintree\Gateway([
|
||||
$this->gateway = new Gateway([
|
||||
'environment' => $this->company_gateway->getConfigField('testMode') ? 'sandbox' : 'production',
|
||||
'merchantId' => $this->company_gateway->getConfigField('merchantId'),
|
||||
'publicKey' => $this->company_gateway->getConfigField('publicKey'),
|
||||
@ -68,10 +70,12 @@ class BraintreePaymentDriver extends BaseDriver
|
||||
|
||||
public function gatewayTypes(): array
|
||||
{
|
||||
return [
|
||||
GatewayType::CREDIT_CARD,
|
||||
$types = [
|
||||
GatewayType::PAYPAL,
|
||||
GatewayType::CREDIT_CARD
|
||||
];
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
public function authorizeView($data)
|
||||
@ -126,11 +130,11 @@ class BraintreePaymentDriver extends BaseDriver
|
||||
return [
|
||||
'transaction_reference' => $response->id,
|
||||
'transaction_response' => json_encode($response),
|
||||
'success' => (bool) $response->success,
|
||||
'success' => (bool)$response->success,
|
||||
'description' => $response->status,
|
||||
'code' => 0,
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'transaction_reference' => null,
|
||||
'transaction_response' => json_encode($e->getMessage()),
|
||||
@ -174,7 +178,7 @@ class BraintreePaymentDriver extends BaseDriver
|
||||
'gateway_type_id' => GatewayType::CREDIT_CARD,
|
||||
];
|
||||
|
||||
$payment = $this->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);
|
||||
$payment = $this->createPayment($data, Payment::STATUS_COMPLETED);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => $result, 'data' => $data],
|
||||
|
@ -72,11 +72,13 @@ class CheckoutComPaymentDriver extends BaseDriver
|
||||
/**
|
||||
* Returns the default gateway type.
|
||||
*/
|
||||
public function gatewayTypes()
|
||||
public function gatewayTypes(): array
|
||||
{
|
||||
return [
|
||||
GatewayType::CREDIT_CARD,
|
||||
];
|
||||
$types = [];
|
||||
|
||||
$types[] = GatewayType::CREDIT_CARD;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -232,7 +234,7 @@ class CheckoutComPaymentDriver extends BaseDriver
|
||||
'transaction_reference' => $response->id,
|
||||
];
|
||||
|
||||
$payment = $this->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);
|
||||
$payment = $this->createPayment($data, Payment::STATUS_COMPLETED);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => $response, 'data' => $data],
|
||||
@ -269,11 +271,11 @@ class CheckoutComPaymentDriver extends BaseDriver
|
||||
|
||||
return false;
|
||||
}
|
||||
} catch (\Exception | CheckoutHttpException $e) {
|
||||
} catch (Exception | CheckoutHttpException $e) {
|
||||
$this->unWindGatewayFees($payment_hash);
|
||||
$message = $e instanceof CheckoutHttpException
|
||||
? $e->getBody()
|
||||
: $e->getMessage();
|
||||
? $e->getBody()
|
||||
: $e->getMessage();
|
||||
|
||||
$data = [
|
||||
'status' => '',
|
||||
|
@ -68,7 +68,7 @@ class ACH
|
||||
$client_gateway_token = $this->storePaymentMethod($source, $request->input('method'), $customer);
|
||||
|
||||
$mailer = new NinjaMailerObject();
|
||||
$mailer->mailable = new ACHVerificationNotification(auth('contact')->user()->client->company);
|
||||
$mailer->mailable = new ACHVerificationNotification(auth('contact')->user()->client->company, route('client.payment_methods.verification', ['payment_method' => $client_gateway_token->hashed_id, 'method' => GatewayType::BANK_TRANSFER]));
|
||||
$mailer->company = auth('contact')->user()->client->company;
|
||||
$mailer->settings = auth('contact')->user()->client->company->settings;
|
||||
$mailer->to_user = auth('contact')->user();
|
||||
@ -80,6 +80,12 @@ class ACH
|
||||
|
||||
public function verificationView(ClientGatewayToken $token)
|
||||
{
|
||||
if (isset($token->meta->state) && $token->meta->state === 'authorized') {
|
||||
return redirect()
|
||||
->route('client.payment_methods.show', $token->hashed_id)
|
||||
->with('message', __('texts.payment_method_verified'));
|
||||
}
|
||||
|
||||
$data = [
|
||||
'token' => $token,
|
||||
'gateway' => $this->stripe,
|
||||
@ -88,8 +94,14 @@ class ACH
|
||||
return render('gateways.stripe.ach.verify', $data);
|
||||
}
|
||||
|
||||
public function processVerification(VerifyPaymentMethodRequest $request, ClientGatewayToken $token)
|
||||
public function processVerification($request, ClientGatewayToken $token)
|
||||
{
|
||||
if (isset($token->meta->state) && $token->meta->state === 'authorized') {
|
||||
return redirect()
|
||||
->route('client.payment_methods.show', $token->hashed_id)
|
||||
->with('message', __('texts.payment_method_verified'));
|
||||
}
|
||||
|
||||
$this->stripe->init();
|
||||
|
||||
$bank_account = Customer::retrieveSource($request->customer, $request->source, $this->stripe->stripe_connect_auth);
|
||||
@ -97,12 +109,14 @@ class ACH
|
||||
try {
|
||||
$bank_account->verify(['amounts' => request()->transactions]);
|
||||
|
||||
$token->meta->verified_at = now();
|
||||
$meta = $token->meta;
|
||||
$meta->state = 'authorized';
|
||||
$token->meta = $meta;
|
||||
$token->save();
|
||||
|
||||
return redirect()
|
||||
->route('client.invoices.index')
|
||||
->with('success', __('texts.payment_method_verified'));
|
||||
->route('client.payment_methods.show', $token->hashed_id)
|
||||
->with('message', __('texts.payment_method_verified'));
|
||||
} catch (CardException $e) {
|
||||
return back()->with('error', $e->getMessage());
|
||||
}
|
||||
@ -114,7 +128,7 @@ class ACH
|
||||
$data['currency'] = $this->stripe->client->getCurrencyCode();
|
||||
$data['payment_method_id'] = GatewayType::BANK_TRANSFER;
|
||||
$data['customer'] = $this->stripe->findOrCreateCustomer();
|
||||
$data['amount'] = $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision);
|
||||
$data['amount'] = $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency());
|
||||
|
||||
return render('gateways.stripe.ach.pay', $data);
|
||||
}
|
||||
@ -137,7 +151,7 @@ class ACH
|
||||
$state = [
|
||||
'payment_method' => $request->payment_method_id,
|
||||
'gateway_type_id' => $request->company_gateway_id,
|
||||
'amount' => $this->stripe->convertToStripeAmount($request->amount, $this->stripe->client->currency()->precision),
|
||||
'amount' => $this->stripe->convertToStripeAmount($request->amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
|
||||
'currency' => $request->currency,
|
||||
'customer' => $request->customer,
|
||||
];
|
||||
@ -182,7 +196,7 @@ class ACH
|
||||
$data = [
|
||||
'payment_method' => $state['source'],
|
||||
'payment_type' => PaymentType::ACH,
|
||||
'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->amount, $this->stripe->client->currency()->precision),
|
||||
'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
|
||||
'transaction_reference' => $state['charge']->id,
|
||||
'gateway_type_id' => GatewayType::BANK_TRANSFER,
|
||||
];
|
||||
|
@ -37,7 +37,7 @@ class Alipay
|
||||
$data['gateway'] = $this->stripe;
|
||||
$data['return_url'] = $this->buildReturnUrl();
|
||||
$data['currency'] = $this->stripe->client->getCurrencyCode();
|
||||
$data['stripe_amount'] = $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision);
|
||||
$data['stripe_amount'] = $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency());
|
||||
$data['invoices'] = $this->stripe->payment_hash->invoices();
|
||||
|
||||
$this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, ['stripe_amount' => $data['stripe_amount']]);
|
||||
@ -74,7 +74,7 @@ class Alipay
|
||||
$data = [
|
||||
'payment_method' => $this->stripe->payment_hash->data->source,
|
||||
'payment_type' => PaymentType::ALIPAY,
|
||||
'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision),
|
||||
'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
|
||||
'transaction_reference' => $source,
|
||||
'gateway_type_id' => GatewayType::ALIPAY,
|
||||
|
||||
@ -104,7 +104,7 @@ class Alipay
|
||||
$this->stripe->client,
|
||||
$server_response,
|
||||
$this->stripe->client->company,
|
||||
$this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision)
|
||||
$this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency())
|
||||
);
|
||||
|
||||
$message = [
|
||||
|
@ -68,7 +68,7 @@ class Charge
|
||||
try {
|
||||
|
||||
$data = [
|
||||
'amount' => $this->stripe->convertToStripeAmount($amount, $this->stripe->client->currency()->precision),
|
||||
'amount' => $this->stripe->convertToStripeAmount($amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
|
||||
'currency' => $this->stripe->client->getCurrencyCode(),
|
||||
'payment_method' => $cgt->token,
|
||||
'customer' => $cgt->gateway_customer_reference,
|
||||
|
@ -60,7 +60,7 @@ class CreditCard
|
||||
public function paymentView(array $data)
|
||||
{
|
||||
$payment_intent_data = [
|
||||
'amount' => $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision),
|
||||
'amount' => $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
|
||||
'currency' => $this->stripe->client->getCurrencyCode(),
|
||||
'customer' => $this->stripe->findOrCreateCustomer(),
|
||||
'description' => ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number'), // TODO: More meaningful description.
|
||||
@ -115,7 +115,7 @@ class CreditCard
|
||||
$data = [
|
||||
'payment_method' => $this->stripe->payment_hash->data->server_response->payment_method,
|
||||
'payment_type' => PaymentType::parseCardType(strtolower($stripe_method->card->brand)),
|
||||
'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->server_response->amount, $this->stripe->client->currency()->precision),
|
||||
'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->server_response->amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
|
||||
'transaction_reference' => optional($this->stripe->payment_hash->data->payment_intent->charges->data[0])->id,
|
||||
'gateway_type_id' => GatewayType::CREDIT_CARD,
|
||||
];
|
||||
|
@ -40,7 +40,7 @@ class SOFORT
|
||||
{
|
||||
$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);
|
||||
$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['country'] = $this->stripe->client->country->iso_3166_2;
|
||||
|
||||
@ -80,7 +80,7 @@ class SOFORT
|
||||
$data = [
|
||||
'payment_method' => $this->stripe->payment_hash->data->source,
|
||||
'payment_type' => PaymentType::SOFORT,
|
||||
'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision),
|
||||
'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
|
||||
'transaction_reference' => $source,
|
||||
'gateway_type_id' => GatewayType::SOFORT,
|
||||
];
|
||||
@ -107,7 +107,7 @@ class SOFORT
|
||||
$this->stripe->client,
|
||||
$server_response,
|
||||
$this->stripe->client->company,
|
||||
$this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision)
|
||||
$this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency())
|
||||
);
|
||||
|
||||
$message = [
|
||||
|
@ -14,13 +14,24 @@ namespace App\PaymentDrivers\Stripe;
|
||||
|
||||
trait Utilities
|
||||
{
|
||||
public function convertFromStripeAmount($amount, $precision)
|
||||
/*Helpers for currency conversions, NOTE* for some currencies we need to change behaviour */
|
||||
public function convertFromStripeAmount($amount, $precision, $currency)
|
||||
{
|
||||
|
||||
if($currency->code == "JPY")
|
||||
return $amount;
|
||||
|
||||
return $amount / pow(10, $precision);
|
||||
|
||||
}
|
||||
|
||||
public function convertToStripeAmount($amount, $precision)
|
||||
public function convertToStripeAmount($amount, $precision, $currency)
|
||||
{
|
||||
return (int)($amount * pow(10, $precision));
|
||||
|
||||
if($currency->code == "JPY")
|
||||
return $amount;
|
||||
|
||||
return round(($amount * pow(10, $precision)),0);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,9 @@ use App\PaymentDrivers\Stripe\UpdatePaymentMethods;
|
||||
use App\PaymentDrivers\Stripe\Utilities;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Exception;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Laracasts\Presenter\Exceptions\PresenterException;
|
||||
use Stripe\Account;
|
||||
use Stripe\Customer;
|
||||
use Stripe\Exception\ApiErrorException;
|
||||
@ -52,7 +54,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
|
||||
public $can_authorise_credit_card = true;
|
||||
|
||||
/** @var \Stripe\StripeClient */
|
||||
/** @var StripeClient */
|
||||
public $stripe;
|
||||
|
||||
protected $customer_reference = 'customerReferenceParam';
|
||||
@ -112,11 +114,9 @@ class StripePaymentDriver extends BaseDriver
|
||||
public function gatewayTypes(): array
|
||||
{
|
||||
$types = [
|
||||
GatewayType::CREDIT_CARD,
|
||||
GatewayType::CRYPTO,
|
||||
// GatewayType::SEPA, // TODO: Missing implementation
|
||||
// GatewayType::APPLE_PAY, // TODO:: Missing implementation
|
||||
];
|
||||
// GatewayType::CRYPTO,
|
||||
GatewayType::CREDIT_CARD
|
||||
];
|
||||
|
||||
if ($this->client
|
||||
&& isset($this->client->country)
|
||||
@ -126,7 +126,8 @@ class StripePaymentDriver extends BaseDriver
|
||||
|
||||
if ($this->client
|
||||
&& isset($this->client->country)
|
||||
&& in_array($this->client->country->iso_3166_3, ['USA'])) {
|
||||
&& in_array($this->client->country->iso_3166_3, ['USA'])
|
||||
) {
|
||||
$types[] = GatewayType::BANK_TRANSFER;
|
||||
}
|
||||
|
||||
@ -167,18 +168,12 @@ class StripePaymentDriver extends BaseDriver
|
||||
|
||||
public function getClientRequiredFields(): array
|
||||
{
|
||||
$fields = [
|
||||
['name' => 'client_postal_code', 'label' => ctrans('texts.postal_code'), 'type' => 'text', 'validation' => 'required'],
|
||||
];
|
||||
$fields = [];
|
||||
|
||||
if ($this->company_gateway->require_client_name) {
|
||||
$fields[] = ['name' => 'client_name', 'label' => ctrans('texts.client_name'), 'type' => 'text', 'validation' => 'required'];
|
||||
}
|
||||
|
||||
if ($this->company_gateway->require_client_phone) {
|
||||
$fields[] = ['name' => 'client_phone', 'label' => ctrans('texts.client_phone'), 'type' => 'tel', 'validation' => 'required'];
|
||||
}
|
||||
|
||||
if ($this->company_gateway->require_contact_name) {
|
||||
$fields[] = ['name' => 'contact_first_name', 'label' => ctrans('texts.first_name'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'contact_last_name', 'label' => ctrans('texts.last_name'), 'type' => 'text', 'validation' => 'required'];
|
||||
@ -188,6 +183,10 @@ class StripePaymentDriver extends BaseDriver
|
||||
$fields[] = ['name' => 'contact_email', 'label' => ctrans('texts.email'), 'type' => 'text', 'validation' => 'required,email:rfc'];
|
||||
}
|
||||
|
||||
if ($this->company_gateway->require_client_phone) {
|
||||
$fields[] = ['name' => 'client_phone', 'label' => ctrans('texts.client_phone'), 'type' => 'tel', 'validation' => 'required'];
|
||||
}
|
||||
|
||||
if ($this->company_gateway->require_billing_address) {
|
||||
$fields[] = ['name' => 'client_address_line_1', 'label' => ctrans('texts.address1'), 'type' => 'text', 'validation' => 'required'];
|
||||
// $fields[] = ['name' => 'client_address_line_2', 'label' => ctrans('texts.address2'), 'type' => 'text', 'validation' => 'nullable'];
|
||||
@ -196,6 +195,8 @@ class StripePaymentDriver extends BaseDriver
|
||||
$fields[] = ['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'text', 'validation' => 'required'];
|
||||
}
|
||||
|
||||
$fields[] = ['name' => 'client_postal_code', 'label' => ctrans('texts.postal_code'), 'type' => 'text', 'validation' => 'required'];
|
||||
|
||||
if ($this->company_gateway->require_shipping_address) {
|
||||
$fields[] = ['name' => 'client_shipping_address_line_1', 'label' => ctrans('texts.shipping_address1'), 'type' => 'text', 'validation' => 'required'];
|
||||
// $fields[] = ['name' => 'client_shipping_address_line_2', 'label' => ctrans('texts.shipping_address2'), 'type' => 'text', 'validation' => 'sometimes'];
|
||||
@ -205,6 +206,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
$fields[] = ['name' => 'client_shipping_country_id', 'label' => ctrans('texts.shipping_country'), 'type' => 'text', 'validation' => 'required'];
|
||||
}
|
||||
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
@ -212,7 +214,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
* Proxy method to pass the data into payment method authorizeView().
|
||||
*
|
||||
* @param array $data
|
||||
* @return \Illuminate\Http\RedirectResponse|mixed
|
||||
* @return RedirectResponse|mixed
|
||||
*/
|
||||
public function authorizeView(array $data)
|
||||
{
|
||||
@ -223,7 +225,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
* Processes the gateway response for credit card authorization.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse|mixed
|
||||
* @return RedirectResponse|mixed
|
||||
*/
|
||||
public function authorizeResponse($request)
|
||||
{
|
||||
@ -234,7 +236,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
* Process the payment with gateway.
|
||||
*
|
||||
* @param array $data
|
||||
* @return \Illuminate\Http\RedirectResponse|mixed
|
||||
* @return RedirectResponse|mixed
|
||||
*/
|
||||
public function processPaymentView(array $data)
|
||||
{
|
||||
@ -292,7 +294,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
* Finds or creates a Stripe Customer object.
|
||||
*
|
||||
* @return null|Customer A Stripe customer object
|
||||
* @throws \Laracasts\Presenter\Exceptions\PresenterException
|
||||
* @throws PresenterException
|
||||
* @throws ApiErrorException
|
||||
*/
|
||||
public function findOrCreateCustomer(): ?Customer
|
||||
@ -336,7 +338,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
try {
|
||||
$response = $this->stripe
|
||||
->refunds
|
||||
->create(['charge' => $payment->transaction_reference, 'amount' => $this->convertToStripeAmount($amount, $this->client->currency()->precision)], $meta);
|
||||
->create(['charge' => $payment->transaction_reference, 'amount' => $this->convertToStripeAmount($amount, $this->client->currency()->precision, $this->client->currency())], $meta);
|
||||
|
||||
if ($response->status == $response::STATUS_SUCCEEDED) {
|
||||
SystemLogger::dispatch(['server_response' => $response, 'data' => request()->all(),], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_STRIPE, $this->client, $this->client->company);
|
||||
|
@ -189,7 +189,7 @@ class ACH
|
||||
|
||||
public function paymentResponse($request)
|
||||
{
|
||||
nlog($request->all());
|
||||
// nlog($request->all());
|
||||
|
||||
$token = ClientGatewayToken::find($this->decodePrimaryKey($request->input('source')));
|
||||
$token_meta = $token->meta;
|
||||
@ -197,14 +197,20 @@ class ACH
|
||||
if($token_meta->state != "authorized")
|
||||
return redirect()->route('client.payment_methods.verification', ['payment_method' => $token->hashed_id, 'method' => GatewayType::BANK_TRANSFER]);
|
||||
|
||||
$app_fee = (config('ninja.wepay.fee_ach_multiplier') * $this->wepay_payment_driver->payment_hash->data->amount_with_fee) + config('ninja.wepay.fee_fixed');
|
||||
|
||||
$response = $this->wepay_payment_driver->wepay->request('checkout/create', array(
|
||||
// 'callback_uri' => route('payment_webhook', ['company_key' => $this->wepay_payment_driver->company_gateway->company->company_key, 'company_gateway_id' => $this->wepay_payment_driver->company_gateway->hashed_id]),
|
||||
'unique_id' => Str::random(40),
|
||||
'account_id' => $this->wepay_payment_driver->company_gateway->getConfigField('accountId'),
|
||||
'amount' => $this->wepay_payment_driver->payment_hash->data->amount_with_fee,
|
||||
'currency' => $this->wepay_payment_driver->client->getCurrencyCode(),
|
||||
'short_description' => 'A vacation home rental',
|
||||
'short_description' => 'Goods and Services',
|
||||
'type' => 'goods',
|
||||
'fee' => [
|
||||
'fee_payer' => config('ninja.wepay.fee_payer'),
|
||||
'app_fee' => $app_fee,
|
||||
],
|
||||
'payment_method' => array(
|
||||
'type' => 'payment_bank',
|
||||
'payment_bank' => array(
|
||||
|
@ -139,16 +139,21 @@ use WePayCommon;
|
||||
}
|
||||
|
||||
// USD, CAD, and GBP.
|
||||
nlog($request->all());
|
||||
// nlog($request->all());
|
||||
|
||||
$app_fee = (config('ninja.wepay.fee_cc_multiplier') * $this->wepay_payment_driver->payment_hash->data->amount_with_fee) + config('ninja.wepay.fee_fixed');
|
||||
// charge the credit card
|
||||
$response = $this->wepay_payment_driver->wepay->request('checkout/create', array(
|
||||
// 'callback_uri' => route('payment_webhook', ['company_key' => $this->wepay_payment_driver->company_gateway->company->company_key, 'company_gateway_id' => $this->wepay_payment_driver->company_gateway->hashed_id]),
|
||||
'unique_id' => Str::random(40),
|
||||
'account_id' => $this->wepay_payment_driver->company_gateway->getConfigField('accountId'),
|
||||
'amount' => $this->wepay_payment_driver->payment_hash->data->amount_with_fee,
|
||||
'currency' => $this->wepay_payment_driver->client->getCurrencyCode(),
|
||||
'short_description' => 'A vacation home rental',
|
||||
'short_description' => 'Goods and services',
|
||||
'type' => 'goods',
|
||||
'fee' => [
|
||||
'fee_payer' => config('ninja.wepay.fee_payer'),
|
||||
'app_fee' => $app_fee,
|
||||
],
|
||||
'payment_method' => array(
|
||||
'type' => 'credit_card',
|
||||
'credit_card' => array(
|
||||
|
@ -24,8 +24,13 @@ trait WePayCommon
|
||||
private function processSuccessfulPayment($response, $payment_status, $gateway_type)
|
||||
{
|
||||
|
||||
if($gateway_type == GatewayType::BANK_TRANSFER)
|
||||
$payment_type = PaymentType::ACH;
|
||||
else
|
||||
$payment_type = PaymentType::CREDIT_CARD_OTHER;
|
||||
|
||||
$data = [
|
||||
'payment_type' => PaymentType::CREDIT_CARD_OTHER,
|
||||
'payment_type' => $payment_type,
|
||||
'amount' => $response->amount,
|
||||
'transaction_reference' => $response->checkout_id,
|
||||
'gateway_type_id' => $gateway_type,
|
||||
|
@ -81,11 +81,8 @@ class WePayPaymentDriver extends BaseDriver
|
||||
{
|
||||
$types = [];
|
||||
|
||||
if($this->company_gateway->fees_and_limits->{GatewayType::BANK_TRANSFER}->is_enabled)
|
||||
$types[] = GatewayType::CREDIT_CARD;
|
||||
|
||||
if($this->company_gateway->fees_and_limits->{GatewayType::BANK_TRANSFER}->is_enabled)
|
||||
$types[] = GatewayType::BANK_TRANSFER;
|
||||
$types[] = GatewayType::CREDIT_CARD;
|
||||
$types[] = GatewayType::BANK_TRANSFER;
|
||||
|
||||
return $types;
|
||||
}
|
||||
@ -266,18 +263,17 @@ class WePayPaymentDriver extends BaseDriver
|
||||
|
||||
$response = $this->wepay->request('checkout/refund', array(
|
||||
'checkout_id' => $payment->transaction_reference,
|
||||
'refund_reason' => 'Refund',
|
||||
'refund_reason' => 'Refund by merchant',
|
||||
'amount' => $amount
|
||||
));
|
||||
|
||||
|
||||
return [
|
||||
'transaction_reference' => $response->checkout_id,
|
||||
'transaction_response' => json_encode($response),
|
||||
'success' => $response->state == 'refunded' ? true : false,
|
||||
'description' => 'refund',
|
||||
'code' => 0,
|
||||
];
|
||||
return [
|
||||
'transaction_reference' => $response->checkout_id,
|
||||
'transaction_response' => json_encode($response),
|
||||
'success' => $response->state == 'refunded' ? true : false,
|
||||
'description' => 'refund',
|
||||
'code' => 0,
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,25 @@ class CompanyRepository extends BaseRepository
|
||||
|
||||
private function parseCustomFields($fields) :array
|
||||
{
|
||||
|
||||
if(array_key_exists('account1', $fields))
|
||||
$fields['company1'] = $fields['account1'];
|
||||
|
||||
if(array_key_exists('account2', $fields))
|
||||
$fields['company2'] = $fields['account2'];
|
||||
|
||||
if(array_key_exists('invoice1', $fields))
|
||||
$fields['surcharge1'] = $fields['invoice1'];
|
||||
|
||||
if(array_key_exists('invoice2', $fields))
|
||||
$fields['surcharge2'] = $fields['invoice2'];
|
||||
|
||||
if(array_key_exists('invoice_text1', $fields))
|
||||
$fields['invoice1'] = $fields['invoice_text1'];
|
||||
|
||||
if(array_key_exists('invoice_text2', $fields))
|
||||
$fields['invoice2'] = $fields['invoice_text2'];
|
||||
|
||||
foreach ($fields as &$value) {
|
||||
$value = (string) $value;
|
||||
}
|
||||
|
@ -11,12 +11,14 @@
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\GroupSetting;
|
||||
|
||||
class GroupSettingRepository extends BaseRepository
|
||||
{
|
||||
public function save($data, GroupSetting $group_setting) :?GroupSetting
|
||||
{
|
||||
|
||||
$group_setting->fill($data);
|
||||
$group_setting->save();
|
||||
|
||||
@ -27,6 +29,13 @@ class GroupSettingRepository extends BaseRepository
|
||||
$group_setting->save();
|
||||
}
|
||||
|
||||
if(!array_key_exists('settings', $data) || count((array)$data['settings']) == 0){
|
||||
$settings = new \stdClass;
|
||||
$settings->entity = Client::class;
|
||||
$group_setting->settings = $settings;
|
||||
$group_setting->save();
|
||||
}
|
||||
|
||||
return $group_setting;
|
||||
}
|
||||
}
|
||||
|
@ -157,9 +157,11 @@ class PaymentRepository extends BaseRepository {
|
||||
|
||||
if ( ! $is_existing_payment && ! $this->import_mode ) {
|
||||
|
||||
if ($payment->client->getSetting('client_manual_payment_notification'))
|
||||
if (array_key_exists('email_receipt', $data) && $data['email_receipt'] == true)
|
||||
$payment->service()->sendEmail();
|
||||
|
||||
elseif(!array_key_exists('email_receipt', $data) && $payment->client->getSetting('client_manual_payment_notification'))
|
||||
$payment->service()->sendEmail();
|
||||
|
||||
event( new PaymentWasCreated( $payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null) ) );
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
namespace App\Repositories;
|
||||
|
||||
|
||||
use App\DataMapper\ClientSettings;
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Models\Client;
|
||||
@ -44,8 +45,8 @@ class SubscriptionRepository extends BaseRepository
|
||||
private function calculatePrice($subscription) :array
|
||||
{
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
// DB::beginTransaction();
|
||||
DB::connection(config('database.default'))->beginTransaction();
|
||||
$data = [];
|
||||
|
||||
$client = Client::factory()->create([
|
||||
@ -53,6 +54,7 @@ class SubscriptionRepository extends BaseRepository
|
||||
'company_id' => $subscription->company_id,
|
||||
'group_settings_id' => $subscription->group_id,
|
||||
'country_id' => $subscription->company->settings->country_id,
|
||||
'settings' => ClientSettings::defaults(),
|
||||
]);
|
||||
|
||||
$contact = ClientContact::factory()->create([
|
||||
@ -88,13 +90,15 @@ class SubscriptionRepository extends BaseRepository
|
||||
|
||||
$data['promo_price'] = $invoice->calc()->getTotal();
|
||||
|
||||
DB::rollBack();
|
||||
|
||||
// DB::rollBack();
|
||||
DB::connection(config('database.default'))->rollBack();
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function generateLineItems($subscription, $is_recurring = false)
|
||||
public function generateLineItems($subscription, $is_recurring = false, $is_credit = false)
|
||||
{
|
||||
$multiplier = $is_credit ? -1 : 1;
|
||||
|
||||
$line_items = [];
|
||||
|
||||
@ -102,13 +106,13 @@ class SubscriptionRepository extends BaseRepository
|
||||
{
|
||||
foreach($subscription->service()->products() as $product)
|
||||
{
|
||||
$line_items[] = (array)$this->makeLineItem($product);
|
||||
$line_items[] = (array)$this->makeLineItem($product, $multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
foreach($subscription->service()->recurring_products() as $product)
|
||||
{
|
||||
$line_items[] = (array)$this->makeLineItem($product);
|
||||
$line_items[] = (array)$this->makeLineItem($product, $multiplier);
|
||||
}
|
||||
|
||||
$line_items = $this->cleanItems($line_items);
|
||||
@ -117,13 +121,13 @@ class SubscriptionRepository extends BaseRepository
|
||||
|
||||
}
|
||||
|
||||
private function makeLineItem($product)
|
||||
private function makeLineItem($product, $multiplier)
|
||||
{
|
||||
$item = new InvoiceItem;
|
||||
$item->quantity = $product->quantity;
|
||||
$item->product_key = $product->product_key;
|
||||
$item->notes = $product->notes;
|
||||
$item->cost = $product->price;
|
||||
$item->cost = $product->price*$multiplier;
|
||||
$item->tax_rate1 = $product->tax_rate1 ?: 0;
|
||||
$item->tax_name1 = $product->tax_name1 ?: '';
|
||||
$item->tax_rate2 = $product->tax_rate2 ?: 0;
|
||||
|
@ -48,20 +48,28 @@ class ClientService
|
||||
|
||||
public function getCreditBalance() :float
|
||||
{
|
||||
$credits = $this->client->credits
|
||||
$credits = $this->client->credits()
|
||||
->where('is_deleted', false)
|
||||
->where('balance', '>', 0)
|
||||
->sortBy('created_at');
|
||||
->where(function ($query){
|
||||
$query->whereDate('due_date', '<=', now()->format('Y-m-d'))
|
||||
->orWhereNull('due_date');
|
||||
})
|
||||
->orderBy('created_at','ASC');
|
||||
|
||||
return Number::roundValue($credits->sum('balance'), $this->client->currency()->precision);
|
||||
}
|
||||
|
||||
public function getCredits() :Collection
|
||||
{
|
||||
return $this->client->credits
|
||||
return $this->client->credits()
|
||||
->where('is_deleted', false)
|
||||
->where('balance', '>', 0)
|
||||
->sortBy('created_at');
|
||||
->where(function ($query){
|
||||
$query->whereDate('due_date', '<=', now()->format('Y-m-d'))
|
||||
->orWhereNull('due_date');
|
||||
})
|
||||
->orderBy('created_at','ASC');
|
||||
}
|
||||
|
||||
public function getPaymentMethods(float $amount)
|
||||
|
@ -129,7 +129,7 @@ class UpdateReminder extends AbstractService
|
||||
|
||||
if ($this->invoice->last_sent_date &&
|
||||
(int)$this->settings->endless_reminder_frequency_id > 0) {
|
||||
$reminder_date = $this->addTimeInterval($this->invoice->last_sent_date, (int)$this->settings->num_days_reminder3)->addSeconds($offset);
|
||||
$reminder_date = $this->addTimeInterval($this->invoice->last_sent_date, (int)$this->settings->endless_reminder_frequency_id)->addSeconds($offset);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
|
||||
$date_collection->push($reminder_date);
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
namespace App\Services\PdfMaker;
|
||||
|
||||
use App\Models\Credit;
|
||||
use App\Models\Quote;
|
||||
use App\Services\PdfMaker\Designs\Utilities\BaseDesign;
|
||||
use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
|
||||
@ -137,10 +138,6 @@ class Design extends BaseDesign
|
||||
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]];
|
||||
}
|
||||
|
||||
foreach (['company1', 'company2', 'company3', 'company4'] as $field) {
|
||||
$elements[] = ['element' => 'p', 'content' => $this->getCustomFieldValue($field), 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . $field]];
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
@ -198,6 +195,10 @@ class Design extends BaseDesign
|
||||
$variables = $this->context['pdf_variables']['quote_details'];
|
||||
}
|
||||
|
||||
if ($this->entity instanceof Credit) {
|
||||
$variables = $this->context['pdf_variables']['credit_details'];
|
||||
}
|
||||
|
||||
$elements = [];
|
||||
|
||||
// We don't want to show account balance or invoice total on PDF.. or any amount with currency.
|
||||
|
@ -20,6 +20,7 @@ use App\Jobs\Util\SubscriptionWebhookHandler;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\Product;
|
||||
@ -212,13 +213,23 @@ class SubscriptionService
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
|
||||
//sometimes the last document could be a credit if the user is paying for their service with credits.
|
||||
if(!$outstanding_invoice){
|
||||
|
||||
$outstanding_invoice = Credit::where('subscription_id', $this->subscription->id)
|
||||
->where('client_id', $recurring_invoice->client_id)
|
||||
->where('is_deleted', 0)
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
|
||||
}
|
||||
if ($outstanding->count() == 0){
|
||||
//nothing outstanding
|
||||
return $target->price - $this->calculateProRataRefund($outstanding_invoice);
|
||||
return $target->price - $this->calculateProRataRefundForSubscription($outstanding_invoice);
|
||||
}
|
||||
elseif ($outstanding->count() == 1){
|
||||
//user has multiple amounts outstanding
|
||||
return $target->price - $this->calculateProRataRefund($outstanding_invoice);
|
||||
return $target->price - $this->calculateProRataRefundForSubscription($outstanding_invoice);
|
||||
}
|
||||
elseif ($outstanding->count() > 1) {
|
||||
//user is changing plan mid frequency cycle
|
||||
@ -230,6 +241,35 @@ class SubscriptionService
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* We refund unused days left.
|
||||
*
|
||||
* @param Invoice $invoice
|
||||
* @return float
|
||||
*/
|
||||
private function calculateProRataRefundForSubscription($invoice) :float
|
||||
{
|
||||
if(!$invoice || !$invoice->date)
|
||||
return 0;
|
||||
|
||||
$start_date = Carbon::parse($invoice->date);
|
||||
|
||||
$current_date = now();
|
||||
|
||||
$days_of_subscription_used = $start_date->diffInDays($current_date);
|
||||
|
||||
$days_in_frequency = $this->getDaysInFrequency();
|
||||
|
||||
$pro_rata_refund = round((($days_in_frequency - $days_of_subscription_used)/$days_in_frequency) * $this->subscription->price ,2);
|
||||
|
||||
// nlog("days in frequency = {$days_in_frequency} - days of subscription used {$days_of_subscription_used}");
|
||||
// nlog("invoice amount = {$invoice->amount}");
|
||||
// nlog("pro rata refund = {$pro_rata_refund}");
|
||||
|
||||
return $pro_rata_refund;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* We refund unused days left.
|
||||
*
|
||||
@ -238,7 +278,7 @@ class SubscriptionService
|
||||
*/
|
||||
private function calculateProRataRefund($invoice) :float
|
||||
{
|
||||
if(!$invoice->date)
|
||||
if(!$invoice || !$invoice->date)
|
||||
return 0;
|
||||
|
||||
$start_date = Carbon::parse($invoice->date);
|
||||
@ -251,6 +291,10 @@ class SubscriptionService
|
||||
|
||||
$pro_rata_refund = round((($days_in_frequency - $days_of_subscription_used)/$days_in_frequency) * $invoice->amount ,2);
|
||||
|
||||
// nlog("days in frequency = {$days_in_frequency} - days of subscription used {$days_of_subscription_used}");
|
||||
// nlog("invoice amount = {$invoice->amount}");
|
||||
// nlog("pro rata refund = {$pro_rata_refund}");
|
||||
|
||||
return $pro_rata_refund;
|
||||
|
||||
}
|
||||
@ -265,6 +309,9 @@ class SubscriptionService
|
||||
*/
|
||||
private function calculateProRataRefundItems($invoice, $is_credit = false) :array
|
||||
{
|
||||
if(!$invoice)
|
||||
return [];
|
||||
|
||||
/* depending on whether we are creating an invoice or a credit*/
|
||||
$multiplier = $is_credit ? 1 : -1;
|
||||
|
||||
@ -384,6 +431,46 @@ class SubscriptionService
|
||||
|
||||
}
|
||||
|
||||
public function changePlanPaymentCheck($data)
|
||||
{
|
||||
|
||||
$recurring_invoice = $data['recurring_invoice'];
|
||||
$old_subscription = $data['subscription'];
|
||||
$target_subscription = $data['target'];
|
||||
|
||||
$pro_rata_charge_amount = 0;
|
||||
$pro_rata_refund_amount = 0;
|
||||
|
||||
$last_invoice = Invoice::where('subscription_id', $recurring_invoice->subscription_id)
|
||||
->where('client_id', $recurring_invoice->client_id)
|
||||
->where('is_deleted', 0)
|
||||
->withTrashed()
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
if(!$last_invoice)
|
||||
return true;
|
||||
|
||||
if($last_invoice->balance > 0)
|
||||
{
|
||||
$pro_rata_charge_amount = $this->calculateProRataCharge($last_invoice, $old_subscription);
|
||||
nlog("pro rata charge = {$pro_rata_charge_amount}");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$pro_rata_refund_amount = $this->calculateProRataRefund($last_invoice, $old_subscription) * -1;
|
||||
nlog("pro rata refund = {$pro_rata_refund_amount}");
|
||||
}
|
||||
|
||||
$total_payable = $pro_rata_refund_amount + $pro_rata_charge_amount + $this->subscription->price;
|
||||
|
||||
if($total_payable > 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* When changing plans, we need to generate a pro rata invoice
|
||||
*
|
||||
@ -392,6 +479,7 @@ class SubscriptionService
|
||||
*/
|
||||
public function createChangePlanInvoice($data)
|
||||
{
|
||||
|
||||
$recurring_invoice = $data['recurring_invoice'];
|
||||
$old_subscription = $data['subscription'];
|
||||
$target_subscription = $data['target'];
|
||||
@ -406,7 +494,10 @@ class SubscriptionService
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
|
||||
if($last_invoice->balance > 0)
|
||||
if(!$last_invoice){
|
||||
//do nothing
|
||||
}
|
||||
else if($last_invoice->balance > 0)
|
||||
{
|
||||
$pro_rata_charge_amount = $this->calculateProRataCharge($last_invoice, $old_subscription);
|
||||
nlog("pro rata charge = {$pro_rata_charge_amount}");
|
||||
@ -431,7 +522,7 @@ class SubscriptionService
|
||||
*/
|
||||
private function handlePlanChange($payment_hash)
|
||||
{
|
||||
|
||||
nlog("handle plan change");
|
||||
$old_recurring_invoice = RecurringInvoice::find($payment_hash->data->billing_context->recurring_invoice);
|
||||
|
||||
$recurring_invoice = $this->createNewRecurringInvoice($old_recurring_invoice);
|
||||
@ -500,7 +591,7 @@ class SubscriptionService
|
||||
$credit->date = now()->format('Y-m-d');
|
||||
$credit->subscription_id = $this->subscription->id;
|
||||
|
||||
$line_items = $subscription_repo->generateLineItems($target);
|
||||
$line_items = $subscription_repo->generateLineItems($target, false, true);
|
||||
|
||||
$credit->line_items = array_merge($line_items, $this->calculateProRataRefundItems($last_invoice, true));
|
||||
|
||||
|
@ -36,6 +36,7 @@ class ActivityTransformer extends EntityTransformer
|
||||
'id' => (string) $this->encodePrimaryKey($activity->id),
|
||||
'activity_type_id' => (string) $activity->activity_type_id,
|
||||
'client_id' => $activity->client_id ? (string) $this->encodePrimaryKey($activity->client_id) : '',
|
||||
'recurring_invoice_id' => $activity->recurring_invoice_id ? (string) $this->encodePrimaryKey($activity->recurring_invoice_id) : '',
|
||||
'company_id' => $activity->company_id ? (string) $this->encodePrimaryKey($activity->company_id) : '',
|
||||
'user_id' => (string) $this->encodePrimaryKey($activity->user_id),
|
||||
'invoice_id' => $activity->invoice_id ? (string) $this->encodePrimaryKey($activity->invoice_id) : '',
|
||||
|
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