mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-10-31 15:47:32 -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