Merge branch 'v5-develop' into v5-689

This commit is contained in:
Benjamin Beganović 2021-09-20 08:12:42 +02:00 committed by GitHub
commit 3f0847749f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 5331 additions and 3495 deletions

View File

@ -1 +1 @@
5.3.15
5.3.16

View File

@ -790,6 +790,27 @@ class CreateSingleAccount extends Command
$cg->fees_and_limits = $fees_and_limits;
$cg->save();
}
if (config('ninja.testvars.square') && ($this->gateway == 'all' || $this->gateway == 'square')) {
$cg = new CompanyGateway;
$cg->company_id = $company->id;
$cg->user_id = $user->id;
$cg->gateway_key = '65faab2ab6e3223dbe848b1686490baz';
$cg->require_cvv = true;
$cg->require_billing_address = true;
$cg->require_shipping_address = true;
$cg->update_details = true;
$cg->config = encrypt(config('ninja.testvars.square'));
$cg->save();
$gateway_types = $cg->driver(new Client)->gatewayTypes();
$fees_and_limits = new stdClass;
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
$cg->fees_and_limits = $fees_and_limits;
$cg->save();
}
}
private function createRecurringInvoice($client)

View File

@ -0,0 +1,59 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Controllers\ClientPortal;
use App\Http\Controllers\Controller;
use App\Http\Requests\ClientPortal\Statements\ShowStatementRequest;
use Illuminate\Http\JsonResponse;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;
class StatementController extends Controller
{
/**
* Show the statement in the client portal.
*
* @return View
*/
public function index(): View
{
return render('statement.index');
}
/**
* Show the raw stream of the PDF.
*
* @param ShowStatementRequest $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|JsonResponse|\Illuminate\Http\Response|StreamedResponse
*/
public function raw(ShowStatementRequest $request)
{
$pdf = $request->client()->service()->statement(
$request->only(['start_date', 'end_date', 'show_payments_table', 'show_aging_table'])
);
if ($pdf && $request->query('download')) {
return response()->streamDownload(function () use ($pdf) {
echo $pdf;
}, 'statement.pdf', ['Content-Type' => 'application/pdf']);
}
if ($pdf) {
return response($pdf, 200)->withHeaders([
'Content-Type' => 'application/pdf',
]);
}
return response()->json(['message' => 'Something went wrong. Please check logs.']);
}
}

View File

@ -110,8 +110,11 @@ class ClientStatementController extends BaseController
public function statement(CreateStatementRequest $request)
{
$pdf = $this->createStatement($request);
$pdf = $request->client()->service()->statement([
'start_date' => $request->start_date,
'end_date' => $request->end_date,
]);
if ($pdf) {
return response()->streamDownload(function () use ($pdf) {
echo $pdf;
@ -120,88 +123,4 @@ class ClientStatementController extends BaseController
return response()->json(['message' => 'Something went wrong. Please check logs.']);
}
protected function createStatement(CreateStatementRequest $request): ?string
{
$invitation = false;
if ($request->getInvoices()->count() >= 1) {
$this->entity = $request->getInvoices()->first();
$invitation = $this->entity->invitations->first();
}
else if ($request->getPayments()->count() >= 1) {
$this->entity = $request->getPayments()->first()->invoices->first()->invitations->first();
$invitation = $this->entity->invitations->first();
}
$entity_design_id = 1;
$entity_design_id = $this->entity->design_id
? $this->entity->design_id
: $this->decodePrimaryKey($this->entity->client->getSetting('invoice_design_id'));
$design = Design::find($entity_design_id);
if (!$design) {
$design = Design::find($entity_design_id);
}
$html = new HtmlEngine($invitation);
$options = [
'start_date' => $request->start_date,
'end_date' => $request->end_date,
'show_payments_table' => $request->show_payments_table,
'show_aging_table' => $request->show_aging_table,
];
if ($design->is_custom) {
$options['custom_partials'] = \json_decode(\json_encode($design->design), true);
$template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options);
} else {
$template = new PdfMakerDesign(strtolower($design->name), $options);
}
$variables = $html->generateLabelsAndValues();
$state = [
'template' => $template->elements([
'client' => $this->entity->client,
'entity' => $this->entity,
'pdf_variables' => (array)$this->entity->company->settings->pdf_variables,
'$product' => $design->design->product,
'variables' => $variables,
'invoices' => $request->getInvoices(),
'payments' => $request->getPayments(),
'aging' => $request->getAging(),
], \App\Services\PdfMaker\Design::STATEMENT),
'variables' => $variables,
'options' => [],
'process_markdown' => $this->entity->client->company->markdown_enabled,
];
$maker = new PdfMakerService($state);
$maker
->design($template)
->build();
$pdf = null;
try {
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
$pdf = (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
}
else if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') {
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
} else {
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
}
} catch (\Exception $e) {
nlog(print_r($e->getMessage(), 1));
}
return $pdf;
}
}

View File

@ -14,6 +14,7 @@ namespace App\Http\Controllers;
use App\Factory\DesignFactory;
use App\Filters\DesignFilters;
use App\Http\Requests\Design\CreateDesignRequest;
use App\Http\Requests\Design\DefaultDesignRequest;
use App\Http\Requests\Design\DestroyDesignRequest;
use App\Http\Requests\Design\EditDesignRequest;
use App\Http\Requests\Design\ShowDesignRequest;
@ -487,4 +488,36 @@ class DesignController extends BaseController
return $this->listResponse(Design::withTrashed()->whereIn('id', $this->transformKeys($ids)));
}
public function default(DefaultDesignRequest $request)
{
$design_id = $request->int('design_id');
$entity = $request->input('entity');
$company = auth()->user()->getCompany();
$design = Design::where('company_id', $company->id)
->where('id', $design_id)
->exists();
if(!$design)
return response()->json(['message' => 'Design does not exist.'], 400);
switch ($entity) {
case 'invoice':
$company->invoices()->update(['design_id' => $design_id]);
break;
case 'quote':
$company->quotes()->update(['design_id' => $design_id]);
break;
case 'credit':
$company->credits()->update(['design_id' => $design_id]);
break;
default:
// code...
break;
}
return response()->json(['message' => 'success'], 200);
}
}

View File

@ -0,0 +1,49 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Livewire;
use Illuminate\View\View;
use Livewire\Component;
class Statement extends Component
{
public string $url;
public array $options = [
'show_payments_table' => 0,
'show_aging_table' => 0,
];
public function mount(): void
{
$this->options['start_date'] = now()->startOfYear()->format('Y-m-d');
$this->options['end_date'] = now()->format('Y-m-d');
}
protected function getCurrentUrl(): string
{
return route('client.statement.raw', $this->options);
}
public function download()
{
return redirect()->route('client.statement.raw', \array_merge($this->options, ['download' => 1]));
}
public function render(): View
{
$this->url = route('client.statement.raw', $this->options);
return render('components.statement');
}
}

View File

@ -56,4 +56,9 @@ class PaymentResponseRequest extends FormRequest
]);
}
}
public function shouldUseToken(): bool
{
return (bool) $this->token;
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Http\Requests\ClientPortal\Statements;
use App\Models\Client;
use Illuminate\Foundation\Http\FormRequest;
class ShowStatementRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
//
];
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
$this->merge([
'show_payments_table' => $this->has('show_payments_table') ? \boolval($this->show_payments_table) : false,
'show_aging_table' => $this->has('show_aging_table') ? \boolval($this->show_aging_table) : false,
]);
}
public function client(): Client
{
return auth('contact')->user()->client;
}
}

View File

@ -22,7 +22,7 @@ class DefaultCompanyRequest extends Request
*/
public function authorize() : bool
{
return auth()->user()->isAdmin()
return auth()->user()->isAdmin();
}
public function rules()

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Requests\Design;
use App\Http\Requests\Request;
class DefaultDesignRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->isAdmin();
}
public function rules()
{
return [
'entity' => 'required',
'design_id' => 'required',
];
}
protected function prepareForValidation()
{
$input = $this->all();
$input = $this->decodePrimaryKeys($input);
$this->replace($input);
}
}

View File

@ -4,11 +4,7 @@ namespace App\Http\Requests\Statements;
use App\Http\Requests\Request;
use App\Models\Client;
use App\Models\Invoice;
use App\Models\Payment;
use App\Utils\Number;
use App\Utils\Traits\MakesHash;
use Carbon\Carbon;
class CreateStatementRequest extends Request
{
@ -33,137 +29,23 @@ class CreateStatementRequest extends Request
return [
'start_date' => 'required|date_format:Y-m-d',
'end_date' => 'required|date_format:Y-m-d',
'client_id' => 'bail|required|exists:clients,id,company_id,'.auth()->user()->company()->id,
'client_id' => 'bail|required|exists:clients,id,company_id,' . auth()->user()->company()->id,
'show_payments_table' => 'boolean',
'show_aging_table' => 'boolean',
];
}
protected function prepareForValidation()
{
$input = $this->all();
$input = $this->decodePrimaryKeys($input);
$this->replace($input);
}
/**
* The collection of invoices for the statement.
*
* @return Invoice[]|\Illuminate\Database\Eloquent\Collection
*/
public function getInvoices()
public function client(): ?Client
{
$input = $this->all();
// $input['start_date & $input['end_date are available.
$client = Client::where('id', $input['client_id'])->first();
$from = Carbon::parse($input['start_date']);
$to = Carbon::parse($input['end_date']);
return Invoice::where('company_id', auth()->user()->company()->id)
->where('client_id', $client->id)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID])
->whereBetween('date',[$from, $to])
->get();
}
/**
* The collection of payments for the statement.
*
* @return Payment[]|\Illuminate\Database\Eloquent\Collection
*/
public function getPayments()
{
// $input['start_date & $input['end_date are available.
$input = $this->all();
$client = Client::where('id', $input['client_id'])->first();
$from = Carbon::parse($input['start_date']);
$to = Carbon::parse($input['end_date']);
return Payment::where('company_id', auth()->user()->company()->id)
->where('client_id', $client->id)
->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
->whereBetween('date',[$from, $to])
->get();
}
/**
* The array of aging data.
*/
public function getAging(): array
{
return [
'0-30' => $this->getAgingAmount('30'),
'30-60' => $this->getAgingAmount('60'),
'60-90' => $this->getAgingAmount('90'),
'90-120' => $this->getAgingAmount('120'),
'120+' => $this->getAgingAmount('120+'),
];
}
private function getAgingAmount($range)
{
$input = $this->all();
$ranges = $this->calculateDateRanges($range);
$from = $ranges[0];
$to = $ranges[1];
$client = Client::where('id', $input['client_id'])->first();
$amount = Invoice::where('company_id', auth()->user()->company()->id)
->where('client_id', $client->id)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('balance', '>', 0)
->whereBetween('date',[$from, $to])
->sum('balance');
return Number::formatMoney($amount, $client);
}
private function calculateDateRanges($range)
{
$ranges = [];
switch ($range) {
case '30':
$ranges[0] = now();
$ranges[1] = now()->subDays(30);
return $ranges;
break;
case '60':
$ranges[0] = now()->subDays(30);
$ranges[1] = now()->subDays(60);
return $ranges;
break;
case '90':
$ranges[0] = now()->subDays(60);
$ranges[1] = now()->subDays(90);
return $ranges;
break;
case '120':
$ranges[0] = now()->subDays(90);
$ranges[1] = now()->subDays(120);
return $ranges;
break;
case '120+':
$ranges[0] = now()->subDays(120);
$ranges[1] = now()->subYears(40);
return $ranges;
break;
default:
$ranges[0] = now()->subDays(0);
$ranges[1] = now()->subDays(30);
return $ranges;
break;
}
return Client::where('id', $this->client_id)->first();
}
}

View File

@ -119,6 +119,8 @@ class PortalComposer
$data[] = ['title' => ctrans('texts.tasks'), 'url' => 'client.tasks.index', 'icon' => 'clock'];
}
$data[] = ['title' => ctrans('texts.statement'), 'url' => 'client.statement', 'icon' => 'activity'];
return $data;
}
}

View File

@ -498,21 +498,28 @@ class CompanyExport implements ShouldQueue
if(Ninja::isHosted()) {
Storage::disk(config('filesystems.default'))->put('backups/'.$file_name, file_get_contents($zip_path));
unlink($zip_path);
}
$storage_file_path = Storage::disk(config('filesystems.default'))->url('backups/'.$file_name);
App::forgetInstance('translator');
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->company->settings));
$company_reference = Company::find($this->company->id);;
$nmo = new NinjaMailerObject;
$nmo->mailable = new DownloadBackup(Storage::disk(config('filesystems.default'))->url('backups/'.$file_name), $this->company);
$nmo->mailable = new DownloadBackup($storage_file_path, $company_reference);
$nmo->to_user = $this->user;
$nmo->company = $this->company;
$nmo->company = $company_reference;
$nmo->settings = $this->company->settings;
NinjaMailerJob::dispatch($nmo);
if(Ninja::isHosted()){
sleep(3);
unlink($zip_path);
}
}
}

View File

