mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Merge branch 'v5-develop' into v5-1405-client-portal-settings
This commit is contained in:
commit
708073a83e
@ -3,6 +3,7 @@
|
||||
## [Unreleased (daily channel)](https://github.com/invoiceninja/invoiceninja/tree/v5-develop)
|
||||
- Add Cache-control: no-cache to prevent overaggressive caching of assets
|
||||
- Improved labelling in the settings (client portal)
|
||||
- Client portal: Multiple accounts access improvements (#5703)
|
||||
|
||||
## [v5.1.56-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.1.56-release)
|
||||
## Fixed:
|
||||
|
@ -1 +1 @@
|
||||
5.1.61
|
||||
5.1.62
|
@ -105,6 +105,8 @@ class CreateSingleAccount extends Command
|
||||
'account_id' => $account->id,
|
||||
'slack_webhook_url' => config('ninja.notification.slack'),
|
||||
'default_password_timeout' => 30*60000,
|
||||
'portal_mode' => 'domain',
|
||||
'portal_domain' => 'http://ninja.test:8000',
|
||||
]);
|
||||
|
||||
$account->default_company_id = $company->id;
|
||||
|
@ -128,7 +128,7 @@ class DemoMode extends Command
|
||||
{
|
||||
$faker = \Faker\Factory::create();
|
||||
|
||||
$this->count = 50;
|
||||
$this->count = 25;
|
||||
|
||||
$this->info('Creating Small Account and Company');
|
||||
|
||||
@ -486,7 +486,7 @@ class DemoMode extends Command
|
||||
if (rand(0, 1)) {
|
||||
$invoice->assigned_user_id = $assigned_user_id;
|
||||
}
|
||||
|
||||
$invoice->number = $this->getNextRecurringInvoiceNumber($client);
|
||||
$invoice->save();
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ class Handler extends ExceptionHandler
|
||||
'email' => 'anonymous@example.com',
|
||||
'name' => 'Anonymous User',
|
||||
]);
|
||||
} elseif (auth()->guard('user') && auth()->guard('user')->user() && auth()->user()->company() && auth()->user()->company()->account->report_errors) {
|
||||
} elseif (auth()->guard('user') && auth()->guard('user')->user() && auth()->user()->companyIsSet() && auth()->user()->company()->account->report_errors) {
|
||||
$scope->setUser([
|
||||
'id' => auth()->user()->account->key,
|
||||
'email' => 'anonymous@example.com',
|
||||
|
@ -33,9 +33,6 @@ class ClientFactory
|
||||
$client->client_hash = Str::random(40);
|
||||
$client->settings = ClientSettings::defaults();
|
||||
|
||||
// $client_contact = ClientContactFactory::create($company_id, $user_id);
|
||||
// $client->contacts->add($client_contact);
|
||||
|
||||
return $client;
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ class CompanyGatewayFactory
|
||||
$company_gateway = new CompanyGateway;
|
||||
$company_gateway->company_id = $company_id;
|
||||
$company_gateway->user_id = $user_id;
|
||||
$company_gateway->require_billing_address = false;
|
||||
$company_gateway->require_shipping_address = false;
|
||||
// $company_gateway->fees_and_limits = new FeesAndLimits;
|
||||
|
||||
return $company_gateway;
|
||||
|
@ -37,10 +37,10 @@ class CreditFactory
|
||||
$credit->tax_rate1 = 0;
|
||||
$credit->tax_name2 = '';
|
||||
$credit->tax_rate2 = 0;
|
||||
$credit->custom_value1 = 0;
|
||||
$credit->custom_value2 = 0;
|
||||
$credit->custom_value3 = 0;
|
||||
$credit->custom_value4 = 0;
|
||||
$credit->custom_value1 = '';
|
||||
$credit->custom_value2 = '';
|
||||
$credit->custom_value3 = '';
|
||||
$credit->custom_value4 = '';
|
||||
$credit->amount = 0;
|
||||
$credit->balance = 0;
|
||||
$credit->partial = 0;
|
||||
|
@ -38,10 +38,10 @@ class InvoiceFactory
|
||||
$invoice->tax_rate2 = 0;
|
||||
$invoice->tax_name3 = '';
|
||||
$invoice->tax_rate3 = 0;
|
||||
$invoice->custom_value1 = 0;
|
||||
$invoice->custom_value2 = 0;
|
||||
$invoice->custom_value3 = 0;
|
||||
$invoice->custom_value4 = 0;
|
||||
$invoice->custom_value1 = '';
|
||||
$invoice->custom_value2 = '';
|
||||
$invoice->custom_value3 = '';
|
||||
$invoice->custom_value4 = '';
|
||||
$invoice->amount = 0;
|
||||
$invoice->balance = 0;
|
||||
$invoice->paid_to_date = 0;
|
||||
|
@ -36,10 +36,10 @@ class RecurringInvoiceFactory
|
||||
$invoice->tax_rate1 = 0;
|
||||
$invoice->tax_name2 = '';
|
||||
$invoice->tax_rate2 = 0;
|
||||
$invoice->custom_value1 = 0;
|
||||
$invoice->custom_value2 = 0;
|
||||
$invoice->custom_value3 = 0;
|
||||
$invoice->custom_value4 = 0;
|
||||
$invoice->custom_value1 = '';
|
||||
$invoice->custom_value2 = '';
|
||||
$invoice->custom_value3 = '';
|
||||
$invoice->custom_value4 = '';
|
||||
$invoice->amount = 0;
|
||||
$invoice->balance = 0;
|
||||
$invoice->partial = 0;
|
||||
|
@ -35,10 +35,10 @@ class RecurringQuoteFactory
|
||||
$quote->tax_rate1 = 0;
|
||||
$quote->tax_name2 = '';
|
||||
$quote->tax_rate2 = 0;
|
||||
$quote->custom_value1 = 0;
|
||||
$quote->custom_value2 = 0;
|
||||
$quote->custom_value3 = 0;
|
||||
$quote->custom_value4 = 0;
|
||||
$quote->custom_value1 = '';
|
||||
$quote->custom_value2 = '';
|
||||
$quote->custom_value3 = '';
|
||||
$quote->custom_value4 = '';
|
||||
$quote->amount = 0;
|
||||
$quote->balance = 0;
|
||||
$quote->partial = 0;
|
||||
|
@ -164,10 +164,10 @@ class InvoiceController extends Controller
|
||||
|
||||
//if only 1 pdf, output to buffer for download
|
||||
if ($invoices->count() == 1) {
|
||||
return response()->streamDownload(function () use ($invoices) {
|
||||
echo file_get_contents($invoices->first()->pdf_file_path());
|
||||
}, basename($invoices->first()->pdf_file_path()), ['Cache-Control:' => 'no-cache']);
|
||||
//return response()->download(TempFile::path($invoices->first()->pdf_file_path()), basename($invoices->first()->pdf_file_path()));
|
||||
|
||||
$file = $invoices->first()->pdf_file_path();
|
||||
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);;
|
||||
|
||||
}
|
||||
|
||||
// enable output of HTTP headers
|
||||
|
@ -76,10 +76,9 @@ class QuoteController extends Controller
|
||||
}
|
||||
|
||||
if ($quotes->count() == 1) {
|
||||
return response()->streamDownload(function () use ($quotes) {
|
||||
echo file_get_contents($quotes->first()->pdf_file_path());
|
||||
}, basename($quotes->first()->pdf_file_path()), ['Cache-Control:' => 'no-cache']);
|
||||
//return response()->download(TempFile::path($invoices->first()->pdf_file_path()), basename($quotes->first()->pdf_file_path()));
|
||||
|
||||
$file = $quotes->first()->pdf_file_path();
|
||||
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
}
|
||||
|
||||
// enable output of HTTP headers
|
||||
|
@ -535,11 +535,9 @@ class CreditController extends BaseController
|
||||
return $this->itemResponse($credit);
|
||||
}
|
||||
break;
|
||||
case 'download':
|
||||
return response()->streamDownload(function () use ($credit) {
|
||||
echo file_get_contents($credit->pdf_file_path());
|
||||
}, basename($credit->pdf_file_path()), ['Cache-Control:' => 'no-cache']);
|
||||
//return response()->download(TempFile::path($credit->pdf_file_path()), basename($credit->pdf_file_path()));
|
||||
case 'download':
|
||||
$file = $credit->pdf_file_path();
|
||||
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
break;
|
||||
case 'archive':
|
||||
$this->credit_repository->archive($credit);
|
||||
@ -589,7 +587,7 @@ class CreditController extends BaseController
|
||||
|
||||
$file_path = $credit->service()->getCreditPdf($invitation);
|
||||
|
||||
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache']);
|
||||
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -671,10 +671,10 @@ class InvoiceController extends BaseController
|
||||
}
|
||||
break;
|
||||
case 'download':
|
||||
return response()->streamDownload(function () use ($invoice) {
|
||||
echo file_get_contents($invoice->pdf_file_path());
|
||||
}, basename($invoice->pdf_file_path()), ['Cache-Control:' => 'no-cache']);
|
||||
//return response()->download(TempFile::path($invoice->pdf_file_path()), basename($invoice->pdf_file_path()));
|
||||
|
||||
$file = $invoice->pdf_file_path();
|
||||
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
|
||||
break;
|
||||
case 'restore':
|
||||
$this->invoice_repo->restore($invoice);
|
||||
@ -793,9 +793,10 @@ class InvoiceController extends BaseController
|
||||
$contact = $invitation->contact;
|
||||
$invoice = $invitation->invoice;
|
||||
|
||||
$file_path = $invoice->service()->getInvoicePdf($contact);
|
||||
$file = $invoice->service()->getInvoicePdf($contact);
|
||||
|
||||
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);;
|
||||
|
||||
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache']);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -844,13 +845,10 @@ class InvoiceController extends BaseController
|
||||
*/
|
||||
public function deliveryNote(ShowInvoiceRequest $request, Invoice $invoice)
|
||||
{
|
||||
$file_path = $invoice->service()->getInvoiceDeliveryNote($invoice, $invoice->invitations->first()->contact);
|
||||
$file = $invoice->service()->getInvoiceDeliveryNote($invoice, $invoice->invitations->first()->contact);
|
||||
|
||||
try {
|
||||
$file = public_path("storage/{$file_path}");
|
||||
|
||||
|
||||
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache']);
|
||||
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
} catch (\Exception $e) {
|
||||
return response(['message' => 'Oops, something went wrong. Make sure you have symlink to storage/ in public/ directory.'], 500);
|
||||
}
|
||||
|
@ -675,10 +675,10 @@ class QuoteController extends BaseController
|
||||
// code...
|
||||
break;
|
||||
case 'download':
|
||||
return response()->streamDownload(function () use ($quote) {
|
||||
echo file_get_contents($quote->pdf_file_path());
|
||||
}, basename($quote->pdf_file_path()), ['Cache-Control:' => 'no-cache']);
|
||||
//return response()->download(TempFile::path($quote->pdf_file_path()), basename($quote->pdf_file_path()));
|
||||
|
||||
$file = $quote->pdf_file_path();
|
||||
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
|
||||
break;
|
||||
case 'restore':
|
||||
$this->quote_repo->restore($quote);
|
||||
@ -730,7 +730,7 @@ class QuoteController extends BaseController
|
||||
|
||||
$file_path = $quote->service()->getQuotePdf($contact);
|
||||
|
||||
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache']);
|
||||
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -497,7 +497,7 @@ class RecurringInvoiceController extends BaseController
|
||||
|
||||
$file_path = $recurring_invoice->service()->getInvoicePdf($contact);
|
||||
|
||||
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache']);
|
||||
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -17,8 +17,11 @@ use App\Factory\CompanyGatewayFactory;
|
||||
use App\Http\Requests\StripeConnect\InitializeStripeConnectRequest;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\GatewayType;
|
||||
use App\PaymentDrivers\Stripe\Connect\Account;
|
||||
use Illuminate\Http\Request;
|
||||
use Stripe\Exception\ApiErrorException;
|
||||
|
||||
class StripeConnectController extends BaseController
|
||||
@ -38,6 +41,8 @@ class StripeConnectController extends BaseController
|
||||
|
||||
MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']);
|
||||
|
||||
$company = Company::where('company_key', $request->getTokenContent()['company_key'])->first();
|
||||
|
||||
$company_gateway = CompanyGateway::query()
|
||||
->where('gateway_key', 'd14dd26a47cecc30fdd65700bfb67b34')
|
||||
->where('company_id', $request->getCompany()->id)
|
||||
@ -45,53 +50,75 @@ class StripeConnectController extends BaseController
|
||||
|
||||
if ($company_gateway) {
|
||||
|
||||
$config = decrypt($company_gateway->config);
|
||||
$config = $company_gateway->getConfig();
|
||||
|
||||
if(property_exists($config, 'account_id'))
|
||||
return render('gateways.stripe.connect.existing');
|
||||
|
||||
}
|
||||
else
|
||||
$company_gateway = CompanyGatewayFactory::create($request->getCompany()->id, $request->getContact()->id);
|
||||
return view('auth.connect.existing');
|
||||
|
||||
/* Set Credit Card To Enabled */
|
||||
$gateway_types = $company_gateway->driver(new Client)->gatewayTypes();
|
||||
}
|
||||
|
||||
$stripe_client_id = config('ninja.ninja_stripe_client_id');
|
||||
$redirect_uri = 'http://ninja.test:8000/stripe/completed';
|
||||
$endpoint = "https://connect.stripe.com/oauth/authorize?response_type=code&client_id={$stripe_client_id}&redirect_uri={$redirect_uri}&scope=read_write&state={$token}";
|
||||
|
||||
if($email = $request->getContact()->email)
|
||||
$endpoint .= "&stripe_user[email]={$email}";
|
||||
|
||||
$company_name = str_replace(" ", "_", $company->present()->name());
|
||||
$endpoint .= "&stripe_user[business_name]={$company_name}";
|
||||
|
||||
return redirect($endpoint);
|
||||
}
|
||||
|
||||
public function completed(InitializeStripeConnectRequest $request)
|
||||
{
|
||||
|
||||
\Stripe\Stripe::setApiKey(config('ninja.ninja_stripe_key'));
|
||||
|
||||
$response = \Stripe\OAuth::token([
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => $request->input('code'),
|
||||
]);
|
||||
|
||||
// nlog($response);
|
||||
|
||||
$company = Company::where('company_key', $request->getTokenContent()['company_key'])->first();
|
||||
|
||||
$company_gateway = CompanyGatewayFactory::create($company->id, $company->id);
|
||||
$fees_and_limits = new \stdClass;
|
||||
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||
|
||||
$fees_and_limits->{GatewayType::CREDIT_CARD} = new FeesAndLimits;
|
||||
$company_gateway->gateway_key = 'd14dd26a47cecc30fdd65700bfb67b34';
|
||||
$company_gateway->fees_and_limits = $fees_and_limits;
|
||||
$company_gateway->setConfig([]);
|
||||
$company_gateway->save();
|
||||
|
||||
$payload = [
|
||||
'account_id' => $response->stripe_user_id,
|
||||
"token_type" => 'bearer',
|
||||
"stripe_publishable_key" => $response->stripe_publishable_key,
|
||||
"scope" => $response->scope,
|
||||
"livemode" => $response->livemode,
|
||||
"stripe_user_id" => $response->stripe_user_id,
|
||||
"refresh_token" => $response->refresh_token,
|
||||
"access_token" => $response->access_token
|
||||
];
|
||||
|
||||
/* Link account if existing account exists */
|
||||
if($account_id = $this->checkAccountAlreadyLinkToEmail($company_gateway, $request->getContact()->email)) {
|
||||
|
||||
$config = json_decode(decrypt($company_gateway->config));
|
||||
|
||||
$config->account_id = $account_id;
|
||||
$company_gateway->config = encrypt(json_encode($config));
|
||||
$payload['account_id'] = $account_id;
|
||||
$company_gateway->setConfig($payload);
|
||||
$company_gateway->save();
|
||||
|
||||
return render('gateways.stripe.connect.existing');
|
||||
return view('auth.connect.existing');
|
||||
|
||||
}
|
||||
|
||||
$data = [
|
||||
'type' => 'standard',
|
||||
'email' => $request->getContact()->email,
|
||||
'country' => $request->getCompany()->country()->iso_3166_2,
|
||||
];
|
||||
|
||||
$account = Account::create($data);
|
||||
$link = Account::link($account->id, $token);
|
||||
$company_gateway->config = encrypt(json_encode(['account_id' => $account->id]));
|
||||
$company_gateway->setConfig($payload);
|
||||
$company_gateway->save();
|
||||
|
||||
return redirect($link['url']);
|
||||
}
|
||||
|
||||
public function completed()
|
||||
{
|
||||
return render('gateways.stripe.connect.completed');
|
||||
//response here
|
||||
return view('auth.connect.completed');
|
||||
}
|
||||
|
||||
|
||||
@ -111,4 +138,22 @@ class StripeConnectController extends BaseController
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*********************************
|
||||
* Stripe OAuth
|
||||
*/
|
||||
|
||||
// public function initialize(InitializeStripeConnectRequest $request, string $token)
|
||||
// {
|
||||
|
||||
// $stripe_key = config('ninja.ninja_stripe_key');
|
||||
|
||||
// $endpoint = "https://connect.stripe.com/oauth/authorize?response_type=code&client_id={$stripe_key}&scope=read_write";
|
||||
|
||||
// return redirect($endpoint);
|
||||
|
||||
// }
|
||||
}
|
||||
|
52
app/Http/Controllers/StripeController.php
Normal file
52
app/Http/Controllers/StripeController.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?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://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Jobs\Util\ImportStripeCustomers;
|
||||
use App\Jobs\Util\StripeUpdatePaymentMethods;
|
||||
|
||||
class StripeController extends BaseController
|
||||
{
|
||||
|
||||
public function update()
|
||||
{
|
||||
if(auth()->user()->isAdmin())
|
||||
{
|
||||
|
||||
StripeUpdatePaymentMethods::dispatch(auth()->user()->getCompany());
|
||||
|
||||
return response()->json(['message' => 'Processing'], 403);
|
||||
|
||||
}
|
||||
|
||||
|
||||
return response()->json(['message' => 'Unauthorized'], 403);
|
||||
}
|
||||
|
||||
public function import()
|
||||
{
|
||||
|
||||
if(auth()->user()->isAdmin())
|
||||
{
|
||||
|
||||
ImportStripeCustomers::dispatch(auth()->user()->getCompany());
|
||||
|
||||
return response()->json(['message' => 'Processing'], 403);
|
||||
|
||||
}
|
||||
|
||||
|
||||
return response()->json(['message' => 'Unauthorized'], 403);
|
||||
}
|
||||
|
||||
}
|
@ -110,7 +110,6 @@ class Kernel extends HttpKernel
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
//\App\Http\Middleware\StartupCheck::class,
|
||||
QueryLogging::class,
|
||||
],
|
||||
'shop' => [
|
||||
@ -163,6 +162,7 @@ class Kernel extends HttpKernel
|
||||
|
||||
|
||||
protected $middlewarePriority = [
|
||||
SetDomainNameDb::class,
|
||||
SetDb::class,
|
||||
SetWebDb::class,
|
||||
UrlSetDb::class,
|
||||
@ -170,7 +170,6 @@ class Kernel extends HttpKernel
|
||||
SetEmailDb::class,
|
||||
SetInviteDb::class,
|
||||
SetDbByCompanyKey::class,
|
||||
SetDomainNameDb::class,
|
||||
TokenAuth::class,
|
||||
ContactTokenAuth::class,
|
||||
ContactKeyLogin::class,
|
||||
|
@ -31,6 +31,7 @@ class CheckClientExistence
|
||||
$multiple_contacts = ClientContact::query()
|
||||
->where('email', auth('contact')->user()->email)
|
||||
->whereNotNull('email')
|
||||
->where('email', '<>', '')
|
||||
->whereNull('deleted_at')
|
||||
->distinct('company_id')
|
||||
->distinct('email')
|
||||
@ -38,6 +39,9 @@ class CheckClientExistence
|
||||
->whereHas('client', function ($query) {
|
||||
return $query->whereNull('deleted_at');
|
||||
})
|
||||
->whereHas('client.company', function ($query){
|
||||
return $query->where('account_id', auth('contact')->user()->client->company->account->id);
|
||||
})
|
||||
->get();
|
||||
|
||||
if (count($multiple_contacts) == 0) {
|
||||
|
@ -34,14 +34,16 @@ class RedirectIfAuthenticated
|
||||
}
|
||||
break;
|
||||
case 'user':
|
||||
if (Auth::guard($guard)->check()) {
|
||||
return redirect()->route('dashboard.index');
|
||||
}
|
||||
Auth::logout();
|
||||
// if (Auth::guard($guard)->check()) {
|
||||
// return redirect()->route('dashboard.index');
|
||||
// }
|
||||
break;
|
||||
default:
|
||||
if (Auth::guard($guard)->check()) {
|
||||
return redirect('/');
|
||||
}
|
||||
Auth::logout();
|
||||
// if (Auth::guard($guard)->check()) {
|
||||
// return redirect('/');
|
||||
// }
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -34,13 +34,50 @@ class SetDomainNameDb
|
||||
/*
|
||||
* Use the host name to set the active DB
|
||||
**/
|
||||
if ($request->getSchemeAndHttpHost() && config('ninja.db.multi_db_enabled') && ! MultiDB::findAndSetDbByDomain($request->getSchemeAndHttpHost())) {
|
||||
if (request()->json) {
|
||||
return response()->json($error, 403);
|
||||
} else {
|
||||
abort(404);
|
||||
|
||||
if(!config('ninja.db.multi_db_enabled'))
|
||||
return $next($request);
|
||||
|
||||
|
||||
if (strpos($request->getHost(), 'invoicing.co') !== false)
|
||||
{
|
||||
$subdomain = explode('.', $request->getHost())[0];
|
||||
|
||||
$query = [
|
||||
'subdomain' => $subdomain,
|
||||
'portal_mode' => 'subdomain',
|
||||
];
|
||||
|
||||
if(!MultiDB::findAndSetDbByDomain($query)){
|
||||
if ($request->json) {
|
||||
return response()->json($error, 403);
|
||||
} else {
|
||||
MultiDB::setDb('db-ninja-01');
|
||||
nlog("I could not set the DB - defaulting to DB1");
|
||||
//abort(400, 'Domain not found');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
$query = [
|
||||
'portal_domain' => $request->getSchemeAndHttpHost(),
|
||||
'portal_mode' => 'domain',
|
||||
];
|
||||
|
||||
if(!MultiDB::findAndSetDbByDomain($query)){
|
||||
if ($request->json) {
|
||||
return response()->json($error, 403);
|
||||
} else {
|
||||
MultiDB::setDb('db-ninja-01');
|
||||
nlog("I could not set the DB - defaulting to DB1");
|
||||
//abort(400, 'Domain not found');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ class SetInviteDb
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$error = [
|
||||
'message' => 'Invalid URL',
|
||||
'message' => 'I could not find the database for this object.',
|
||||
'errors' => new stdClass,
|
||||
];
|
||||
/*
|
||||
@ -46,7 +46,7 @@ class SetInviteDb
|
||||
if (request()->json) {
|
||||
return response()->json($error, 403);
|
||||
} else {
|
||||
abort(404);
|
||||
abort(404,'I could not find the database for this object.');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,8 +55,9 @@ class StoreCompanyRequest extends Request
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if(array_key_exists('portal_domain', $input) && strlen($input['portal_domain']) > 1)
|
||||
$input['portal_domain'] = str_replace("http:", "https:", $input['portal_domain']);
|
||||
//https not sure i should be forcing this.
|
||||
// if(array_key_exists('portal_domain', $input) && strlen($input['portal_domain']) > 1)
|
||||
// $input['portal_domain'] = str_replace("http:", "https:", $input['portal_domain']);
|
||||
|
||||
if (array_key_exists('google_analytics_url', $input)) {
|
||||
$input['google_analytics_key'] = $input['google_analytics_url'];
|
||||
|
@ -60,8 +60,8 @@ class UpdateCompanyRequest extends Request
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if(array_key_exists('portal_domain', $input) && strlen($input['portal_domain']) > 1)
|
||||
$input['portal_domain'] = str_replace("http:", "https:", $input['portal_domain']);
|
||||
// if(array_key_exists('portal_domain', $input) && strlen($input['portal_domain']) > 1)
|
||||
// $input['portal_domain'] = str_replace("http:", "https:", $input['portal_domain']);
|
||||
|
||||
if (array_key_exists('settings', $input)) {
|
||||
$input['settings'] = $this->filterSaveableSettings($input['settings']);
|
||||
|
@ -49,6 +49,9 @@ class InitializeStripeConnectRequest extends FormRequest
|
||||
*/
|
||||
public function getTokenContent()
|
||||
{
|
||||
if($this->state)
|
||||
$this->token = $this->state;
|
||||
|
||||
$data = Cache::get($this->token);
|
||||
|
||||
return $data;
|
||||
|
@ -71,6 +71,8 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$this->company = Company::where('company_key', $this->company->company_key)->first();
|
||||
|
||||
set_time_limit(0);
|
||||
|
||||
$this->export_data['app_version'] = config('ninja.app_version');
|
||||
@ -114,6 +116,16 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
})->toArray();
|
||||
|
||||
$this->export_data['users'] = $this->company->users()->withTrashed()->cursor()->map(function ($user){
|
||||
|
||||
$user->account_id = $this->encodePrimaryKey($user->account_id);
|
||||
$user->id = $this->encodePrimaryKey($user->id);
|
||||
|
||||
return $user;
|
||||
|
||||
})->toArray();
|
||||
|
||||
|
||||
$this->export_data['client_contacts'] = $this->company->client_contacts->map(function ($client_contact){
|
||||
|
||||
$client_contact = $this->transformArrayOfKeys($client_contact, ['id', 'company_id', 'user_id',' client_id']);
|
||||
@ -140,11 +152,7 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
})->toArray();
|
||||
|
||||
$temp_co = $this->company;
|
||||
$temp_co->id = $this->encodePrimaryKey($temp_co->id);
|
||||
$temp_co->account_id = $this->encodePrimaryKey($temp_co->account_id);
|
||||
|
||||
$this->export_data['company'] = $temp_co->toArray();
|
||||
$this->export_data['company'] = $this->company->toArray();
|
||||
|
||||
$this->export_data['company_gateways'] = $this->company->company_gateways->map(function ($company_gateway){
|
||||
|
||||
@ -273,7 +281,7 @@ class CompanyExport implements ShouldQueue
|
||||
$payment = $this->transformBasicEntities($payment);
|
||||
$payment = $this->transformArrayOfKeys($payment, ['client_id','project_id', 'vendor_id', 'client_contact_id', 'invitation_id', 'company_gateway_id']);
|
||||
|
||||
return $project;
|
||||
return $payment;
|
||||
|
||||
})->toArray();
|
||||
|
||||
@ -370,15 +378,6 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
})->makeHidden(['id'])->toArray();
|
||||
|
||||
$this->export_data['users'] = $this->company->users->map(function ($user){
|
||||
|
||||
$user->account_id = $this->encodePrimaryKey($user->account_id);
|
||||
$user->id = $this->encodePrimaryKey($user->id);
|
||||
|
||||
return $user;
|
||||
|
||||
})->makeHidden(['ip'])->toArray();
|
||||
|
||||
$this->export_data['vendors'] = $this->company->vendors->map(function ($vendor){
|
||||
|
||||
return $this->transformBasicEntities($vendor);
|
||||
@ -431,7 +430,6 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
private function zipAndSend()
|
||||
{
|
||||
nlog("zipping");
|
||||
|
||||
$tempStream = fopen('php://memory', 'w+');
|
||||
|
||||
@ -450,11 +448,8 @@ class CompanyExport implements ShouldQueue
|
||||
$zip->finish();
|
||||
|
||||
$path = 'backups/';
|
||||
|
||||
nlog($path.$file_name);
|
||||
|
||||
Storage::disk(config('filesystems.default'))->put($path.$file_name, $tempStream);
|
||||
// fclose($fp);
|
||||
|
||||
fclose($tempStream);
|
||||
|
||||
|
@ -33,6 +33,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use ZipArchive;
|
||||
use ZipStream\Option\Archive;
|
||||
use ZipStream\ZipStream;
|
||||
@ -41,15 +42,45 @@ class CompanyImport implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
|
||||
|
||||
protected $current_app_version;
|
||||
|
||||
public $company;
|
||||
|
||||
private $account;
|
||||
|
||||
public $file_path;
|
||||
|
||||
private $backup_file;
|
||||
|
||||
public $import_company;
|
||||
|
||||
public $ids = [];
|
||||
|
||||
private $options = '';
|
||||
|
||||
private $importables = [
|
||||
'company',
|
||||
'users',
|
||||
// 'payment_terms',
|
||||
// 'tax_rates',
|
||||
// 'clients',
|
||||
// 'company_gateways',
|
||||
// 'client_gateway_tokens',
|
||||
// 'vendors',
|
||||
// 'projects',
|
||||
// 'products',
|
||||
// 'credits',
|
||||
// 'invoices',
|
||||
// 'recurring_invoices',
|
||||
// 'quotes',
|
||||
// 'payments',
|
||||
// 'expense_categories',
|
||||
// 'task_statuses',
|
||||
// 'expenses',
|
||||
// 'tasks',
|
||||
// 'documents',
|
||||
];
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
@ -62,6 +93,7 @@ class CompanyImport implements ShouldQueue
|
||||
$this->company = $company;
|
||||
$this->file_path = $file_path;
|
||||
$this->options = $options;
|
||||
$this->current_app_version = config('ninja.app_version');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
@ -69,10 +101,19 @@ class CompanyImport implements ShouldQueue
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$this->company =Company::where('company_key', $this->company->company_key)->firstOrFail();
|
||||
$this->account = $this->company->account;
|
||||
|
||||
$this->unzipFile()
|
||||
->preFlightChecks();
|
||||
|
||||
foreach($this->importables as $import){
|
||||
|
||||
$method = Str::ucfirst(Str::camel($import));
|
||||
|
||||
$this->{$method}();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -85,10 +126,12 @@ class CompanyImport implements ShouldQueue
|
||||
private function preFlightChecks()
|
||||
{
|
||||
//check the file version and perform any necessary adjustments to the file in order to proceed - needed when we change schema
|
||||
//
|
||||
$app_version = $this->backup_file->app_version;
|
||||
|
||||
if($this->current_app_version != $this->backup_file->app_version)
|
||||
{
|
||||
//perform some magic here
|
||||
}
|
||||
|
||||
nlog($app_version);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -118,10 +161,39 @@ class CompanyImport implements ShouldQueue
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function importData()
|
||||
private function importUsers()
|
||||
{
|
||||
// $this->import_company = Company::where('company_key', $this->company->company_key)->firstOrFail();
|
||||
User::unguard();
|
||||
|
||||
return $this;
|
||||
foreach ($this->backup_file->users as $user)
|
||||
{
|
||||
|
||||
$new_user = User::firstOrNew(
|
||||
['email' => $user->email],
|
||||
(array)$user,
|
||||
);
|
||||
|
||||
$new_user->account_id = $this->account->id;
|
||||
$new_user->save(['timestamps' => false]);
|
||||
|
||||
$this->ids['users']["{$user->id}"] = $new_user->id;
|
||||
}
|
||||
|
||||
Expense::reguard();
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function transformId(string$resource, string $old): int
|
||||
{
|
||||
if (! array_key_exists($resource, $this->ids)) {
|
||||
throw new \Exception("Resource {$resource} not available.");
|
||||
}
|
||||
|
||||
if (! array_key_exists("{$old}", $this->ids[$resource])) {
|
||||
throw new \Exception("Missing resource key: {$old}");
|
||||
}
|
||||
|
||||
return $this->ids[$resource]["{$old}"];
|
||||
}
|
||||
}
|
@ -64,7 +64,7 @@ class CreateEntityPdf implements ShouldQueue
|
||||
*
|
||||
* @param $invitation
|
||||
*/
|
||||
public function __construct($invitation)
|
||||
public function __construct($invitation, $disk = 'public')
|
||||
{
|
||||
$this->invitation = $invitation;
|
||||
|
||||
@ -86,7 +86,9 @@ class CreateEntityPdf implements ShouldQueue
|
||||
|
||||
$this->contact = $invitation->contact;
|
||||
|
||||
$this->disk = $disk ?? config('filesystems.default');
|
||||
$this->disk = $disk;
|
||||
|
||||
// $this->disk = $disk ?? config('filesystems.default');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
@ -192,12 +194,12 @@ class CreateEntityPdf implements ShouldQueue
|
||||
try{
|
||||
|
||||
Storage::disk($this->disk)->put($file_path, $pdf);
|
||||
|
||||
|
||||
}
|
||||
catch(\Exception $e)
|
||||
{
|
||||
|
||||
throw new FilePermissionsFailure('Could not write the PDF, permission issues!');
|
||||
throw new FilePermissionsFailure($e->getMessage());
|
||||
|
||||
}
|
||||
}
|
||||
@ -209,8 +211,5 @@ class CreateEntityPdf implements ShouldQueue
|
||||
{
|
||||
|
||||
}
|
||||
// public function failed(\Exception $exception)
|
||||
// {
|
||||
// nlog("help!");
|
||||
// }
|
||||
|
||||
}
|
||||
|
@ -83,23 +83,24 @@ class ZipInvoices implements ShouldQueue
|
||||
$zip = new ZipStream($file_name, $options);
|
||||
|
||||
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()), TempFile::path($invoice->pdf_file_path()));
|
||||
$zip->addFileFromPath(basename($invoice->pdf_file_path()), $invoice->pdf_file_path());
|
||||
}
|
||||
|
||||
$zip->finish();
|
||||
|
||||
Storage::disk(config('filesystems.default'))->put($path.$file_name, $tempStream);
|
||||
Storage::disk('public')->put($path.$file_name, $tempStream);
|
||||
|
||||
fclose($tempStream);
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new DownloadInvoices(Storage::disk(config('filesystems.default'))->url($path.$file_name), $this->company);
|
||||
$nmo->mailable = new DownloadInvoices(Storage::disk('public')->url($path.$file_name), $this->company);
|
||||
$nmo->to_user = $this->user;
|
||||
$nmo->settings = $this->settings;
|
||||
$nmo->company = $this->company;
|
||||
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
|
||||
UnlinkFile::dispatch(config('filesystems.default'), $path.$file_name)->delay(now()->addHours(1));
|
||||
UnlinkFile::dispatch('public', $path.$file_name)->delay(now()->addHours(1));
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,8 @@ class NinjaMailerJob implements ShouldQueue
|
||||
|
||||
public $override;
|
||||
|
||||
public $company;
|
||||
|
||||
public function __construct(NinjaMailerObject $nmo, bool $override = false)
|
||||
{
|
||||
|
||||
@ -73,7 +75,8 @@ class NinjaMailerJob implements ShouldQueue
|
||||
/*Set the correct database*/
|
||||
MultiDB::setDb($this->nmo->company->db);
|
||||
|
||||
$company = Company::where('company_key', $this->nmo->company->company_key)->first();
|
||||
/* Serializing models from other jobs wipes the primary key */
|
||||
$this->company = Company::where('company_key', $this->nmo->company->company_key)->first();
|
||||
|
||||
/* Set the email driver */
|
||||
$this->setMailDriver();
|
||||
@ -89,7 +92,7 @@ class NinjaMailerJob implements ShouldQueue
|
||||
|
||||
}
|
||||
else {
|
||||
$this->nmo->mailable->replyTo($company->owner()->email, $company->owner()->present()->name());
|
||||
$this->nmo->mailable->replyTo($this->company->owner()->email, $this->company->owner()->present()->name());
|
||||
}
|
||||
|
||||
|
||||
@ -178,7 +181,15 @@ class NinjaMailerJob implements ShouldQueue
|
||||
nlog("Sending via {$user->name()}");
|
||||
|
||||
$google = (new Google())->init();
|
||||
$google->getClient()->setAccessToken(json_encode($user->oauth_user_token));
|
||||
|
||||
try{
|
||||
$google->getClient()->setAccessToken(json_encode($user->oauth_user_token));
|
||||
}
|
||||
catch(\Exception $e) {
|
||||
$this->logMailError('Gmail Token Invalid', $this->company->clients()->first());
|
||||
$this->nmo->settings->email_sending_method = 'default';
|
||||
return $this->setMailDriver();
|
||||
}
|
||||
|
||||
if ($google->getClient()->isAccessTokenExpired()) {
|
||||
$google->refreshToken($user);
|
||||
|
@ -1264,7 +1264,7 @@ class Import implements ShouldQueue
|
||||
$modified['fees_and_limits'] = $this->cleanFeesAndLimits($modified['fees_and_limits']);
|
||||
}
|
||||
|
||||
if(Ninja::isHosted() && $modified['gateway_key'] == 'd14dd26a37cecc30fdd65700bfb55b23'){
|
||||
else if(Ninja::isHosted() && !Ninja::isPaidHostedClient() && $modified['gateway_key'] == 'd14dd26a37cecc30fdd65700bfb55b23'){
|
||||
$modified['gateway_key'] = 'd14dd26a47cecc30fdd65700bfb67b34';
|
||||
$modified['fees_and_limits'] = [];
|
||||
}
|
||||
@ -1296,6 +1296,8 @@ class Import implements ShouldQueue
|
||||
|
||||
$modified['company_id'] = $this->company->id;
|
||||
$modified['client_id'] = $this->transformId('clients', $resource['client_id']);
|
||||
$modified['company_gateway_id'] = $this->transformId('company_gateways', $resource['company_gateway_id']);
|
||||
|
||||
//$modified['user_id'] = $this->processUserId($resource);
|
||||
|
||||
$cgt = ClientGatewayToken::Create($modified);
|
||||
|
68
app/Jobs/Util/ImportStripeCustomers.php
Normal file
68
app/Jobs/Util/ImportStripeCustomers.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?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://opensource.org/licenses/AAL
|
||||
*/
|
||||
namespace App\Jobs\Util;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Client;
|
||||
use App\Models\CompanyGateway;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
|
||||
class ImportStripeCustomers implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $company;
|
||||
|
||||
private $stripe_keys = ['d14dd26a47cecc30fdd65700bfb67b34', 'd14dd26a37cecc30fdd65700bfb55b23'];
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param $event_id
|
||||
* @param $entity
|
||||
*/
|
||||
public function __construct($company)
|
||||
{
|
||||
$this->company = $company;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$cgs = CompanyGateway::where('company_id', $this->company->id)
|
||||
->whereIn('gateway_key', $this->stripe_keys)
|
||||
->get();
|
||||
|
||||
$cgs->each(function ($company_gateway){
|
||||
|
||||
$company_gateway->driver(new Client)->importCustomers();
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public function failed($exception)
|
||||
{
|
||||
nlog("Stripe import customer methods exception");
|
||||
nlog($exception->getMessage());
|
||||
}
|
||||
}
|
68
app/Jobs/Util/StripeUpdatePaymentMethods.php
Normal file
68
app/Jobs/Util/StripeUpdatePaymentMethods.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?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://opensource.org/licenses/AAL
|
||||
*/
|
||||
namespace App\Jobs\Util;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Client;
|
||||
use App\Models\CompanyGateway;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
|
||||
class StripeUpdatePaymentMethods implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $company;
|
||||
|
||||
private $stripe_keys = ['d14dd26a47cecc30fdd65700bfb67b34', 'd14dd26a37cecc30fdd65700bfb55b23'];
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param $event_id
|
||||
* @param $entity
|
||||
*/
|
||||
public function __construct($company)
|
||||
{
|
||||
$this->company = $company;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$cgs = CompanyGateway::where('company_id', $this->company->id)
|
||||
->whereIn('gateway_key', $this->stripe_keys)
|
||||
->get();
|
||||
|
||||
$cgs->each(function ($company_gateway){
|
||||
|
||||
$company_gateway->driver(new Client)->updateAllPaymentMethods();
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public function failed($exception)
|
||||
{
|
||||
nlog("Stripe update payment methods exception");
|
||||
nlog($exception->getMessage());
|
||||
}
|
||||
}
|
@ -39,8 +39,6 @@ class UnlinkFile implements ShouldQueue
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// nlog("deleting");
|
||||
// nlog($this->file_path);
|
||||
Storage::disk($this->disk)->delete($this->file_path);
|
||||
}
|
||||
}
|
||||
|
@ -253,14 +253,14 @@ class MultiDB
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function findAndSetDbByDomain($subdomain) :bool
|
||||
public static function findAndSetDbByDomain($query_array) :bool
|
||||
{
|
||||
|
||||
if (! config('ninja.db.multi_db_enabled'))
|
||||
return (Company::whereSubdomain($subdomain)->exists() === true);
|
||||
return (Company::where($query_array)->exists() === true);
|
||||
|
||||
foreach (self::$dbs as $db) {
|
||||
if ($company = Company::on($db)->whereSubdomain($subdomain)->first()) {
|
||||
if ($company = Company::on($db)->where($query_array)->first()) {
|
||||
self::setDb($company->db);
|
||||
return true;
|
||||
}
|
||||
|
@ -1,4 +1,13 @@
|
||||
<?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://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
@ -9,7 +18,6 @@ use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class DownloadBackup extends Mailable
|
||||
{
|
||||
// use Queueable, SerializesModels;
|
||||
|
||||
public $file_path;
|
||||
|
||||
@ -30,14 +38,15 @@ class DownloadBackup extends Mailable
|
||||
$company = Company::where('company_key', $this->company->company_key)->first();
|
||||
|
||||
return $this->from(config('mail.from.address'), config('mail.from.name'))
|
||||
->subject(ctrans('texts.download_backup_subject'))
|
||||
->subject(ctrans('texts.download_backup_subject', ['company' => $company->present()->name()]))
|
||||
->markdown(
|
||||
'email.admin.download_files',
|
||||
[
|
||||
'url' => $this->file_path,
|
||||
'logo' => $company->present()->logo,
|
||||
'whitelabel' => $company->account->isPaid() ? true : false,
|
||||
'settings' => $company->settings
|
||||
'settings' => $company->settings,
|
||||
'greeting' => $company->present()->name(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace App\Mail\Engine;
|
||||
|
||||
use App\DataMapper\EmailTemplateDefaults;
|
||||
use App\Models\Account;
|
||||
use App\Utils\Helpers;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
@ -72,6 +73,16 @@ class PaymentEmailEngine extends BaseEmailEngine
|
||||
->setViewLink('')
|
||||
->setViewText('');
|
||||
|
||||
if ($this->client->getSetting('pdf_email_attachment') !== false && $this->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
|
||||
|
||||
$this->payment->invoices->each(function ($invoice){
|
||||
|
||||
$this->setAttachments([$invoice->pdf_file_path()]);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -13,12 +13,14 @@ namespace App\Models;
|
||||
|
||||
use App\DataMapper\ClientSettings;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\Jobs\Entity\CreateEntityPdf;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\UserSessionAttributes;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
|
||||
/**
|
||||
@ -199,4 +201,5 @@ class BaseModel extends Model
|
||||
|
||||
return $formatted_number;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -443,12 +443,12 @@ class Company extends BaseModel
|
||||
|
||||
public function domain()
|
||||
{
|
||||
if (Ninja::isNinja()) {
|
||||
if (Ninja::isHosted()) {
|
||||
|
||||
if($this->portal_mode == 'domain')
|
||||
return $this->portal_domain;
|
||||
|
||||
return "https://{$this->subdomain}" . config('ninja.app_domain');
|
||||
return "https://{$this->subdomain}." . config('ninja.app_domain');
|
||||
}
|
||||
|
||||
return config('ninja.app_url');
|
||||
|
@ -309,14 +309,14 @@ class CompanyGateway extends BaseModel
|
||||
$fees_and_limits = $this->getFeesAndLimits($gateway_type_id);
|
||||
|
||||
if (! $fees_and_limits) {
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
$fee = 0;
|
||||
|
||||
if ($fees_and_limits->fee_amount) {
|
||||
$fee += $fees_and_limits->fee_amount;
|
||||
// info("fee after adding fee amount = {$fee}");
|
||||
nlog("fee after adding fee amount = {$fee}");
|
||||
}
|
||||
|
||||
if ($fees_and_limits->fee_percent) {
|
||||
@ -325,7 +325,7 @@ class CompanyGateway extends BaseModel
|
||||
} else {
|
||||
$fee += round(($amount * $fees_and_limits->fee_percent / 100), 2);
|
||||
}
|
||||
// info("fee after adding fee percent = {$fee}");
|
||||
nlog("fee after adding fee percent = {$fee}");
|
||||
}
|
||||
|
||||
/* Cap fee if we have to here. */
|
||||
@ -334,6 +334,7 @@ class CompanyGateway extends BaseModel
|
||||
}
|
||||
|
||||
$pre_tax_fee = $fee;
|
||||
nlog("fee after adding fee percent = {$fee}");
|
||||
|
||||
/**/
|
||||
if ($include_taxes) {
|
||||
@ -352,6 +353,7 @@ class CompanyGateway extends BaseModel
|
||||
// info("fee after adding fee tax 3 = {$fee}");
|
||||
}
|
||||
}
|
||||
nlog("fee after adding fee percent = {$fee}");
|
||||
|
||||
return $fee;
|
||||
}
|
||||
|
@ -251,23 +251,37 @@ class Credit extends BaseModel
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function pdf_file_path($invitation = null)
|
||||
public function pdf_file_path($invitation = null, string $type = 'path', bool $portal = false)
|
||||
{
|
||||
$storage_path = Storage::url($this->client->credit_filepath().$this->numberFormatter().'.pdf');
|
||||
|
||||
if (Storage::exists($this->client->credit_filepath().$this->numberFormatter().'.pdf')) {
|
||||
return $storage_path;
|
||||
}
|
||||
|
||||
if (! $invitation) {
|
||||
event(new CreditWasUpdated($this, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
CreateEntityPdf::dispatchNow($this->invitations->first());
|
||||
} else {
|
||||
event(new CreditWasUpdated($this, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
CreateEntityPdf::dispatchNow($invitation);
|
||||
|
||||
if($this->invitations()->exists())
|
||||
$invitation = $this->invitations()->first();
|
||||
else{
|
||||
$this->service()->createInvitations();
|
||||
$invitation = $this->invitations()->first();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $storage_path;
|
||||
if(!$invitation)
|
||||
throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?');
|
||||
|
||||
$file_path = $this->client->credit_filepath().$this->numberFormatter().'.pdf';
|
||||
|
||||
if(Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)){
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
}
|
||||
elseif(Ninja::isHosted() && $portal){
|
||||
$file_path = CreateEntityPdf::dispatchNow($invitation,config('filesystems.default'));
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
}
|
||||
|
||||
if(Storage::disk('public')->exists($file_path))
|
||||
return Storage::disk('public')->{$type}($file_path);
|
||||
|
||||
$file_path = CreateEntityPdf::dispatchNow($invitation);
|
||||
return Storage::disk('public')->{$type}($file_path);
|
||||
}
|
||||
|
||||
public function markInvitationsSent()
|
||||
|
@ -96,4 +96,9 @@ class Expense extends BaseModel
|
||||
{
|
||||
return $this->belongsTo(Vendor::class);
|
||||
}
|
||||
|
||||
public function client()
|
||||
{
|
||||
return $this->belongsTo(Client::class);
|
||||
}
|
||||
}
|
||||
|
@ -392,7 +392,7 @@ class Invoice extends BaseModel
|
||||
return $invoice_calc->build();
|
||||
}
|
||||
|
||||
public function pdf_file_path($invitation = null, string $type = 'url')
|
||||
public function pdf_file_path($invitation = null, string $type = 'path', bool $portal = false)
|
||||
{
|
||||
if (! $invitation) {
|
||||
|
||||
@ -408,21 +408,23 @@ class Invoice extends BaseModel
|
||||
if(!$invitation)
|
||||
throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?');
|
||||
|
||||
$storage_path = Storage::$type($this->client->invoice_filepath().$this->numberFormatter().'.pdf');
|
||||
$file_path = $this->client->invoice_filepath().$this->numberFormatter().'.pdf';
|
||||
|
||||
if (! Storage::exists($this->client->invoice_filepath().$this->numberFormatter().'.pdf')) {
|
||||
event(new InvoiceWasUpdated($this, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
CreateEntityPdf::dispatchNow($invitation);
|
||||
if(Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)){
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
}
|
||||
elseif(Ninja::isHosted() && $portal){
|
||||
$file_path = CreateEntityPdf::dispatchNow($invitation,config('filesystems.default'));
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
}
|
||||
|
||||
if(Storage::disk('public')->exists($file_path))
|
||||
return Storage::disk('public')->{$type}($file_path);
|
||||
|
||||
nlog($storage_path);
|
||||
|
||||
return $storage_path;
|
||||
$file_path = CreateEntityPdf::dispatchNow($invitation);
|
||||
return Storage::disk('public')->{$type}($file_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates Invites to SENT.
|
||||
*/
|
||||
public function markInvitationsSent()
|
||||
{
|
||||
$this->invitations->each(function ($invitation) {
|
||||
|
@ -207,26 +207,39 @@ class Quote extends BaseModel
|
||||
}
|
||||
|
||||
|
||||
public function pdf_file_path($invitation = null, string $type = 'url')
|
||||
public function pdf_file_path($invitation = null, string $type = 'path', bool $portal = false)
|
||||
{
|
||||
if (! $invitation) {
|
||||
$invitation = $this->invitations->first();
|
||||
|
||||
if($this->invitations()->exists())
|
||||
$invitation = $this->invitations()->first();
|
||||
else{
|
||||
$this->service()->createInvitations();
|
||||
$invitation = $this->invitations()->first();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$storage_path = Storage::$type($this->client->quote_filepath().$this->numberFormatter().'.pdf');
|
||||
if(!$invitation)
|
||||
throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?');
|
||||
|
||||
nlog($storage_path);
|
||||
$file_path = $this->client->quote_filepath().$this->numberFormatter().'.pdf';
|
||||
|
||||
if (! Storage::exists($this->client->quote_filepath().$this->numberFormatter().'.pdf')) {
|
||||
event(new QuoteWasUpdated($this, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
CreateEntityPdf::dispatchNow($invitation);
|
||||
if(Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)){
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
}
|
||||
elseif(Ninja::isHosted() && $portal){
|
||||
$file_path = CreateEntityPdf::dispatchNow($invitation,config('filesystems.default'));
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
}
|
||||
|
||||
if(Storage::disk('public')->exists($file_path))
|
||||
return Storage::disk('public')->{$type}($file_path);
|
||||
|
||||
return $storage_path;
|
||||
$file_path = CreateEntityPdf::dispatchNow($invitation);
|
||||
return Storage::disk('public')->{$type}($file_path);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param int $status
|
||||
* @return string
|
||||
|
@ -179,10 +179,19 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
return $company_token->company;
|
||||
}
|
||||
|
||||
// return false;
|
||||
throw new \Exception('No Company Found');
|
||||
//return Company::find(config('ninja.company_id'));
|
||||
}
|
||||
|
||||
public function companyIsSet()
|
||||
{
|
||||
if($this->company)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current company.
|
||||
*
|
||||
|
73
app/PaymentDrivers/Stripe/Connect/ConnectOauth.php
Normal file
73
app/PaymentDrivers/Stripe/Connect/ConnectOauth.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?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://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers\Stripe\Connect;
|
||||
|
||||
use Stripe\Stripe;
|
||||
|
||||
class ConnectOauth
|
||||
{
|
||||
|
||||
/**
|
||||
* Response payload
|
||||
* "token_type": "bearer",
|
||||
"stripe_publishable_key": "{PUBLISHABLE_KEY}",
|
||||
"scope": "read_write",
|
||||
"livemode": false,
|
||||
"stripe_user_id": "{ACCOUNT_ID}",
|
||||
"refresh_token": "{REFRESH_TOKEN}",
|
||||
"access_token": "{ACCESS_TOKEN}"
|
||||
*/
|
||||
public function getAccountId($code)
|
||||
{
|
||||
|
||||
Stripe::setApiKey(config('ninja.ninja_stripe_key'));
|
||||
|
||||
$response = \Stripe\OAuth::token([
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => $code,
|
||||
]);
|
||||
|
||||
// Access the connected account id in the response
|
||||
$connected_account_id = $response->stripe_user_id;
|
||||
|
||||
return $response;
|
||||
//return $connected_account_id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Revokes access to Stripe from Invoice Ninja
|
||||
* for the given account id
|
||||
*/
|
||||
public function revoke($account_id)
|
||||
{
|
||||
|
||||
Stripe::setApiKey(config('ninja.ninja_stripe_key'));
|
||||
|
||||
\Stripe\OAuth::deauthorize([
|
||||
'client_id' => config('ninja.ninja_stripe_key'),
|
||||
'stripe_user_id' => $account_id,
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
121
app/PaymentDrivers/Stripe/ImportCustomers.php
Normal file
121
app/PaymentDrivers/Stripe/ImportCustomers.php
Normal file
@ -0,0 +1,121 @@
|
||||
<?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://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers\Stripe;
|
||||
|
||||
use App\Factory\ClientContactFactory;
|
||||
use App\Factory\ClientFactory;
|
||||
use App\Factory\ClientGatewayTokenFactory;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\Country;
|
||||
use App\Models\Currency;
|
||||
use App\Models\GatewayType;
|
||||
use App\PaymentDrivers\StripePaymentDriver;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Stripe\Customer;
|
||||
use Stripe\PaymentMethod;
|
||||
|
||||
class ImportCustomers
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
/** @var StripePaymentDriver */
|
||||
public $stripe;
|
||||
|
||||
public function __construct(StripePaymentDriver $stripe)
|
||||
{
|
||||
$this->stripe = $stripe;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
|
||||
$this->stripe->init();
|
||||
|
||||
$customers = Customer::all();
|
||||
|
||||
foreach($customers as $customer)
|
||||
{
|
||||
$this->addCustomer($customer);
|
||||
}
|
||||
|
||||
/* Now call the update payment methods handler*/
|
||||
$this->stripe->updateAllPaymentMethods();
|
||||
|
||||
}
|
||||
|
||||
private function addCustomer(Customer $customer)
|
||||
{
|
||||
|
||||
$account = $this->stripe->company_gateway->company->account;
|
||||
|
||||
$existing_customer = $this->stripe
|
||||
->company_gateway
|
||||
->client_gateway_tokens()
|
||||
->where('gateway_customer_reference', $customer->id)
|
||||
->exists();
|
||||
|
||||
|
||||
if($existing_customer)
|
||||
return;
|
||||
|
||||
$client = ClientFactory::create($this->stripe->company_gateway->company_id, $this->stripe->company_gateway->user_id);
|
||||
|
||||
if(property_exists($customer, 'address'))
|
||||
{
|
||||
$client->address1 = property_exists($customer->address, 'line1') ? $customer->address->line1 : '';
|
||||
$client->address2 = property_exists($customer->address, 'line2') ? $customer->address->line2 : '';
|
||||
$client->city = property_exists($customer->address, 'city') ? $customer->address->city : '';
|
||||
$client->state = property_exists($customer->address, 'state') ? $customer->address->state : '';
|
||||
$client->phone = property_exists($customer->address, 'phone') ? $customer->phone : '';
|
||||
|
||||
if(property_exists($customer->address, 'country')){
|
||||
|
||||
$country = Country::where('iso_3166_2', $customer->address->country)->first();
|
||||
|
||||
if($country)
|
||||
$client->country_id = $country->id;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if($customer->currency) {
|
||||
|
||||
$currency = Currency::where('code', $customer->currency)->first();
|
||||
|
||||
if($currency){
|
||||
|
||||
$settings = $client->settings;
|
||||
$settings->currency_id = (string)$currency->id;
|
||||
$client->settings = $settings;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$client->name = property_exists($customer, 'name') ? $customer->name : '';
|
||||
|
||||
if(!$account->isPaidHostedClient() && Client::where('company_id', $this->stripe->company_gateway->company_id)->count() <= config('ninja.quotas.free.clients')){
|
||||
|
||||
$client->save();
|
||||
|
||||
$contact = ClientContactFactory::create($client->company_id, $client->user_id);
|
||||
$contact->client_id = $client->id;
|
||||
$contact->first_name = $client->name ?: '';
|
||||
$contact->phone = $client->phone ?: '';
|
||||
$contact->email = $client->email ?: '';
|
||||
$contact->save();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
168
app/PaymentDrivers/Stripe/UpdatePaymentMethods.php
Normal file
168
app/PaymentDrivers/Stripe/UpdatePaymentMethods.php
Normal file
@ -0,0 +1,168 @@
|
||||
<?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://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers\Stripe;
|
||||
|
||||
use App\Factory\ClientGatewayTokenFactory;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\PaymentDrivers\StripePaymentDriver;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Stripe\Customer;
|
||||
use Stripe\PaymentMethod;
|
||||
|
||||
class UpdatePaymentMethods
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
/** @var StripePaymentDriver */
|
||||
public $stripe;
|
||||
|
||||
public function __construct(StripePaymentDriver $stripe)
|
||||
{
|
||||
$this->stripe = $stripe;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
$this->stripe->init();
|
||||
|
||||
$this->stripe
|
||||
->company_gateway
|
||||
->client_gateway_tokens
|
||||
->each(function ($token){
|
||||
|
||||
$card_methods = PaymentMethod::all([
|
||||
'customer' => $token->gateway_customer_reference,
|
||||
'type' => 'card',
|
||||
],
|
||||
$this->stripe->stripe_connect_auth);
|
||||
|
||||
foreach($card_methods as $method)
|
||||
{
|
||||
$this->addOrUpdateCard($method, $token, GatewayType::CREDIT_CARD);
|
||||
}
|
||||
|
||||
$alipay_methods = PaymentMethod::all([
|
||||
'customer' => $token->gateway_customer_reference,
|
||||
'type' => 'alipay',
|
||||
],
|
||||
$this->stripe->stripe_connect_auth);
|
||||
|
||||
foreach($alipay_methods as $method)
|
||||
{
|
||||
$this->addOrUpdateCard($method, $token, GatewayType::ALIPAY);
|
||||
}
|
||||
|
||||
$sofort_methods = PaymentMethod::all([
|
||||
'customer' => $token->gateway_customer_reference,
|
||||
'type' => 'sofort',
|
||||
],
|
||||
$this->stripe->stripe_connect_auth);
|
||||
|
||||
foreach($alipay_methods as $method)
|
||||
{
|
||||
$this->addOrUpdateCard($method, $token, GatewayType::SOFORT);
|
||||
}
|
||||
|
||||
$bank_accounts = Customer::allSources(
|
||||
$token->gateway_customer_reference,
|
||||
['object' => 'bank_account', 'limit' => 300]
|
||||
);
|
||||
|
||||
foreach($bank_accounts as $bank_account)
|
||||
{
|
||||
$this->addOrUpdateBankAccount($bank_account, $token);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private function addOrUpdateBankAccount($bank_account, ClientGatewayToken $token)
|
||||
{
|
||||
$token_exists = ClientGatewayToken::where([
|
||||
'gateway_customer_reference' => $token->gateway_customer_reference,
|
||||
'token' => $bank_account->id,
|
||||
])->exists();
|
||||
|
||||
/* Already exists return */
|
||||
if($token_exists)
|
||||
return;
|
||||
|
||||
$cgt = ClientGatewayTokenFactory::create($token->company_id);
|
||||
$cgt->client_id = $token->client_id;
|
||||
$cgt->token = $bank_account->id;
|
||||
$cgt->gateway_customer_reference = $token->gateway_customer_reference;
|
||||
$cgt->company_gateway_id = $token->company_gateway_id;
|
||||
$cgt->gateway_type_id = GatewayType::BANK_TRANSFER;
|
||||
$cgt->meta = new \stdClass;
|
||||
$cgt->routing_number = $bank_account->routing_number;
|
||||
$cgt->save();
|
||||
|
||||
}
|
||||
|
||||
private function addOrUpdateCard(PaymentMethod $method, ClientGatewayToken $token, GatewayType $type_id)
|
||||
{
|
||||
|
||||
$token_exists = ClientGatewayToken::where([
|
||||
'gateway_customer_reference' => $token->gateway_customer_reference,
|
||||
'token' => $method->id,
|
||||
])->exists();
|
||||
|
||||
/* Already exists return */
|
||||
if($token_exists)
|
||||
return;
|
||||
|
||||
/* Ignore Expired cards */
|
||||
if($method->card->exp_year <= date('Y') && $method->card->exp_month < date('m'))
|
||||
return;
|
||||
|
||||
$cgt = ClientGatewayTokenFactory::create($token->company_id);
|
||||
$cgt->client_id = $token->client_id;
|
||||
$cgt->token = $method->id;
|
||||
$cgt->gateway_customer_reference = $token->gateway_customer_reference;
|
||||
$cgt->company_gateway_id = $token->company_gateway_id;
|
||||
$cgt->gateway_type_id = $type_id;
|
||||
$cgt->meta = $this->buildPaymentMethodMeta($method, $type_id);
|
||||
$cgt->save();
|
||||
|
||||
}
|
||||
|
||||
private function buildPaymentMethodMeta(PaymentMethod $method, GatewayType $type_id)
|
||||
{
|
||||
|
||||
switch ($type_id) {
|
||||
case GatewayType::CREDIT_CARD:
|
||||
|
||||
$payment_meta = new \stdClass;
|
||||
$payment_meta->exp_month = (string) $method->card->exp_month;
|
||||
$payment_meta->exp_year = (string) $method->card->exp_year;
|
||||
$payment_meta->brand = (string) $method->card->brand;
|
||||
$payment_meta->last4 = (string) $method->card->last4;
|
||||
$payment_meta->type = GatewayType::CREDIT_CARD;
|
||||
return $payment_meta;
|
||||
|
||||
break;
|
||||
|
||||
case GatewayType::ALIPAY:
|
||||
case GatewayType::SOFORT:
|
||||
|
||||
return new \stdClass;
|
||||
|
||||
default:
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -25,7 +25,9 @@ use App\PaymentDrivers\Stripe\ACH;
|
||||
use App\PaymentDrivers\Stripe\Alipay;
|
||||
use App\PaymentDrivers\Stripe\Charge;
|
||||
use App\PaymentDrivers\Stripe\CreditCard;
|
||||
use App\PaymentDrivers\Stripe\ImportCustomers;
|
||||
use App\PaymentDrivers\Stripe\SOFORT;
|
||||
use App\PaymentDrivers\Stripe\UpdatePaymentMethods;
|
||||
use App\PaymentDrivers\Stripe\Utilities;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Exception;
|
||||
@ -493,4 +495,30 @@ class StripePaymentDriver extends BaseDriver
|
||||
|
||||
return Account::all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull all client payment methods and update
|
||||
* the respective tokens in the system.
|
||||
*
|
||||
*/
|
||||
public function updateAllPaymentMethods()
|
||||
{
|
||||
return (new UpdatePaymentMethods($this))->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports stripe customers and their payment methods
|
||||
* Matches users in the system based on the $match_on_record
|
||||
* ie. email
|
||||
*
|
||||
* Phone
|
||||
* Email
|
||||
*/
|
||||
public function importCustomers()
|
||||
{
|
||||
|
||||
return (new ImportCustomers($this))->run();
|
||||
//match clients based on the gateway_customer_reference column
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ class PaymentMethod
|
||||
|
||||
foreach ($gateway->driver($this->client)->gatewayTypes() as $type) {
|
||||
|
||||
if (isset($gateway->fees_and_limits) && is_object($gateway->fees_and_limits) && property_exists($gateway->fees_and_limits, $type)) {
|
||||
if (isset($gateway->fees_and_limits) && is_object($gateway->fees_and_limits) && property_exists($gateway->fees_and_limits, GatewayType::CREDIT_CARD)) {
|
||||
|
||||
if ($this->validGatewayForAmount($gateway->fees_and_limits->{GatewayType::CREDIT_CARD}, $this->amount))
|
||||
$this->payment_methods[] = [$gateway->id => $type];
|
||||
|
@ -16,6 +16,7 @@ use App\Factory\CreditInvitationFactory;
|
||||
use App\Models\Credit;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Services\AbstractService;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreateInvitations extends AbstractService
|
||||
{
|
||||
|
@ -41,16 +41,9 @@ class GetCreditPdf extends AbstractService
|
||||
|
||||
$file_path = $path.$this->credit->numberFormatter().'.pdf';
|
||||
|
||||
$disk = config('filesystems.default');
|
||||
$disk = 'public';
|
||||
|
||||
$file = Storage::disk($disk)->exists($file_path);
|
||||
|
||||
if (! $file) {
|
||||
$file_path = CreateEntityPdf::dispatchNow($this->invitation);
|
||||
}
|
||||
|
||||
if(config('filesystems.default') == 's3')
|
||||
return TempFile::path(Storage::disk($disk)->url($file_path));
|
||||
$file_path = CreateEntityPdf::dispatchNow($this->invitation);
|
||||
|
||||
return Storage::disk($disk)->path($file_path);
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class AddGatewayFee extends AbstractService
|
||||
{
|
||||
$gateway_fee = round($this->company_gateway->calcGatewayFee($this->amount, $this->gateway_type_id, $this->invoice->uses_inclusive_taxes), $this->invoice->client->currency()->precision);
|
||||
|
||||
if ((int)$gateway_fee == 0)
|
||||
if (!$gateway_fee)
|
||||
return $this->invoice;
|
||||
|
||||
// Removes existing stale gateway fees
|
||||
|
@ -49,7 +49,9 @@ class GenerateDeliveryNote
|
||||
|
||||
$this->contact = $contact;
|
||||
|
||||
$this->disk = $disk ?? config('filesystems.default');
|
||||
$this->disk = 'public';
|
||||
|
||||
// $this->disk = $disk ?? config('filesystems.default');
|
||||
}
|
||||
|
||||
public function run()
|
||||
@ -105,6 +107,7 @@ class GenerateDeliveryNote
|
||||
|
||||
Storage::disk($this->disk)->put($file_path, $pdf);
|
||||
|
||||
return $file_path;
|
||||
return Storage::disk($this->disk)->path($file_path);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ class GetInvoicePdf extends AbstractService
|
||||
|
||||
$file_path = $path.$this->invoice->numberFormatter().'.pdf';
|
||||
|
||||
$disk = config('filesystems.default');
|
||||
$disk = 'public';
|
||||
|
||||
$file = Storage::disk($disk)->exists($file_path);
|
||||
|
||||
@ -47,12 +47,6 @@ class GetInvoicePdf extends AbstractService
|
||||
$file_path = CreateEntityPdf::dispatchNow($invitation);
|
||||
}
|
||||
|
||||
|
||||
/* Copy from remote disk to local when using cloud file storage. */
|
||||
if(config('filesystems.default') == 's3')
|
||||
return TempFile::path(Storage::disk($disk)->url($file_path));
|
||||
|
||||
// return Storage::disk($disk)->url($file_path);
|
||||
return Storage::disk($disk)->path($file_path);
|
||||
}
|
||||
}
|
||||
|
@ -62,32 +62,11 @@ trait PdfMakerUtilities
|
||||
}
|
||||
|
||||
if (isset($element['elements'])) {
|
||||
$sorted = $this->processChildrenOrder($element['elements']);
|
||||
|
||||
$this->createElementContent($node, $sorted);
|
||||
$this->createElementContent($node, $element['elements']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function processChildrenOrder(array $children)
|
||||
{
|
||||
$processed = [];
|
||||
|
||||
foreach ($children as $child) {
|
||||
if (!isset($child['order'])) {
|
||||
$child['order'] = 0;
|
||||
}
|
||||
|
||||
$processed[] = $child;
|
||||
}
|
||||
|
||||
usort($processed, function ($a, $b) {
|
||||
return $a['order'] <=> $b['order'];
|
||||
});
|
||||
|
||||
return $processed;
|
||||
}
|
||||
|
||||
public function updateElementProperty($element, string $attribute, ?string $value)
|
||||
{
|
||||
// We have exception for "hidden" property.
|
||||
@ -154,9 +133,7 @@ trait PdfMakerUtilities
|
||||
}
|
||||
|
||||
if (isset($child['elements'])) {
|
||||
$sorted = $this->processChildrenOrder($child['elements']);
|
||||
|
||||
$this->createElementContent($_child, $sorted);
|
||||
$this->createElementContent($_child, $child['elements']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,16 +39,9 @@ class GetQuotePdf extends AbstractService
|
||||
|
||||
$file_path = $path.$this->quote->numberFormatter().'.pdf';
|
||||
|
||||
$disk = config('filesystems.default');
|
||||
$disk = 'public';
|
||||
|
||||
$file = Storage::disk($disk)->exists($file_path);
|
||||
|
||||
if (! $file) {
|
||||
$file_path = CreateEntityPdf::dispatchNow($invitation);
|
||||
}
|
||||
|
||||
if(config('filesystems.default') == 's3')
|
||||
return TempFile::path(Storage::disk($disk)->url($file_path));
|
||||
$file_path = CreateEntityPdf::dispatchNow($invitation);
|
||||
|
||||
return Storage::disk($disk)->path($file_path);
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ class SubscriptionTransformer extends EntityTransformer
|
||||
|
||||
public function transform(Subscription $subscription): array
|
||||
{
|
||||
$company = $subscription->company;
|
||||
|
||||
return [
|
||||
'id' => $this->encodePrimaryKey($subscription->id),
|
||||
@ -59,7 +60,8 @@ class SubscriptionTransformer extends EntityTransformer
|
||||
'allow_plan_changes' => (bool)$subscription->allow_plan_changes,
|
||||
'refund_period' => (int)$subscription->refund_period,
|
||||
'webhook_configuration' => $subscription->webhook_configuration ?: [],
|
||||
'purchase_page' => (string)route('client.subscription.purchase', $subscription->hashed_id),
|
||||
'purchase_page' => (string)$company->domain() . "/client/subscriptions/{$subscription->hashed_id}/purchase",
|
||||
//'purchase_page' => (string)route('client.subscription.purchase', $subscription->hashed_id),
|
||||
'currency_id' => (string) $subscription->currency_id,
|
||||
'is_deleted' => (bool)$subscription->is_deleted,
|
||||
'created_at' => (int)$subscription->created_at,
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Utils\Traits;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
@ -46,7 +47,10 @@ trait Inviteable
|
||||
{
|
||||
$entity_type = Str::snake(class_basename($this->entityType()));
|
||||
|
||||
$domain = isset($this->company->portal_domain) ? $this->company->portal_domain : $this->company->domain();
|
||||
if(Ninja::isHosted())
|
||||
$domain = isset($this->company->portal_domain) ? $this->company->portal_domain : $this->company->domain();
|
||||
else
|
||||
$domain = config('ninja.app_url');
|
||||
|
||||
switch ($this->company->portal_mode) {
|
||||
case 'subdomain':
|
||||
@ -69,7 +73,10 @@ trait Inviteable
|
||||
public function getPortalLink() :string
|
||||
{
|
||||
|
||||
$domain = isset($this->company->portal_domain) ? $this->company->portal_domain : $this->company->domain();
|
||||
if(Ninja::isHosted())
|
||||
$domain = isset($this->company->portal_domain) ? $this->company->portal_domain : $this->company->domain();
|
||||
else
|
||||
$domain = config('ninja.app_url');
|
||||
|
||||
switch ($this->company->portal_mode) {
|
||||
case 'subdomain':
|
||||
|
@ -149,6 +149,7 @@ return [
|
||||
'invoiceninja_hosted_pdf_generation' => env('NINJA_HOSTED_PDF', false),
|
||||
'ninja_stripe_key' => env('NINJA_STRIPE_KEY', null),
|
||||
'ninja_stripe_publishable_key' => env('NINJA_PUBLISHABLE_KEY', null),
|
||||
'ninja_stripe_client_id' => env('NINJA_STRIPE_CLIENT_ID', null),
|
||||
'pdf_generator' => env('PDF_GENERATOR', false),
|
||||
'internal_queue_enabled' => env('INTERNAL_QUEUE_ENABLED', true),
|
||||
];
|
||||
|
2
public/flutter_service_worker.js
vendored
2
public/flutter_service_worker.js
vendored
@ -30,7 +30,7 @@ const RESOURCES = {
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"version.json": "ea1781094b87723b953889a712b1feba",
|
||||
"manifest.json": "ce1b79950eb917ea619a0a30da27c6a3",
|
||||
"main.dart.js": "b6fdfe160593f3083646123daf386773",
|
||||
"main.dart.js": "818f2adef47fec836b904d8b334de88d",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
"/": "23224b5e03519aaa87594403d54412cf"
|
||||
};
|
||||
|
78869
public/main.dart.js
vendored
78869
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
80241
public/main.foss.dart.js
vendored
80241
public/main.foss.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -4250,6 +4250,7 @@ $LANG = array(
|
||||
'new_login_description' => 'You recently logged in to your Invoice Ninja account from a new location or device:<br><br><b>IP:</b> :ip<br><b>Time:</b> :time<br><b>Email:</b> :email',
|
||||
'download_backup_subject' => 'Your company backup is ready for download',
|
||||
'contact_details' => 'Contact Details',
|
||||
'download_backup_subject' => ':company backup is ready for download',
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
13
resources/views/auth/connect/completed.blade.php
Normal file
13
resources/views/auth/connect/completed.blade.php
Normal file
@ -0,0 +1,13 @@
|
||||
@extends('layouts.ninja')
|
||||
@section('meta_title', ctrans('texts.success'))
|
||||
|
||||
@section('body')
|
||||
<div class="flex flex-col justify-center items-center mt-10">
|
||||
<div class="mb-4">
|
||||
<svg height="60" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 468 222.5" xml:space="preserve"><style>.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#635bff}</style><path class="st0" d="M414 113.4c0-25.6-12.4-45.8-36.1-45.8-23.8 0-38.2 20.2-38.2 45.6 0 30.1 17 45.3 41.4 45.3 11.9 0 20.9-2.7 27.7-6.5v-20c-6.8 3.4-14.6 5.5-24.5 5.5-9.7 0-18.3-3.4-19.4-15.2h48.9c0-1.3.2-6.5.2-8.9zm-49.4-9.5c0-11.3 6.9-16 13.2-16 6.1 0 12.6 4.7 12.6 16h-25.8zM301.1 67.6c-9.8 0-16.1 4.6-19.6 7.8l-1.3-6.2h-22v116.6l25-5.3.1-28.3c3.6 2.6 8.9 6.3 17.7 6.3 17.9 0 34.2-14.4 34.2-46.1-.1-29-16.6-44.8-34.1-44.8zm-6 68.9c-5.9 0-9.4-2.1-11.8-4.7l-.1-37.1c2.6-2.9 6.2-4.9 11.9-4.9 9.1 0 15.4 10.2 15.4 23.3 0 13.4-6.2 23.4-15.4 23.4zM223.8 61.7l25.1-5.4V36l-25.1 5.3zM223.8 69.3h25.1v87.5h-25.1zM196.9 76.7l-1.6-7.4h-21.6v87.5h25V97.5c5.9-7.7 15.9-6.3 19-5.2v-23c-3.2-1.2-14.9-3.4-20.8 7.4zM146.9 47.6l-24.4 5.2-.1 80.1c0 14.8 11.1 25.7 25.9 25.7 8.2 0 14.2-1.5 17.5-3.3V135c-3.2 1.3-19 5.9-19-8.9V90.6h19V69.3h-19l.1-21.7zM79.3 94.7c0-3.9 3.2-5.4 8.5-5.4 7.6 0 17.2 2.3 24.8 6.4V72.2c-8.3-3.3-16.5-4.6-24.8-4.6C67.5 67.6 54 78.2 54 95.9c0 27.6 38 23.2 38 35.1 0 4.6-4 6.1-9.6 6.1-8.3 0-18.9-3.4-27.3-8v23.8c9.3 4 18.7 5.7 27.3 5.7 20.8 0 35.1-10.3 35.1-28.2-.1-29.8-38.2-24.5-38.2-35.7z"/></svg>
|
||||
</div>
|
||||
|
||||
<p>Connecting your account using Stripe has been successfully completed.</p>
|
||||
<span>Click <a class="font-semibold hover:underline" href="{{ url('/#/settings/company_gateways') }}">here</a> to continue.</span>
|
||||
</div>
|
||||
@endsection
|
13
resources/views/auth/connect/existing.blade.php
Normal file
13
resources/views/auth/connect/existing.blade.php
Normal file
@ -0,0 +1,13 @@
|
||||
@extends('layouts.ninja')
|
||||
@section('meta_title', ctrans('texts.success'))
|
||||
|
||||
@section('body')
|
||||
<div class="flex flex-col justify-center items-center mt-10">
|
||||
<div class="mb-4">
|
||||
<svg height="60" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 468 222.5" xml:space="preserve"><style>.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#635bff}</style><path class="st0" d="M414 113.4c0-25.6-12.4-45.8-36.1-45.8-23.8 0-38.2 20.2-38.2 45.6 0 30.1 17 45.3 41.4 45.3 11.9 0 20.9-2.7 27.7-6.5v-20c-6.8 3.4-14.6 5.5-24.5 5.5-9.7 0-18.3-3.4-19.4-15.2h48.9c0-1.3.2-6.5.2-8.9zm-49.4-9.5c0-11.3 6.9-16 13.2-16 6.1 0 12.6 4.7 12.6 16h-25.8zM301.1 67.6c-9.8 0-16.1 4.6-19.6 7.8l-1.3-6.2h-22v116.6l25-5.3.1-28.3c3.6 2.6 8.9 6.3 17.7 6.3 17.9 0 34.2-14.4 34.2-46.1-.1-29-16.6-44.8-34.1-44.8zm-6 68.9c-5.9 0-9.4-2.1-11.8-4.7l-.1-37.1c2.6-2.9 6.2-4.9 11.9-4.9 9.1 0 15.4 10.2 15.4 23.3 0 13.4-6.2 23.4-15.4 23.4zM223.8 61.7l25.1-5.4V36l-25.1 5.3zM223.8 69.3h25.1v87.5h-25.1zM196.9 76.7l-1.6-7.4h-21.6v87.5h25V97.5c5.9-7.7 15.9-6.3 19-5.2v-23c-3.2-1.2-14.9-3.4-20.8 7.4zM146.9 47.6l-24.4 5.2-.1 80.1c0 14.8 11.1 25.7 25.9 25.7 8.2 0 14.2-1.5 17.5-3.3V135c-3.2 1.3-19 5.9-19-8.9V90.6h19V69.3h-19l.1-21.7zM79.3 94.7c0-3.9 3.2-5.4 8.5-5.4 7.6 0 17.2 2.3 24.8 6.4V72.2c-8.3-3.3-16.5-4.6-24.8-4.6C67.5 67.6 54 78.2 54 95.9c0 27.6 38 23.2 38 35.1 0 4.6-4 6.1-9.6 6.1-8.3 0-18.9-3.4-27.3-8v23.8c9.3 4 18.7 5.7 27.3 5.7 20.8 0 35.1-10.3 35.1-28.2-.1-29.8-38.2-24.5-38.2-35.7z"/></svg>
|
||||
</div>
|
||||
|
||||
<p>You have already configured a Stripe Connect account.</p>
|
||||
<span>Click <a class="font-semibold hover:underline" href="{{ url('/#/settings/company_gateways') }}">here</a> to continue.</span>
|
||||
</div>
|
||||
@endsection
|
@ -7,15 +7,20 @@
|
||||
|
||||
@endslot
|
||||
|
||||
@slot('greeting')
|
||||
@endslot
|
||||
|
||||
@if(isset($greeting))
|
||||
<p style="padding-top:20px">{{ $greeting }}</p>
|
||||
@endif
|
||||
|
||||
<p style="padding-top:20px">
|
||||
@lang('texts.download_timeframe')
|
||||
</p>
|
||||
|
||||
|
||||
<p style="padding-top:20px">
|
||||
@component('email.components.button', ['url' => $url])
|
||||
@lang('texts.download')
|
||||
@endcomponent
|
||||
</p>
|
||||
|
||||
@slot('signature')
|
||||
InvoiceNinja (contact@invoiceninja.com)
|
||||
|
115
resources/views/layouts/ninja.blade.php
Normal file
115
resources/views/layouts/ninja.blade.php
Normal file
@ -0,0 +1,115 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
|
||||
<head>
|
||||
<!-- Error: {{ session('error') }} -->
|
||||
|
||||
@if (config('services.analytics.tracking_id'))
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-122229484-1"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
|
||||
gtag('js', new Date());
|
||||
gtag('config', '{{ config('services.analytics.tracking_id') }}', {'anonymize_ip': true});
|
||||
|
||||
function trackEvent(category, action) {
|
||||
ga('send', 'event', category, action, this.src);
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
Vue.config.devtools = true;
|
||||
</script>
|
||||
@else
|
||||
<script>
|
||||
function gtag() {
|
||||
}
|
||||
</script>
|
||||
@endif
|
||||
|
||||
|
||||
<!-- Title -->
|
||||
@auth()
|
||||
<title></title>
|
||||
@endauth
|
||||
|
||||
@guest
|
||||
<title>@yield('meta_title', '') — {{ config('app.name') }}</title>
|
||||
@endguest
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="@yield('meta_description')"/>
|
||||
|
||||
<!-- CSRF Token -->
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="{{ mix('js/app.js') }}" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.x/dist/alpine.min.js" defer></script>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Styles -->
|
||||
<link href="{{ mix('css/app.css') }}" rel="stylesheet">
|
||||
<link rel="canonical" href="{{ config('ninja.site_url') }}/{{ request()->path() }}"/>
|
||||
|
||||
|
||||
@livewireStyles
|
||||
|
||||
{{-- Feel free to push anything to header using @push('header') --}}
|
||||
@stack('head')
|
||||
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/cookieconsent@3/build/cookieconsent.min.css" />
|
||||
</head>
|
||||
|
||||
<body class="antialiased">
|
||||
@if(session()->has('message'))
|
||||
<div class="py-1 text-sm text-center text-white bg-primary disposable-alert">
|
||||
{{ session('message') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@yield('body')
|
||||
|
||||
@livewireScripts
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/cookieconsent@3/build/cookieconsent.min.js" data-cfasync="false"></script>
|
||||
<script>
|
||||
window.addEventListener("load", function(){
|
||||
if (! window.cookieconsent) {
|
||||
return;
|
||||
}
|
||||
window.cookieconsent.initialise({
|
||||
"palette": {
|
||||
"popup": {
|
||||
"background": "#000"
|
||||
},
|
||||
"button": {
|
||||
"background": "#f1d600"
|
||||
},
|
||||
},
|
||||
"content": {
|
||||
"href": "https://www.invoiceninja.com/privacy-policy/",
|
||||
"message": "This website uses cookies to ensure you get the best experience on our website.",
|
||||
"dismiss": "Got it!",
|
||||
"link": "Learn more",
|
||||
}
|
||||
})}
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
<footer>
|
||||
@yield('footer')
|
||||
@stack('footer')
|
||||
|
||||
</footer>
|
||||
|
||||
</html>
|
@ -2,7 +2,7 @@
|
||||
@section('meta_title', ctrans('texts.entity_number_placeholder', ['entity' => ctrans('texts.credit'), 'entity_number' => $credit->number]))
|
||||
|
||||
@push('head')
|
||||
<meta name="pdf-url" content="{{ $credit->pdf_file_path() }}">
|
||||
<meta name="pdf-url" content="{{ $credit->pdf_file_path(null, 'url', true) }}">
|
||||
<script src="{{ asset('js/vendor/pdf.js/pdf.min.js') }}"></script>
|
||||
@endpush
|
||||
|
||||
|
@ -1,2 +1,2 @@
|
||||
<iframe src="{{ $invoice->pdf_file_path() }}"
|
||||
<iframe src="{{ $invoice->pdf_file_path(null, 'url', true) }}"
|
||||
style="position:fixed; top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0; overflow:hidden; z-index:999999;"></iframe>
|
||||
|
@ -2,7 +2,7 @@
|
||||
@section('meta_title', ctrans('texts.view_invoice'))
|
||||
|
||||
@push('head')
|
||||
<meta name="pdf-url" content="{{ $invoice->pdf_file_path() }}">
|
||||
<meta name="pdf-url" content="{{ $invoice->pdf_file_path(null, 'url', true) }}">
|
||||
<meta name="show-invoice-terms" content="{{ $settings->show_accept_invoice_terms ? true : false }}">
|
||||
<meta name="require-invoice-signature" content="{{ $client->user->account->hasFeature(\App\Models\Account::FEATURE_INVOICE_SETTINGS) && $settings->require_invoice_signature }}">
|
||||
<script src="{{ asset('js/vendor/pdf.js/pdf.min.js') }}"></script>
|
||||
@ -174,7 +174,7 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<iframe src="{{ $invoice->pdf_file_path() }}" class="h-screen w-full border-0 hidden lg:block mt-4"></iframe>
|
||||
<iframe src="{{ $invoice->pdf_file_path(null, 'url', true) }}" class="h-screen w-full border-0 hidden lg:block mt-4"></iframe>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<canvas id="pdf-placeholder" class="shadow rounded-lg bg-white lg:hidden mt-4 p-4"></canvas>
|
||||
|
@ -45,7 +45,7 @@
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<div class="inline-flex items-center">
|
||||
<label for="website" class="input-label">{{ ctrans('texts.website') }}</label>
|
||||
<span class="text-xs ml-2 text-gray-600">E.g. https://invoiceninja.com</span>
|
||||
<span class="text-xs ml-2 text-gray-600">E.g. https://example.com</span>
|
||||
</div>
|
||||
<input id="website" class="input w-full" name="website" wire:model.defer="website"/>
|
||||
@error('website')
|
||||
|
@ -1,2 +1,2 @@
|
||||
<iframe src="{{ $quote->pdf_file_path() }}"
|
||||
<iframe src="{{ $quote->pdf_file_path(null,'url',true) }}"
|
||||
style="position:fixed; top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0; overflow:hidden; z-index:999999;"></iframe>
|
||||
|
@ -2,7 +2,7 @@
|
||||
@section('meta_title', ctrans('texts.entity_number_placeholder', ['entity' => ctrans('texts.quote'), 'entity_number' => $quote->number]))
|
||||
|
||||
@push('head')
|
||||
<meta name="pdf-url" content="{{ asset($quote->pdf_file_path()) }}">
|
||||
<meta name="pdf-url" content="{{ asset($quote->pdf_file_path(null, 'url', true)) }}">
|
||||
<script src="{{ asset('js/vendor/pdf.js/pdf.min.js') }}"></script>
|
||||
|
||||
<meta name="show-quote-terms" content="{{ $settings->show_accept_quote_terms ? true : false }}">
|
||||
@ -78,7 +78,7 @@
|
||||
<canvas id="pdf-placeholder" class="shadow rounded-lg bg-white lg:hidden mt-4 p-4"></canvas>
|
||||
</div>
|
||||
|
||||
<iframe src="{{ $quote->pdf_file_path() }}" class="h-screen w-full border-0 hidden lg:block mt-4"></iframe>
|
||||
<iframe src="{{ $quote->pdf_file_path(null, 'url',true) }}" class="h-screen w-full border-0 hidden lg:block mt-4"></iframe>
|
||||
|
||||
@include('portal.ninja2020.invoices.includes.terms', ['entities' => [$quote], 'entity_type' => ctrans('texts.quote')])
|
||||
@include('portal.ninja2020.invoices.includes.signature')
|
||||
|
@ -1,7 +1,7 @@
|
||||
@extends('portal.ninja2020.layout.clean')
|
||||
|
||||
@push('head')
|
||||
<meta name="pdf-url" content="{{ asset($entity->pdf_file_path()) }}">
|
||||
<meta name="pdf-url" content="{{ asset($entity->pdf_file_path(null, 'url',true)) }}">
|
||||
<script src="{{ asset('js/vendor/pdf.js/pdf.min.js') }}"></script>
|
||||
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.x/dist/alpine.min.js" defer></script>
|
||||
@endpush
|
||||
@ -39,7 +39,7 @@
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-100" x-transition:enter-start="transform opacity-0 scale-95" x-transition:enter-end="transform opacity-100 scale-100" x-transition:leave="transition ease-in duration-75" x-transition:leave-start="transform opacity-100 scale-100" x-transition:leave-end="transform opacity-0 scale-95" class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
|
||||
<div class="rounded-md bg-white shadow-xs">
|
||||
<div class="py-1">
|
||||
<a target="_blank" href="{{ asset($entity->pdf_file_path()) }}" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">{{ ctrans('texts.open_in_new_tab') }}</a>
|
||||
<a target="_blank" href="{{ asset($entity->pdf_file_path(null, 'url',true)) }}" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">{{ ctrans('texts.open_in_new_tab') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -183,6 +183,9 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
|
||||
// Route::post('hooks', 'SubscriptionController@subscribe')->name('hooks.subscribe');
|
||||
// Route::delete('hooks/{subscription_id}', 'SubscriptionController@unsubscribe')->name('hooks.unsubscribe');
|
||||
|
||||
Route::post('stripe/update_payment_methods', 'StripeController@update')->middleware('password_protected')->name('stripe.update');
|
||||
Route::post('stripe/import_customers', 'StripeController@import')->middleware('password_protected')->name('stripe.import');
|
||||
|
||||
Route::resource('subscriptions', 'SubscriptionController');
|
||||
Route::post('subscriptions/bulk', 'SubscriptionController@bulk')->name('subscriptions.bulk');
|
||||
|
||||
@ -193,9 +196,7 @@ Route::match(['get', 'post'], 'payment_webhook/{company_key}/{company_gateway_id
|
||||
->name('payment_webhook');
|
||||
|
||||
Route::post('api/v1/postmark_webhook', 'PostMarkController@webhook');
|
||||
|
||||
Route::get('token_hash_router', 'OneTimeTokenController@router');
|
||||
|
||||
Route::get('webcron', 'WebCronController@index');
|
||||
|
||||
Route::fallback('BaseController@notFound');
|
||||
|
@ -26,8 +26,7 @@ Route::get('client/magic_link/{magic_link}', 'ClientPortal\ContactHashLoginContr
|
||||
Route::get('documents/{document_hash}', 'ClientPortal\DocumentController@publicDownload')->name('documents.public_download');
|
||||
Route::get('error', 'ClientPortal\ContactHashLoginController@errorPage')->name('client.error');
|
||||
|
||||
//todo implement domain DB
|
||||
Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence'], 'prefix' => 'client', 'as' => 'client.'], function () {
|
||||
Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence','domain_db'], 'prefix' => 'client', 'as' => 'client.'], function () {
|
||||
Route::get('dashboard', 'ClientPortal\DashboardController@index')->name('dashboard'); // name = (dashboard. index / create / show / update / destroy / edit
|
||||
|
||||
Route::get('invoices', 'ClientPortal\InvoiceController@index')->name('invoices.index')->middleware('portal_enabled');
|
||||
@ -81,7 +80,7 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence
|
||||
Route::get('logout', 'Auth\ContactLoginController@logout')->name('logout');
|
||||
});
|
||||
|
||||
Route::get('client/subscription/{subscription}/purchase/', 'ClientPortal\SubscriptionPurchaseController@index')->name('client.subscription.purchase');
|
||||
Route::get('client/subscriptions/{subscription}/purchase', 'ClientPortal\SubscriptionPurchaseController@index')->name('client.subscription.purchase')->middleware('domain_db');
|
||||
|
||||
Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'client.'], function () {
|
||||
/*Invitation catches*/
|
||||
|
@ -11,11 +11,13 @@
|
||||
namespace Tests\Feature\Import;
|
||||
|
||||
use App\Jobs\Import\CSVImport;
|
||||
use App\Models\Account;
|
||||
use App\Models\Client;
|
||||
use App\Models\Expense;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Product;
|
||||
use App\Models\User;
|
||||
use App\Models\Vendor;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
@ -34,6 +36,8 @@ class ImportCompanyTest extends TestCase
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public $account;
|
||||
|
||||
public function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
@ -44,13 +48,76 @@ class ImportCompanyTest extends TestCase
|
||||
|
||||
|
||||
$this->withoutExceptionHandling();
|
||||
|
||||
$this->account = Account::factory()->create();
|
||||
}
|
||||
|
||||
public function testBackupJsonRead()
|
||||
{
|
||||
$backup_json_file = base_path().'/tests/Feature/Import/backup.json';
|
||||
$backup_json_file_zip = base_path().'/tests/Feature/Import/backup.zip';
|
||||
|
||||
$zip = new \ZipArchive;
|
||||
$res = $zip->open($backup_json_file_zip);
|
||||
|
||||
if ($res === TRUE) {
|
||||
$zip->extractTo(sys_get_temp_dir());
|
||||
$zip->close();
|
||||
}
|
||||
|
||||
$backup_json_file = sys_get_temp_dir() . "/backup/backup.json";
|
||||
|
||||
$this->assertTrue(is_array(json_decode(file_get_contents($backup_json_file),1)));
|
||||
|
||||
unlink($backup_json_file);
|
||||
}
|
||||
|
||||
public function testImportUsers()
|
||||
{
|
||||
$backup_json_file_zip = base_path().'/tests/Feature/Import/backup.zip';
|
||||
|
||||
$zip = new \ZipArchive;
|
||||
$res = $zip->open($backup_json_file_zip);
|
||||
if ($res === TRUE) {
|
||||
$zip->extractTo(sys_get_temp_dir());
|
||||
$zip->close();
|
||||
}
|
||||
|
||||
$backup_json_file = sys_get_temp_dir() . "/backup/backup.json";
|
||||
|
||||
$backup_json_object = json_decode(file_get_contents($backup_json_file));
|
||||
|
||||
$this->assertTrue(property_exists($backup_json_object, 'app_version'));
|
||||
$this->assertTrue(property_exists($backup_json_object, 'users'));
|
||||
|
||||
unlink($backup_json_file);
|
||||
|
||||
User::all()->each(function ($user){
|
||||
$user->forceDelete();
|
||||
});
|
||||
|
||||
User::unguard();
|
||||
|
||||
foreach ($backup_json_object->users as $user)
|
||||
{
|
||||
$user_array = (array)$user;
|
||||
unset($user_array['laravel_through_key']);
|
||||
unset($user_array['hashed_id']);
|
||||
|
||||
$new_user = User::firstOrNew(
|
||||
['email' => $user->email],
|
||||
array_merge($user_array, ['account_id' => $this->account->id]),
|
||||
);
|
||||
|
||||
$new_user->save(['timestamps' => false]);
|
||||
|
||||
$this->ids['users']["{$user->hashed_id}"] = $new_user->id;
|
||||
|
||||
}
|
||||
|
||||
User::reguard();
|
||||
|
||||
$this->assertEquals(2, User::count());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
BIN
tests/Feature/Import/backup.zip
Normal file
BIN
tests/Feature/Import/backup.zip
Normal file
Binary file not shown.
@ -40,20 +40,20 @@ class PdfCreatorTest extends TestCase
|
||||
{
|
||||
$credit_path = CreateEntityPdf::dispatchNow($this->credit->invitations->first());
|
||||
|
||||
$this->assertTrue(Storage::exists($this->client->credit_filepath().$this->credit->number.'.pdf'));
|
||||
$this->assertTrue(Storage::disk('public')->exists($credit_path));
|
||||
}
|
||||
|
||||
public function testInvoicePdfCreated()
|
||||
{
|
||||
$invoice_path = CreateEntityPdf::dispatchNow($this->invoice->invitations->first());
|
||||
|
||||
$this->assertTrue(Storage::exists($this->client->invoice_filepath().$this->invoice->number.'.pdf'));
|
||||
}
|
||||
|
||||
$this->assertTrue(Storage::disk('public')->exists($invoice_path));
|
||||
}
|
||||
|
||||
public function testQuotePdfCreated()
|
||||
{
|
||||
$quote_path = CreateEntityPdf::dispatchNow($this->quote->invitations->first());
|
||||
|
||||
$this->assertTrue(Storage::exists($this->client->quote_filepath().$this->quote->number.'.pdf'));
|
||||
$this->assertTrue(Storage::disk('public')->exists($quote_path));
|
||||
}
|
||||
}
|
||||
|
@ -209,65 +209,6 @@ class PdfMakerTest extends TestCase
|
||||
$this->assertNotSame($output1, $output2);
|
||||
}
|
||||
|
||||
public function testOrderingElements()
|
||||
{
|
||||
$design = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]);
|
||||
|
||||
$maker = new PdfMaker([
|
||||
'template' => [
|
||||
'header' => [
|
||||
'id' => 'header',
|
||||
'properties' => [],
|
||||
'elements' => [
|
||||
['element' => 'h1', 'content' => 'h1-element'],
|
||||
['element' => 'span', 'content' => 'span-element'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$maker
|
||||
->design($design)
|
||||
->build();
|
||||
|
||||
$node = $maker->getSectionNode('header');
|
||||
|
||||
$before = [];
|
||||
|
||||
foreach ($node->childNodes as $child) {
|
||||
$before[] = $child->nodeName;
|
||||
}
|
||||
|
||||
$this->assertEquals('h1', $before[1]);
|
||||
|
||||
$maker = new PdfMaker([
|
||||
'template' => [
|
||||
'header' => [
|
||||
'id' => 'header',
|
||||
'properties' => [],
|
||||
'elements' => [
|
||||
['element' => 'h1', 'content' => 'h1-element', 'order' => 1],
|
||||
['element' => 'span', 'content' => 'span-element', 'order' => 0],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$maker
|
||||
->design($design)
|
||||
->build();
|
||||
|
||||
$node = $maker->getSectionNode('header');
|
||||
|
||||
$after = [];
|
||||
|
||||
foreach ($node->childNodes as $child) {
|
||||
$after[] = $child->nodeName;
|
||||
}
|
||||
|
||||
$this->assertEquals('span', $after[1]);
|
||||
}
|
||||
|
||||
public function testGeneratingPdf()
|
||||
{
|
||||
$state = [
|
||||
|
Loading…
x
Reference in New Issue
Block a user