diff --git a/app/Console/Commands/CheckData.php b/app/Console/Commands/CheckData.php index d4dc97902a54..568a5ff96c14 100644 --- a/app/Console/Commands/CheckData.php +++ b/app/Console/Commands/CheckData.php @@ -314,7 +314,7 @@ class CheckData extends Command $new_contact->client_id = $client->id; $new_contact->contact_key = Str::random(40); $new_contact->is_primary = true; - $new_contact->save(); + $new_contact->saveQuietly(); } } } @@ -362,7 +362,7 @@ class CheckData extends Command $new_contact->vendor_id = $vendor->id; $new_contact->contact_key = Str::random(40); $new_contact->is_primary = true; - $new_contact->save(); + $new_contact->saveQuietly(); } } } @@ -392,7 +392,7 @@ class CheckData extends Command $invitation->invoice_id = $invoice->id; $invitation->client_contact_id = ClientContact::whereClientId($invoice->client_id)->first()->id; $invitation->key = Str::random(config('ninja.key_length')); - $invitation->save(); + $invitation->saveQuietly(); } } } @@ -583,7 +583,7 @@ class CheckData extends Command if ($this->option('paid_to_date')) { $this->logMessage("# {$client->id} " . $client->present()->name().' - '.$client->number." Fixing {$client->paid_to_date} to {$total_paid_to_date}"); $client->paid_to_date = $total_paid_to_date; - $client->save(); + $client->saveQuietly(); } } } @@ -632,7 +632,7 @@ class CheckData extends Command if ($this->option('client_balance')) { $this->logMessage("# {$client_object->id} " . $client_object->present()->name().' - '.$client_object->number." Fixing {$client_object->balance} to " . $client['invoice_balance']); $client_object->balance = $client['invoice_balance']; - $client_object->save(); + $client_object->saveQuietly(); } $this->isValid = false; @@ -678,7 +678,7 @@ class CheckData extends Command $this->logMessage("# {$client->id} " . $client->present()->name().' - '.$client->number." Fixing {$client->balance} to 0"); $client->balance = $over_payment; - $client->save(); + $client->saveQuietly(); } } }); @@ -732,7 +732,7 @@ class CheckData extends Command if ($this->option('client_balance')) { $this->logMessage("# {$client->id} " . $client->present()->name().' - '.$client->number." Fixing {$client->balance} to {$invoice_balance}"); $client->balance = $invoice_balance; - $client->save(); + $client->saveQuietly(); } if ($ledger && (number_format($invoice_balance, 4) != number_format($ledger->balance, 4))) { @@ -766,7 +766,7 @@ class CheckData extends Command if ($this->option('ledger_balance')) { $this->logMessage("# {$client->id} " . $client->present()->name().' - '.$client->number." Fixing {$client->balance} to {$invoice_balance}"); $client->balance = $invoice_balance; - $client->save(); + $client->saveQuietly(); $ledger->adjustment = $invoice_balance; $ledger->balance = $invoice_balance; @@ -884,7 +884,7 @@ class CheckData extends Command if ($this->option('fix') == 'true') { Client::query()->whereNull('country_id')->cursor()->each(function ($client) { $client->country_id = $client->company->settings->country_id; - $client->save(); + $client->saveQuietly(); $this->logMessage("Fixing country for # {$client->id}"); }); @@ -896,7 +896,7 @@ class CheckData extends Command if ($this->option('fix') == 'true') { Vendor::query()->whereNull('currency_id')->orWhere('currency_id', '')->cursor()->each(function ($vendor) { $vendor->currency_id = $vendor->company->settings->currency_id; - $vendor->save(); + $vendor->saveQuietly(); $this->logMessage("Fixing vendor currency for # {$vendor->id}"); }); @@ -919,14 +919,14 @@ class CheckData extends Command $invoice->balance = 0; $invoice->paid_to_date=$val; - $invoice->save(); + $invoice->saveQuietly(); $p = $invoice->payments->first(); if ($p && (int)$p->amount == 0) { $p->amount = $val; $p->applied = $val; - $p->save(); + $p->saveQuietly(); $pivot = $p->paymentables->first(); $pivot->amount = $val; diff --git a/app/Http/Controllers/CreditController.php b/app/Http/Controllers/CreditController.php index 9ad71d06756c..9f1caaa1d5be 100644 --- a/app/Http/Controllers/CreditController.php +++ b/app/Http/Controllers/CreditController.php @@ -11,36 +11,37 @@ namespace App\Http\Controllers; -use App\Events\Credit\CreditWasCreated; -use App\Events\Credit\CreditWasUpdated; -use App\Factory\CloneCreditFactory; +use App\Utils\Ninja; +use App\Models\Client; +use App\Models\Credit; +use App\Models\Account; +use App\Models\Invoice; +use App\Models\Webhook; +use Illuminate\Http\Response; use App\Factory\CreditFactory; use App\Filters\CreditFilters; -use App\Http\Requests\Credit\ActionCreditRequest; +use App\Jobs\Credit\ZipCredits; +use App\Utils\Traits\MakesHash; +use App\Jobs\Entity\EmailEntity; +use App\Factory\CloneCreditFactory; +use App\Services\PdfMaker\PdfMerge; +use Illuminate\Support\Facades\App; +use App\Utils\Traits\SavesDocuments; +use App\Repositories\CreditRepository; +use App\Events\Credit\CreditWasCreated; +use App\Events\Credit\CreditWasUpdated; +use App\Transformers\CreditTransformer; +use Illuminate\Support\Facades\Storage; +use App\Services\Template\TemplateAction; use App\Http\Requests\Credit\BulkCreditRequest; -use App\Http\Requests\Credit\CreateCreditRequest; -use App\Http\Requests\Credit\DestroyCreditRequest; use App\Http\Requests\Credit\EditCreditRequest; use App\Http\Requests\Credit\ShowCreditRequest; use App\Http\Requests\Credit\StoreCreditRequest; +use App\Http\Requests\Credit\ActionCreditRequest; +use App\Http\Requests\Credit\CreateCreditRequest; use App\Http\Requests\Credit\UpdateCreditRequest; use App\Http\Requests\Credit\UploadCreditRequest; -use App\Jobs\Credit\ZipCredits; -use App\Jobs\Entity\EmailEntity; -use App\Models\Account; -use App\Models\Client; -use App\Models\Credit; -use App\Models\Invoice; -use App\Repositories\CreditRepository; -use App\Services\PdfMaker\PdfMerge; -use App\Services\Template\TemplateAction; -use App\Transformers\CreditTransformer; -use App\Utils\Ninja; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\SavesDocuments; -use Illuminate\Http\Response; -use Illuminate\Support\Facades\App; -use Illuminate\Support\Facades\Storage; +use App\Http\Requests\Credit\DestroyCreditRequest; /** * Class CreditController. @@ -153,6 +154,7 @@ class CreditController extends BaseController $user = auth()->user(); $credit = CreditFactory::create($user->company()->id, $user->id); + $credit->date = now()->addSeconds($user->company()->utc_offset())->format('Y-m-d'); return $this->itemResponse($credit); } @@ -638,23 +640,14 @@ class CreditController extends BaseController } break; case 'email': - - $credit->invitations->load('contact.client.country', 'credit.client.country', 'credit.company')->each(function ($invitation) use ($credit) { - EmailEntity::dispatch($invitation, $credit->company, 'credit'); - }); - - - if (! $bulk) { - return response()->json(['message'=>'email sent'], 200); - } - break; - case 'send_email': $credit->invitations->load('contact.client.country', 'credit.client.country', 'credit.company')->each(function ($invitation) use ($credit) { EmailEntity::dispatch($invitation, $credit->company, 'credit'); }); + $credit->sendEvent(Webhook::EVENT_SENT_CREDIT, "client"); + if (! $bulk) { return response()->json(['message'=>'email sent'], 200); } diff --git a/app/Http/Controllers/EmailController.php b/app/Http/Controllers/EmailController.php index 5bdcdb8ac712..6da3f9bc3bcd 100644 --- a/app/Http/Controllers/EmailController.php +++ b/app/Http/Controllers/EmailController.php @@ -11,25 +11,26 @@ namespace App\Http\Controllers; -use App\Events\Credit\CreditWasEmailed; -use App\Events\Quote\QuoteWasEmailed; -use App\Http\Requests\Email\SendEmailRequest; -use App\Jobs\PurchaseOrder\PurchaseOrderEmail; +use App\Utils\Ninja; +use App\Models\Quote; use App\Models\Credit; use App\Models\Invoice; +use App\Models\Webhook; use App\Models\PurchaseOrder; -use App\Models\Quote; -use App\Models\RecurringInvoice; use App\Services\Email\Email; +use App\Utils\Traits\MakesHash; +use App\Models\RecurringInvoice; use App\Services\Email\EmailObject; +use App\Events\Quote\QuoteWasEmailed; +use App\Transformers\QuoteTransformer; +use Illuminate\Mail\Mailables\Address; +use App\Events\Credit\CreditWasEmailed; use App\Transformers\CreditTransformer; use App\Transformers\InvoiceTransformer; +use App\Http\Requests\Email\SendEmailRequest; +use App\Jobs\PurchaseOrder\PurchaseOrderEmail; use App\Transformers\PurchaseOrderTransformer; -use App\Transformers\QuoteTransformer; use App\Transformers\RecurringInvoiceTransformer; -use App\Utils\Ninja; -use App\Utils\Traits\MakesHash; -use Illuminate\Mail\Mailables\Address; class EmailController extends BaseController { @@ -100,6 +101,7 @@ class EmailController extends BaseController if ($entity_obj->invitations->count() >= 1) { $entity_obj->entityEmailEvent($entity_obj->invitations->first(), 'invoice', $template); + $entity_obj->sendEvent(Webhook::EVENT_SENT_INVOICE, "client"); } } @@ -109,6 +111,8 @@ class EmailController extends BaseController if ($entity_obj->invitations->count() >= 1) { event(new QuoteWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null), 'quote')); + $entity_obj->sendEvent(Webhook::EVENT_SENT_QUOTE, "client"); + } } @@ -118,6 +122,7 @@ class EmailController extends BaseController if ($entity_obj->invitations->count() >= 1) { event(new CreditWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null), 'credit')); + $entity_obj->sendEvent(Webhook::EVENT_SENT_CREDIT, "client"); } } @@ -143,7 +148,8 @@ class EmailController extends BaseController $data['template'] = $template; PurchaseOrderEmail::dispatch($entity_obj, $entity_obj->company, $data); - + $entity_obj->sendEvent(Webhook::EVENT_SENT_PURCHASE_ORDER, "vendor"); + return $this->itemResponse($entity_obj); } diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 477ab8111d2a..13d1284cbc64 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -166,6 +166,7 @@ class InvoiceController extends BaseController /** @var \App\Models\User $user */ $user = auth()->user(); $invoice = InvoiceFactory::create($user->company()->id, $user->id); + $invoice->date = now()->addSeconds($user->company()->utc_offset())->format('Y-m-d'); return $this->itemResponse($invoice); } @@ -538,8 +539,6 @@ class InvoiceController extends BaseController return (new \App\Jobs\Entity\CreateRawPdf($invoice->invitations->first()))->handle(); }); - - return response()->streamDownload(function () use ($paths) { echo $merge = (new PdfMerge($paths->toArray()))->run(); }, 'print.pdf', ['Content-Type' => 'application/pdf']); diff --git a/app/Http/Controllers/PurchaseOrderController.php b/app/Http/Controllers/PurchaseOrderController.php index f25294bb6e1b..43c86523472a 100644 --- a/app/Http/Controllers/PurchaseOrderController.php +++ b/app/Http/Controllers/PurchaseOrderController.php @@ -144,6 +144,7 @@ class PurchaseOrderController extends BaseController $user = auth()->user(); $purchase_order = PurchaseOrderFactory::create($user->company()->id, $user->id); + $purchase_order->date = now()->addSeconds($user->company()->utc_offset())->format('Y-m-d'); return $this->itemResponse($purchase_order); } diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index adb36637667a..3a79d2bc225d 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -168,6 +168,7 @@ class QuoteController extends BaseController $user = auth()->user(); $quote = QuoteFactory::create($user->company()->id, $user->id); + $quote->date = now()->addSeconds($user->company()->utc_offset())->format('Y-m-d'); return $this->itemResponse($quote); } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 03f9f0584d8a..574596fa3b9c 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -196,7 +196,7 @@ class UserController extends BaseController */ public function destroy(DestroyUserRequest $request, User $user) { - if ($user->isOwner()) { + if ($user->hasOwnerFlag()) { return response()->json(['message', 'Cannot detach owner.'], 401); } diff --git a/app/Http/Livewire/PdfSlot.php b/app/Http/Livewire/PdfSlot.php index 1a12a4dc68bf..707903bd204a 100644 --- a/app/Http/Livewire/PdfSlot.php +++ b/app/Http/Livewire/PdfSlot.php @@ -55,6 +55,8 @@ class PdfSlot extends Component public $is_quote = false; + private $entity_calc; + public function mount() { MultiDB::setDb($this->db); @@ -123,6 +125,7 @@ class PdfSlot extends Component { $this->entity_type = $this->resolveEntityType(); + $this->entity_calc = $this->entity->calc(); $this->settings = $this->entity->client ? $this->entity->client->getMergedSettings() : $this->entity->company->settings; @@ -149,6 +152,8 @@ class PdfSlot extends Component 'services' => $this->getServices(), 'amount' => Number::formatMoney($this->entity->amount, $this->entity->client ?: $this->entity->vendor), 'balance' => Number::formatMoney($this->entity->balance, $this->entity->client ?: $this->entity->vendor), + 'discount' => $this->entity_calc->getTotalDiscount() > 0 ? Number::formatMoney($this->entity_calc->getTotalDiscount(), $this->entity->client ?: $this->entity->vendor) : false, + 'taxes' => $this->entity_calc->getTotalTaxes() > 0 ? Number::formatMoney($this->entity_calc->getTotalTaxes(), $this->entity->client ?: $this->entity->vendor) : false, 'company_details' => $this->getCompanyDetails(), 'company_address' => $this->getCompanyAddress(), 'entity_details' => $this->getEntityDetails(), diff --git a/app/Http/Requests/Company/UpdateCompanyRequest.php b/app/Http/Requests/Company/UpdateCompanyRequest.php index 4ffa0e33093d..abd670f6b02f 100644 --- a/app/Http/Requests/Company/UpdateCompanyRequest.php +++ b/app/Http/Requests/Company/UpdateCompanyRequest.php @@ -60,7 +60,7 @@ class UpdateCompanyRequest extends Request // $rules['client_registration_fields'] = 'array'; if (isset($input['portal_mode']) && ($input['portal_mode'] == 'domain' || $input['portal_mode'] == 'iframe')) { - $rules['portal_domain'] = 'sometimes|url'; + $rules['portal_domain'] = 'bail|nullable|sometimes|url'; } if (Ninja::isHosted()) { diff --git a/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php b/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php index fff0e66c47e5..bf3d2a16e96d 100644 --- a/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php +++ b/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php @@ -74,7 +74,7 @@ class StoreSchedulerRequest extends Request if(isset($input['parameters']['status'])) { $input['parameters']['status'] = collect(explode(",", $input['parameters']['status'])) - ->filter(function($status) { + ->filter(function ($status) { return in_array($status, ['all','draft','paid','unpaid','overdue']); })->implode(",") ?? ''; } diff --git a/app/Http/Requests/TaskScheduler/UpdateSchedulerRequest.php b/app/Http/Requests/TaskScheduler/UpdateSchedulerRequest.php index 2b5ffc4ba7fd..6395eeb23d44 100644 --- a/app/Http/Requests/TaskScheduler/UpdateSchedulerRequest.php +++ b/app/Http/Requests/TaskScheduler/UpdateSchedulerRequest.php @@ -44,7 +44,7 @@ class UpdateSchedulerRequest extends Request 'parameters.end_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom', 'after_or_equal:parameters.start_date'], 'parameters.entity' => ['bail', 'sometimes', 'string', 'in:invoice,credit,quote,purchase_order'], 'parameters.entity_id' => ['bail', 'sometimes', 'string'], - 'parameters.report_name' => ['bail','sometimes', 'string', 'required_if:template,email_report', 'in:ar_detailed,ar_summary,client_balance,tax_summary,profitloss,client_sales,user_sales,product_sales,client,client_contact,credit,document,expense,invoice,invoice_item,quote,quote_item,recurring_invoice,payment,product,task'], + 'parameters.report_name' => ['bail','sometimes', 'string', 'required_if:template,email_report','in:ar_detailed,ar_summary,client_balance,tax_summary,profitloss,client_sales,user_sales,product_sales,activity,client,contact,client_contact,credit,document,expense,invoice,invoice_item,quote,quote_item,recurring_invoice,payment,product,task'], 'parameters.date_key' => ['bail','sometimes', 'string'], 'parameters.status' => ['bail','sometimes', 'string'], ]; @@ -67,7 +67,7 @@ class UpdateSchedulerRequest extends Request if(isset($input['parameters']) && !isset($input['parameters']['clients'])) { $input['parameters']['clients'] = []; } - + if(isset($input['parameters']['status'])) { $input['parameters']['status'] = collect(explode(",", $input['parameters']['status'])) @@ -76,9 +76,9 @@ class UpdateSchedulerRequest extends Request })->implode(",") ?? ''; } - $this->replace($input); + } -} +} \ No newline at end of file diff --git a/app/Jobs/Company/CompanyExport.php b/app/Jobs/Company/CompanyExport.php index 0fc36b2500af..9e97bad7f522 100644 --- a/app/Jobs/Company/CompanyExport.php +++ b/app/Jobs/Company/CompanyExport.php @@ -224,7 +224,7 @@ class CompanyExport implements ShouldQueue $this->export_data['invoices'] = $this->company->invoices()->orderBy('number', 'DESC')->cursor()->map(function ($invoice) { $invoice = $this->transformBasicEntities($invoice); - $invoice = $this->transformArrayOfKeys($invoice, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id','project_id']); + $invoice = $this->transformArrayOfKeys($invoice, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']); $invoice->tax_data = ''; return $invoice->makeVisible(['id', @@ -331,7 +331,8 @@ class CompanyExport implements ShouldQueue $task = $this->transformBasicEntities($task); $task = $this->transformArrayOfKeys($task, ['client_id', 'invoice_id', 'project_id', 'status_id']); - return $task->makeVisible(['id']); + return $task->makeHidden(['hash','meta'])->makeVisible(['id']); +// return $task->makeHidden(['hash','meta'])->makeVisible(['id']); //@release v5.7.63 })->all(); $this->export_data['task_statuses'] = $this->company->task_statuses->map(function ($status) { diff --git a/app/Jobs/Cron/AutoBill.php b/app/Jobs/Cron/AutoBill.php index cc48b9142341..035f15f2b60c 100644 --- a/app/Jobs/Cron/AutoBill.php +++ b/app/Jobs/Cron/AutoBill.php @@ -11,14 +11,15 @@ namespace App\Jobs\Cron; -use App\Jobs\Entity\EmailEntity; -use App\Libraries\MultiDB; use App\Models\Invoice; +use App\Models\Webhook; +use App\Libraries\MultiDB; use Illuminate\Bus\Queueable; +use App\Jobs\Entity\EmailEntity; +use Illuminate\Queue\SerializesModels; +use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\SerializesModels; class AutoBill implements ShouldQueue { @@ -77,6 +78,8 @@ class AutoBill implements ShouldQueue } }); + $invoice->sendEvent(Webhook::EVENT_SENT_INVOICE, "client"); + } } diff --git a/app/Jobs/Invoice/BulkInvoiceJob.php b/app/Jobs/Invoice/BulkInvoiceJob.php index be2224d12378..353ce69b255a 100644 --- a/app/Jobs/Invoice/BulkInvoiceJob.php +++ b/app/Jobs/Invoice/BulkInvoiceJob.php @@ -11,13 +11,14 @@ namespace App\Jobs\Invoice; -use App\Jobs\Entity\EmailEntity; use App\Models\Invoice; +use App\Models\Webhook; use Illuminate\Bus\Queueable; +use App\Jobs\Entity\EmailEntity; +use Illuminate\Queue\SerializesModels; +use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\SerializesModels; class BulkInvoiceJob implements ShouldQueue { @@ -50,6 +51,8 @@ class BulkInvoiceJob implements ShouldQueue if ($this->invoice->invitations->count() >= 1) { $this->invoice->entityEmailEvent($this->invoice->invitations->first(), 'invoice', $this->reminder_template); + $this->invoice->sendEvent(Webhook::EVENT_SENT_INVOICE, "client"); + } } } diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index 60c3b7fbd81c..ed69bb357ec9 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -549,7 +549,7 @@ class NinjaMailerJob implements ShouldQueue /* On the hosted platform if the user has not verified their account we fail here - but still check what they are trying to send! */ if (Ninja::isHosted() && $this->company->account && !$this->company->account->account_sms_verified) { if (class_exists(\Modules\Admin\Jobs\Account\EmailQuality::class)) { - return (new \Modules\Admin\Jobs\Account\EmailQuality($this->nmo, $this->company))->run(); + (new \Modules\Admin\Jobs\Account\EmailQuality($this->nmo, $this->company))->run(); } return true; diff --git a/app/Jobs/Ninja/SendReminders.php b/app/Jobs/Ninja/SendReminders.php index a83364dd2c33..2a2097a519e5 100644 --- a/app/Jobs/Ninja/SendReminders.php +++ b/app/Jobs/Ninja/SendReminders.php @@ -215,6 +215,7 @@ class SendReminders implements ShouldQueue EmailEntity::dispatch($invitation, $invitation->company, $template)->delay(10); event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars(), $template)); + $invoice->sendEvent(Webhook::EVENT_REMIND_INVOICE, "client"); } }); diff --git a/app/Jobs/RecurringInvoice/SendRecurring.php b/app/Jobs/RecurringInvoice/SendRecurring.php index b2247a93f747..f8a53377cb06 100644 --- a/app/Jobs/RecurringInvoice/SendRecurring.php +++ b/app/Jobs/RecurringInvoice/SendRecurring.php @@ -11,24 +11,25 @@ namespace App\Jobs\RecurringInvoice; -use App\DataMapper\Analytics\SendRecurringFailure; -use App\Events\Invoice\InvoiceWasCreated; -use App\Factory\InvoiceInvitationFactory; -use App\Factory\RecurringInvoiceToInvoiceFactory; -use App\Jobs\Cron\AutoBill; -use App\Jobs\Entity\EmailEntity; -use App\Models\Invoice; -use App\Models\RecurringInvoice; -use App\Utils\Ninja; -use App\Utils\Traits\GeneratesCounter; -use App\Utils\Traits\MakesHash; use Carbon\Carbon; +use App\Utils\Ninja; +use App\Models\Invoice; +use App\Models\Webhook; +use App\Jobs\Cron\AutoBill; use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Queue\InteractsWithQueue; +use App\Utils\Traits\MakesHash; +use App\Jobs\Entity\EmailEntity; +use App\Models\RecurringInvoice; +use App\Utils\Traits\GeneratesCounter; use Illuminate\Queue\SerializesModels; use Turbo124\Beacon\Facades\LightLogs; +use Illuminate\Queue\InteractsWithQueue; +use App\Events\Invoice\InvoiceWasCreated; +use App\Factory\InvoiceInvitationFactory; +use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Foundation\Bus\Dispatchable; +use App\Factory\RecurringInvoiceToInvoiceFactory; +use App\DataMapper\Analytics\SendRecurringFailure; class SendRecurring implements ShouldQueue { @@ -117,6 +118,7 @@ class SendRecurring implements ShouldQueue //04-08-2023 edge case to support where online payment notifications are not enabled if(!$invoice->client->getSetting('client_online_payment_notification')) { $this->sendRecurringEmails($invoice); + $invoice->sendEvent(Webhook::EVENT_SENT_INVOICE, "client"); } } elseif ($invoice->auto_bill_enabled && $invoice->client->getSetting('auto_bill_date') == 'on_due_date' && $invoice->client->getSetting('auto_email_invoice') && ($invoice->due_date && Carbon::parse($invoice->due_date)->startOfDay()->lte(now()->startOfDay()))) { nlog("attempting to autobill {$invoice->number}"); @@ -125,10 +127,12 @@ class SendRecurring implements ShouldQueue //04-08-2023 edge case to support where online payment notifications are not enabled if(!$invoice->client->getSetting('client_online_payment_notification')) { $this->sendRecurringEmails($invoice); + $invoice->sendEvent(Webhook::EVENT_SENT_INVOICE, "client"); } } elseif ($invoice->client->getSetting('auto_email_invoice')) { $this->sendRecurringEmails($invoice); + $invoice->sendEvent(Webhook::EVENT_SENT_INVOICE, "client"); } } diff --git a/app/Jobs/Util/ReminderJob.php b/app/Jobs/Util/ReminderJob.php index e4cab43e6aff..80817578c23a 100644 --- a/app/Jobs/Util/ReminderJob.php +++ b/app/Jobs/Util/ReminderJob.php @@ -11,22 +11,23 @@ namespace App\Jobs\Util; +use App\Utils\Ninja; +use App\Models\Invoice; +use App\Models\Webhook; +use App\Libraries\MultiDB; +use Illuminate\Bus\Queueable; +use Illuminate\Support\Carbon; use App\DataMapper\InvoiceItem; use App\Factory\InvoiceFactory; use App\Jobs\Entity\EmailEntity; -use App\Libraries\MultiDB; -use App\Models\Invoice; -use App\Utils\Ninja; use App\Utils\Traits\MakesDates; +use Illuminate\Support\Facades\App; use App\Utils\Traits\MakesReminders; -use Illuminate\Bus\Queueable; +use Illuminate\Support\Facades\Auth; +use Illuminate\Queue\SerializesModels; +use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\App; -use Illuminate\Support\Facades\Auth; class ReminderJob implements ShouldQueue { @@ -150,6 +151,7 @@ class ReminderJob implements ShouldQueue EmailEntity::dispatch($invitation, $invitation->company, $reminder_template); nlog("Firing reminder email for invoice {$invoice->number} - {$reminder_template}"); $invoice->entityEmailEvent($invitation, $reminder_template); + $invoice->sendEvent(Webhook::EVENT_REMIND_INVOICE, "client"); } }); } @@ -220,6 +222,7 @@ class ReminderJob implements ShouldQueue EmailEntity::dispatch($invitation, $invitation->company, $reminder_template); nlog("Firing reminder email for invoice {$invoice->number} - {$reminder_template}"); $invoice->entityEmailEvent($invitation, $reminder_template); + $invoice->sendEvent(Webhook::EVENT_REMIND_INVOICE, "client"); } }); } diff --git a/app/Libraries/MultiDB.php b/app/Libraries/MultiDB.php index 57e734253bcf..73fc82d65848 100644 --- a/app/Libraries/MultiDB.php +++ b/app/Libraries/MultiDB.php @@ -11,16 +11,17 @@ namespace App\Libraries; -use App\Models\Account; -use App\Models\Client; -use App\Models\ClientContact; -use App\Models\Company; -use App\Models\CompanyToken; -use App\Models\Document; use App\Models\User; +use App\Models\Client; +use App\Models\Account; +use App\Models\Company; +use App\Models\Document; +use App\Models\PaymentHash; +use Illuminate\Support\Str; +use App\Models\CompanyToken; +use App\Models\ClientContact; use App\Models\VendorContact; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Str; /** * Class MultiDB. @@ -485,6 +486,27 @@ class MultiDB return false; } + public static function findAndSetByPaymentHash(string $hash) + { + if (! config('ninja.db.multi_db_enabled')) { + return PaymentHash::with('fee_invoice')->where('hash', $hash)->first(); + } + + $current_db = config('database.default'); + + foreach (self::$dbs as $db) { + if ($payment_hash = PaymentHash::on($db)->where('hash', $hash)->first()) { + self::setDb($db); + + return $payment_hash; + } + } + + self::setDB($current_db); + + return false; + } + public static function findAndSetDbByInvitation($entity, $invitation_key) { $class = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation'; diff --git a/app/Listeners/Mail/MailSentListener.php b/app/Listeners/Mail/MailSentListener.php index 3ad8c7a70c68..a0389d14de9e 100644 --- a/app/Listeners/Mail/MailSentListener.php +++ b/app/Listeners/Mail/MailSentListener.php @@ -76,18 +76,18 @@ class MailSentListener implements ShouldQueue foreach (MultiDB::$dbs as $db) { if ($invitation = InvoiceInvitation::on($db)->where('key', $key)->first()) { - $invitation->invoice->sendEvent(Webhook::EVENT_SENT_INVOICE, "client"); + // $invitation->invoice->sendEvent(Webhook::EVENT_SENT_INVOICE, "client"); return $invitation; } elseif ($invitation = QuoteInvitation::on($db)->where('key', $key)->first()) { - $invitation->quote->sendEvent(Webhook::EVENT_SENT_QUOTE, "client"); + // $invitation->quote->sendEvent(Webhook::EVENT_SENT_QUOTE, "client"); return $invitation; } elseif ($invitation = RecurringInvoiceInvitation::on($db)->where('key', $key)->first()) { return $invitation; } elseif ($invitation = CreditInvitation::on($db)->where('key', $key)->first()) { - $invitation->credit->sendEvent(Webhook::EVENT_SENT_CREDIT, "client"); + // $invitation->credit->sendEvent(Webhook::EVENT_SENT_CREDIT, "client"); return $invitation; } elseif ($invitation = PurchaseOrderInvitation::on($db)->where('key', $key)->first()) { - $invitation->purchase_order->sendEvent(Webhook::EVENT_SENT_PURCHASE_ORDER, "vendor"); + // $invitation->purchase_order->sendEvent(Webhook::EVENT_SENT_PURCHASE_ORDER, "vendor"); return $invitation; } } diff --git a/app/Models/User.php b/app/Models/User.php index d6c023b61269..ca37b13755d5 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -372,9 +372,12 @@ class User extends Authenticatable implements MustVerifyEmail public function isOwner() : bool { return $this->token()->cu->is_owner; - } + public function hasOwnerFlag(): bool + { + return $this->company_users()->where('is_owner',true)->exists(); + } /** * Returns true is user is an admin _or_ owner * diff --git a/app/Notifications/Ninja/PayPalUnlinkedTransaction.php b/app/Notifications/Ninja/PayPalUnlinkedTransaction.php new file mode 100644 index 000000000000..e0b209005e23 --- /dev/null +++ b/app/Notifications/Ninja/PayPalUnlinkedTransaction.php @@ -0,0 +1,78 @@ +order_id}\n"; + $content .= "Transaction ref: {$this->transaction_reference}\n"; + + + return (new SlackMessage) + ->success() + ->from(ctrans('texts.notification_bot')) + ->image('https://app.invoiceninja.com/favicon.png') + ->content($content); + } +} diff --git a/app/PaymentDrivers/PayPal/PayPalWebhook.php b/app/PaymentDrivers/PayPal/PayPalWebhook.php new file mode 100644 index 000000000000..8d991981a3c5 --- /dev/null +++ b/app/PaymentDrivers/PayPal/PayPalWebhook.php @@ -0,0 +1,393 @@ +endpoint = $this->test_endpoint; + + //this can cause problems verifying the webhook, so unset it if it exists + if(isset($this->webhook_request['q'])) + unset($this->webhook_request['q']); + + if($this->verifyWebhook()) { + nlog('verified'); + + match($this->webhook_request['event_type']) { + 'CHECKOUT.ORDER.COMPLETED' => $this->checkoutOrderCompleted(), + }; + + return; + } + + nlog(" NOT VERIFIED "); + } + /* + 'id' => 'WH-COC11055RA711503B-4YM959094A144403T', + 'create_time' => '2018-04-16T21:21:49.000Z', + 'event_type' => 'CHECKOUT.ORDER.COMPLETED', + 'resource_type' => 'checkout-order', + 'resource_version' => '2.0', + 'summary' => 'Checkout Order Completed', + 'resource' => + array ( + 'id' => '5O190127TN364715T', + 'status' => 'COMPLETED', + 'intent' => 'CAPTURE', + 'gross_amount' => + array ( + 'currency_code' => 'USD', + 'value' => '100.00', + ), + 'payer' => + array ( + 'name' => + array ( + 'given_name' => 'John', + 'surname' => 'Doe', + ), + 'email_address' => 'buyer@example.com', + 'payer_id' => 'QYR5Z8XDVJNXQ', + ), + 'purchase_units' => + array ( + 0 => + array ( + 'reference_id' => 'd9f80740-38f0-11e8-b467-0ed5f89f718b', + 'amount' => + array ( + 'currency_code' => 'USD', + 'value' => '100.00', + ), + 'payee' => + array ( + 'email_address' => 'seller@example.com', + ), + 'shipping' => + array ( + 'method' => 'United States Postal Service', + 'address' => + array ( + 'address_line_1' => '2211 N First Street', + 'address_line_2' => 'Building 17', + 'admin_area_2' => 'San Jose', + 'admin_area_1' => 'CA', + 'postal_code' => '95131', + 'country_code' => 'US', + ), + ), + 'payments' => + array ( + 'captures' => + array ( + 0 => + array ( + 'id' => '3C679366HH908993F', + 'status' => 'COMPLETED', + 'amount' => + array ( + 'currency_code' => 'USD', + 'value' => '100.00', + ), + 'seller_protection' => + array ( + 'status' => 'ELIGIBLE', + 'dispute_categories' => + array ( + 0 => 'ITEM_NOT_RECEIVED', + 1 => 'UNAUTHORIZED_TRANSACTION', + ), + ), + 'final_capture' => true, + 'seller_receivable_breakdown' => + array ( + 'gross_amount' => + array ( + 'currency_code' => 'USD', + 'value' => '100.00', + ), + 'paypal_fee' => + array ( + 'currency_code' => 'USD', + 'value' => '3.00', + ), + 'net_amount' => + array ( + 'currency_code' => 'USD', + 'value' => '97.00', + ), + ), + 'create_time' => '2018-04-01T21:20:49Z', + 'update_time' => '2018-04-01T21:20:49Z', + 'links' => + array ( + 0 => + array ( + 'href' => 'https://api.paypal.com/v2/payments/captures/3C679366HH908993F', + 'rel' => 'self', + 'method' => 'GET', + ), + 1 => + array ( + 'href' => 'https://api.paypal.com/v2/payments/captures/3C679366HH908993F/refund', + 'rel' => 'refund', + 'method' => 'POST', + ), + ), + ), + ), + ), + ), + ), + 'create_time' => '2018-04-01T21:18:49Z', + 'update_time' => '2018-04-01T21:20:49Z', + 'links' => + */ + private function checkoutOrderCompleted() + { + $order = $this->webhook_request['resource']; + $transaction_reference = $order['purchase_units'][0]['payments']['captures'][0]['id']; + $amount = $order['purchase_units'][0]['payments']['captures'][0]['amount']['value']; + $payment_hash = MultiDB::findAndSetByPaymentHash($order['purchase_units'][0]['custom_id']); + $merchant_id = $order['purchase_units'][0]['payee']['merchant_id']; + if(!$payment_hash) { + + $ninja_company = Company::on('db-ninja-01')->find(config('ninja.ninja_default_company_id')); + $ninja_company->notification(new PayPalUnlinkedTransaction($order['id'], $transaction_reference))->ninja(); + return; + } + + nlog("payment completed check"); + if($payment_hash->payment && $payment_hash->payment->status_id == Payment::STATUS_COMPLETED) // Payment made, all good! + return; + + nlog("invoice paid check"); + if($payment_hash->fee_invoice && $payment_hash->fee_invoice->status_id == Invoice::STATUS_PAID){ // Payment made, all good! + + nlog("payment status check"); + if($payment_hash->payment && $payment_hash->payment->status_id != Payment::STATUS_COMPLETED) { // Make sure the payment is marked as completed + $payment_hash->payment->status_id = Payment::STATUS_COMPLETED; + $payment_hash->push(); + } + return; + } + + nlog("create payment check"); + if($payment_hash->fee_invoice && in_array($payment_hash->fee_invoice->status_id, [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])) { + + $payment = Payment::where('transaction_reference', $transaction_reference)->first(); + + if(!$payment) { nlog("make payment here!"); + $payment = $this->createPayment($payment_hash, [ + 'amount' => $amount, + 'transaction_reference' => $transaction_reference, + 'merchant_id' => $merchant_id, + ]); + } + + } + + } + + private function getPaymentType($source): int + { + $method = 'paypal'; + + match($source) { + "card" => $method = PaymentType::CREDIT_CARD_OTHER, + "paypal" => $method = PaymentType::PAYPAL, + "venmo" => $method = PaymentType::VENMO, + "paylater" => $method = PaymentType::PAY_LATER, + default => $method = PaymentType::PAYPAL, + }; + + return $method; + } + + private function createPayment(PaymentHash $payment_hash, array $data) + { + + $client = $payment_hash->fee_invoice->client; + + $company_gateway = $this->harvestGateway($client->company, $data['merchant_id']); + $driver = $company_gateway->driver($client)->init(); + $driver->setPaymentHash($payment_hash); + + $order = $driver->getOrder($this->webhook_request['resource']['id']); + $source = 'paypal'; + + if(isset($order['payment_source'])) { + $source = array_key_first($order['payment_source']); + } + + $data = [ + 'payment_type' => $this->getPaymentType($source), + 'amount' => $data['amount'], + 'transaction_reference' => $data['transaction_reference'], + 'gateway_type_id' => GatewayType::PAYPAL, + ]; + + $payment = $driver->createPayment($data, \App\Models\Payment::STATUS_COMPLETED); + + SystemLogger::dispatch( + ['response' => $this->webhook_request, 'data' => $data], + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_SUCCESS, + SystemLog::TYPE_PAYPAL, + $client, + $client->company, + ); + + } + + private function harvestGateway(Company $company, string $merchant_id): ?CompanyGateway + { + $gateway = CompanyGateway::query() + ->where('company_id', $company->id) + ->where('gateway_key', $this->gateway_key) + ->cursor() + ->first(function ($cg) use ($merchant_id){ + $config = $cg->getConfig(); + + if($config->merchantId == $merchant_id) + return $cg; + + }); + + return $gateway ?? false; + } + + //--------------------------------------------------------------------------------------// + private function verifyWebhook(): bool + {nlog($this->headers); + $request = [ + 'auth_algo' => $this->headers['paypal-auth-algo'][0], + 'cert_url' => $this->headers['paypal-cert-url'][0], + 'transmission_id' => $this->headers['paypal-transmission-id'][0], + 'transmission_sig' => $this->headers['paypal-transmission-sig'][0], + 'transmission_time' => $this->headers['paypal-transmission-time'][0], + 'webhook_id' => config('ninja.paypal.webhook_id'), + 'webhook_event' => $this->webhook_request + ]; + + nlog($request); + + $headers = [ + 'Content-type' => 'application/json', + ]; + + $r = Http::withToken($this->access_token) + ->withHeaders($headers) + ->post("{$this->endpoint}/v1/notifications/verify-webhook-signature", $request); + + nlog($r); + nlog($r->json()); + + if($r->successful() && $r->json()['verification_status'] == 'SUCCESS') { + return true; + } + + return false; + + } +} +/* +{ +"auth_algo": "SHA256withRSA", +"cert_url": "cert_url", +"transmission_id": "69cd13f0-d67a-11e5-baa3-778b53f4ae55", +"transmission_sig": "lmI95Jx3Y9nhR5SJWlHVIWpg4AgFk7n9bCHSRxbrd8A9zrhdu2rMyFrmz+Zjh3s3boXB07VXCXUZy/UFzUlnGJn0wDugt7FlSvdKeIJenLRemUxYCPVoEZzg9VFNqOa48gMkvF+XTpxBeUx/kWy6B5cp7GkT2+pOowfRK7OaynuxUoKW3JcMWw272VKjLTtTAShncla7tGF+55rxyt2KNZIIqxNMJ48RDZheGU5w1npu9dZHnPgTXB9iomeVRoD8O/jhRpnKsGrDschyNdkeh81BJJMH4Ctc6lnCCquoP/GzCzz33MMsNdid7vL/NIWaCsekQpW26FpWPi/tfj8nLA==", +"transmission_time": "2016-02-18T20:01:35Z", +"webhook_id": "1JE4291016473214C", +"webhook_event": { +"id": "8PT597110X687430LKGECATA", +"create_time": "2013-06-25T21:41:28Z", +"resource_type": "authorization", +"event_type": "PAYMENT.AUTHORIZATION.CREATED", +"summary": "A payment authorization was created", +"resource": { +"id": "2DC87612EK520411B", +"create_time": "2013-06-25T21:39:15Z", +"update_time": "2013-06-25T21:39:17Z", +"state": "authorized", +"amount": { +"total": "7.47", +"currency": "USD", +"details": { +"subtotal": "7.47" +} +}, +"parent_payment": "PAY-36246664YD343335CKHFA4AY", +"valid_until": "2013-07-24T21:39:15Z", +"links": [ +{ +"href": "https://api-m.paypal.com/v1/payments/authorization/2DC87612EK520411B", +"rel": "self", +"method": "GET" +}, +{ +"href": "https://api-m.paypal.com/v1/payments/authorization/2DC87612EK520411B/capture", +"rel": "capture", +"method": "POST" +}, +{ +"href": "https://api-m.paypal.com/v1/payments/authorization/2DC87612EK520411B/void", +"rel": "void", +"method": "POST" +}, +{ +"href": "https://api-m.paypal.com/v1/payments/payment/PAY-36246664YD343335CKHFA4AY", +"rel": "parent_payment", +"method": "GET" +} +] +} +} +} +*/ diff --git a/app/PaymentDrivers/PayPalPPCPPaymentDriver.php b/app/PaymentDrivers/PayPalPPCPPaymentDriver.php index 58b7570182b4..267a944f16c6 100644 --- a/app/PaymentDrivers/PayPalPPCPPaymentDriver.php +++ b/app/PaymentDrivers/PayPalPPCPPaymentDriver.php @@ -1,5 +1,4 @@ init()->checkPaymentsReceivable(); @@ -238,7 +256,13 @@ class PayPalPPCPPaymentDriver extends BaseDriver return render('gateways.paypal.ppcp.pay', $data); } - + + /** + * Processes the payment response + * + * @param mixed $request + * @return void + */ public function processPaymentResponse($request) { @@ -293,9 +317,21 @@ class PayPalPPCPPaymentDriver extends BaseDriver throw new PaymentFailed($message, 400); } } + + public function getOrder(string $order_id) + { + $this->init(); + $r = $this->gatewayRequest("/v2/checkout/orders/{$order_id}", 'get', ['body' => '']); + return $r->json(); + } + /** + * Generates a client token for the payment form. + * + * @return string + */ private function getClientToken(): string { @@ -308,7 +344,12 @@ class PayPalPPCPPaymentDriver extends BaseDriver throw new PaymentFailed('Unable to gain client token from Paypal. Check your configuration', 401); } - + + /** + * Builds the payment request. + * + * @return array + */ private function paymentSource(): array { /** we only need to support paypal as payment source until as we are only using hosted payment buttons */ @@ -335,7 +376,13 @@ class PayPalPPCPPaymentDriver extends BaseDriver ]; } - + + /** + * Creates the PayPal Order object + * + * @param array $data + * @return string + */ private function createOrder(array $data): string { @@ -353,7 +400,8 @@ class PayPalPPCPPaymentDriver extends BaseDriver "payment_source" => $this->paymentSource(), "purchase_units" => [ [ - "description" =>ctrans('texts.invoice_number').'# '.$invoice->number, + "custom_id" => $this->payment_hash->hash, + "description" => ctrans('texts.invoice_number').'# '.$invoice->number, "invoice_id" => $invoice->number, "payee" => [ "merchant_id" => $this->company_gateway->getConfigField('merchantId'), @@ -394,7 +442,7 @@ class PayPalPPCPPaymentDriver extends BaseDriver $r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order); - nlog($r->json()); + // nlog($r->json()); return $r->json()['id']; @@ -432,7 +480,16 @@ class PayPalPPCPPaymentDriver extends BaseDriver : null; } - + + /** + * Generates the gateway request + * + * @param string $uri + * @param string $verb + * @param array $data + * @param ?array $headers + * @return \Illuminate\Http\Client\Response + */ public function gatewayRequest(string $uri, string $verb, array $data, ?array $headers = []) { $this->init(); @@ -441,14 +498,13 @@ class PayPalPPCPPaymentDriver extends BaseDriver ->withHeaders($this->getHeaders($headers)) ->{$verb}("{$this->api_endpoint_url}{$uri}", $data); - nlog($r); - nlog($r->json()); + // nlog($r); + // nlog($r->json()); if($r->successful()) { return $r; } - SystemLogger::dispatch( ['response' => $r->body()], SystemLog::CATEGORY_GATEWAY_RESPONSE, @@ -461,7 +517,13 @@ class PayPalPPCPPaymentDriver extends BaseDriver throw new PaymentFailed("Gateway failure - {$r->body()}", 401); } - + + /** + * Generates the request headers + * + * @param array $headers + * @return array + */ private function getHeaders(array $headers = []): array { return array_merge([ @@ -473,8 +535,13 @@ class PayPalPPCPPaymentDriver extends BaseDriver ], $headers); } - private function feeCalc($invoice, $invoice_total) + public function processWebhookRequest(Request $request) { + + // nlog(json_encode($request->all())); + $this->init(); + PayPalWebhook::dispatch($request->all(), $request->headers->all(), $this->access_token); } + } diff --git a/app/PaymentDrivers/PayPalRestPaymentDriver.php b/app/PaymentDrivers/PayPalRestPaymentDriver.php index efcdba1bfa9d..6a5cd9cd001a 100644 --- a/app/PaymentDrivers/PayPalRestPaymentDriver.php +++ b/app/PaymentDrivers/PayPalRestPaymentDriver.php @@ -300,115 +300,6 @@ class PayPalRestPaymentDriver extends BaseDriver ], $headers); } - /* - public function processPaymentResponse($request) - { - $this->initializeOmnipayGateway(); - - $response = $this->omnipay_gateway - ->completePurchase(['amount' => $this->payment_hash->data->amount, 'currency' => $this->client->getCurrencyCode()]) - ->send(); - - if ($response->isCancelled() && $this->client->getSetting('enable_client_portal')) { - return redirect()->route('client.invoices.index')->with('warning', ctrans('texts.status_cancelled')); - } elseif ($response->isCancelled() && !$this->client->getSetting('enable_client_portal')) { - redirect()->route('client.invoices.show', ['invoice' => $this->payment_hash->fee_invoice])->with('warning', ctrans('texts.status_cancelled')); - } - - if ($response->isSuccessful()) { - $data = [ - 'payment_method' => $response->getData()['TOKEN'], - 'payment_type' => PaymentType::PAYPAL, - 'amount' => $this->payment_hash->data->amount, - 'transaction_reference' => $response->getTransactionReference(), - 'gateway_type_id' => GatewayType::PAYPAL, - ]; - - $payment = $this->createPayment($data, \App\Models\Payment::STATUS_COMPLETED); - - SystemLogger::dispatch( - ['response' => (array) $response->getData(), 'data' => $data], - SystemLog::CATEGORY_GATEWAY_RESPONSE, - SystemLog::EVENT_GATEWAY_SUCCESS, - SystemLog::TYPE_PAYPAL, - $this->client, - $this->client->company, - ); - - return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]); - } - - if (! $response->isSuccessful()) { - $data = $response->getData(); - - $this->sendFailureMail($response->getMessage() ?: ''); - - $message = [ - 'server_response' => $data['L_LONGMESSAGE0'], - 'data' => $this->payment_hash->data, - ]; - - SystemLogger::dispatch( - $message, - SystemLog::CATEGORY_GATEWAY_RESPONSE, - SystemLog::EVENT_GATEWAY_FAILURE, - SystemLog::TYPE_PAYPAL, - $this->client, - $this->client->company, - ); - - throw new PaymentFailed($response->getMessage(), $response->getCode()); - } - } - - public function generatePaymentDetails(array $data) - { - $_invoice = collect($this->payment_hash->data->invoices)->first(); - $invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id)); - - // $this->fee = $this->feeCalc($invoice, $data['total']['amount_with_fee']); - - return [ - 'currency' => $this->client->getCurrencyCode(), - 'transactionType' => 'Purchase', - 'clientIp' => request()->getClientIp(), - // 'amount' => round(($data['total']['amount_with_fee'] + $this->fee),2), - 'amount' => round($data['total']['amount_with_fee'], 2), - 'returnUrl' => route('client.payments.response', [ - 'company_gateway_id' => $this->company_gateway->id, - 'payment_hash' => $this->payment_hash->hash, - 'payment_method_id' => GatewayType::PAYPAL, - ]), - 'cancelUrl' => $this->client->company->domain()."/client/invoices/{$invoice->hashed_id}", - 'description' => implode(',', collect($this->payment_hash->data->invoices) - ->map(function ($invoice) { - return sprintf('%s: %s', ctrans('texts.invoice_number'), $invoice->invoice_number); - })->toArray()), - 'transactionId' => $this->payment_hash->hash.'-'.time(), - 'ButtonSource' => 'InvoiceNinja_SP', - 'solutionType' => 'Sole', - ]; - } - - public function generatePaymentItems(array $data) - { - $_invoice = collect($this->payment_hash->data->invoices)->first(); - $invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id)); - - $items = []; - - $items[] = new Item([ - 'name' => ' ', - 'description' => ctrans('texts.invoice_number').'# '.$invoice->number, - 'price' => $data['total']['amount_with_fee'], - 'quantity' => 1, - ]); - - return $items; - } - - */ - private function feeCalc($invoice, $invoice_total) { $invoice->service()->removeUnpaidGatewayFees(); diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php index 287d2d156a16..8a4996e04506 100644 --- a/app/Repositories/UserRepository.php +++ b/app/Repositories/UserRepository.php @@ -120,7 +120,7 @@ class UserRepository extends BaseRepository public function destroy(array $data, User $user) { - if ($user->isOwner()) { + if ($user->hasOwnerFlag()) { return $user; } diff --git a/app/Services/Credit/SendEmail.php b/app/Services/Credit/SendEmail.php index 9c2fd948afa2..109974f77ab9 100644 --- a/app/Services/Credit/SendEmail.php +++ b/app/Services/Credit/SendEmail.php @@ -11,10 +11,11 @@ namespace App\Services\Credit; -use App\Events\Credit\CreditWasEmailed; -use App\Jobs\Entity\EmailEntity; -use App\Models\ClientContact; use App\Utils\Ninja; +use App\Models\Webhook; +use App\Models\ClientContact; +use App\Jobs\Entity\EmailEntity; +use App\Events\Credit\CreditWasEmailed; class SendEmail { @@ -52,6 +53,8 @@ class SendEmail if ($this->credit->invitations->count() >= 1) { event(new CreditWasEmailed($this->credit->invitations->first(), $this->credit->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null), 'credit')); + $this->credit->sendEvent(Webhook::EVENT_SENT_CREDIT, "client"); + } } diff --git a/app/Services/Credit/TriggeredActions.php b/app/Services/Credit/TriggeredActions.php index 40eba04bce18..04ce784ad523 100644 --- a/app/Services/Credit/TriggeredActions.php +++ b/app/Services/Credit/TriggeredActions.php @@ -11,13 +11,14 @@ namespace App\Services\Credit; -use App\Events\Credit\CreditWasEmailed; -use App\Jobs\Entity\EmailEntity; -use App\Models\Credit; -use App\Services\AbstractService; use App\Utils\Ninja; -use App\Utils\Traits\GeneratesCounter; +use App\Models\Credit; +use App\Models\Webhook; use Illuminate\Http\Request; +use App\Jobs\Entity\EmailEntity; +use App\Services\AbstractService; +use App\Utils\Traits\GeneratesCounter; +use App\Events\Credit\CreditWasEmailed; class TriggeredActions extends AbstractService { @@ -78,6 +79,7 @@ class TriggeredActions extends AbstractService if ($this->credit->invitations->count() > 0) { event(new CreditWasEmailed($this->credit->invitations->first(), $this->credit->company, Ninja::eventVars(), 'credit')); + $this->credit->sendEvent(Webhook::EVENT_SENT_CREDIT, "client"); } } } diff --git a/app/Services/Email/Email.php b/app/Services/Email/Email.php index fa7cd008e33a..c2cc67655740 100644 --- a/app/Services/Email/Email.php +++ b/app/Services/Email/Email.php @@ -398,7 +398,7 @@ class Email implements ShouldQueue /* On the hosted platform if the user has not verified their account we fail here - but still check what they are trying to send! */ if ($this->company->account && !$this->company->account->account_sms_verified) { if (class_exists(\Modules\Admin\Jobs\Account\EmailFilter::class)) { - return (new \Modules\Admin\Jobs\Account\EmailFilter($this->email_object, $this->company))->run(); + (new \Modules\Admin\Jobs\Account\EmailFilter($this->email_object, $this->company))->run(); } return true; diff --git a/app/Services/Invoice/MarkSent.php b/app/Services/Invoice/MarkSent.php index c4d07dc037bb..f364df6ae02a 100644 --- a/app/Services/Invoice/MarkSent.php +++ b/app/Services/Invoice/MarkSent.php @@ -61,10 +61,9 @@ class MarkSent extends AbstractService if ($fire_webhook) { event('eloquent.updated: App\Models\Invoice', $this->invoice); + $this->invoice->sendEvent(Webhook::EVENT_SENT_INVOICE, "client"); } - $this->invoice->sendEvent(Webhook::EVENT_SENT_INVOICE, "client"); - return $this->invoice->fresh(); } } diff --git a/app/Services/Invoice/SendEmail.php b/app/Services/Invoice/SendEmail.php index 365201a5acb5..a690a97d1408 100644 --- a/app/Services/Invoice/SendEmail.php +++ b/app/Services/Invoice/SendEmail.php @@ -11,12 +11,13 @@ namespace App\Services\Invoice; -use App\Events\Invoice\InvoiceWasEmailed; -use App\Jobs\Entity\EmailEntity; -use App\Models\ClientContact; -use App\Models\Invoice; -use App\Services\AbstractService; use App\Utils\Ninja; +use App\Models\Invoice; +use App\Models\Webhook; +use App\Models\ClientContact; +use App\Jobs\Entity\EmailEntity; +use App\Services\AbstractService; +use App\Events\Invoice\InvoiceWasEmailed; class SendEmail extends AbstractService { @@ -41,6 +42,8 @@ class SendEmail extends AbstractService if ($this->invoice->invitations->count() >= 1) { event(new InvoiceWasEmailed($this->invoice->invitations->first(), $this->invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null), $this->reminder_template ?? 'invoice')); + $this->invoice->sendEvent(Webhook::EVENT_SENT_INVOICE, "client"); + } } diff --git a/app/Services/Invoice/TriggeredActions.php b/app/Services/Invoice/TriggeredActions.php index 3dae6339b743..0d16537ac91e 100644 --- a/app/Services/Invoice/TriggeredActions.php +++ b/app/Services/Invoice/TriggeredActions.php @@ -11,13 +11,14 @@ namespace App\Services\Invoice; -use App\Events\Invoice\InvoiceWasEmailed; -use App\Jobs\Entity\EmailEntity; -use App\Models\Invoice; -use App\Services\AbstractService; use App\Utils\Ninja; -use App\Utils\Traits\GeneratesCounter; +use App\Models\Invoice; +use App\Models\Webhook; use Illuminate\Http\Request; +use App\Jobs\Entity\EmailEntity; +use App\Services\AbstractService; +use App\Utils\Traits\GeneratesCounter; +use App\Events\Invoice\InvoiceWasEmailed; class TriggeredActions extends AbstractService { @@ -45,12 +46,12 @@ class TriggeredActions extends AbstractService if ($this->request->has('mark_sent') && $this->request->input('mark_sent') == 'true' && $this->invoice->status_id == Invoice::STATUS_DRAFT) { $this->invoice = $this->invoice->service()->markSent()->save(); //update notification NOT sent - $this->updated = false; + $this->updated = true; } if ($this->request->has('amount_paid') && is_numeric($this->request->input('amount_paid'))) { $this->invoice = $this->invoice->service()->applyPaymentAmount($this->request->input('amount_paid'), $this->request->input('reference'))->save(); - $this->updated = false; + // $this->updated = false; } if ($this->request->has('send_email') && $this->request->input('send_email') == 'true') { @@ -80,8 +81,10 @@ class TriggeredActions extends AbstractService $company->save(); } - if ($this->updated) { - event('eloquent.updated: App\Models\Invoice', $this->invoice); + if ($this->updated) { + // event('eloquent.updated: App\Models\Invoice', $this->invoice); + $this->invoice->sendEvent(Webhook::EVENT_SENT_INVOICE, "client"); + } @@ -98,6 +101,7 @@ class TriggeredActions extends AbstractService if ($this->invoice->invitations->count() > 0) { event(new InvoiceWasEmailed($this->invoice->invitations->first(), $this->invoice->company, Ninja::eventVars(), 'invoice')); + $this->invoice->sendEvent(Webhook::EVENT_SENT_INVOICE, "client"); } } } diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php index c54ef2812646..fbc4ebb3494c 100644 --- a/app/Services/Payment/DeletePayment.php +++ b/app/Services/Payment/DeletePayment.php @@ -70,7 +70,12 @@ class DeletePayment /** @return $this */ private function deletePaymentables() { - $this->payment->paymentables()->update(['deleted_at' => now()]); + // $this->payment->paymentables()->update(['deleted_at' => now()]); + + $this->payment->paymentables() + ->each(function ($pp){ + $pp->forceDelete(); + }); return $this; } diff --git a/app/Services/Quote/SendEmail.php b/app/Services/Quote/SendEmail.php index 3cd0cf301bac..1ab1e9d023d5 100644 --- a/app/Services/Quote/SendEmail.php +++ b/app/Services/Quote/SendEmail.php @@ -11,8 +11,9 @@ namespace App\Services\Quote; -use App\Jobs\Entity\EmailEntity; +use App\Models\Webhook; use App\Models\ClientContact; +use App\Jobs\Entity\EmailEntity; class SendEmail { @@ -50,7 +51,7 @@ class SendEmail } }); - + $this->quote->sendEvent(Webhook::EVENT_SENT_QUOTE, "client"); } } diff --git a/app/Services/Quote/TriggeredActions.php b/app/Services/Quote/TriggeredActions.php index 3e6283fc89b9..bfc4c2a6df3a 100644 --- a/app/Services/Quote/TriggeredActions.php +++ b/app/Services/Quote/TriggeredActions.php @@ -11,13 +11,14 @@ namespace App\Services\Quote; -use App\Events\Quote\QuoteWasEmailed; -use App\Jobs\Entity\EmailEntity; -use App\Models\Quote; -use App\Services\AbstractService; use App\Utils\Ninja; -use App\Utils\Traits\GeneratesCounter; +use App\Models\Quote; +use App\Models\Webhook; use Illuminate\Http\Request; +use App\Jobs\Entity\EmailEntity; +use App\Services\AbstractService; +use App\Events\Quote\QuoteWasEmailed; +use App\Utils\Traits\GeneratesCounter; class TriggeredActions extends AbstractService { @@ -86,6 +87,8 @@ class TriggeredActions extends AbstractService if ($this->quote->invitations->count() > 0) { event(new QuoteWasEmailed($this->quote->invitations->first(), $this->quote->company, Ninja::eventVars(), 'quote')); + $this->quote->sendEvent(Webhook::EVENT_SENT_QUOTE, "client"); + } } } diff --git a/composer.lock b/composer.lock index a35b4329b536..8939b22cea8f 100644 --- a/composer.lock +++ b/composer.lock @@ -485,16 +485,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.293.7", + "version": "3.294.1", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "3bf86ba8b9bbea2b298f89e6f5edc58de276690b" + "reference": "63c720229a9c9cdedff6bac98d6e72be8cc241f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3bf86ba8b9bbea2b298f89e6f5edc58de276690b", - "reference": "3bf86ba8b9bbea2b298f89e6f5edc58de276690b", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/63c720229a9c9cdedff6bac98d6e72be8cc241f1", + "reference": "63c720229a9c9cdedff6bac98d6e72be8cc241f1", "shasum": "" }, "require": { @@ -574,9 +574,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.293.7" + "source": "https://github.com/aws/aws-sdk-php/tree/3.294.1" }, - "time": "2023-12-08T19:11:21+00:00" + "time": "2023-12-15T19:25:52+00:00" }, { "name": "bacon/bacon-qr-code", @@ -790,16 +790,16 @@ }, { "name": "carbonphp/carbon-doctrine-types", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", - "reference": "67a77972b9f398ae7068dabacc39c08aeee170d5" + "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/67a77972b9f398ae7068dabacc39c08aeee170d5", - "reference": "67a77972b9f398ae7068dabacc39c08aeee170d5", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", + "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", "shasum": "" }, "require": { @@ -839,7 +839,7 @@ ], "support": { "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", - "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/2.0.0" + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/2.1.0" }, "funding": [ { @@ -855,7 +855,7 @@ "type": "tidelift" } ], - "time": "2023-10-01T14:29:01+00:00" + "time": "2023-12-11T17:09:12+00:00" }, { "name": "checkout/checkout-sdk-php", @@ -2617,16 +2617,16 @@ }, { "name": "google/apiclient-services", - "version": "v0.326.1", + "version": "v0.327.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", - "reference": "4e89c28c499f87eb517679e13356469896a119c6" + "reference": "51a11d4ff70dd9f60334525e71bf4cf592e6d282" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/4e89c28c499f87eb517679e13356469896a119c6", - "reference": "4e89c28c499f87eb517679e13356469896a119c6", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/51a11d4ff70dd9f60334525e71bf4cf592e6d282", + "reference": "51a11d4ff70dd9f60334525e71bf4cf592e6d282", "shasum": "" }, "require": { @@ -2655,9 +2655,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.326.1" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.327.0" }, - "time": "2023-12-04T01:18:18+00:00" + "time": "2023-12-11T00:52:16+00:00" }, { "name": "google/auth", @@ -3717,16 +3717,16 @@ }, { "name": "imdhemy/google-play-billing", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/imdhemy/google-play-billing.git", - "reference": "a227174a71bc5d7b3e5f9aa4fcad2c4a9a11a8a4" + "reference": "bb94f3b6ddb021605815e528f31b8c930c41677c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/imdhemy/google-play-billing/zipball/a227174a71bc5d7b3e5f9aa4fcad2c4a9a11a8a4", - "reference": "a227174a71bc5d7b3e5f9aa4fcad2c4a9a11a8a4", + "url": "https://api.github.com/repos/imdhemy/google-play-billing/zipball/bb94f3b6ddb021605815e528f31b8c930c41677c", + "reference": "bb94f3b6ddb021605815e528f31b8c930c41677c", "shasum": "" }, "require": { @@ -3762,22 +3762,22 @@ "description": "Google Play Billing", "support": { "issues": "https://github.com/imdhemy/google-play-billing/issues", - "source": "https://github.com/imdhemy/google-play-billing/tree/1.5.0" + "source": "https://github.com/imdhemy/google-play-billing/tree/1.5.1" }, - "time": "2023-09-17T12:33:33+00:00" + "time": "2023-12-15T10:25:05+00:00" }, { "name": "imdhemy/laravel-purchases", - "version": "1.9.0", + "version": "1.9.1", "source": { "type": "git", "url": "https://github.com/imdhemy/laravel-in-app-purchases.git", - "reference": "4471f5dc211931b847ac0bf88f78bd4fa9e3760d" + "reference": "b74e09b78fb3e0f1b1630dbcfd23d9f6fe251b90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/imdhemy/laravel-in-app-purchases/zipball/4471f5dc211931b847ac0bf88f78bd4fa9e3760d", - "reference": "4471f5dc211931b847ac0bf88f78bd4fa9e3760d", + "url": "https://api.github.com/repos/imdhemy/laravel-in-app-purchases/zipball/b74e09b78fb3e0f1b1630dbcfd23d9f6fe251b90", + "reference": "b74e09b78fb3e0f1b1630dbcfd23d9f6fe251b90", "shasum": "" }, "require": { @@ -3833,7 +3833,7 @@ ], "support": { "issues": "https://github.com/imdhemy/laravel-in-app-purchases/issues", - "source": "https://github.com/imdhemy/laravel-in-app-purchases/tree/1.9.0" + "source": "https://github.com/imdhemy/laravel-in-app-purchases/tree/1.9.1" }, "funding": [ { @@ -3841,7 +3841,7 @@ "type": "github" } ], - "time": "2023-09-19T06:01:35+00:00" + "time": "2023-12-15T10:35:56+00:00" }, { "name": "intervention/image", @@ -4114,47 +4114,47 @@ }, { "name": "jms/serializer", - "version": "3.28.0", + "version": "3.29.1", "source": { "type": "git", "url": "https://github.com/schmittjoh/serializer.git", - "reference": "5a5a03a71a28a480189c5a0ca95893c19f1d120c" + "reference": "111451f43abb448ce297361a8ab96a9591e848cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/5a5a03a71a28a480189c5a0ca95893c19f1d120c", - "reference": "5a5a03a71a28a480189c5a0ca95893c19f1d120c", + "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/111451f43abb448ce297361a8ab96a9591e848cd", + "reference": "111451f43abb448ce297361a8ab96a9591e848cd", "shasum": "" }, "require": { - "doctrine/annotations": "^1.13 || ^2.0", - "doctrine/instantiator": "^1.0.3 || ^2.0", + "doctrine/annotations": "^1.14 || ^2.0", + "doctrine/instantiator": "^1.3.1 || ^2.0", "doctrine/lexer": "^2.0 || ^3.0", "jms/metadata": "^2.6", - "php": "^7.2||^8.0", - "phpstan/phpdoc-parser": "^0.4 || ^0.5 || ^1.0" + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.20" }, "require-dev": { "doctrine/coding-standard": "^12.0", - "doctrine/orm": "~2.1", - "doctrine/persistence": "^1.3.3|^2.0|^3.0", - "doctrine/phpcr-odm": "^1.3|^2.0", + "doctrine/orm": "^2.14 || ^3.0", + "doctrine/persistence": "^2.5.2 || ^3.0", + "doctrine/phpcr-odm": "^1.5.2 || ^2.0", "ext-pdo_sqlite": "*", - "jackalope/jackalope-doctrine-dbal": "^1.1.5", - "ocramius/proxy-manager": "^1.0|^2.0", + "jackalope/jackalope-doctrine-dbal": "^1.3", + "ocramius/proxy-manager": "^1.0 || ^2.0", "phpbench/phpbench": "^1.0", "phpstan/phpstan": "^1.0.2", - "phpunit/phpunit": "^8.5.21||^9.0||^10.0", - "psr/container": "^1.0|^2.0", - "symfony/dependency-injection": "^3.0|^4.0|^5.0|^6.0", - "symfony/expression-language": "^3.2|^4.0|^5.0|^6.0", - "symfony/filesystem": "^3.0|^4.0|^5.0|^6.0", - "symfony/form": "^3.0|^4.0|^5.0|^6.0", - "symfony/translation": "^3.0|^4.0|^5.0|^6.0", - "symfony/uid": "^5.1|^6.0", - "symfony/validator": "^3.1.9|^4.0|^5.0|^6.0", - "symfony/yaml": "^3.3|^4.0|^5.0|^6.0", - "twig/twig": "~1.34|~2.4|^3.0" + "phpunit/phpunit": "^8.5.21 || ^9.0 || ^10.0", + "psr/container": "^1.0 || ^2.0", + "symfony/dependency-injection": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/expression-language": "^3.2 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/filesystem": "^4.2 || ^5.0 || ^6.0 || ^7.0", + "symfony/form": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/translation": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/uid": "^5.1 || ^6.0 || ^7.0", + "symfony/validator": "^3.1.9 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "twig/twig": "^1.34 || ^2.4 || ^3.0" }, "suggest": { "doctrine/collections": "Required if you like to use doctrine collection types as ArrayCollection.", @@ -4198,7 +4198,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/serializer/issues", - "source": "https://github.com/schmittjoh/serializer/tree/3.28.0" + "source": "https://github.com/schmittjoh/serializer/tree/3.29.1" }, "funding": [ { @@ -4206,7 +4206,7 @@ "type": "github" } ], - "time": "2023-08-03T14:43:08+00:00" + "time": "2023-12-14T15:25:09+00:00" }, { "name": "josemmo/facturae-php", @@ -4366,16 +4366,16 @@ }, { "name": "laravel/framework", - "version": "v10.35.0", + "version": "v10.37.3", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "91ec2d92d2f6007e9084fe06438b99c91845da69" + "reference": "996375dd61f8c6e4ac262b57ed485655d71fcbdc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/91ec2d92d2f6007e9084fe06438b99c91845da69", - "reference": "91ec2d92d2f6007e9084fe06438b99c91845da69", + "url": "https://api.github.com/repos/laravel/framework/zipball/996375dd61f8c6e4ac262b57ed485655d71fcbdc", + "reference": "996375dd61f8c6e4ac262b57ed485655d71fcbdc", "shasum": "" }, "require": { @@ -4564,7 +4564,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-12-05T14:50:33+00:00" + "time": "2023-12-13T20:10:58+00:00" }, { "name": "laravel/prompts", @@ -5273,16 +5273,16 @@ }, { "name": "league/csv", - "version": "9.12.0", + "version": "9.13.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "c1dc31e23eb3cd0f7b537ee1a669d5f58d5cfe21" + "reference": "3690cc71bfe8dc3b6daeef356939fac95348f0a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/c1dc31e23eb3cd0f7b537ee1a669d5f58d5cfe21", - "reference": "c1dc31e23eb3cd0f7b537ee1a669d5f58d5cfe21", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/3690cc71bfe8dc3b6daeef356939fac95348f0a8", + "reference": "3690cc71bfe8dc3b6daeef356939fac95348f0a8", "shasum": "" }, "require": { @@ -5297,11 +5297,11 @@ "ext-xdebug": "*", "friendsofphp/php-cs-fixer": "^v3.22.0", "phpbench/phpbench": "^1.2.15", - "phpstan/phpstan": "^1.10.46", + "phpstan/phpstan": "^1.10.50", "phpstan/phpstan-deprecation-rules": "^1.1.4", "phpstan/phpstan-phpunit": "^1.3.15", "phpstan/phpstan-strict-rules": "^1.5.2", - "phpunit/phpunit": "^10.4.2", + "phpunit/phpunit": "^10.5.3", "symfony/var-dumper": "^6.4.0" }, "suggest": { @@ -5358,7 +5358,7 @@ "type": "github" } ], - "time": "2023-12-01T17:54:07+00:00" + "time": "2023-12-16T11:03:20+00:00" }, { "name": "league/flysystem", @@ -8447,16 +8447,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.24.4", + "version": "1.24.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496" + "reference": "fedf211ff14ec8381c9bf5714e33a7a552dd1acc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6bd0c26f3786cd9b7c359675cb789e35a8e07496", - "reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fedf211ff14ec8381c9bf5714e33a7a552dd1acc", + "reference": "fedf211ff14ec8381c9bf5714e33a7a552dd1acc", "shasum": "" }, "require": { @@ -8488,9 +8488,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.4" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.5" }, - "time": "2023-11-26T18:29:22+00:00" + "time": "2023-12-16T09:33:33+00:00" }, { "name": "pragmarx/google2fa", @@ -9148,16 +9148,16 @@ }, { "name": "pusher/pusher-php-server", - "version": "7.2.3", + "version": "7.2.4", "source": { "type": "git", "url": "https://github.com/pusher/pusher-http-php.git", - "reference": "416e68dd5f640175ad5982131c42a7a666d1d8e9" + "reference": "de2f72296808f9cafa6a4462b15a768ff130cddb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/416e68dd5f640175ad5982131c42a7a666d1d8e9", - "reference": "416e68dd5f640175ad5982131c42a7a666d1d8e9", + "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/de2f72296808f9cafa6a4462b15a768ff130cddb", + "reference": "de2f72296808f9cafa6a4462b15a768ff130cddb", "shasum": "" }, "require": { @@ -9203,9 +9203,9 @@ ], "support": { "issues": "https://github.com/pusher/pusher-http-php/issues", - "source": "https://github.com/pusher/pusher-http-php/tree/7.2.3" + "source": "https://github.com/pusher/pusher-http-php/tree/7.2.4" }, - "time": "2023-05-17T16:00:06+00:00" + "time": "2023-12-15T10:58:53+00:00" }, { "name": "ralouphie/getallheaders", @@ -10009,16 +10009,16 @@ }, { "name": "setasign/fpdi", - "version": "v2.5.0", + "version": "v2.6.0", "source": { "type": "git", "url": "https://github.com/Setasign/FPDI.git", - "reference": "ecf0459643ec963febfb9a5d529dcd93656006a4" + "reference": "a6db878129ec6c7e141316ee71872923e7f1b7ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Setasign/FPDI/zipball/ecf0459643ec963febfb9a5d529dcd93656006a4", - "reference": "ecf0459643ec963febfb9a5d529dcd93656006a4", + "url": "https://api.github.com/repos/Setasign/FPDI/zipball/a6db878129ec6c7e141316ee71872923e7f1b7ad", + "reference": "a6db878129ec6c7e141316ee71872923e7f1b7ad", "shasum": "" }, "require": { @@ -10030,8 +10030,8 @@ }, "require-dev": { "phpunit/phpunit": "~5.7", - "setasign/fpdf": "~1.8", - "setasign/tfpdf": "~1.31", + "setasign/fpdf": "~1.8.6", + "setasign/tfpdf": "~1.33", "squizlabs/php_codesniffer": "^3.5", "tecnickcom/tcpdf": "~6.2" }, @@ -10069,7 +10069,7 @@ ], "support": { "issues": "https://github.com/Setasign/FPDI/issues", - "source": "https://github.com/Setasign/FPDI/tree/v2.5.0" + "source": "https://github.com/Setasign/FPDI/tree/v2.6.0" }, "funding": [ { @@ -10077,7 +10077,7 @@ "type": "tidelift" } ], - "time": "2023-09-28T10:46:27+00:00" + "time": "2023-12-11T16:03:32+00:00" }, { "name": "shopify/shopify-api", @@ -16067,16 +16067,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.48", + "version": "1.10.50", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "087ed4b5f4a7a6e8f3bbdfbfe98ce5c181380bc6" + "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/087ed4b5f4a7a6e8f3bbdfbfe98ce5c181380bc6", - "reference": "087ed4b5f4a7a6e8f3bbdfbfe98ce5c181380bc6", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/06a98513ac72c03e8366b5a0cb00750b487032e4", + "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4", "shasum": "" }, "require": { @@ -16125,20 +16125,20 @@ "type": "tidelift" } ], - "time": "2023-12-08T14:34:28+00:00" + "time": "2023-12-13T10:59:42+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "10.1.9", + "version": "10.1.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "a56a9ab2f680246adcf3db43f38ddf1765774735" + "reference": "599109c8ca6bae97b23482d557d2874c25a65e59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/a56a9ab2f680246adcf3db43f38ddf1765774735", - "reference": "a56a9ab2f680246adcf3db43f38ddf1765774735", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/599109c8ca6bae97b23482d557d2874c25a65e59", + "reference": "599109c8ca6bae97b23482d557d2874c25a65e59", "shasum": "" }, "require": { @@ -16195,7 +16195,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.9" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.10" }, "funding": [ { @@ -16203,7 +16203,7 @@ "type": "github" } ], - "time": "2023-11-23T12:23:20+00:00" + "time": "2023-12-11T06:28:43+00:00" }, { "name": "phpunit/php-file-iterator", @@ -16450,16 +16450,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.2", + "version": "10.5.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "5aedff46afba98dddecaa12349ec044d9103d4fe" + "reference": "6fce887c71076a73f32fd3e0774a6833fc5c7f19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5aedff46afba98dddecaa12349ec044d9103d4fe", - "reference": "5aedff46afba98dddecaa12349ec044d9103d4fe", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6fce887c71076a73f32fd3e0774a6833fc5c7f19", + "reference": "6fce887c71076a73f32fd3e0774a6833fc5c7f19", "shasum": "" }, "require": { @@ -16531,7 +16531,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.2" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.3" }, "funding": [ { @@ -16547,7 +16547,7 @@ "type": "tidelift" } ], - "time": "2023-12-05T14:54:33+00:00" + "time": "2023-12-13T07:25:23+00:00" }, { "name": "sebastian/cli-parser", @@ -17681,16 +17681,16 @@ }, { "name": "spatie/laravel-ignition", - "version": "2.3.1", + "version": "2.3.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "bf21cd15aa47fa4ec5d73bbc932005c70261efc8" + "reference": "4800661a195e15783477d99f7f8f669a49793996" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/bf21cd15aa47fa4ec5d73bbc932005c70261efc8", - "reference": "bf21cd15aa47fa4ec5d73bbc932005c70261efc8", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/4800661a195e15783477d99f7f8f669a49793996", + "reference": "4800661a195e15783477d99f7f8f669a49793996", "shasum": "" }, "require": { @@ -17769,7 +17769,7 @@ "type": "github" } ], - "time": "2023-10-09T12:55:26+00:00" + "time": "2023-12-15T13:44:49+00:00" }, { "name": "spaze/phpstan-stripe", diff --git a/config/ninja.php b/config/ninja.php index 6221456e165f..db2b42e4ae47 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -227,5 +227,6 @@ return [ 'paypal' => [ 'secret' => env('PAYPAL_SECRET', null), 'client_id' => env('PAYPAL_CLIENT_ID', null), + 'webhook_id' => env('PAYPAL_WEBHOOK_ID', null), ] ]; diff --git a/public/build/assets/authorize-checkout-card-d561d355.js b/public/build/assets/authorize-checkout-card-d561d355.js new file mode 100644 index 000000000000..af477dbd8489 --- /dev/null +++ b/public/build/assets/authorize-checkout-card-d561d355.js @@ -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 + */class t{constructor(){this.button=document.getElementById("pay-button")}init(){this.frames=Frames.init(document.querySelector("meta[name=public-key]").content)}handle(){this.init(),Frames.addEventHandler(Frames.Events.CARD_VALIDATION_CHANGED,e=>{this.button.disabled=!Frames.isCardValid()}),Frames.addEventHandler(Frames.Events.CARD_TOKENIZED,e=>{document.querySelector('input[name="gateway_response"]').value=JSON.stringify(e),document.getElementById("server_response").submit()}),document.querySelector("#authorization-form").addEventListener("submit",e=>{this.button.disabled=!0,e.preventDefault(),Frames.submitCard()})}}new t().handle(); diff --git a/public/build/assets/checkout-credit-card-8a04938c.js b/public/build/assets/checkout-credit-card-8a04938c.js new file mode 100644 index 000000000000..0125ca29bec6 --- /dev/null +++ b/public/build/assets/checkout-credit-card-8a04938c.js @@ -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 + */class o{constructor(){this.tokens=[]}mountFrames(){console.log("Mount checkout frames..")}handlePaymentUsingToken(t){document.getElementById("checkout--container").classList.add("hidden"),document.getElementById("pay-now-with-token--container").classList.remove("hidden"),document.getElementById("save-card--container").style.display="none",document.querySelector("input[name=token]").value=t.target.dataset.token}handlePaymentUsingCreditCard(t){document.getElementById("checkout--container").classList.remove("hidden"),document.getElementById("pay-now-with-token--container").classList.add("hidden"),document.getElementById("save-card--container").style.display="grid",document.querySelector("input[name=token]").value="";const e=document.getElementById("pay-button"),d=document.querySelector('meta[name="public-key"]').content??"",a=document.getElementById("payment-form");Frames.init(d),Frames.addEventHandler(Frames.Events.CARD_VALIDATION_CHANGED,function(n){e.disabled=!Frames.isCardValid()}),Frames.addEventHandler(Frames.Events.CARD_TOKENIZATION_FAILED,function(n){e.disabled=!1}),Frames.addEventHandler(Frames.Events.CARD_TOKENIZED,function(n){e.disabled=!0,document.querySelector('input[name="gateway_response"]').value=JSON.stringify(n),document.querySelector('input[name="store_card"]').value=document.querySelector("input[name=token-billing-checkbox]:checked").value,document.getElementById("server-response").submit()}),a.addEventListener("submit",function(n){n.preventDefault(),e.disabled=!0,Frames.submitCard()})}completePaymentUsingToken(t){let e=document.getElementById("pay-now-with-token");e.disabled=!0,e.querySelector("svg").classList.remove("hidden"),e.querySelector("span").classList.add("hidden"),document.getElementById("server-response").submit()}handle(){this.handlePaymentUsingCreditCard(),Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach(t=>t.addEventListener("click",this.handlePaymentUsingToken)),document.getElementById("toggle-payment-with-credit-card").addEventListener("click",this.handlePaymentUsingCreditCard),document.getElementById("pay-now-with-token").addEventListener("click",this.completePaymentUsingToken)}}new o().handle(); diff --git a/resources/views/portal/ninja2020/components/html-viewer.blade.php b/resources/views/portal/ninja2020/components/html-viewer.blade.php index f20885864a6a..ab54e246d5ea 100644 --- a/resources/views/portal/ninja2020/components/html-viewer.blade.php +++ b/resources/views/portal/ninja2020/components/html-viewer.blade.php @@ -132,6 +132,18 @@ span {
+ @if($discount) + + + + + @endif + @if($taxes) + + + + + @endif diff --git a/resources/views/portal/ninja2020/gateways/paypal/ppcp/pay.blade.php b/resources/views/portal/ninja2020/gateways/paypal/ppcp/pay.blade.php index 4570359a3e99..dd7c487d0fe1 100644 --- a/resources/views/portal/ninja2020/gateways/paypal/ppcp/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/paypal/ppcp/pay.blade.php @@ -53,11 +53,9 @@ return actions.restart(); } - // return actions.order.capture().then(function(details) { document.getElementById("gateway_response").value =JSON.stringify( data ); - // document.getElementById("gateway_response").value =JSON.stringify( details ); document.getElementById("server_response").submit(); - // }); + }, onCancel: function() { window.location.href = "/client/invoices/"; @@ -65,6 +63,9 @@ onError: function(error) { document.getElementById("gateway_response").value = error; document.getElementById("server_response").submit(); + }, + onClick: function (){ + document.getElementById('paypal-button-container').hidden = true; } }).render('#paypal-button-container').catch(function(err) { @@ -74,6 +75,14 @@ }); + document.getElementById("server_response").addEventListener('submit', (e) => { + if (document.getElementById("server_response").classList.contains('is-submitting')) { + e.preventDefault(); + } + + document.getElementById("server_response").classList.add('is-submitting'); + }); + @endpush \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 4d648054e298..a559d65e8e7c 100644 --- a/routes/api.php +++ b/routes/api.php @@ -113,6 +113,7 @@ use App\Http\Controllers\UserController; use App\Http\Controllers\VendorController; use App\Http\Controllers\WebCronController; use App\Http\Controllers\WebhookController; +use App\PaymentDrivers\PayPalPPCPPaymentDriver; use Illuminate\Support\Facades\Route; Route::group(['middleware' => ['throttle:api', 'api_secret_check']], function () { @@ -426,5 +427,6 @@ Route::post('api/v1/yodlee/refresh_updates', [YodleeController::class, 'refreshU Route::post('api/v1/yodlee/balance', [YodleeController::class, 'balanceWebhook'])->middleware('throttle:100,1'); Route::get('api/v1/protected_download/{hash}', [ProtectedDownloadController::class, 'index'])->name('protected_download')->middleware('throttle:300,1'); +Route::post('api/v1/ppcp/webhook', [PayPalPPCPPaymentDriver::class, 'processWebhookRequest'])->middleware('throttle:1000,1'); Route::fallback([BaseController::class, 'notFound'])->middleware('throttle:404'); diff --git a/tests/Feature/ClientTest.php b/tests/Feature/ClientTest.php index 54f1e1054504..b09978d6af67 100644 --- a/tests/Feature/ClientTest.php +++ b/tests/Feature/ClientTest.php @@ -65,6 +65,34 @@ class ClientTest extends TestCase $this->makeTestData(); } + public function testStoreClientFixes2() + { + $data = [ + "contacts" => [ + [ + "email" => "tenda@gmail.com", + "first_name" => "Tenda", + "last_name" => "Bavuma", + ], + ], + "name" => "Tenda Bavuma", + ]; + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/clients', $data); + + $response->assertStatus(200); + $arr = $response->json(); + + $this->assertTrue($arr['data']['contacts'][0]['is_primary']); + $this->assertTrue($arr['data']['contacts'][0]['send_email']); + + } + + public function testStoreClientFixes() { $data = [ diff --git a/tests/Feature/PayPal/WebhookTest.php b/tests/Feature/PayPal/WebhookTest.php new file mode 100644 index 000000000000..e454d9c804bf --- /dev/null +++ b/tests/Feature/PayPal/WebhookTest.php @@ -0,0 +1,144 @@ +makeTestData(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + + } + + public function testWebhooks() + { + $hook = json_decode($this->webhook_string, true); + $this->assertIsArray($hook); + } + + public function testPaymentCreation() + { + $hook = json_decode($this->webhook_string, true); + + $company_gateway = $this->buildGateway(); + + $invoice = Invoice::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $this->client->id, + 'number' => $this->invoice_number, + 'status_id' => 2, + 'amount' => $this->amount, + 'balance' => $this->amount, + ]); + + $hash_data = [ + 'invoices' => [ + ['invoice_id' => $invoice->hashed_id, 'amount' => $this->amount], + ], + 'credits' => 0, + 'amount_with_fee' => $this->amount, + 'pre_payment' => false, + 'frequency_id' => null, + 'remaining_cycles' => null, + 'is_recurring' => false, + ]; + + $payment_hash = new PaymentHash; + $payment_hash->hash = Str::random(32); + $payment_hash->data = $hash_data; + $payment_hash->fee_total = 0; + $payment_hash->fee_invoice_id = $invoice->id; + $payment_hash->save(); + + + $driver = $company_gateway->driver($this->client); + $driver->setPaymentHash($payment_hash); + + $source = 'paypal'; + $transaction_reference = $hook['resource']['purchase_units'][0]['payments']['captures'][0]['id']; + $amount = $hook['resource']['purchase_units'][0]['payments']['captures'][0]['amount']['value']; + + $data = [ + 'payment_type' => 3, + 'amount' => $amount, + 'transaction_reference' => $transaction_reference, + 'gateway_type_id' => GatewayType::PAYPAL, + ]; + + $payment = $driver->createPayment($data, \App\Models\Payment::STATUS_COMPLETED); + + $this->assertNotNull($payment); + + + } + + private function buildGateway() + { + $config = new \stdClass; + $config->merchantId = $this->merchant_id; + $config->status = 'activated'; + $config->consent = 'true'; + $config->emailVerified = 'true'; + $config->permissions = 'true'; + $config->returnMessage = 'true'; + $config->paymentsReceivable = 'Yes'; + + $cg = new CompanyGateway; + $cg->company_id = $this->company->id; + $cg->user_id = $this->user->id; + $cg->gateway_key = '80af24a6a691230bbec33e930ab40666'; + $cg->require_cvv = true; + $cg->require_billing_address = true; + $cg->require_shipping_address = true; + $cg->update_details = true; + $cg->config = encrypt($config); + $cg->save(); + + $fees_and_limits = new stdClass; + $fees_and_limits->{3} = new FeesAndLimits; + + $cg->fees_and_limits = $fees_and_limits; + $cg->save(); + + return $cg; + } +} \ No newline at end of file diff --git a/tests/Feature/Scheduler/ScheduleEntityTest.php b/tests/Feature/Scheduler/ScheduleEntityTest.php index 05b1ec9904c9..df627c02505a 100644 --- a/tests/Feature/Scheduler/ScheduleEntityTest.php +++ b/tests/Feature/Scheduler/ScheduleEntityTest.php @@ -27,7 +27,9 @@ class ScheduleEntityTest extends TestCase { use MakesHash; use MockAccountData; - + + public $faker; + protected function setUp(): void { parent::setUp(); diff --git a/tests/MockAccountData.php b/tests/MockAccountData.php index 703b008e356f..959224548d7e 100644 --- a/tests/MockAccountData.php +++ b/tests/MockAccountData.php @@ -269,6 +269,9 @@ trait MockAccountData $this->company->save(); $this->account->default_company_id = $this->company->id; + $this->account->plan = 'pro'; + $this->account->plan_expires = now()->addMonth(); + $this->account->plan_term = "month"; $this->account->save(); $user = User::whereEmail($fake_email)->first();
{{ ctrans('texts.discount') }}{{ $discount }}
{{ ctrans('texts.tax') }}{{ $taxes }}
{{ ctrans('texts.total') }} {{ $amount }}