@ -58,6 +58,7 @@ use App\Models\VendorContact;
use App\Models\Webhook;
use App\Utils\Ninja;
use App\Utils\TempFile;
use App\Utils\Traits\GeneratesCounter;
use App\Utils\Traits\MakesHash;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -70,10 +71,12 @@ use Illuminate\Support\Str;
use ZipArchive;
use ZipStream\Option\Archive;
use ZipStream\ZipStream;
use JsonMachine\JsonMachine;
use JsonMachine\JsonDecoder\ExtJsonDecoder;
class CompanyImport implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash, GeneratesCounter;
public $tries = 1;
@ -103,6 +106,8 @@ class CompanyImport implements ShouldQueue
public $company_owner;
private $file_path;
private $importables = [
// 'company',
'users',
@ -157,6 +162,18 @@ class CompanyImport implements ShouldQueue
$this->current_app_version = config('ninja.app_version');
}
private function getObject($key, $force_array = false)
{
set_time_limit(0);
$json = JsonMachine::fromFile($this->file_path, '/'.$key, new ExtJsonDecoder);
if($force_array)
return iterator_to_array($json);
return $json;
}
public function handle()
{
MultiDB::setDb($this->company->db);
@ -176,9 +193,8 @@ class CompanyImport implements ShouldQueue
// $this->backup_file = json_decode(file_get_contents($this->file_location));
$tmp_file = $this->unzipFile();
$this->backup_file = json_decode(file_get_contents($tmp_file));
$this->file_path = $tmp_file;
// nlog($this->backup_file);
$this->checkUserCount();
if(array_key_exists('import_settings', $this->request_array) && $this->request_array['import_settings'] == 'true') {
@ -256,11 +272,11 @@ class CompanyImport implements ShouldQueue
if(Ninja::isSelfHost())
$this->pre_flight_checks_pass = true;
$backup_users = $this->backup_file->users;
// $backup_users = $this->backup_file->users;
$backup_users = $this->getObject('users', true);
$company_users = $this->company->users;
nlog("This is a free account");
nlog("Backup user count = ".count($backup_users));
if(count($backup_users) > 1){
@ -307,12 +323,10 @@ class CompanyImport implements ShouldQueue
}
}
if($this->company->account->isFreeHostedClient() && count($this->backup_file->clients) > config('ninja.quotas.free.clients')){
if($this->company->account->isFreeHostedClient() && $client_count = count($this->getObject('clients', true)) > config('ninja.quotas.free.clients')){
nlog("client quota busted");
$client_count = count($this->backup_file->clients);
$client_limit = config('ninja.quotas.free.clients');
$this->message = "You are attempting to import ({$client_count}) clients, your current plan allows a total of ({$client_limit})";
@ -333,7 +347,10 @@ 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
if($this->current_app_version != $this->backup_file->app_version)
$data = (object)$this->getObject('app_version', true);
if($this->current_app_version != $data->app_version)
{
//perform some magic here
}
@ -351,8 +368,9 @@ class CompanyImport implements ShouldQueue
private function importSettings()
{
$this->company->settings = $this->backup_file->company->settings;
$co = (object)$this->getObject("company", true);
$this->company->settings = $co->settings;
// $this->company->settings = $this->backup_file->company->settings;
$this->company->save();
return $this;
@ -375,7 +393,8 @@ class CompanyImport implements ShouldQueue
private function importCompany()
{
$tmp_company = $this->backup_file->company;
//$tmp_company = $this->backup_file->company;
$tmp_company = (object)$this->getObject("company");
$tmp_company->company_key = $this->createHash();
$tmp_company->db = config('database.default');
$tmp_company->account_id = $this->account->id;
@ -427,7 +446,8 @@ class CompanyImport implements ShouldQueue
private function import_tax_rates()
{
foreach($this->backup_file->tax_rates as $obj)
// foreach($this->backup_file->tax_rates as $obj)
foreach((object)$this->getObject("tax_rates") as $obj)
{
$user_id = $this->transformId('users', $obj->user_id);
@ -804,13 +824,14 @@ class CompanyImport implements ShouldQueue
$activities = [];
foreach($this->backup_file->activities as $activity)
{
$activity->account_id = $this->account->id;
$activities[] = $activity;
}
// foreach($this->backup_file->activities as $activity)
// foreach((object)$this->getObject("activities") as $obj)
// {
// $activity->account_id = $this->account->id;
// $activities[] = $activity;
// }
$this->backup_file->activities = $activities;
// $this->backup_file->activities = $activities;
$this->genericNewClassImport(Activity::class,
[
@ -889,7 +910,8 @@ class CompanyImport implements ShouldQueue
private function import_documents()
{
foreach($this->backup_file->documents as $document)
// foreach($this->backup_file->documents as $document)
foreach((object)$this->getObject("documents") as $document)
{
$new_document = new Document();
@ -947,7 +969,8 @@ class CompanyImport implements ShouldQueue
{
User::unguard();
foreach ($this->backup_file->users as $user)
//foreach ($this->backup_file->users as $user)
foreach((object)$this->getObject("users") as $user)
{
if(User::where('email', $user->email)->where('account_id', '!=', $this->account->id)->exists())
@ -978,7 +1001,8 @@ class CompanyImport implements ShouldQueue
{
CompanyUser::unguard();
foreach($this->backup_file->company_users as $cu)
// foreach($this->backup_file->company_users as $cu)
foreach((object)$this->getObject("company_users") as $cu)
{
$user_id = $this->transformId('users', $cu->user_id);
@ -1050,7 +1074,8 @@ class CompanyImport implements ShouldQueue
private function paymentablesImport()
{
foreach($this->backup_file->payments as $payment)
// foreach($this->backup_file->payments as $payment)
foreach((object)$this->getObject("payments") as $payment)
{
foreach($payment->paymentables as $paymentable_obj)
@ -1096,7 +1121,8 @@ class CompanyImport implements ShouldQueue
$class::unguard();
foreach($this->backup_file->{$object_property} as $obj)
// foreach($this->backup_file->{$object_property} as $obj)
foreach((object)$this->getObject($object_property) as $obj)
{
/* Remove unwanted keys*/
$obj_array = (array)$obj;
@ -1131,6 +1157,8 @@ class CompanyImport implements ShouldQueue
}
$obj_array['account_id'] = $this->account->id;
}
/* Transform old keys to new keys */
@ -1170,7 +1198,8 @@ class CompanyImport implements ShouldQueue
$class::unguard();
foreach($this->backup_file->{$object_property} as $obj)
//foreach($this->backup_file->{$object_property} as $obj)
foreach((object)$this->getObject($object_property) as $obj)
{
if(is_null($obj))
@ -1221,8 +1250,9 @@ class CompanyImport implements ShouldQueue
{
$class::unguard();
foreach($this->backup_file->{$object_property} as $obj)
$x = 0;
foreach((object)$this->getObject($object_property) as $obj)
// foreach($this->backup_file->{$object_property} as $obj)
{
/* Remove unwanted keys*/
$obj_array = (array)$obj;
@ -1248,10 +1278,41 @@ class CompanyImport implements ShouldQueue
$obj_array['product_ids'] = '';
}
$new_obj = $class::firstOrNew(
[$match_key => $obj->{$match_key}, 'company_id' => $this->company->id],
$obj_array,
);
/* Expenses that don't have a number will not be inserted - so need to override here*/
if($class == 'App\Models\Expense' && is_null($obj->{$match_key})){
$new_obj = new Expense();
$new_obj->company_id = $this->company->id;
$new_obj->fill($obj_array);
$new_obj->save(['timestamps' => false]);
$new_obj->number = $this->getNextExpenseNumber($new_obj);
}
elseif($class == 'App\Models\Invoice' && is_null($obj->{$match_key})){
$new_obj = new Invoice();
$new_obj->company_id = $this->company->id;
$new_obj->fill($obj_array);
$new_obj->save(['timestamps' => false]);
$new_obj->number = $this->getNextInvoiceNumber($client = Client::find($obj_array['client_id']),$new_obj);
}
elseif($class == 'App\Models\Payment' && is_null($obj->{$match_key})){
$new_obj = new Payment();
$new_obj->company_id = $this->company->id;
$new_obj->fill($obj_array);
$new_obj->save(['timestamps' => false]);
$new_obj->number = $this->getNextPaymentNumber($client = Client::find($obj_array['client_id']), $new_obj);
}
elseif($class == 'App\Models\Quote' && is_null($obj->{$match_key})){
$new_obj = new Quote();
$new_obj->company_id = $this->company->id;
$new_obj->fill($obj_array);
$new_obj->save(['timestamps' => false]);
$new_obj->number = $this->getNextQuoteNumber($client = Client::find($obj_array['client_id']), $new_obj);
}
else{
$new_obj = $class::firstOrNew(
[$match_key => $obj->{$match_key}, 'company_id' => $this->company->id],
$obj_array,
);
}
$new_obj->save(['timestamps' => false]);

View File

@ -43,7 +43,6 @@ class InvoiceViewedActivity implements ShouldQueue
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->invitation->invoice->user_id;
$fields->user_id = $user_id;

View File

@ -87,17 +87,14 @@ class Gateway extends StaticModel
case 11:
return [GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => false]];//Payfast
break;
case 7:
return [
GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true], // Mollie
];
case 15:
return [GatewayType::PAYPAL => ['refund' => true, 'token_billing' => false]]; //Paypal
break;
case 20:
case 56:
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true],
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable','charge.succeeded']],
GatewayType::ALIPAY => ['refund' => false, 'token_billing' => false],
GatewayType::APPLE_PAY => ['refund' => false, 'token_billing' => false],
GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']]]; //Stripe
break;
case 39:
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]]; //Checkout
break;
@ -114,11 +111,19 @@ class Gateway extends StaticModel
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true],
];
break;
case 7:
case 56:
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true],
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable','charge.succeeded']],
GatewayType::ALIPAY => ['refund' => false, 'token_billing' => false],
GatewayType::APPLE_PAY => ['refund' => false, 'token_billing' => false],
GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']]]; //Stripe
break;
case 57:
return [
GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true], // Mollie
GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true], //Square
];
break;
break;
default:
return [];
break;

View File

@ -72,6 +72,7 @@ class SystemLog extends Model
const TYPE_PAYTRACE = 311;
const TYPE_MOLLIE = 312;
const TYPE_EWAY = 313;
const TYPE_SQUARE = 320;
const TYPE_QUOTA_EXCEEDED = 400;
const TYPE_UPSTREAM_FAILURE = 401;

View File

@ -95,7 +95,7 @@ class Token
$response_status = ErrorCode::getStatus($response->ResponseMessage);
$error = $response_status['message']
$error = $response_status['message'];
$error_code = $response->ResponseMessage;
$data = [

View File

@ -0,0 +1,319 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\PaymentDrivers\Square;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
use App\Models\Payment;
use App\Models\PaymentType;
use App\PaymentDrivers\SquarePaymentDriver;
use App\Utils\Traits\MakesHash;
use Illuminate\Support\Str;
use Square\Http\ApiResponse;
class CreditCard
{
use MakesHash;
public $square_driver;
public function __construct(SquarePaymentDriver $square_driver)
{
$this->square_driver = $square_driver;
$this->square_driver->init();
}
public function authorizeView($data)
{
$data['gateway'] = $this->square_driver;
return render('gateways.square.credit_card.authorize', $data);
}
public function authorizeResponse($request)
{
/* Step one - process a $1 payment - but don't complete it*/
$payment = false;
$amount_money = new \Square\Models\Money();
$amount_money->setAmount(100); //amount in cents
$amount_money->setCurrency($this->square_driver->client->currency()->code);
$body = new \Square\Models\CreatePaymentRequest(
$request->sourceId,
Str::random(32),
$amount_money
);
$body->setAutocomplete(false);
$body->setLocationId($this->square_driver->company_gateway->getConfigField('locationId'));
$body->setReferenceId(Str::random(16));
$api_response = $this->square_driver->square->getPaymentsApi()->createPayment($body);
if ($api_response->isSuccess()) {
$result = $api_response->getBody();
$payment = json_decode($result);
} else {
$errors = $api_response->getErrors();
return $this->processUnsuccessfulPayment($errors);
}
/* Step 3 create the card */
$card = new \Square\Models\Card();
$card->setCardholderName($this->square_driver->client->present()->name());
// $card->setBillingAddress($billing_address);
$card->setCustomerId($this->findOrCreateClient());
$card->setReferenceId(Str::random(8));
$body = new \Square\Models\CreateCardRequest(
Str::random(32),
$payment->payment->id,
$card
);
$api_response = $this->square_driver
->square
->getCardsApi()
->createCard($body);
$card = false;
if ($api_response->isSuccess()) {
$card = $api_response->getBody();
$card = json_decode($card);
} else {
$errors = $api_response->getErrors();
return $this->processUnsuccessfulPayment($errors);
}
/* Create the token in Invoice Ninja*/
$cgt = [];
$cgt['token'] = $card->card->id;
$cgt['payment_method_id'] = GatewayType::CREDIT_CARD;
$payment_meta = new \stdClass;
$payment_meta->exp_month = $card->card->exp_month;
$payment_meta->exp_year = $card->card->exp_year;
$payment_meta->brand = $card->card->card_brand;
$payment_meta->last4 = $card->card->last_4;
$payment_meta->type = GatewayType::CREDIT_CARD;
$cgt['payment_meta'] = $payment_meta;
$token = $this->square_driver->storeGatewayToken($cgt, [
'gateway_customer_reference' => $this->findOrCreateClient(),
]);
return redirect()->route('client.payment_methods.index');
}
public function paymentView($data)
{
$data['gateway'] = $this->square_driver;
return render('gateways.square.credit_card.pay', $data);
}
public function paymentResponse(PaymentResponseRequest $request)
{
$token = $request->sourceId;
$amount = $this->square_driver->convertAmount(
$this->square_driver->payment_hash->data->amount_with_fee
);
if ($request->shouldUseToken()) {
$cgt = ClientGatewayToken::where('token', $request->token)->first();
$token = $cgt->token;
}
$amount_money = new \Square\Models\Money();
$amount_money->setAmount($amount);
$amount_money->setCurrency($this->square_driver->client->currency()->code);
$body = new \Square\Models\CreatePaymentRequest($token, Str::random(32), $amount_money);
$body->setAutocomplete(true);
$body->setLocationId($this->square_driver->company_gateway->getConfigField('locationId'));
$body->setReferenceId(Str::random(16));
if ($request->shouldUseToken()) {
$body->setCustomerId($cgt->gateway_customer_reference);
}
/** @var ApiResponse */
$response = $this->square_driver->square->getPaymentsApi()->createPayment($body);
if ($response->isSuccess()) {
if ($request->shouldStoreToken()) {
$this->storePaymentMethod($response);
}
return $this->processSuccessfulPayment($response);
}
return $this->processUnsuccessfulPayment($response);
}
private function storePaymentMethod(ApiResponse $response)
{
$payment = \json_decode($response->getBody());
$card = new \Square\Models\Card();
$card->setCardholderName($this->square_driver->client->present()->name());
$card->setCustomerId($this->findOrCreateClient());
$card->setReferenceId(Str::random(8));
$body = new \Square\Models\CreateCardRequest(Str::random(32), $payment->payment->id, $card);
/** @var ApiResponse */
$api_response = $this->square_driver
->square
->getCardsApi()
->createCard($body);
if (!$api_response->isSuccess()) {
return $this->processUnsuccessfulPayment($api_response);
}
$card = \json_decode($api_response->getBody());
$cgt = [];
$cgt['token'] = $card->card->id;
$cgt['payment_method_id'] = GatewayType::CREDIT_CARD;
$payment_meta = new \stdClass;
$payment_meta->exp_month = $card->card->exp_month;
$payment_meta->exp_year = $card->card->exp_year;
$payment_meta->brand = $card->card->card_brand;
$payment_meta->last4 = $card->card->last_4;
$payment_meta->type = GatewayType::CREDIT_CARD;
$cgt['payment_meta'] = $payment_meta;
$this->square_driver->storeGatewayToken($cgt, [
'gateway_customer_reference' => $this->findOrCreateClient(),
]);
}
private function processSuccessfulPayment(ApiResponse $response)
{
$body = json_decode($response->getBody());
$amount = array_sum(array_column($this->square_driver->payment_hash->invoices(), 'amount')) + $this->square_driver->payment_hash->fee_total;
$payment_record = [];
$payment_record['amount'] = $amount;
$payment_record['payment_type'] = PaymentType::CREDIT_CARD_OTHER;
$payment_record['gateway_type_id'] = GatewayType::CREDIT_CARD;
$payment_record['transaction_reference'] = $body->payment->id;
$payment = $this->square_driver->createPayment($payment_record, Payment::STATUS_COMPLETED);
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
}
private function processUnsuccessfulPayment(ApiResponse $response)
{
$body = \json_decode($response->getBody());
$data = [
'response' => $response,
'error' => $body->errors[0]->detail,
'error_code' => '',
];
return $this->square_driver->processUnsuccessfulTransaction($data);
}
private function findOrCreateClient()
{
$email_address = new \Square\Models\CustomerTextFilter();
$email_address->setExact($this->square_driver->client->present()->email());
$filter = new \Square\Models\CustomerFilter();
$filter->setEmailAddress($email_address);
$query = new \Square\Models\CustomerQuery();
$query->setFilter($filter);
$body = new \Square\Models\SearchCustomersRequest();
$body->setQuery($query);
$api_response = $this->square_driver
->init()
->square
->getCustomersApi()
->searchCustomers($body);
$customers = false;
if ($api_response->isSuccess()) {
$customers = $api_response->getBody();
$customers = json_decode($customers);
} else {
$errors = $api_response->getErrors();
}
if ($customers) {
return $customers->customers[0]->id;
}
return $this->createClient();
}
private function createClient()
{
/* Step two - create the customer */
$billing_address = new \Square\Models\Address();
$billing_address->setAddressLine1($this->square_driver->client->address1);
$billing_address->setAddressLine2($this->square_driver->client->address2);
$billing_address->setLocality($this->square_driver->client->city);
$billing_address->setAdministrativeDistrictLevel1($this->square_driver->client->state);
$billing_address->setPostalCode($this->square_driver->client->postal_code);
$billing_address->setCountry($this->square_driver->client->country->iso_3166_2);
$body = new \Square\Models\CreateCustomerRequest();
$body->setGivenName($this->square_driver->client->present()->name());
$body->setFamilyName('');
$body->setEmailAddress($this->square_driver->client->present()->email());
$body->setAddress($billing_address);
$body->setPhoneNumber($this->square_driver->client->phone);
$body->setReferenceId($this->square_driver->client->number);
$body->setNote('Created by Invoice Ninja.');
$api_response = $this->square_driver
->init()
->square
->getCustomersApi()
->createCustomer($body);
if ($api_response->isSuccess()) {
$result = $api_response->getResult();
return $result->getCustomer()->getId();
} else {
$errors = $api_response->getErrors();
return $this->processUnsuccessfulPayment($errors);
}
}
}

View File

@ -0,0 +1,261 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\PaymentDrivers;
use App\Http\Requests\Payments\PaymentWebhookRequest;
use App\Jobs\Mail\PaymentFailureMailer;
use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentHash;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\Square\CreditCard;
use App\Utils\Traits\MakesHash;
use Square\Http\ApiResponse;
class SquarePaymentDriver extends BaseDriver
{
use MakesHash;
public $refundable = false; //does this gateway support refunds?
public $token_billing = true; //does this gateway support token billing?
public $can_authorise_credit_card = true; //does this gateway support authorizations?
public $square;
public $payment_method;
public static $methods = [
GatewayType::CREDIT_CARD => CreditCard::class, //maps GatewayType => Implementation class
];
const SYSTEM_LOG_TYPE = SystemLog::TYPE_SQUARE;
public function init()
{
$this->square = new \Square\SquareClient([
'accessToken' => $this->company_gateway->getConfigField('accessToken'),
'environment' => $this->company_gateway->getConfigField('testMode') ? \Square\Environment::SANDBOX : \Square\Environment::PRODUCTION,
]);
return $this; /* This is where you boot the gateway with your auth credentials*/
}
/* Returns an array of gateway types for the payment gateway */
public function gatewayTypes(): array
{
$types = [];
$types[] = GatewayType::CREDIT_CARD;
return $types;
}
/* Sets the payment method initialized */
public function setPaymentMethod($payment_method_id)
{
$class = self::$methods[$payment_method_id];
$this->payment_method = new $class($this);
return $this;
}
public function authorizeView(array $data)
{
return $this->payment_method->authorizeView($data); //this is your custom implementation from here
}
public function authorizeResponse($request)
{
return $this->payment_method->authorizeResponse($request); //this is your custom implementation from here
}
public function processPaymentView(array $data)
{
return $this->payment_method->paymentView($data); //this is your custom implementation from here
}
public function processPaymentResponse($request)
{
return $this->payment_method->paymentResponse($request); //this is your custom implementation from here
}
public function refund(Payment $payment, $amount, $return_client_response = false)
{
$this->init();
$amount_money = new \Square\Models\Money();
$amount_money->setAmount($this->convertAmount($amount));
$amount_money->setCurrency($this->square_driver->client->currency()->code);
$body = new \Square\Models\RefundPaymentRequest(\Illuminate\Support\Str::random(32), $amount_money, $payment->transaction_reference);
/** @var ApiResponse */
$response = $this->square->getRefundsApi()->refund($body);
// if ($response->isSuccess()) {
// return [
// 'transaction_reference' => $refund->action_id,
// 'transaction_response' => json_encode($response),
// 'success' => $checkout_payment->status == 'Refunded',
// 'description' => $checkout_payment->status,
// 'code' => $checkout_payment->http_code,
// ];
// }
}
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
{
$this->init();
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
$amount = $this->convertAmount($amount);
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->withTrashed()->first();
if ($invoice) {
$description = "Invoice {$invoice->number} for {$amount} for client {$this->client->present()->name()}";
} else {
$description = "Payment with no invoice for amount {$amount} for client {$this->client->present()->name()}";
}
$amount_money = new \Square\Models\Money();
$amount_money->setAmount($amount);
$amount_money->setCurrency($this->client->currency()->code);
$body = new \Square\Models\CreatePaymentRequest($cgt->token, \Illuminate\Support\Str::random(32), $amount_money);
/** @var ApiResponse */
$response = $this->square->getPaymentsApi()->createPayment($body);
$body = json_decode($response->getBody());
if ($response->isSuccess()) {
$amount = array_sum(array_column($this->payment_hash->invoices(), 'amount')) + $this->payment_hash->fee_total;
$payment_record = [];
$payment_record['amount'] = $amount;
$payment_record['payment_type'] = PaymentType::CREDIT_CARD_OTHER;
$payment_record['gateway_type_id'] = GatewayType::CREDIT_CARD;
$payment_record['transaction_reference'] = $body->payment->id;
$payment = $this->createPayment($payment_record, Payment::STATUS_COMPLETED);
SystemLogger::dispatch(
['response' => $response, 'data' => $payment_record],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_CHECKOUT,
$this->client,
$this->client->company,
);
return $payment;
}
$this->unWindGatewayFees($payment_hash);
PaymentFailureMailer::dispatch(
$this->client,
$body->errors[0]->detail,
$this->client->company,
$amount
);
$message = [
'server_response' => $response,
'data' => $payment_hash->data,
];
SystemLogger::dispatch(
$message,
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_SQUARE,
$this->client,
$this->client->company,
);
return false;
}
public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null)
{
}
public function getClientRequiredFields(): array
{
$fields = [];
$fields[] = ['name' => 'client_postal_code', 'label' => ctrans('texts.postal_code'), 'type' => 'text', 'validation' => 'required'];
if ($this->company_gateway->require_client_name) {
$fields[] = ['name' => 'client_name', 'label' => ctrans('texts.client_name'), 'type' => 'text', 'validation' => 'required'];
}
if ($this->company_gateway->require_contact_name) {
$fields[] = ['name' => 'contact_first_name', 'label' => ctrans('texts.first_name'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'contact_last_name', 'label' => ctrans('texts.last_name'), 'type' => 'text', 'validation' => 'required'];
}
if ($this->company_gateway->require_contact_email) {
$fields[] = ['name' => 'contact_email', 'label' => ctrans('texts.email'), 'type' => 'text', 'validation' => 'required,email:rfc'];
}
if ($this->company_gateway->require_client_phone) {
$fields[] = ['name' => 'client_phone', 'label' => ctrans('texts.client_phone'), 'type' => 'tel', 'validation' => 'required'];
}
if ($this->company_gateway->require_billing_address) {
$fields[] = ['name' => 'client_address_line_1', 'label' => ctrans('texts.address1'), 'type' => 'text', 'validation' => 'required'];
// $fields[] = ['name' => 'client_address_line_2', 'label' => ctrans('texts.address2'), 'type' => 'text', 'validation' => 'nullable'];
$fields[] = ['name' => 'client_city', 'label' => ctrans('texts.city'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'client_state', 'label' => ctrans('texts.state'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'text', 'validation' => 'required'];
}
if ($this->company_gateway->require_shipping_address) {
$fields[] = ['name' => 'client_shipping_address_line_1', 'label' => ctrans('texts.shipping_address1'), 'type' => 'text', 'validation' => 'required'];
// $fields[] = ['name' => 'client_shipping_address_line_2', 'label' => ctrans('texts.shipping_address2'), 'type' => 'text', 'validation' => 'sometimes'];
$fields[] = ['name' => 'client_shipping_city', 'label' => ctrans('texts.shipping_city'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'client_shipping_state', 'label' => ctrans('texts.shipping_state'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'client_shipping_postal_code', 'label' => ctrans('texts.shipping_postal_code'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'client_shipping_country_id', 'label' => ctrans('texts.shipping_country'), 'type' => 'text', 'validation' => 'required'];
}
return $fields;
}
public function convertAmount($amount)
{
$precision = $this->client->currency()->precision;
if ($precision == 0) {
return $amount;
}
if ($precision == 1) {
return $amount*10;
}
if ($precision == 2) {
return $amount*100;
}
return $amount;
}
}

View File

@ -43,7 +43,7 @@ class SEPA
'customer' => $customer->id,
], $this->stripe_driver->stripe_connect_auth);
$client_secret = $setup_intent->client_secret
$client_secret = $setup_intent->client_secret;
// Pass the client secret to the client

View File

@ -85,6 +85,16 @@ class ClientService
return $this;
}
/**
* Generate the client statement.
*
* @param array $options
*/
public function statement(array $options = [])
{
return (new Statement($this->client, $options))->run();
}
public function save() :Client
{
$this->client->save();

View File

@ -0,0 +1,355 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\Client;
use App\Factory\InvoiceFactory;
use App\Factory\InvoiceInvitationFactory;
use App\Factory\InvoiceItemFactory;
use App\Models\Client;
use App\Models\Design;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Product;
use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Services\PdfMaker\PdfMaker;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\HtmlEngine;
use App\Utils\Number;
use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\Pdf\PdfMaker as PdfMakerTrait;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
class Statement
{
use PdfMakerTrait;
protected Client $client;
/**
* @var Invoice|Payment|null
*/
protected $entity;
protected array $options;
protected bool $rollback = false;
public function __construct(Client $client, array $options)
{
$this->client = $client;
$this->options = $options;
}
public function run(): ?string
{
$this
->setupOptions()
->setupEntity();
$html = new HtmlEngine($this->getInvitation());
if ($this->getDesign()->is_custom) {
$this->options['custom_partials'] = \json_decode(\json_encode($this->getDesign()->design), true);
$template = new PdfMakerDesign(\App\Services\PdfMaker\Design::CUSTOM, $this->options);
} else {
$template = new PdfMakerDesign(strtolower($this->getDesign()->name), $this->options);
}
$variables = $html->generateLabelsAndValues();
$state = [
'template' => $template->elements([
'client' => $this->entity->client,
'entity' => $this->entity,
'pdf_variables' => (array)$this->entity->company->settings->pdf_variables,
'$product' => $this->getDesign()->design->product,
'variables' => $variables,
'invoices' => $this->getInvoices(),
'payments' => $this->getPayments(),
'aging' => $this->getAging(),
], \App\Services\PdfMaker\Design::STATEMENT),
'variables' => $variables,
'options' => [],
'process_markdown' => $this->entity->client->company->markdown_enabled,
];
$maker = new PdfMaker($state);
$maker
->design($template)
->build();
$pdf = null;
try {
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
$pdf = (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
} elseif (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') {
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
} else {
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
}
} catch (\Exception $e) {
nlog(print_r($e->getMessage(), 1));
}
if ($this->rollback) {
DB::rollBack();
}
return $pdf;
}
/**
* Setup correct entity instance.
*
* @return Statement
*/
protected function setupEntity(): self
{
if (count($this->getInvoices()) >= 1) {
$this->entity = $this->getInvoices()->first();
}
if (\is_null($this->entity)) {
DB::beginTransaction();
$this->rollback = true;
$invoice = InvoiceFactory::create($this->client->company->id, $this->client->user->id);
$invoice->client_id = $this->client->id;
$invoice->line_items = $this->buildLineItems();
$invoice->save();
$invitation = InvoiceInvitationFactory::create($invoice->company_id, $invoice->user_id);
$invitation->invoice_id = $invoice->id;
$invitation->client_contact_id = $this->client->contacts->first()->id;
$invitation->save();
$this->entity = $invoice;
}
return $this;
}
protected function buildLineItems($count = 1)
{
$line_items = [];
for ($x = 0; $x < $count; $x++) {
$item = InvoiceItemFactory::create();
$item->quantity = 1;
//$item->cost = 10;
if (rand(0, 1)) {
$item->tax_name1 = 'GST';
$item->tax_rate1 = 10.00;
}
if (rand(0, 1)) {
$item->tax_name1 = 'VAT';
$item->tax_rate1 = 17.50;
}
if (rand(0, 1)) {
$item->tax_name1 = 'Sales Tax';
$item->tax_rate1 = 5;
}
$product = Product::all()->random();
$item->cost = (float) $product->cost;
$item->product_key = $product->product_key;
$item->notes = $product->notes;
$item->custom_value1 = $product->custom_value1;
$item->custom_value2 = $product->custom_value2;
$item->custom_value3 = $product->custom_value3;
$item->custom_value4 = $product->custom_value4;
$line_items[] = $item;
}
return $line_items;
}
/**
* Setup & prepare options.
*
* @return Statement
*/
protected function setupOptions(): self
{
if (! \array_key_exists('start_date', $this->options)) {
$this->options['start_date'] = now()->startOfYear()->format('Y-m-d');
}
if (! \array_key_exists('end_date', $this->options)) {
$this->options['end_date'] = now()->format('Y-m-d');
}
if (! \array_key_exists('show_payments_table', $this->options)) {
$this->options['show_payments_table'] = false;
}
if (! \array_key_exists('show_aging_table', $this->options)) {
$this->options['show_aging_table'] = false;
}
return $this;
}
/**
* The collection of invoices for the statement.
*
* @return Invoice[]|\Illuminate\Database\Eloquent\Collection
*/
protected function getInvoices(): Collection
{
return Invoice::where('company_id', $this->client->company->id)
->where('client_id', $this->client->id)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID])
->whereBetween('date', [$this->options['start_date'], $this->options['end_date']])
->get();
}
/**
* The collection of payments for the statement.
*
* @return Payment[]|\Illuminate\Database\Eloquent\Collection
*/
protected function getPayments(): Collection
{
return Payment::where('company_id', $this->client->company->id)
->where('client_id', $this->client->id)
->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
->whereBetween('date', [$this->options['start_date'], $this->options['end_date']])
->get();
}
/**
* Get correct invitation ID.
*
* @return int|bool
*/
protected function getInvitation()
{
if ($this->entity instanceof Invoice || $this->entity instanceof Payment) {
return $this->entity->invitations->first();
}
return false;
}
/**
* Get the array of aging data.
*
* @return array
*/
protected function getAging(): array
{
return [
'0-30' => $this->getAgingAmount('30'),
'30-60' => $this->getAgingAmount('60'),
'60-90' => $this->getAgingAmount('90'),
'90-120' => $this->getAgingAmount('120'),
'120+' => $this->getAgingAmount('120+'),
];
}
/**
* Generate aging amount.
*
* @param mixed $range
* @return string
*/
private function getAgingAmount($range)
{
$ranges = $this->calculateDateRanges($range);
$from = $ranges[0];
$to = $ranges[1];
$client = Client::where('id', $this->client->id)->first();
$amount = Invoice::where('company_id', $this->client->company->id)
->where('client_id', $client->id)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('balance', '>', 0)
->whereBetween('date', [$from, $to])
->sum('balance');
return Number::formatMoney($amount, $client);
}
/**
* Calculate date ranges for aging.
*
* @param mixed $range
* @return array
*/
private function calculateDateRanges($range)
{
$ranges = [];
switch ($range) {
case '30':
$ranges[0] = now();
$ranges[1] = now()->subDays(30);
return $ranges;
break;
case '60':
$ranges[0] = now()->subDays(30);
$ranges[1] = now()->subDays(60);
return $ranges;
break;
case '90':
$ranges[0] = now()->subDays(60);
$ranges[1] = now()->subDays(90);
return $ranges;
break;
case '120':
$ranges[0] = now()->subDays(90);
$ranges[1] = now()->subDays(120);
return $ranges;
break;
case '120+':
$ranges[0] = now()->subDays(120);
$ranges[1] = now()->subYears(40);
return $ranges;
break;
default:
$ranges[0] = now()->subDays(0);
$ranges[1] = now()->subDays(30);
return $ranges;
break;
}
}
/**
* Get correct design for statement.
*
* @return \App\Models\Design
*/
protected function getDesign(): Design
{
$id = 1;
if (!empty($this->client->getSetting('entity_design_id'))) {
$id = (int) $this->client->getSetting('entity_design_id');
}
return Design::find($id);
}
}

View File

@ -40,7 +40,10 @@ trait DesignHelpers
if (isset($this->context['invoices'])) {
$this->invoices = $this->context['invoices'];
$this->entity = $this->invoices->first();
if (\count($this->invoices) >= 1) {
$this->entity = $this->invoices->first();
}
}
if (isset($this->context['payments'])) {

View File

@ -82,6 +82,12 @@ class PdfMaker
return $this;
}
/**
* Return compiled HTML.
*
* @param bool $final deprecated
* @return mixed
*/
public function getCompiledHTML($final = false)
{
$html = $this->document->saveHTML();

View File

@ -335,6 +335,15 @@ trait GeneratesCounter
{
$this->resetCompanyCounters($expense->company);
// - 18/09/21 need to set this property if it doesn't exist. //todo refactor this for other properties
if(!property_exists($expense->company->settings, 'recurring_expense_number_counter')){
$settings = $expense->company->settings;
$settings->recurring_expense_number_counter = 1;
$settings->recurring_expense_number_pattern = '';
$expense->company->settings = $settings;
$expense->company->save();
}
$counter = $expense->company->settings->recurring_expense_number_counter;
$setting_entity = $expense->company->settings->recurring_expense_number_counter;
@ -585,6 +594,7 @@ trait GeneratesCounter
$settings->project_number_counter = 1;
$settings->task_number_counter = 1;
$settings->expense_number_counter = 1;
$settings->recurring_expense_number_counter =1;
$company->settings = $settings;
$company->save();

View File

@ -297,6 +297,9 @@ trait MakesInvoiceValues
$data[$key][$table_type.'.notes'] = $this->processReservedKeywords($item->notes);
$data[$key][$table_type.'.description'] = $this->processReservedKeywords($item->notes);
/* need to test here as this is new - 18/09/2021*/
if(!array_key_exists($table_type.'.gross_line_total', $data[$key]))
$data[$key][$table_type.'.gross_line_total'] = 0;
$data[$key][$table_type . ".{$_table_type}1"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}1", $item->custom_value1, $this->client);
$data[$key][$table_type . ".{$_table_type}2"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}2", $item->custom_value2, $this->client);
@ -311,7 +314,7 @@ trait MakesInvoiceValues
$data[$key][$table_type.'.cost'] = Number::formatMoney($item->cost, $this->client);
$data[$key][$table_type.'.line_total'] = Number::formatMoney($item->line_total, $this->client);
$data[$key][$table_type.'.gross_line_total'] = Number::formatMoney($item->gross_line_total, $this->client);
if (isset($item->discount) && $item->discount > 0) {
if ($item->is_amount_discount) {

View File

@ -48,6 +48,7 @@
"gocardless/gocardless-pro": "^4.12",
"google/apiclient": "^2.7",
"guzzlehttp/guzzle": "^7.0.1",
"halaxa/json-machine": "^0.7.0",
"hashids/hashids": "^4.0",
"hedii/laravel-gelf-logger": "^6.0",
"intervention/image": "^2.5",
@ -72,6 +73,7 @@
"pragmarx/google2fa": "^8.0",
"predis/predis": "^1.1",
"sentry/sentry-laravel": "^2",
"square/square": "13.0.0.20210721",
"stripe/stripe-php": "^7.50",
"symfony/http-client": "^5.2",
"tijsverkoyen/css-to-inline-styles": "^2.2",

448
composer.lock generated
View File

@ -4,8 +4,122 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "b5675738fe061ea09b0bbd89e553d074",
"content-hash": "54d84c4ecc41d25ece12b91b181e3431",
"packages": [
{
"name": "apimatic/jsonmapper",
"version": "v2.0.3",
"source": {
"type": "git",
"url": "https://github.com/apimatic/jsonmapper.git",
"reference": "f7588f1ab692c402a9118e65cb9fd42b74e5e0db"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/apimatic/jsonmapper/zipball/f7588f1ab692c402a9118e65cb9fd42b74e5e0db",
"reference": "f7588f1ab692c402a9118e65cb9fd42b74e5e0db",
"shasum": ""
},
"require-dev": {
"phpunit/phpunit": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
"squizlabs/php_codesniffer": "^3.0.0"
},
"type": "library",
"autoload": {
"psr-4": {
"apimatic\\jsonmapper\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"OSL-3.0"
],
"authors": [
{
"name": "Christian Weiske",
"email": "christian.weiske@netresearch.de",
"homepage": "http://www.netresearch.de/",
"role": "Developer"
},
{
"name": "Mehdi Jaffery",
"email": "mehdi.jaffery@apimatic.io",
"homepage": "http://apimatic.io/",
"role": "Developer"
}
],
"description": "Map nested JSON structures onto PHP classes",
"support": {
"email": "mehdi.jaffery@apimatic.io",
"issues": "https://github.com/apimatic/jsonmapper/issues",
"source": "https://github.com/apimatic/jsonmapper/tree/v2.0.3"
},
"time": "2021-07-16T09:02:23+00:00"
},
{
"name": "apimatic/unirest-php",
"version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/apimatic/unirest-php.git",
"reference": "b4e399a8970c3a5c611f734282f306381f9d1eee"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/apimatic/unirest-php/zipball/b4e399a8970c3a5c611f734282f306381f9d1eee",
"reference": "b4e399a8970c3a5c611f734282f306381f9d1eee",
"shasum": ""
},
"require": {
"ext-curl": "*",
"php": ">=5.6.0"
},
"require-dev": {
"phpunit/phpunit": "^5 || ^6 || ^7"
},
"suggest": {
"ext-json": "Allows using JSON Bodies for sending and parsing requests"
},
"type": "library",
"autoload": {
"psr-0": {
"Unirest\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mashape",
"email": "opensource@mashape.com",
"homepage": "https://www.mashape.com",
"role": "Developer"
},
{
"name": "APIMATIC",
"email": "opensource@apimatic.io",
"homepage": "https://www.apimatic.io",
"role": "Developer"
}
],
"description": "Unirest PHP",
"homepage": "https://github.com/apimatic/unirest-php",
"keywords": [
"client",
"curl",
"http",
"https",
"rest"
],
"support": {
"email": "opensource@apimatic.io",
"issues": "https://github.com/apimatic/unirest-php/issues",
"source": "https://github.com/apimatic/unirest-php/tree/2.0.0"
},
"time": "2020-04-07T17:16:29+00:00"
},
{
"name": "asm/php-ansible",
"version": "dev-main",
@ -209,16 +323,16 @@
},
{
"name": "aws/aws-sdk-php",
"version": "3.193.2",
"version": "3.194.1",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "d5080f7a1bb63569c19b8de06f787808eca70cc6"
"reference": "67bdee05acef9e8ad60098090996690b49babd09"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d5080f7a1bb63569c19b8de06f787808eca70cc6",
"reference": "d5080f7a1bb63569c19b8de06f787808eca70cc6",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/67bdee05acef9e8ad60098090996690b49babd09",
"reference": "67bdee05acef9e8ad60098090996690b49babd09",
"shasum": ""
},
"require": {
@ -294,9 +408,9 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.193.2"
"source": "https://github.com/aws/aws-sdk-php/tree/3.194.1"
},
"time": "2021-09-10T18:21:27+00:00"
"time": "2021-09-17T18:15:42+00:00"
},
{
"name": "bacon/bacon-qr-code",
@ -935,16 +1049,16 @@
},
{
"name": "composer/composer",
"version": "2.1.6",
"version": "2.1.8",
"source": {
"type": "git",
"url": "https://github.com/composer/composer.git",
"reference": "e5cac5f9d2354d08b67f1d21c664ae70d748c603"
"reference": "24d38e9686092de05214cafa187dc282a5d89497"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/composer/zipball/e5cac5f9d2354d08b67f1d21c664ae70d748c603",
"reference": "e5cac5f9d2354d08b67f1d21c664ae70d748c603",
"url": "https://api.github.com/repos/composer/composer/zipball/24d38e9686092de05214cafa187dc282a5d89497",
"reference": "24d38e9686092de05214cafa187dc282a5d89497",
"shasum": ""
},
"require": {
@ -1013,7 +1127,7 @@
"support": {
"irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/composer/issues",
"source": "https://github.com/composer/composer/tree/2.1.6"
"source": "https://github.com/composer/composer/tree/2.1.8"
},
"funding": [
{
@ -1029,7 +1143,7 @@
"type": "tidelift"
}
],
"time": "2021-08-19T15:11:08+00:00"
"time": "2021-09-15T11:55:15+00:00"
},
{
"name": "composer/metadata-minifier",
@ -1102,16 +1216,16 @@
},
{
"name": "composer/package-versions-deprecated",
"version": "1.11.99.3",
"version": "1.11.99.4",
"source": {
"type": "git",
"url": "https://github.com/composer/package-versions-deprecated.git",
"reference": "fff576ac850c045158a250e7e27666e146e78d18"
"reference": "b174585d1fe49ceed21928a945138948cb394600"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/fff576ac850c045158a250e7e27666e146e78d18",
"reference": "fff576ac850c045158a250e7e27666e146e78d18",
"url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b174585d1fe49ceed21928a945138948cb394600",
"reference": "b174585d1fe49ceed21928a945138948cb394600",
"shasum": ""
},
"require": {
@ -1155,7 +1269,7 @@
"description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)",
"support": {
"issues": "https://github.com/composer/package-versions-deprecated/issues",
"source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.3"
"source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.4"
},
"funding": [
{
@ -1171,7 +1285,7 @@
"type": "tidelift"
}
],
"time": "2021-08-17T13:49:14+00:00"
"time": "2021-09-13T08:41:34+00:00"
},
{
"name": "composer/semver",
@ -1620,16 +1734,16 @@
},
{
"name": "doctrine/dbal",
"version": "3.1.1",
"version": "3.1.2",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
"reference": "8e0fde2b90e3f61361013d1e928621beeea07bc0"
"reference": "3ee2622b57370c786f531678f6641208747f7bfc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/8e0fde2b90e3f61361013d1e928621beeea07bc0",
"reference": "8e0fde2b90e3f61361013d1e928621beeea07bc0",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/3ee2622b57370c786f531678f6641208747f7bfc",
"reference": "3ee2622b57370c786f531678f6641208747f7bfc",
"shasum": ""
},
"require": {
@ -1641,15 +1755,15 @@
},
"require-dev": {
"doctrine/coding-standard": "9.0.0",
"jetbrains/phpstorm-stubs": "2020.2",
"phpstan/phpstan": "0.12.81",
"phpstan/phpstan-strict-rules": "^0.12.2",
"jetbrains/phpstorm-stubs": "2021.1",
"phpstan/phpstan": "0.12.96",
"phpstan/phpstan-strict-rules": "^0.12.11",
"phpunit/phpunit": "9.5.5",
"psalm/plugin-phpunit": "0.13.0",
"psalm/plugin-phpunit": "0.16.1",
"squizlabs/php_codesniffer": "3.6.0",
"symfony/cache": "^5.2|^6.0",
"symfony/console": "^2.0.5|^3.0|^4.0|^5.0|^6.0",
"vimeo/psalm": "4.6.4"
"vimeo/psalm": "4.10.0"
},
"suggest": {
"symfony/console": "For helpful console commands such as SQL execution and import of files."
@ -1709,7 +1823,7 @@
],
"support": {
"issues": "https://github.com/doctrine/dbal/issues",
"source": "https://github.com/doctrine/dbal/tree/3.1.1"
"source": "https://github.com/doctrine/dbal/tree/3.1.2"
},
"funding": [
{
@ -1725,7 +1839,7 @@
"type": "tidelift"
}
],
"time": "2021-06-19T17:59:55+00:00"
"time": "2021-09-12T20:56:32+00:00"
},
{
"name": "doctrine/deprecations",
@ -2607,16 +2721,16 @@
},
{
"name": "google/apiclient-services",
"version": "v0.211.0",
"version": "v0.212.0",
"source": {
"type": "git",
"url": "https://github.com/googleapis/google-api-php-client-services.git",
"reference": "01b020d4d7f120a5ac3c12e1d7e3a6067bc93881"
"reference": "2c4bd512502ad9cdfec8ea711ea1592c79d345e5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/01b020d4d7f120a5ac3c12e1d7e3a6067bc93881",
"reference": "01b020d4d7f120a5ac3c12e1d7e3a6067bc93881",
"url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/2c4bd512502ad9cdfec8ea711ea1592c79d345e5",
"reference": "2c4bd512502ad9cdfec8ea711ea1592c79d345e5",
"shasum": ""
},
"require": {
@ -2645,9 +2759,9 @@
],
"support": {
"issues": "https://github.com/googleapis/google-api-php-client-services/issues",
"source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.211.0"
"source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.212.0"
},
"time": "2021-09-03T11:20:26+00:00"
"time": "2021-09-12T11:18:27+00:00"
},
{
"name": "google/auth",
@ -3057,6 +3171,57 @@
},
"time": "2021-04-26T09:17:50+00:00"
},
{
"name": "halaxa/json-machine",
"version": "0.7.0",
"source": {
"type": "git",
"url": "https://github.com/halaxa/json-machine.git",
"reference": "39ec702c434e72ddae25d3af14c1d0fe60b03ef6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/halaxa/json-machine/zipball/39ec702c434e72ddae25d3af14c1d0fe60b03ef6",
"reference": "39ec702c434e72ddae25d3af14c1d0fe60b03ef6",
"shasum": ""
},
"require": {
"php": ">=5.6"
},
"require-dev": {
"ext-json": "*",
"guzzlehttp/guzzle": "^6",
"phpunit/phpunit": "^5.7.27"
},
"suggest": {
"ext-json": "To run JSON Machine out of the box without custom decoders."
},
"type": "library",
"autoload": {
"psr-4": {
"JsonMachine\\": "src/"
},
"files": [
"src/functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Filip Halaxa",
"email": "filip@halaxa.cz"
}
],
"description": "Efficient, easy-to-use and fast JSON pull parser",
"support": {
"issues": "https://github.com/halaxa/json-machine/issues",
"source": "https://github.com/halaxa/json-machine/tree/0.7.0"
},
"time": "2021-05-06T11:47:56+00:00"
},
{
"name": "hashids/hashids",
"version": "4.1.0",
@ -3570,16 +3735,16 @@
},
{
"name": "laravel/framework",
"version": "v8.60.0",
"version": "v8.61.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "44f16a31a1d4ac8a51605550d7796e2273984a48"
"reference": "3d528d3d3c8ecb444b50a266c212a52973a6669b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/44f16a31a1d4ac8a51605550d7796e2273984a48",
"reference": "44f16a31a1d4ac8a51605550d7796e2273984a48",
"url": "https://api.github.com/repos/laravel/framework/zipball/3d528d3d3c8ecb444b50a266c212a52973a6669b",
"reference": "3d528d3d3c8ecb444b50a266c212a52973a6669b",
"shasum": ""
},
"require": {
@ -3616,7 +3781,8 @@
"tightenco/collect": "<5.5.33"
},
"provide": {
"psr/container-implementation": "1.0"
"psr/container-implementation": "1.0",
"psr/simple-cache-implementation": "1.0"
},
"replace": {
"illuminate/auth": "self.version",
@ -3734,7 +3900,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2021-09-08T13:37:21+00:00"
"time": "2021-09-14T13:31:32+00:00"
},
{
"name": "laravel/slack-notification-channel",
@ -4730,16 +4896,16 @@
},
{
"name": "livewire/livewire",
"version": "v2.5.5",
"version": "v2.6.5",
"source": {
"type": "git",
"url": "https://github.com/livewire/livewire.git",
"reference": "de192292d68276d831e5fd9824c80c3b78a21ddf"
"reference": "e39edcae6b1971b2d0f327a8e25c40e3d68cb7a0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/livewire/livewire/zipball/de192292d68276d831e5fd9824c80c3b78a21ddf",
"reference": "de192292d68276d831e5fd9824c80c3b78a21ddf",
"url": "https://api.github.com/repos/livewire/livewire/zipball/e39edcae6b1971b2d0f327a8e25c40e3d68cb7a0",
"reference": "e39edcae6b1971b2d0f327a8e25c40e3d68cb7a0",
"shasum": ""
},
"require": {
@ -4790,7 +4956,7 @@
"description": "A front-end framework for Laravel.",
"support": {
"issues": "https://github.com/livewire/livewire/issues",
"source": "https://github.com/livewire/livewire/tree/v2.5.5"
"source": "https://github.com/livewire/livewire/tree/v2.6.5"
},
"funding": [
{
@ -4798,7 +4964,7 @@
"type": "github"
}
],
"time": "2021-07-13T05:03:28+00:00"
"time": "2021-09-18T23:19:07+00:00"
},
{
"name": "maennchen/zipstream-php",
@ -5044,24 +5210,24 @@
},
{
"name": "monolog/monolog",
"version": "2.3.2",
"version": "2.3.4",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "71312564759a7db5b789296369c1a264efc43aad"
"reference": "437e7a1c50044b92773b361af77620efb76fff59"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/71312564759a7db5b789296369c1a264efc43aad",
"reference": "71312564759a7db5b789296369c1a264efc43aad",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/437e7a1c50044b92773b361af77620efb76fff59",
"reference": "437e7a1c50044b92773b361af77620efb76fff59",
"shasum": ""
},
"require": {
"php": ">=7.2",
"psr/log": "^1.0.1"
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"provide": {
"psr/log-implementation": "1.0.0"
"psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
@ -5076,7 +5242,7 @@
"phpunit/phpunit": "^8.5",
"predis/predis": "^1.1",
"rollbar/rollbar": "^1.3",
"ruflin/elastica": ">=0.90 <7.0.1",
"ruflin/elastica": ">=0.90@dev",
"swiftmailer/swiftmailer": "^5.3|^6.0"
},
"suggest": {
@ -5084,8 +5250,11 @@
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
"ext-mbstring": "Allow to work properly with unicode symbols",
"ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
"ext-openssl": "Required to send log messages using SSL",
"ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
@ -5124,7 +5293,7 @@
],
"support": {
"issues": "https://github.com/Seldaek/monolog/issues",
"source": "https://github.com/Seldaek/monolog/tree/2.3.2"
"source": "https://github.com/Seldaek/monolog/tree/2.3.4"
},
"funding": [
{
@ -5136,7 +5305,7 @@
"type": "tidelift"
}
],
"time": "2021-07-23T07:42:52+00:00"
"time": "2021-09-15T11:27:21+00:00"
},
{
"name": "mtdowling/jmespath.php",
@ -6180,16 +6349,16 @@
},
{
"name": "php-http/discovery",
"version": "1.14.0",
"version": "1.14.1",
"source": {
"type": "git",
"url": "https://github.com/php-http/discovery.git",
"reference": "778f722e29250c1fac0bbdef2c122fa5d038c9eb"
"reference": "de90ab2b41d7d61609f504e031339776bc8c7223"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/discovery/zipball/778f722e29250c1fac0bbdef2c122fa5d038c9eb",
"reference": "778f722e29250c1fac0bbdef2c122fa5d038c9eb",
"url": "https://api.github.com/repos/php-http/discovery/zipball/de90ab2b41d7d61609f504e031339776bc8c7223",
"reference": "de90ab2b41d7d61609f504e031339776bc8c7223",
"shasum": ""
},
"require": {
@ -6242,9 +6411,9 @@
],
"support": {
"issues": "https://github.com/php-http/discovery/issues",
"source": "https://github.com/php-http/discovery/tree/1.14.0"
"source": "https://github.com/php-http/discovery/tree/1.14.1"
},
"time": "2021-06-01T14:30:21+00:00"
"time": "2021-09-18T07:57:46+00:00"
},
{
"name": "php-http/guzzle7-adapter",
@ -8094,17 +8263,74 @@
"time": "2021-08-08T09:03:08+00:00"
},
{
"name": "stripe/stripe-php",
"version": "v7.95.0",
"name": "square/square",
"version": "13.0.0.20210721",
"source": {
"type": "git",
"url": "https://github.com/stripe/stripe-php.git",
"reference": "ed372a1f6430b06dda408bb33b27d2be35ceaf07"
"url": "https://github.com/square/square-php-sdk.git",
"reference": "03d90445854cd3b500f75061a9c63956799b8ecf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/stripe/stripe-php/zipball/ed372a1f6430b06dda408bb33b27d2be35ceaf07",
"reference": "ed372a1f6430b06dda408bb33b27d2be35ceaf07",
"url": "https://api.github.com/repos/square/square-php-sdk/zipball/03d90445854cd3b500f75061a9c63956799b8ecf",
"reference": "03d90445854cd3b500f75061a9c63956799b8ecf",
"shasum": ""
},
"require": {
"apimatic/jsonmapper": "^2.0.2",
"apimatic/unirest-php": "^2.0",
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"php": ">=7.2"
},
"require-dev": {
"phan/phan": "^3.0",
"phpunit/phpunit": "^7.5 || ^8.5",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"autoload": {
"psr-4": {
"Square\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Square Developer Platform",
"email": "developers@squareup.com",
"homepage": "https://squareup.com/developers"
}
],
"description": "Use Square APIs to manage and run business including payment, customer, product, inventory, and employee management.",
"homepage": "https://squareup.com/developers",
"keywords": [
"api",
"sdk",
"square"
],
"support": {
"issues": "https://github.com/square/square-php-sdk/issues",
"source": "https://github.com/square/square-php-sdk/tree/13.0.0.20210721"
},
"time": "2021-07-21T06:43:15+00:00"
},
{
"name": "stripe/stripe-php",
"version": "v7.97.0",
"source": {
"type": "git",
"url": "https://github.com/stripe/stripe-php.git",
"reference": "ae41c309ce113362706f8d5f19cf0cf2c730bc4a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/stripe/stripe-php/zipball/ae41c309ce113362706f8d5f19cf0cf2c730bc4a",
"reference": "ae41c309ce113362706f8d5f19cf0cf2c730bc4a",
"shasum": ""
},
"require": {
@ -8150,9 +8376,9 @@
],
"support": {
"issues": "https://github.com/stripe/stripe-php/issues",
"source": "https://github.com/stripe/stripe-php/tree/v7.95.0"
"source": "https://github.com/stripe/stripe-php/tree/v7.97.0"
},
"time": "2021-09-02T02:30:40+00:00"
"time": "2021-09-16T21:28:33+00:00"
},
{
"name": "swiftmailer/swiftmailer",
@ -12219,16 +12445,16 @@
},
{
"name": "facade/flare-client-php",
"version": "1.8.1",
"version": "1.9.1",
"source": {
"type": "git",
"url": "https://github.com/facade/flare-client-php.git",
"reference": "47b639dc02bcfdfc4ebb83de703856fa01e35f5f"
"reference": "b2adf1512755637d0cef4f7d1b54301325ac78ed"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facade/flare-client-php/zipball/47b639dc02bcfdfc4ebb83de703856fa01e35f5f",
"reference": "47b639dc02bcfdfc4ebb83de703856fa01e35f5f",
"url": "https://api.github.com/repos/facade/flare-client-php/zipball/b2adf1512755637d0cef4f7d1b54301325ac78ed",
"reference": "b2adf1512755637d0cef4f7d1b54301325ac78ed",
"shasum": ""
},
"require": {
@ -12272,7 +12498,7 @@
],
"support": {
"issues": "https://github.com/facade/flare-client-php/issues",
"source": "https://github.com/facade/flare-client-php/tree/1.8.1"
"source": "https://github.com/facade/flare-client-php/tree/1.9.1"
},
"funding": [
{
@ -12280,26 +12506,26 @@
"type": "github"
}
],
"time": "2021-05-31T19:23:29+00:00"
"time": "2021-09-13T12:16:46+00:00"
},
{
"name": "facade/ignition",
"version": "2.12.1",
"version": "2.13.1",
"source": {
"type": "git",
"url": "https://github.com/facade/ignition.git",
"reference": "567b0a4ab04367603e61729b0ca133fb7b4819db"
"reference": "e3f49bef7b4165fa4b8a9dc579e7b63fa06aef78"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facade/ignition/zipball/567b0a4ab04367603e61729b0ca133fb7b4819db",
"reference": "567b0a4ab04367603e61729b0ca133fb7b4819db",
"url": "https://api.github.com/repos/facade/ignition/zipball/e3f49bef7b4165fa4b8a9dc579e7b63fa06aef78",
"reference": "e3f49bef7b4165fa4b8a9dc579e7b63fa06aef78",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-mbstring": "*",
"facade/flare-client-php": "^1.6",
"facade/flare-client-php": "^1.9.1",
"facade/ignition-contracts": "^1.0.2",
"illuminate/support": "^7.0|^8.0",
"monolog/monolog": "^2.0",
@ -12356,7 +12582,7 @@
"issues": "https://github.com/facade/ignition/issues",
"source": "https://github.com/facade/ignition"
},
"time": "2021-09-10T07:19:07+00:00"
"time": "2021-09-13T13:01:30+00:00"
},
{
"name": "facade/ignition-contracts",
@ -12883,16 +13109,16 @@
},
{
"name": "mockery/mockery",
"version": "1.4.3",
"version": "1.4.4",
"source": {
"type": "git",
"url": "https://github.com/mockery/mockery.git",
"reference": "d1339f64479af1bee0e82a0413813fe5345a54ea"
"reference": "e01123a0e847d52d186c5eb4b9bf58b0c6d00346"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mockery/mockery/zipball/d1339f64479af1bee0e82a0413813fe5345a54ea",
"reference": "d1339f64479af1bee0e82a0413813fe5345a54ea",
"url": "https://api.github.com/repos/mockery/mockery/zipball/e01123a0e847d52d186c5eb4b9bf58b0c6d00346",
"reference": "e01123a0e847d52d186c5eb4b9bf58b0c6d00346",
"shasum": ""
},
"require": {
@ -12949,9 +13175,9 @@
],
"support": {
"issues": "https://github.com/mockery/mockery/issues",
"source": "https://github.com/mockery/mockery/tree/1.4.3"
"source": "https://github.com/mockery/mockery/tree/1.4.4"
},
"time": "2021-02-24T09:51:49+00:00"
"time": "2021-09-13T15:28:59+00:00"
},
{
"name": "myclabs/deep-copy",
@ -13546,16 +13772,16 @@
},
{
"name": "phpdocumentor/type-resolver",
"version": "1.4.0",
"version": "1.5.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0"
"reference": "30f38bffc6f24293dadd1823936372dfa9e86e2f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
"reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/30f38bffc6f24293dadd1823936372dfa9e86e2f",
"reference": "30f38bffc6f24293dadd1823936372dfa9e86e2f",
"shasum": ""
},
"require": {
@ -13563,7 +13789,8 @@
"phpdocumentor/reflection-common": "^2.0"
},
"require-dev": {
"ext-tokenizer": "*"
"ext-tokenizer": "*",
"psalm/phar": "^4.8"
},
"type": "library",
"extra": {
@ -13589,9 +13816,9 @@
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0"
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.0"
},
"time": "2020-09-17T18:55:26+00:00"
"time": "2021-09-17T15:28:14+00:00"
},
{
"name": "phpspec/prophecy",
@ -13662,23 +13889,23 @@
},
{
"name": "phpunit/php-code-coverage",
"version": "9.2.6",
"version": "9.2.7",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "f6293e1b30a2354e8428e004689671b83871edde"
"reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde",
"reference": "f6293e1b30a2354e8428e004689671b83871edde",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d4c798ed8d51506800b441f7a13ecb0f76f12218",
"reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^4.10.2",
"nikic/php-parser": "^4.12.0",
"php": ">=7.3",
"phpunit/php-file-iterator": "^3.0.3",
"phpunit/php-text-template": "^2.0.2",
@ -13727,7 +13954,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.7"
},
"funding": [
{
@ -13735,7 +13962,7 @@
"type": "github"
}
],
"time": "2021-03-28T07:26:59+00:00"
"time": "2021-09-17T05:39:03+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -14934,7 +15161,6 @@
"type": "github"
}
],
"abandoned": true,
"time": "2020-09-28T06:45:17+00:00"
},
{
@ -15048,16 +15274,16 @@
},
{
"name": "swagger-api/swagger-ui",
"version": "v3.52.1",
"version": "v3.52.2",
"source": {
"type": "git",
"url": "https://github.com/swagger-api/swagger-ui.git",
"reference": "06a4cdb4274e1b5d754897e7c1e6aca78da119ea"
"reference": "e5611d72ff6b4affb373fa8859cc5feb6981f367"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/06a4cdb4274e1b5d754897e7c1e6aca78da119ea",
"reference": "06a4cdb4274e1b5d754897e7c1e6aca78da119ea",
"url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/e5611d72ff6b4affb373fa8859cc5feb6981f367",
"reference": "e5611d72ff6b4affb373fa8859cc5feb6981f367",
"shasum": ""
},
"type": "library",
@ -15103,9 +15329,9 @@
],
"support": {
"issues": "https://github.com/swagger-api/swagger-ui/issues",
"source": "https://github.com/swagger-api/swagger-ui/tree/v3.52.1"
"source": "https://github.com/swagger-api/swagger-ui/tree/v3.52.2"
},
"time": "2021-09-10T12:04:46+00:00"
"time": "2021-09-13T12:46:28+00:00"
},
{
"name": "symfony/debug",
@ -15675,5 +15901,5 @@
"platform-dev": {
"php": "^7.3|^7.4|^8.0"
},
"plugin-api-version": "2.1.0"
"plugin-api-version": "2.0.0"
}

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.3.15',
'app_tag' => '5.3.15',
'app_version' => '5.3.16',
'app_tag' => '5.3.16',
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),
@ -91,6 +91,7 @@ return [
'decrypted' => env('PAYTRACE_KEYS', ''),
],
'mollie' => env('MOLLIE_KEYS', ''),
'square' => env('SQUARE_KEYS',''),
],
'contact' => [
'email' => env('MAIL_FROM_ADDRESS'),

View File

@ -0,0 +1,49 @@
<?php
use App\Models\Gateway;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Eloquent\Model;
class SquarePaymentDriver extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Model::unguard();
$fields = new \stdClass;
$fields->accessToken = "";
$fields->applicationId = "";
$fields->locationId = "";
$fields->testMode = false;
$square = new Gateway();
$square->id = 57;
$square->name = "Square";
$square->provider = "Square";
$square->key = '65faab2ab6e3223dbe848b1686490baz';
$square->sort_order = 4343;
$square->is_offsite = false;
$square->visible = true;
$square->fields = json_encode($fields);
$square->save();
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -80,6 +80,7 @@ class PaymentLibrariesSeeder extends Seeder
['id' => 53, 'name' => 'PagSeguro', 'provider' => 'PagSeguro', 'key' => 'ef498756b54db63c143af0ec433da803', 'fields' => '{"email":"","token":"","sandbox":false}'],
['id' => 54, 'name' => 'PAYMILL', 'provider' => 'Paymill', 'key' => 'ca52f618a39367a4c944098ebf977e1c', 'fields' => '{"apiKey":""}'],
['id' => 55, 'name' => 'Custom', 'provider' => 'Custom', 'is_offsite' => true, 'sort_order' => 21, 'key' => '54faab2ab6e3223dbe848b1686490baa', 'fields' => '{"name":"","text":""}'],
['id' => 57, 'name' => 'Square', 'provider' => 'Square', 'is_offsite' => false, 'sort_order' => 21, 'key' => '65faab2ab6e3223dbe848b1686490baz', 'fields' => '{"accessToken":"","applicationId":"","locationId":"","testMode":"false"}'],
];
foreach ($gateways as $gateway) {
@ -96,7 +97,7 @@ class PaymentLibrariesSeeder extends Seeder
Gateway::query()->update(['visible' => 0]);
Gateway::whereIn('id', [1,3,7,15,20,39,46,55,50])->update(['visible' => 1]);
Gateway::whereIn('id', [1,7,15,20,39,46,55,50,57])->update(['visible' => 1]);
if (Ninja::isHosted()) {
Gateway::whereIn('id', [20])->update(['visible' => 0]);

View File

@ -4,7 +4,7 @@ const TEMP = 'flutter-temp-cache';
const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = {
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
"/": "e322964422ac408e5ee56c9e7c6032ca",
"/": "fd698ff0c4251992cffae325bd810e94",
"assets/NOTICES": "9eb7e2eb2888ea5bae5f536720db37cd",
"assets/assets/images/logo_light.png": "e5f46d5a78e226e7a9553d4ca6f69219",
"assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024",
@ -34,7 +34,7 @@ const RESOURCES = {
"favicon.ico": "51636d3a390451561744c42188ccd628",
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
"main.dart.js": "1d43e99a13f32cfa5c34493b577084e0"
"main.dart.js": "8fc88d4b5a3256d5f9ff5c0f1bdeb92b"
};
// The application shell files that are downloaded before a service worker can

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,9 @@
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/

View File

@ -1,2 +1,2 @@
/*! For license information please see stripe-sofort.js.LICENSE.txt */
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=6)}({6:function(e,t,n){e.exports=n("RFiP")},RFiP:function(e,t){var n,r,o,u;function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var c=null!==(n=null===(r=document.querySelector('meta[name="stripe-publishable-key"]'))||void 0===r?void 0:r.content)&&void 0!==n?n:"",l=null!==(o=null===(u=document.querySelector('meta[name="stripe-account-id"]'))||void 0===u?void 0:u.content)&&void 0!==o?o:"";new function e(t,n){var r=this;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),i(this,"setupStripe",(function(){return r.stripe=Stripe(r.key),r.stripeConnect&&(r.stripe.stripeAccount=l),r})),i(this,"handle",(function(){document.getElementById("pay-now").addEventListener("click",(function(e){document.getElementById("pay-now").disabled=!0,document.querySelector("#pay-now > svg").classList.remove("hidden"),document.querySelector("#pay-now > span").classList.add("hidden"),r.stripe.confirmSofortPayment(document.querySelector("meta[name=pi-client-secret").content,{payment_method:{sofort:{country:document.querySelector('meta[name="country"]').content}},return_url:document.querySelector('meta[name="return-url"]').content})}))})),this.key=t,this.errors=document.getElementById("errors"),this.stripeConnect=n}(c,l).setupStripe().handle()}});
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=6)}({6:function(e,t,n){e.exports=n("RFiP")},RFiP:function(e,t){var n,r,o,u;function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var c=null!==(n=null===(r=document.querySelector('meta[name="stripe-publishable-key"]'))||void 0===r?void 0:r.content)&&void 0!==n?n:"",l=null!==(o=null===(u=document.querySelector('meta[name="stripe-account-id"]'))||void 0===u?void 0:u.content)&&void 0!==o?o:"";new function e(t,n){var r=this;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),i(this,"setupStripe",(function(){return r.stripe=Stripe(r.key),r.stripeConnect&&(r.stripe.stripeAccount=l),r})),i(this,"handle",(function(){document.getElementById("pay-now").addEventListener("click",(function(e){document.getElementById("pay-now").disabled=!0,document.querySelector("#pay-now > svg").classList.remove("hidden"),document.querySelector("#pay-now > span").classList.add("hidden"),r.stripe.confirmSofortPayment(document.querySelector("meta[name=pi-client-secret").content,{payment_method:{sofort:{country:document.querySelector('meta[name="country"]').content}},return_url:document.querySelector('meta[name="return-url"]').content})}))})),this.key=t,this.errors=document.getElementById("errors"),this.stripeConnect=n}(c,l).setupStripe().handle()}});

45
public/main.dart.js vendored
View File

@ -39398,7 +39398,7 @@ while(true)switch(s){case 0:p=a.a
a.a=p==null?null:p
if(a.db==null){q=a.r1.aW4(a.k4)
a.db=q}p=a.cy
a.cy=p==null?"79311a08c5194e17c3df206d4a87f28b858c49c3":p
a.cy=p==null?"0f9789ea89d0fc5f4867c5351193f2423d03e64e":p
p=a.go
a.go=p==null?null:p
return P.Q(null,r)}})
@ -155327,7 +155327,7 @@ return s.a=J.f0(s.a,"\n \u2022 "+H.j(a))},
$S:9}
F.dh2.prototype={
$1:function(a){a.a="https://634363c8dd6048b8ae89ab6c66dd9c24@sentry.invoicing.co/7"
a.cy="79311a08c5194e17c3df206d4a87f28b858c49c3"
a.cy="0f9789ea89d0fc5f4867c5351193f2423d03e64e"
a.go="5.0.59"
a.ch=new F.dh1(this.a)},
$S:743}
@ -161999,8 +161999,8 @@ $S:4}
R.cKr.prototype={
$0:function(){var s="/dashboard",r=this.a,q=r.gv(),p=q.y,o=q.x.a
if(J.a(p.a,o).gdY()||q.f.gdY())J.a(r.gj(),0).$1(new M.cj(null,!1,!1,!1))
this.b.$1(this.c)
J.a(r.gj(),0).$1(new Q.b7(s))
this.b.$1(this.c)
if(r.gv().r.a===C.t)$.bi().gag().i3(s,new R.cKq(),t._)},
$S:1}
R.cKq.prototype={
@ -174922,16 +174922,15 @@ s=$.bi()
s.toString
s=$.am.P$.Q.h(0,s)
b.toString
M.Lw(new R.d2s(c,b,a),s,!1,a)},
M.Lw(new R.d2s(a,c,b),s,!1,a)},
$C:"$3",
$R:3,
$S:4}
R.d2s.prototype={
$0:function(){var s,r="/reports"
this.a.$1(this.b)
s=this.c
J.a(s.gj(),0).$1(new Q.b7(r))
if(s.gv().r.a===C.t)$.bi().gag().i3(r,new R.d2r(),t._)},
$0:function(){var s="/reports",r=this.a
J.a(r.gj(),0).$1(new Q.b7(s))
this.b.$1(this.c)
if(r.gv().r.a===C.t)$.bi().gag().i3(s,new R.d2r(),t._)},
$S:1}
R.d2r.prototype={
$1:function(a){return!1},
@ -175128,24 +175127,24 @@ t.nX.a(b)
s=a.gv().x
r=$.bi()
r.toString
M.Lw(new D.d2v(b,s,c,a),$.am.P$.Q.h(0,r),b.e,a)},
M.Lw(new D.d2v(b,s,a,c),$.am.P$.Q.h(0,r),b.e,a)},
$C:"$3",
$R:3,
$S:4}
D.d2v.prototype={
$0:function(){var s,r,q,p=this,o=p.a,n=o.f,m=n==null
if(!m)s="/settings"+("/"+n)
else{n=p.b
s=n.gvP()==="settings"?"/settings/company_details":"/settings"+("/"+n.L.ch)}p.c.$1(o)
o=p.d
n=o.gv()
r=n.y
q=n.x.a
if(J.a(r.a,q).gdY()||n.f.gdY())J.a(o.gj(),0).$1(new M.cj(null,!1,!1,!1))
J.a(o.gj(),0).$1(new Q.b7(s))
if(o.gv().r.a===C.t){o=t._
if(m)$.bi().gag().i3("/settings",new D.d2u(),o)
else $.bi().gag().ed(s,o)}},
$0:function(){var s,r,q,p,o=this,n=o.a,m=n.f,l=m==null
if(!l)s="/settings"+("/"+m)
else{m=o.b
s=m.gvP()==="settings"?"/settings/company_details":"/settings"+("/"+m.L.ch)}m=o.c
r=m.gv()
q=r.y
p=r.x.a
if(J.a(q.a,p).gdY()||r.f.gdY())J.a(m.gj(),0).$1(new M.cj(null,!1,!1,!1))
J.a(m.gj(),0).$1(new Q.b7(s))
o.d.$1(n)
if(m.gv().r.a===C.t){n=t._
if(l)$.bi().gag().i3("/settings",new D.d2u(),n)
else $.bi().gag().ed(s,n)}},
$S:1}
D.d2u.prototype={
$1:function(a){return!1},

File diff suppressed because one or more lines are too long

2452
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

2336
public/main.last.dart.js vendored

File diff suppressed because one or more lines are too long

1328
public/main.next.dart.js vendored

File diff suppressed because one or more lines are too long

View File

@ -19389,8 +19389,8 @@
var _ = this;
_.action = t0;
_.uiState = t1;
_.next = t2;
_.store = t3;
_.store = t2;
_.next = t3;
},
_viewSettings___closure: function _viewSettings___closure() {
},
@ -22825,9 +22825,9 @@
_viewReports_closure: function _viewReports_closure() {
},
_viewReports__closure: function _viewReports__closure(t0, t1, t2) {
this.next = t0;
this.action = t1;
this.store = t2;
this.store = t0;
this.next = t1;
this.action = t2;
},
_viewReports___closure: function _viewReports___closure() {
},
@ -309564,8 +309564,8 @@
t4 = t2.uiState.selectedCompanyIndex;
if (J.$index$asx(t3._list, t4).get$isStale() || t2.staticState.get$isStale())
J.$index$asx(t1.get$_dispatchers(), 0).call$1(new M.RefreshData(null, false, false, false));
this.next.call$1(this.action);
J.$index$asx(t1.get$_dispatchers(), 0).call$1(new Q.UpdateCurrentRoute(_s10_));
this.next.call$1(this.action);
if (t1.get$_store$_state().prefState.appLayout === C.AppLayout_mobile)
$.$get$navigatorKey().get$currentState().pushNamedAndRemoveUntil$1$2(_s10_, new R._createViewDashboard___closure(), type$.legacy_Object);
},
@ -332029,7 +332029,7 @@
t1.toString;
t1 = $.WidgetsBinding__instance.WidgetsBinding__buildOwner._globalKeyRegistry.$index(0, t1);
dynamicAction.toString;
M.checkForChanges(new R._viewReports__closure(next, dynamicAction, store), t1, false, store);
M.checkForChanges(new R._viewReports__closure(store, next, dynamicAction), t1, false, store);
},
"call*": "call$3",
$requiredArgCount: 3,
@ -332037,11 +332037,10 @@
};
R._viewReports__closure.prototype = {
call$0: function() {
var t1,
_s8_ = "/reports";
this.next.call$1(this.action);
t1 = this.store;
var _s8_ = "/reports",
t1 = this.store;
J.$index$asx(t1.get$_dispatchers(), 0).call$1(new Q.UpdateCurrentRoute(_s8_));
this.next.call$1(this.action);
if (t1.get$_store$_state().prefState.appLayout === C.AppLayout_mobile)
$.$get$navigatorKey().get$currentState().pushNamedAndRemoveUntil$1$2(_s8_, new R._viewReports___closure(), type$.legacy_Object);
},
@ -332382,7 +332381,7 @@
uiState = store.get$_store$_state().uiState;
t1 = $.$get$navigatorKey();
t1.toString;
M.checkForChanges(new D._viewSettings__closure(dynamicAction, uiState, next, store), $.WidgetsBinding__instance.WidgetsBinding__buildOwner._globalKeyRegistry.$index(0, t1), dynamicAction.force, store);
M.checkForChanges(new D._viewSettings__closure(dynamicAction, uiState, store, next), $.WidgetsBinding__instance.WidgetsBinding__buildOwner._globalKeyRegistry.$index(0, t1), dynamicAction.force, store);
},
"call*": "call$3",
$requiredArgCount: 3,
@ -332390,7 +332389,7 @@
};
D._viewSettings__closure.prototype = {
call$0: function() {
var route, t4, t5, _this = this,
var route, t4, t5, t6, _this = this,
t1 = _this.action,
t2 = t1.section,
t3 = t2 == null;
@ -332400,15 +332399,15 @@
t2 = _this.uiState;
route = t2.get$mainRoute() === "settings" ? "/settings/company_details" : "/settings" + ("/" + t2.settingsUIState.section);
}
t2 = _this.store;
t4 = t2.get$_store$_state();
t5 = t4.userCompanyStates;
t6 = t4.uiState.selectedCompanyIndex;
if (J.$index$asx(t5._list, t6).get$isStale() || t4.staticState.get$isStale())
J.$index$asx(t2.get$_dispatchers(), 0).call$1(new M.RefreshData(null, false, false, false));
J.$index$asx(t2.get$_dispatchers(), 0).call$1(new Q.UpdateCurrentRoute(route));
_this.next.call$1(t1);
t1 = _this.store;
t2 = t1.get$_store$_state();
t4 = t2.userCompanyStates;
t5 = t2.uiState.selectedCompanyIndex;
if (J.$index$asx(t4._list, t5).get$isStale() || t2.staticState.get$isStale())
J.$index$asx(t1.get$_dispatchers(), 0).call$1(new M.RefreshData(null, false, false, false));
J.$index$asx(t1.get$_dispatchers(), 0).call$1(new Q.UpdateCurrentRoute(route));
if (t1.get$_store$_state().prefState.appLayout === C.AppLayout_mobile) {
if (t2.get$_store$_state().prefState.appLayout === C.AppLayout_mobile) {
t1 = type$.legacy_Object;
if (t3)
$.$get$navigatorKey().get$currentState().pushNamedAndRemoveUntil$1$2("/settings", new D._viewSettings___closure(), t1);

View File

@ -157802,8 +157802,8 @@ $S:4}
R.cK5.prototype={
$0:function(){var s="/dashboard",r=this.a,q=r.gv(),p=q.y,o=q.x.a
if(J.a(p.a,o).gdU()||q.f.gdU())J.a(r.gj(),0).$1(new M.cj(null,!1,!1,!1))
this.b.$1(this.c)
J.a(r.gj(),0).$1(new Q.b7(s))
this.b.$1(this.c)
if(r.gv().r.a===C.t)$.bi().gag().i0(s,new R.cK4(),t._)},
$S:1}
R.cK4.prototype={
@ -170725,16 +170725,15 @@ s=$.bi()
s.toString
s=$.am.P$.Q.h(0,s)
b.toString
M.Ly(new R.d26(c,b,a),s,!1,a)},
M.Ly(new R.d26(a,c,b),s,!1,a)},
$C:"$3",
$R:3,
$S:4}
R.d26.prototype={
$0:function(){var s,r="/reports"
this.a.$1(this.b)
s=this.c
J.a(s.gj(),0).$1(new Q.b7(r))
if(s.gv().r.a===C.t)$.bi().gag().i0(r,new R.d25(),t._)},
$0:function(){var s="/reports",r=this.a
J.a(r.gj(),0).$1(new Q.b7(s))
this.b.$1(this.c)
if(r.gv().r.a===C.t)$.bi().gag().i0(s,new R.d25(),t._)},
$S:1}
R.d25.prototype={
$1:function(a){return!1},
@ -170931,24 +170930,24 @@ t.nX.a(b)
s=a.gv().x
r=$.bi()
r.toString
M.Ly(new D.d29(b,s,c,a),$.am.P$.Q.h(0,r),b.e,a)},
M.Ly(new D.d29(b,s,a,c),$.am.P$.Q.h(0,r),b.e,a)},
$C:"$3",
$R:3,
$S:4}
D.d29.prototype={
$0:function(){var s,r,q,p=this,o=p.a,n=o.f,m=n==null
if(!m)s="/settings"+("/"+n)
else{n=p.b
s=n.gvA()==="settings"?"/settings/company_details":"/settings"+("/"+n.L.ch)}p.c.$1(o)
o=p.d
n=o.gv()
r=n.y
q=n.x.a
if(J.a(r.a,q).gdU()||n.f.gdU())J.a(o.gj(),0).$1(new M.cj(null,!1,!1,!1))
J.a(o.gj(),0).$1(new Q.b7(s))
if(o.gv().r.a===C.t){o=t._
if(m)$.bi().gag().i0("/settings",new D.d28(),o)
else $.bi().gag().ea(s,o)}},
$0:function(){var s,r,q,p,o=this,n=o.a,m=n.f,l=m==null
if(!l)s="/settings"+("/"+m)
else{m=o.b
s=m.gvA()==="settings"?"/settings/company_details":"/settings"+("/"+m.L.ch)}m=o.c
r=m.gv()
q=r.y
p=r.x.a
if(J.a(q.a,p).gdU()||r.f.gdU())J.a(m.gj(),0).$1(new M.cj(null,!1,!1,!1))
J.a(m.gj(),0).$1(new Q.b7(s))
o.d.$1(n)
if(m.gv().r.a===C.t){n=t._
if(l)$.bi().gag().i0("/settings",new D.d28(),n)
else $.bi().gag().ea(s,n)}},
$S:1}
D.d28.prototype={
$1:function(a){return!1},

View File

@ -15,6 +15,7 @@
"/js/clients/payments/eway-credit-card.js": "/js/clients/payments/eway-credit-card.js?id=08ea84e9451abd434cff",
"/js/clients/payments/mollie-credit-card.js": "/js/clients/payments/mollie-credit-card.js?id=73b66e88e2daabcd6549",
"/js/clients/payments/paytrace-credit-card.js": "/js/clients/payments/paytrace-credit-card.js?id=c2b5f7831e1a46dd5fb2",
"/js/clients/payments/square-credit-card.js": "/js/clients/payments/square-credit-card.js?id=994c79534ee0a7391f69",
"/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=81c2623fc1e5769b51c7",
"/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=665ddf663500767f1a17",
"/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=a30464874dee84678344",

View File

@ -0,0 +1,134 @@
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
class SquareCreditCard {
constructor() {
this.appId = document.querySelector('meta[name=square-appId]').content;
this.locationId = document.querySelector(
'meta[name=square-locationId]'
).content;
this.isLoaded = false;
}
async init() {
this.payments = Square.payments(this.appId, this.locationId);
this.card = await this.payments.card();
await this.card.attach('#card-container');
this.isLoaded = true;
let iframeContainer = document.querySelector(
'.sq-card-iframe-container'
);
if (iframeContainer) {
iframeContainer.setAttribute('style', '150px !important');
}
let toggleWithToken = document.querySelector(
'.toggle-payment-with-token'
);
if (toggleWithToken) {
document.getElementById('card-container').classList.add('hidden');
}
}
async completePaymentWithoutToken(e) {
document.getElementById('errors').hidden = true;
e.target.parentElement.disabled = true;
let result = await this.card.tokenize();
if (result.status === 'OK') {
document.getElementById('sourceId').value = result.token;
let tokenBillingCheckbox = document.querySelector(
'input[name="token-billing-checkbox"]:checked'
);
if (tokenBillingCheckbox) {
document.querySelector('input[name="store_card"]').value =
tokenBillingCheckbox.value;
}
return document.getElementById('server_response').submit();
}
document.getElementById('errors').textContent =
result.errors[0].message;
document.getElementById('errors').hidden = false;
e.target.parentElement.disabled = false;
}
async completePaymentUsingToken(e) {
e.target.parentElement.disabled = true;
return document.getElementById('server_response').submit();
}
async handle() {
await this.init();
document
.getElementById('authorize-card')
?.addEventListener('click', (e) =>
this.completePaymentWithoutToken(e)
);
document.getElementById('pay-now')?.addEventListener('click', (e) => {
let tokenInput = document.querySelector('input[name=token]');
if (tokenInput.value) {
return this.completePaymentUsingToken(e);
}
return this.completePaymentWithoutToken(e);
});
Array.from(
document.getElementsByClassName('toggle-payment-with-token')
).forEach((element) =>
element.addEventListener('click', (element) => {
document
.getElementById('card-container')
.classList.add('hidden');
document.getElementById('save-card--container').style.display =
'none';
document.querySelector('input[name=token]').value =
element.target.dataset.token;
})
);
document
.getElementById('toggle-payment-with-credit-card')
?.addEventListener('click', async (element) => {
document
.getElementById('card-container')
.classList.remove('hidden');
document.getElementById('save-card--container').style.display =
'grid';
document.querySelector('input[name=token]').value = '';
});
let toggleWithToken = document.querySelector(
'.toggle-payment-with-token'
);
if (!toggleWithToken) {
document.getElementById('toggle-payment-with-credit-card')?.click();
}
}
}
new SquareCreditCard().handle();

View File

@ -3,7 +3,7 @@
@endphp
@push('head')
<meta name="pdf-url" content="{{ $entity->pdf_file_path(null, 'url', true) }}">
<meta name="pdf-url" content="{{ $url ?? $entity->pdf_file_path(null, 'url', true) }}">
<script src="{{ asset('js/vendor/pdf.js/pdf.min.js') }}"></script>
@endpush
@ -72,7 +72,7 @@
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="?mode=fullscreen"
<a target="_blank" href="{{ $fullscreen_url ?? '?mode=fullscreen' }}"
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>
@ -86,7 +86,7 @@
<canvas id="pdf-placeholder" class="shadow rounded-lg bg-white mt-4 p-4"></canvas>
</div>
@else
<iframe src="{{ $entity->pdf_file_path(null, 'url', true) }}" class="h-screen w-full border-0 mt-4"></iframe>
<iframe src="{{ $url ?? $entity->pdf_file_path(null, 'url', true) }}" class="h-screen w-full border-0 mt-4"></iframe>
@endif

View File

@ -0,0 +1,39 @@
<div>
<div class="flex flex-col md:flex-row md:justify-between">
<div class="flex flex-col md:flex-row md:items-center">
{{-- <label for="status" class="flex items-center mr-4">
<span class="mr-2">{{ ctrans('texts.status') }}</span>
<select class="input">
<option value="all">{{ ctrans('texts.all') }}</option>
<option value="unpaid">{{ ctrans('texts.unpaid') }}</option>
<option value="paid">{{ ctrans('texts.paid') }}</option>
</select>
</label> <!-- End status dropdown --> --}}
<div class="flex">
<label for="from" class="block w-full flex items-center mr-4">
<span class="mr-2">{{ ctrans('texts.from') }}:</span>
<input wire:model="options.start_date" type="date" class="input w-full">
</label>
<label for="to" class="block w-full flex items-center mr-4">
<span class="mr-2">{{ ctrans('texts.to') }}:</span>
<input wire:model="options.end_date" type="date" class="input w-full">
</label>
</div> <!-- End date range -->
<label for="show_payments" class="block flex items-center mr-4 mt-4 md:mt-0">
<input wire:model="options.show_payments_table" type="checkbox" class="form-checkbox" autocomplete="off">
<span class="ml-2">{{ ctrans('texts.show_payments') }}</span>
</label> <!-- End show payments checkbox -->
<label for="show_aging" class="block flex items-center">
<input wire:model="options.show_aging_table" type="checkbox" class="form-checkbox" autocomplete="off">
<span class="ml-2">{{ ctrans('texts.show_aging') }}</span>
</label> <!-- End show aging checkbox -->
</div>
<button wire:click="download" class="button button-primary bg-primary mt-4 md:mt-0">{{ ctrans('texts.download') }}</button>
</div>
@include('portal.ninja2020.components.pdf-viewer', ['url' => $url])
</div>

View File

@ -0,0 +1,37 @@
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_credit_card'), 'card_title'
=> ctrans('texts.payment_type_credit_card')])
@section('gateway_head')
<meta name="square-appId" content="{{ $gateway->company_gateway->getConfigField('applicationId') }}">
<meta name="square-locationId" content="{{ $gateway->company_gateway->getConfigField('locationId') }}">
<meta name="square-authorize" content="true">
@endsection
@section('gateway_content')
<form action="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}"
method="post" id="server_response">
@csrf
<input type="text" name="sourceId" id="sourceId" hidden>
</form>
<div class="alert alert-failure mb-4" hidden id="errors"></div>
@component('portal.ninja2020.components.general.card-element-single')
<div id="card-container"></div>
<div id="payment-status-container"></div>
@endcomponent
@component('portal.ninja2020.gateways.includes.pay_now', ['id' => 'authorize-card'])
{{ ctrans('texts.add_payment_method') }}
@endcomponent
@endsection
@section('gateway_footer')
@if ($gateway->company_gateway->getConfigField('testMode'))
<script type="text/javascript" src="https://sandbox.web.squarecdn.com/v1/square.js"></script>
@else
<script type="text/javascript" src="https://web.squarecdn.com/v1/square.js"></script>
@endif
<script src="{{ asset('js/clients/payments/square-credit-card.js') }}"></script>
@endsection

View File

@ -0,0 +1,67 @@
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_credit_card'), 'card_title'
=> ctrans('texts.payment_type_credit_card')])
@section('gateway_head')
<meta name="square-appId" content="{{ $gateway->company_gateway->getConfigField('applicationId') }}">
<meta name="square-locationId" content="{{ $gateway->company_gateway->getConfigField('locationId') }}">
@endsection
@section('gateway_content')
<form action="{{ route('client.payments.response') }}" method="post" id="server_response">
@csrf
<input type="hidden" name="store_card">
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
<input type="hidden" name="company_gateway_id" value="{{ $gateway->getCompanyGatewayId() }}">
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}">
<input type="hidden" name="token">
<input type="hidden" name="sourceId" id="sourceId">
</form>
<div class="alert alert-failure mb-4" hidden id="errors"></div>
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.payment_type')])
{{ ctrans('texts.credit_card') }}
@endcomponent
@include('portal.ninja2020.gateways.includes.payment_details')
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')])
@if (count($tokens) > 0)
@foreach ($tokens as $token)
<label class="mr-4">
<input type="radio" data-token="{{ $token->token }}" name="payment-type"
class="form-radio cursor-pointer toggle-payment-with-token" />
<span class="ml-1 cursor-pointer">**** {{ optional($token->meta)->last4 }}</span>
</label>
@endforeach
@endisset
<label>
<input type="radio" id="toggle-payment-with-credit-card" class="form-radio cursor-pointer" name="payment-type"
checked />
<span class="ml-1 cursor-pointer">{{ __('texts.new_card') }}</span>
</label>
@endcomponent
@include('portal.ninja2020.gateways.includes.save_card')
@component('portal.ninja2020.components.general.card-element-single')
<div id="card-container"></div>
<div id="payment-status-container"></div>
@endcomponent
@include('portal.ninja2020.gateways.includes.pay_now')
@endsection
@section('gateway_footer')
@if ($gateway->company_gateway->getConfigField('testMode'))
<script type="text/javascript" src="https://sandbox.web.squarecdn.com/v1/square.js"></script>
@else
<script type="text/javascript" src="https://web.squarecdn.com/v1/square.js"></script>
@endif
<script src="{{ asset('js/clients/payments/square-credit-card.js') }}"></script>
@endsection

View File

@ -0,0 +1,7 @@
@extends('portal.ninja2020.layout.app')
@section('meta_title', ctrans('texts.statement'))
@section('body')
@livewire('statement')
@endsection

View File

@ -67,7 +67,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
Route::resource('designs', 'DesignController'); // name = (payments. index / create / show / update / destroy / edit
Route::post('designs/bulk', 'DesignController@bulk')->name('designs.bulk');
Route::post('designs/set/default', 'DesignController@default')->name('designs.default');
Route::resource('documents', 'DocumentController'); // name = (documents. index / create / show / update / destroy / edit
Route::get('documents/{document}/download', 'DocumentController@download')->name('documents.download');

View File

@ -80,6 +80,9 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence
Route::resource('tasks', 'ClientPortal\TaskController')->only(['index']);
Route::get('statement', 'ClientPortal\StatementController@index')->name('statement');
Route::get('statement/raw', 'ClientPortal\StatementController@raw')->name('statement.raw');
Route::post('upload', 'ClientPortal\UploadController')->name('upload.store');
Route::get('logout', 'Auth\ContactLoginController@logout')->name('logout');

View File

@ -0,0 +1,128 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace Tests\Browser\ClientPortal\Gateways\Square;
use Laravel\Dusk\Browser;
use Tests\Browser\Pages\ClientPortal\Login;
use Tests\DuskTestCase;
class CreditCardTest extends DuskTestCase
{
protected function setUp(): void
{
parent::setUp();
foreach (static::$browsers as $browser) {
$browser->driver->manage()->deleteAllCookies();
}
$this->browse(function (Browser $browser) {
$browser
->visit(new Login())
->auth();
});
}
public function testPaymentWithNewCard()
{
$this->browse(function (Browser $browser) {
$browser
->visitRoute('client.invoices.index')
->click('@pay-now')
->click('@pay-now-dropdown')
->clickLink('Credit Card')
->type('#cardholder-name', 'John Doe')
->withinFrame('iframe', function (Browser $browser) {
$browser
->type('#cardNumber', '4111 1111 1111 1111')
->type('#expirationDate', '04/22')
->type('#cvv', '1111')
->type('#postalCode', '12345');
})
->click('#pay-now')
->waitForText('Details of the payment', 60);
});
}
public function testPayWithNewCardAndSaveForFutureUse()
{
$this->browse(function (Browser $browser) {
$browser
->visitRoute('client.invoices.index')
->click('@pay-now')
->click('@pay-now-dropdown')
->clickLink('Credit Card')
->type('#cardholder-name', 'John Doe')
->withinFrame('iframe', function (Browser $browser) {
$browser
->type('#cardNumber', '4111 1111 1111 1111')
->type('#expirationDate', '04/22')
->type('#cvv', '1111')
->type('#postalCode', '12345');
})
->radio('#proxy_is_default', true)
->click('#pay-now')
->waitForText('Details of the payment', 60)
->visitRoute('client.payment_methods.index')
->clickLink('View')
->assertSee('4242');
});
}
public function testPayWithSavedCreditCard()
{
$this->browse(function (Browser $browser) {
$browser
->visitRoute('client.invoices.index')
->click('@pay-now')
->click('@pay-now-dropdown')
->clickLink('Credit Card')
->click('.toggle-payment-with-token')
->click('#pay-now')
->waitForText('Details of the payment', 60);
});
}
public function testRemoveCreditCard()
{
$this->browse(function (Browser $browser) {
$browser
->visitRoute('client.payment_methods.index')
->clickLink('View')
->press('Remove Payment Method')
->waitForText('Confirmation')
->click('@confirm-payment-removal')
->assertSee('Payment method has been successfully removed.');
});
}
public function testAddingCreditCardStandalone()
{
$this->browse(function (Browser $browser) {
$browser
->visitRoute('client.payment_methods.index')
->press('Add Payment Method')
->clickLink('Credit Card')
->type('#cardholder-name', 'John Doe')
->withinFrame('iframe', function (Browser $browser) {
$browser
->type('#cardNumber', '4111 1111 1111 1111')
->type('#expirationDate', '04/22')
->type('#cvv', '1111')
->type('#postalCode', '12345');
})
->press('Add Payment Method')
->waitForText('**** 1111');
});
}
}

4
webpack.mix.js vendored
View File

@ -97,6 +97,10 @@ mix.js("resources/js/app.js", "public/js")
.js(
"resources/js/clients/payment_methods/braintree-ach.js",
"public/js/clients/payment_methods/braintree-ach.js"
)
.js(
"resources/js/clients/payments/square-credit-card.js",
"public/js/clients/payments/square-credit-card.js"
);
mix.copyDirectory('node_modules/card-js/card-js.min.css', 'public/css/card-js.min.css');